Python - asistanto en trovado de malmultekostaj flugbiletoj por tiuj, kiuj amas vojaĝi

La aŭtoro de la artikolo, kies tradukon ni publikigas hodiaŭ, diras, ke ĝia celo estas paroli pri la disvolviĝo de TTT-skrapilo en Python uzante Selenium, kiu serĉas flugajn biletojn. Serĉante biletojn, oni uzas flekseblajn datojn (+- 3 tagoj rilate al la specifitaj datoj). La skrapilo konservas la serĉrezultojn en Excel-dosiero kaj sendas al la persono, kiu faris la serĉon, retpoŝton kun resumo de tio, kion ili trovis. La celo de ĉi tiu projekto estas helpi vojaĝantojn trovi la plej bonajn ofertojn.

Python - asistanto en trovado de malmultekostaj flugbiletoj por tiuj, kiuj amas vojaĝi

Se, komprenante la materialon, vi sentas vin perdita, rigardu ĉi tio artikolo.

Kion ni serĉos?

Vi rajtas uzi la ĉi tie priskribitan sistemon kiel vi volas. Ekzemple, mi uzis ĝin por serĉi semajnfinajn ekskursojn kaj biletojn al mia hejmurbo. Se vi serioze volas trovi profitajn biletojn, vi povas ruli la skripton sur la servilo (simpla servilo, por 130 rubloj monate, estas sufiĉe taŭga por ĉi tio) kaj certigu, ke ĝi funkcias unu aŭ dufoje tage. Serĉrezultoj estos senditaj al vi retpoŝte. Krome, mi rekomendas agordi ĉion, por ke la skripto konservu Excel-dosieron kun serĉrezultoj en Dropbox-dosierujo, kiu permesos al vi vidi tiajn dosierojn de ie ajn kaj kiam ajn.

Python - asistanto en trovado de malmultekostaj flugbiletoj por tiuj, kiuj amas vojaĝi
Mi ankoraŭ ne trovis tarifojn kun eraroj, sed mi pensas, ke ĝi eblas

Dum serĉado, kiel jam menciite, "fleksebla dato" estas uzata; la skripto trovas ofertojn kiuj estas ene de tri tagoj de la donitaj datoj. Kvankam dum rulado de la skripto, ĝi serĉas ofertojn en nur unu direkto, estas facile modifi ĝin por ke ĝi povu kolekti datumojn pri pluraj flugdirektoj. Kun ĝia helpo, vi eĉ povas serĉi erarajn tarifojn; tiaj trovoj povas esti tre interesaj.

Kial vi bezonas alian retan skrapilon?

Kiam mi unue komencis retan skrapadon, mi sincere ne aparte interesiĝis pri ĝi. Mi volis fari pli da projektoj en la kampo de prognoza modelado, financa analizo, kaj, eble, en la kampo de analizado de la emocia kolorigo de tekstoj. Sed montriĝis, ke estis tre interese ekscii kiel krei programon, kiu kolektas datumojn de retejoj. Dum mi enprofundiĝis en ĉi tiun temon, mi rimarkis, ke TTT-skrapado estas la "motoro" de la Interreto.

Vi eble pensas, ke ĉi tio estas tro aŭdaca deklaro. Sed konsideru, ke Guglo komencis per interreta skrapilo, kiun Larry Page kreis uzante Java kaj Python. Guglo-robotoj esploris la Interreton, provante provizi al siaj uzantoj la plej bonajn respondojn al iliaj demandoj. Reta skrapado havas senfinajn uzojn, kaj eĉ se vi interesiĝas pri io alia en Datuma Scienco, vi bezonos kelkajn skrapajn kapablojn por akiri la datumojn, kiujn vi bezonas analizi.

Mi trovis kelkajn el la teknikoj uzataj ĉi tie en mirinda la libro pri retskrapado, kiun mi ĵus akiris. Ĝi enhavas multajn simplajn ekzemplojn kaj ideojn por praktika aplikado de tio, kion vi lernis. Krome, estas tre interesa ĉapitro pri preterpasi reCaptcha-kontrolojn. Ĉi tio venis kiel novaĵo al mi, ĉar mi eĉ ne sciis, ke ekzistas specialaj iloj kaj eĉ tutaj servoj por solvi tiajn problemojn.

Ĉu vi ŝatas vojaĝi?!

Al la simpla kaj sufiĉe sendanĝera demando prezentita en la titolo de ĉi tiu sekcio, oni ofte povas aŭdi pozitivan respondon, akompanata de kelkaj rakontoj el la vojaĝoj de la persono al kiu ĝi estis demandita. Plej multaj el ni konsentus, ke vojaĝi estas bonega maniero mergi vin en novajn kulturajn mediojn kaj plilarĝigi viajn horizontojn. Tamen, se vi demandas iun, ĉu li ŝatas serĉi flugbiletojn, mi certas, ke la respondo ne estos tiel pozitiva. Fakte, Python venas al nia helpo ĉi tie.

La unua tasko, kiun ni devas solvi survoje al kreado de sistemo por serĉi informojn pri flugbiletoj, estos elekti taŭgan platformon, de kiu ni prenos informojn. Solvi ĉi tiun problemon ne estis facila por mi, sed finfine mi elektis la Kajak-servon. Mi provis la servojn de Momondo, Skyscanner, Expedia, kaj kelkaj aliaj, sed la robotprotektaj mekanismoj sur ĉi tiuj rimedoj estis nepenetreblaj. Post pluraj provoj, dum kiuj mi devis trakti semaforojn, piedirajn transirejojn kaj biciklojn, penante konvinki la sistemojn, ke mi estas homo, mi decidis, ke Kajak plej taŭgas por mi, malgraŭ tio, ke eĉ se Tro da paĝoj estas ŝarĝitaj. post mallonga tempo, kaj ankaŭ komenciĝas kontroloj. Mi sukcesis igi la roboton sendi petojn al la retejo je intervaloj de 4 ĝis 6 horoj, kaj ĉio funkciis bone. De tempo al tempo, malfacilaĵoj aperas kiam vi laboras kun Kajak, sed se ili komencas ĝeni vin per ĉekoj, tiam vi devas aŭ trakti ilin permane kaj poste lanĉi la roboton, aŭ atendi kelkajn horojn kaj la ĉekoj ĉesos. Se necese, vi povas facile adapti la kodon por alia platformo, kaj se vi faras tion, vi povas raporti ĝin en la komentoj.

Se vi ĵus komencas kun interreta skrapado kaj ne scias kial iuj retejoj luktas kun ĝi, tiam antaŭ ol vi komencas vian unuan projekton en ĉi tiu areo, faru al vi favoron kaj faru serĉon en Guglo per la vortoj "reteja skrapado" . Viaj eksperimentoj eble finiĝos pli frue ol vi pensas, se vi malprudente faras retajn skrapadon.

Kiel ekuzi

Jen ĝenerala superrigardo pri tio, kio okazos en nia interreta skrapkodo:

  • Importu la bezonatajn bibliotekojn.
  • Malfermante langeton de Google Chrome.
  • Voku funkcion, kiu lanĉas la roboton, pasigante al ĝi la urbojn kaj datojn, kiuj estos uzataj dum serĉado de biletoj.
  • Ĉi tiu funkcio prenas la unuajn serĉrezultojn, ordigitajn laŭ plej bonaj, kaj alklakas butonon por ŝargi pliajn rezultojn.
  • Alia funkcio kolektas datumojn de la tuta paĝo kaj resendas datumkadron.
  • La du antaŭaj paŝoj estas faritaj per ordigaj tipoj laŭ biletprezo (malmultekosta) kaj laŭ flugrapideco (plej rapida).
  • Al la uzanto de la skripto estas sendita retpoŝto enhavante resumon de biletprezoj (plej malmultekostaj biletoj kaj meza prezo), kaj datumkadro kun informoj ordigitaj per la tri supre menciitaj indikiloj estas konservita kiel Excel-dosiero.
  • Ĉiuj ĉi-supraj agoj estas faritaj en ciklo post difinita tempodaŭro.

Oni devas rimarki, ke ĉiu Selenium-projekto komenciĝas per TTT-ŝoforo. Mi uzas Chromedriver, mi laboras kun Google Chrome, sed estas aliaj ebloj. PhantomJS kaj Fajrovulpo ankaŭ estas popularaj. Post elŝuto de la ŝoforo, vi devas meti ĝin en la taŭgan dosierujon, kaj ĉi tio kompletigas la preparadon por ĝia uzo. La unuaj linioj de nia skripto malfermas novan Chrome-langeton.

Memoru, ke en mia rakonto mi ne provas malfermi novajn horizontojn por trovi grandajn ofertojn pri flugbiletoj. Estas multe pli altnivelaj metodoj serĉi tiajn ofertojn. Mi volas nur proponi al la legantoj de ĉi tiu materialo simplan sed praktikan manieron solvi ĉi tiun problemon.

Jen la kodo, pri kiu ni parolis supre.

from time import sleep, strftime
from random import randint
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import smtplib
from email.mime.multipart import MIMEMultipart

# Используйте тут ваш путь к chromedriver!
chromedriver_path = 'C:/{YOUR PATH HERE}/chromedriver_win32/chromedriver.exe'

driver = webdriver.Chrome(executable_path=chromedriver_path) # Этой командой открывается окно Chrome
sleep(2)

Komence de la kodo vi povas vidi la pakajn importkomandojn, kiuj estas uzataj tra nia projekto. Do, randint uzata por igi la roboton "endormi" dum hazarda nombro da sekundoj antaŭ ol komenci novan serĉan operacion. Kutime, eĉ ne unu bot povas malhavi ĉi tion. Se vi rulas la ĉi-supran kodon, Chrome-fenestro malfermos, kiun la roboto uzos por labori kun retejoj.

Ni faru etan eksperimenton kaj malfermu la retejon kayak.com en aparta fenestro. Ni elektos la urbon el kiu ni flugos, kaj la urbon, al kiu ni volas atingi, kaj ankaŭ la flugdatojn. Elektante datojn, certigu, ke la intervalo de +-3 tagoj estas uzata. Mi skribis la kodon konsiderante kion la retejo produktas responde al tiaj petoj. Se, ekzemple, vi bezonas serĉi biletojn nur por specifitaj datoj, tiam estas alta probablo, ke vi devos modifi la bot-kodon. Kiam mi parolas pri la kodo, mi donas taŭgajn klarigojn, sed se vi sentas vin konfuzita, informu min.

Nun alklaku la serĉbutonon kaj rigardu la ligilon en la adresbreto. Ĝi devus esti simila al la ligilo, kiun mi uzas en la malsupra ekzemplo, kie la variablo estas deklarita kayak, kiu konservas la URL, kaj la metodo estas uzata get TTT-ŝoforo. Post klakado de la serĉbutono, rezultoj devus aperi sur la paĝo.

Python - asistanto en trovado de malmultekostaj flugbiletoj por tiuj, kiuj amas vojaĝi
Kiam mi uzis la komandon get pli ol du aŭ tri fojojn ene de kelkaj minutoj, mi estis petita kompletigi konfirmon per reCaptcha. Vi povas pasi ĉi tiun kontrolon permane kaj daŭrigi eksperimenti ĝis la sistemo decidas fari novan kontrolon. Kiam mi testis la skripton, ŝajnis, ke la unua serĉsesio ĉiam iris glate, do se vi volus eksperimenti kun la kodo, vi nur devus periode permane kontroli kaj lasi la kodon funkcii, uzante longajn intervalojn inter serĉsesioj. Kaj, se vi pensas pri tio, homo verŝajne ne bezonos informojn pri biletprezoj ricevitaj je 10-minutaj intervaloj inter serĉaj operacioj.

Laborante kun paĝo uzante XPath

Do, ni malfermis fenestron kaj ŝargis la retejon. Por akiri prezojn kaj aliajn informojn, ni devas uzi XPath-teknologion aŭ CSS-elektilojn. Mi decidis resti kun XPath kaj ne sentis la bezonon uzi CSS-elektilojn, sed estas tute eble tiel funkcii. Navigado ĉirkaŭ paĝo uzante XPath povas esti malfacila, kaj eĉ se vi uzas la teknikojn, kiujn mi priskribis ĉi tio artikolo, kiu implikis kopii la respondajn identigilojn de la paĝa kodo, mi rimarkis, ke tio fakte ne estas la optimuma maniero por aliri la necesajn elementojn. Cetere, en ĉi tio La libro provizas bonegan priskribon de la bazoj pri laboro kun paĝoj uzante XPath kaj CSS-elektilojn. Jen kiel aspektas la responda retŝoformetodo.

Python - asistanto en trovado de malmultekostaj flugbiletoj por tiuj, kiuj amas vojaĝi
Do, ni daŭrigu labori pri la bot. Ni uzu la kapablojn de la programo por elekti la plej malmultekostajn biletojn. En la sekva bildo, la elekta kodo XPath estas elstarigita ruĝe. Por vidi la kodon, vi devas dekstre alklaki la paĝan elementon, pri kiu vi interesiĝas, kaj elekti la komandon Inspekti el la menuo, kiu aperas. Ĉi tiu komando povas esti vokita por malsamaj paĝaj elementoj, kies kodo estos montrita kaj emfazita en la kodo-spektilo.

Python - asistanto en trovado de malmultekostaj flugbiletoj por tiuj, kiuj amas vojaĝi
Vidu paĝan kodon

Por trovi konfirmon de mia rezonado pri la malavantaĝoj de kopii elektilojn el kodo, atentu la jenajn funkciojn.

Jen kion vi ricevas kiam vi kopias la kodon:

//*[@id="wtKI-price_aTab"]/div[1]/div/div/div[1]/div/span/span

Por kopii ion tian, vi devas dekstre alklaki la sekcion de kodo, pri kiu vi interesiĝas, kaj elekti la komandon Kopiu > Kopiu XPath el la menuo, kiu aperas.

Jen kion mi uzis por difini la Plej malmultekosta butonon:

cheap_results = ‘//a[@data-code = "price"]’

Python - asistanto en trovado de malmultekostaj flugbiletoj por tiuj, kiuj amas vojaĝi
Kopiu Komando > Kopiu XPath

Estas sufiĉe evidente, ke la dua opcio aspektas multe pli simpla. Kiam ĝi estas uzata, ĝi serĉas elementon a kiu havas la atributon data-codeegala al price. Kiam oni uzas la unuan opcion, la elemento estas serĉata id kiu egalas al wtKI-price_aTab, kaj la XPath-vojo al la elemento aspektas kiel /div[1]/div/div/div[1]/div/span/span. XPath-demando kiel ĉi tiu al paĝo faros la lertaĵon, sed nur unufoje. Mi povas diri tion nun id ŝanĝos la venontan fojon kiam la paĝo estos ŝarĝita. Sinsekvo de signoj wtKI ŝanĝas dinamike ĉiufoje kiam la paĝo estas ŝarĝita, do la kodo kiu uzas ĝin estos senutila post la sekva paĝo reŝargi. Do prenu iom da tempo por kompreni XPath. Ĉi tiu scio servos al vi bone.

Tamen, oni devas rimarki, ke kopii XPath-elektilojn povas esti utila kiam oni laboras kun sufiĉe simplaj retejoj, kaj se vi estas komforta pri tio, estas nenio malbona pri tio.

Nun ni pensu pri kion fari se vi bezonas ricevi ĉiujn serĉrezultojn en pluraj linioj, ene de listo. Tre simpla. Ĉiu rezulto estas ene de objekto kun klaso resultWrapper. Ŝargado de ĉiuj rezultoj povas esti farita en buklo simila al tiu montrita sube.

Oni devas rimarki, ke se vi komprenas la supre, tiam vi facile komprenu la plej grandan parton de la kodo, kiun ni analizos. Dum ĉi tiu kodo funkcias, ni aliras tion, kion ni bezonas (fakte, la elementon en kiu la rezulto estas envolvita) uzante iun specon de vojo-specifanta mekanismo (XPath). Ĉi tio estas farita por akiri la tekston de la elemento kaj meti ĝin en objekton el kiu datumoj povas esti legitaj (unue uzata flight_containers, tiam - flights_list).

Python - asistanto en trovado de malmultekostaj flugbiletoj por tiuj, kiuj amas vojaĝi
La unuaj tri linioj estas montrataj kaj ni povas klare vidi ĉion, kion ni bezonas. Tamen ni havas pli interesajn manierojn akiri informojn. Ni devas preni datumojn de ĉiu elemento aparte.

Eklaboru!

La plej facila maniero por skribi funkcion estas ŝargi pliajn rezultojn, do tie ni komencos. Mi ŝatus maksimumigi la nombron da flugoj pri kiuj la programo ricevas informojn, sen levi suspektojn en la servo, kiu kondukas al inspektado, do mi alklakas la butonon Ŝargi pli da rezultoj unufoje ĉiufoje kiam la paĝo estas montrata. En ĉi tiu kodo, vi devus atenti la blokon try, kiun mi aldonis ĉar foje la butono ne ĝuste ŝargiĝas. Se vi ankaŭ renkontas ĉi tion, komentu vokojn al ĉi tiu funkcio en la funkciokodo start_kayak, kiun ni rigardos malsupre.

# Загрузка большего количества результатов для того, чтобы максимизировать объём собираемых данных
def load_more():
    try:
        more_results = '//a[@class = "moreButton"]'
        driver.find_element_by_xpath(more_results).click()
        # Вывод этих заметок в ходе работы программы помогает мне быстро выяснить то, чем она занята
        print('sleeping.....')
        sleep(randint(45,60))
    except:
        pass

Nun, post longa analizo de ĉi tiu funkcio (kelkfoje mi povas forlogi), ni pretas deklari funkcion, kiu skrapos la paĝon.

Mi jam kolektis la plej grandan parton de tio, kio necesas en la sekva funkcio nomata page_scrape. Kelkfoje la redonitaj paddatenoj estas kombinitaj, do mi uzas simplan metodon por apartigi ĝin. Ekzemple, kiam mi unuafoje uzas variablojn section_a_list и section_b_list. Nia funkcio resendas datumkadron flights_df, ĉi tio permesas al ni apartigi la rezultojn akiritajn de malsamaj datumordigaj metodoj kaj poste kombini ilin.

def page_scrape():
    """This function takes care of the scraping part"""
    
    xp_sections = '//*[@class="section duration"]'
    sections = driver.find_elements_by_xpath(xp_sections)
    sections_list = [value.text for value in sections]
    section_a_list = sections_list[::2] # так мы разделяем информацию о двух полётах
    section_b_list = sections_list[1::2]
    
    # Если вы наткнулись на reCaptcha, вам может понадобиться что-то предпринять.
    # О том, что что-то пошло не так, вы узнаете исходя из того, что вышеприведённые списки пусты
    # это выражение if позволяет завершить работу программы или сделать ещё что-нибудь
    # тут можно приостановить работу, что позволит вам пройти проверку и продолжить скрапинг
    # я использую тут SystemExit так как хочу протестировать всё с самого начала
    if section_a_list == []:
        raise SystemExit
    
    # Я буду использовать букву A для уходящих рейсов и B для прибывающих
    a_duration = []
    a_section_names = []
    for n in section_a_list:
        # Получаем время
        a_section_names.append(''.join(n.split()[2:5]))
        a_duration.append(''.join(n.split()[0:2]))
    b_duration = []
    b_section_names = []
    for n in section_b_list:
        # Получаем время
        b_section_names.append(''.join(n.split()[2:5]))
        b_duration.append(''.join(n.split()[0:2]))

    xp_dates = '//div[@class="section date"]'
    dates = driver.find_elements_by_xpath(xp_dates)
    dates_list = [value.text for value in dates]
    a_date_list = dates_list[::2]
    b_date_list = dates_list[1::2]
    # Получаем день недели
    a_day = [value.split()[0] for value in a_date_list]
    a_weekday = [value.split()[1] for value in a_date_list]
    b_day = [value.split()[0] for value in b_date_list]
    b_weekday = [value.split()[1] for value in b_date_list]
    
    # Получаем цены
    xp_prices = '//a[@class="booking-link"]/span[@class="price option-text"]'
    prices = driver.find_elements_by_xpath(xp_prices)
    prices_list = [price.text.replace('$','') for price in prices if price.text != '']
    prices_list = list(map(int, prices_list))

    # stops - это большой список, в котором первый фрагмент пути находится по чётному индексу, а второй - по нечётному
    xp_stops = '//div[@class="section stops"]/div[1]'
    stops = driver.find_elements_by_xpath(xp_stops)
    stops_list = [stop.text[0].replace('n','0') for stop in stops]
    a_stop_list = stops_list[::2]
    b_stop_list = stops_list[1::2]

    xp_stops_cities = '//div[@class="section stops"]/div[2]'
    stops_cities = driver.find_elements_by_xpath(xp_stops_cities)
    stops_cities_list = [stop.text for stop in stops_cities]
    a_stop_name_list = stops_cities_list[::2]
    b_stop_name_list = stops_cities_list[1::2]
    
    # сведения о компании-перевозчике, время отправления и прибытия для обоих рейсов
    xp_schedule = '//div[@class="section times"]'
    schedules = driver.find_elements_by_xpath(xp_schedule)
    hours_list = []
    carrier_list = []
    for schedule in schedules:
        hours_list.append(schedule.text.split('n')[0])
        carrier_list.append(schedule.text.split('n')[1])
    # разделяем сведения о времени и о перевозчиках между рейсами a и b
    a_hours = hours_list[::2]
    a_carrier = carrier_list[1::2]
    b_hours = hours_list[::2]
    b_carrier = carrier_list[1::2]

    
    cols = (['Out Day', 'Out Time', 'Out Weekday', 'Out Airline', 'Out Cities', 'Out Duration', 'Out Stops', 'Out Stop Cities',
            'Return Day', 'Return Time', 'Return Weekday', 'Return Airline', 'Return Cities', 'Return Duration', 'Return Stops', 'Return Stop Cities',
            'Price'])

    flights_df = pd.DataFrame({'Out Day': a_day,
                               'Out Weekday': a_weekday,
                               'Out Duration': a_duration,
                               'Out Cities': a_section_names,
                               'Return Day': b_day,
                               'Return Weekday': b_weekday,
                               'Return Duration': b_duration,
                               'Return Cities': b_section_names,
                               'Out Stops': a_stop_list,
                               'Out Stop Cities': a_stop_name_list,
                               'Return Stops': b_stop_list,
                               'Return Stop Cities': b_stop_name_list,
                               'Out Time': a_hours,
                               'Out Airline': a_carrier,
                               'Return Time': b_hours,
                               'Return Airline': b_carrier,                           
                               'Price': prices_list})[cols]
    
    flights_df['timestamp'] = strftime("%Y%m%d-%H%M") # время сбора данных
    return flights_df

Mi provis nomi la variablojn por ke la kodo estu komprenebla. Memoru, ke variabloj komencante per a apartenas al la unua etapo de la vojo, kaj b - al la dua. Ni transiru al la sekva funkcio.

Subtenaj mekanismoj

Ni nun havas funkcion, kiu permesas al ni ŝargi pliajn serĉrezultojn kaj funkcion por prilabori tiujn rezultojn. Ĉi tiu artikolo povus esti finita ĉi tie, ĉar ĉi tiuj du funkcioj provizas ĉion, kion vi bezonas por skrapi paĝojn, kiujn vi povas malfermi mem. Sed ni ankoraŭ ne konsideris kelkajn el la helpaj mekanismoj diskutitaj supre. Ekzemple, ĉi tio estas la kodo por sendi retpoŝtojn kaj iujn aliajn aferojn. Ĉio ĉi troveblas en la funkcio start_kayak, kiun ni nun konsideros.

Por ke ĉi tiu funkcio funkciu, vi bezonas informojn pri urboj kaj datoj. Uzante ĉi tiun informon, ĝi formas ligilon en variablo kayak, kiu estas uzata por konduki vin al paĝo kiu enhavos serĉrezultojn ordigitajn laŭ ilia plej bona kongruo al la demando. Post la unua skrapsesio, ni laboros kun la prezoj en la tabelo ĉe la supro de la paĝo. Nome, ni trovos la minimuman biletprezon kaj la mezan prezon. Ĉio ĉi, kune kun la antaŭdiro elsendita de la retejo, estos sendita retpoŝte. Sur la paĝo, la responda tabelo devus esti en la supra maldekstra angulo. Labori kun ĉi tiu tabelo, cetere, povas kaŭzi eraron dum serĉado uzante precizajn datojn, ĉar ĉi-kaze la tabelo ne estas montrata sur la paĝo.

def start_kayak(city_from, city_to, date_start, date_end):
    """City codes - it's the IATA codes!
    Date format -  YYYY-MM-DD"""
    
    kayak = ('https://www.kayak.com/flights/' + city_from + '-' + city_to +
             '/' + date_start + '-flexible/' + date_end + '-flexible?sort=bestflight_a')
    driver.get(kayak)
    sleep(randint(8,10))
    
    # иногда появляется всплывающее окно, для проверки на это и его закрытия можно воспользоваться блоком try
    try:
        xp_popup_close = '//button[contains(@id,"dialog-close") and contains(@class,"Button-No-Standard-Style close ")]'
        driver.find_elements_by_xpath(xp_popup_close)[5].click()
    except Exception as e:
        pass
    sleep(randint(60,95))
    print('loading more.....')
    
#     load_more()
    
    print('starting first scrape.....')
    df_flights_best = page_scrape()
    df_flights_best['sort'] = 'best'
    sleep(randint(60,80))
    
    # Возьмём самую низкую цену из таблицы, расположенной в верхней части страницы
    matrix = driver.find_elements_by_xpath('//*[contains(@id,"FlexMatrixCell")]')
    matrix_prices = [price.text.replace('$','') for price in matrix]
    matrix_prices = list(map(int, matrix_prices))
    matrix_min = min(matrix_prices)
    matrix_avg = sum(matrix_prices)/len(matrix_prices)
    
    print('switching to cheapest results.....')
    cheap_results = '//a[@data-code = "price"]'
    driver.find_element_by_xpath(cheap_results).click()
    sleep(randint(60,90))
    print('loading more.....')
    
#     load_more()
    
    print('starting second scrape.....')
    df_flights_cheap = page_scrape()
    df_flights_cheap['sort'] = 'cheap'
    sleep(randint(60,80))
    
    print('switching to quickest results.....')
    quick_results = '//a[@data-code = "duration"]'
    driver.find_element_by_xpath(quick_results).click()  
    sleep(randint(60,90))
    print('loading more.....')
    
#     load_more()
    
    print('starting third scrape.....')
    df_flights_fast = page_scrape()
    df_flights_fast['sort'] = 'fast'
    sleep(randint(60,80))
    
    # Сохранение нового фрейма в Excel-файл, имя которого отражает города и даты
    final_df = df_flights_cheap.append(df_flights_best).append(df_flights_fast)
    final_df.to_excel('search_backups//{}_flights_{}-{}_from_{}_to_{}.xlsx'.format(strftime("%Y%m%d-%H%M"),
                                                                                   city_from, city_to, 
                                                                                   date_start, date_end), index=False)
    print('saved df.....')
    
    # Можно следить за тем, как прогноз, выдаваемый сайтом, соотносится с реальностью
    xp_loading = '//div[contains(@id,"advice")]'
    loading = driver.find_element_by_xpath(xp_loading).text
    xp_prediction = '//span[@class="info-text"]'
    prediction = driver.find_element_by_xpath(xp_prediction).text
    print(loading+'n'+prediction)
    
    # иногда в переменной loading оказывается эта строка, которая, позже, вызывает проблемы с отправкой письма
    # если это прозошло - меняем её на "Not Sure"
    weird = '¯_(ツ)_/¯'
    if loading == weird:
        loading = 'Not sure'
    
    username = '[email protected]'
    password = 'YOUR PASSWORD'

    server = smtplib.SMTP('smtp.outlook.com', 587)
    server.ehlo()
    server.starttls()
    server.login(username, password)
    msg = ('Subject: Flight Scrapernn
Cheapest Flight: {}nAverage Price: {}nnRecommendation: {}nnEnd of message'.format(matrix_min, matrix_avg, (loading+'n'+prediction)))
    message = MIMEMultipart()
    message['From'] = '[email protected]'
    message['to'] = '[email protected]'
    server.sendmail('[email protected]', '[email protected]', msg)
    print('sent email.....')

Mi testis ĉi tiun skripton per Outlook-konto (hotmail.com). Mi ne testis ĝin por funkcii ĝuste kun Gmail-konto, ĉi tiu retpoŝta sistemo estas sufiĉe populara, sed estas multaj eblaj opcioj. Se vi uzas Hotmail-konton, tiam por ke ĉio funkciu, vi nur bezonas enigi viajn datumojn en la kodon.

Se vi volas kompreni, kio estas ĝuste farita en specifaj sekcioj de la kodo por ĉi tiu funkcio, vi povas kopii ilin kaj eksperimenti kun ili. Eksperimenti kun la kodo estas la sola maniero por vere kompreni ĝin.

Preta sistemo

Nun kiam ni faris ĉion, pri kio ni parolis, ni povas krei simplan buklon, kiu vokas niajn funkciojn. La skripto petas datumojn de la uzanto pri urboj kaj datoj. Dum testado kun konstanta rekomenco de la skripto, vi verŝajne ne volos enigi ĉi tiujn datumojn permane ĉiufoje, do la respondaj linioj, dum la daŭro de la testado, povas esti komentitaj malkomentante tiujn sub ili, en kiuj la datumoj bezonataj de la skripto estas malmola kodita.

city_from = input('From which city? ')
city_to = input('Where to? ')
date_start = input('Search around which departure date? Please use YYYY-MM-DD format only ')
date_end = input('Return when? Please use YYYY-MM-DD format only ')

# city_from = 'LIS'
# city_to = 'SIN'
# date_start = '2019-08-21'
# date_end = '2019-09-07'

for n in range(0,5):
    start_kayak(city_from, city_to, date_start, date_end)
    print('iteration {} was complete @ {}'.format(n, strftime("%Y%m%d-%H%M")))
    
    # Ждём 4 часа
    sleep(60*60*4)
    print('sleep finished.....')

Jen kiel prova ekzekuto de la skripto aspektas.
Python - asistanto en trovado de malmultekostaj flugbiletoj por tiuj, kiuj amas vojaĝi
Provo de la skripto

Rezultoj

Se vi atingis ĉi tien, gratulon! Vi nun havas funkciantan TTT-skrapilon, kvankam mi jam povas vidi multajn manierojn plibonigi ĝin. Ekzemple, ĝi povas esti integrita kun Twilio tiel ke ĝi sendu tekstmesaĝojn anstataŭ retpoŝtojn. Vi povas uzi VPN aŭ ion alian por samtempe ricevi rezultojn de pluraj serviloj. Ankaŭ estas periode aperanta problemo pri kontrolo de la retejo-uzanto por vidi ĉu li estas homo, sed ĉi tiu problemo ankaŭ povas esti solvita. Ĉiukaze, nun vi havas bazon, kiun vi povas pligrandigi se vi deziras. Ekzemple, certigu, ke Excel-dosiero estas sendita al la uzanto kiel aldonaĵo al retpoŝto.

Python - asistanto en trovado de malmultekostaj flugbiletoj por tiuj, kiuj amas vojaĝi

Nur registritaj uzantoj povas partopreni la enketon. Ensaluti, bonvolu.

Ĉu vi uzas retajn skrapajn teknologiojn?

  • Jes

  • Neniu

Voĉdonis 8 uzantoj. 1 uzanto sindetenis.

fonto: www.habr.com

Aldoni komenton