Python - asystent w wyszukiwaniu niedrogich biletów lotniczych dla miłośników podróży

Autor artykułu, którego tłumaczenie dziś publikujemy, twierdzi, że jego celem jest omówienie rozwoju w Pythonie skrobaka sieciowego z wykorzystaniem Selenium, który wyszukuje ceny biletów lotniczych. Przy wyszukiwaniu biletów stosowane są terminy elastyczne (+- 3 dni w stosunku do podanych dat). Skrobak zapisuje wyniki wyszukiwania w pliku Excel i wysyła osobie, która przeprowadziła wyszukiwanie, wiadomość e-mail z podsumowaniem tego, co znalazła. Celem tego projektu jest pomoc podróżnym w znalezieniu najlepszych ofert.

Python - asystent w wyszukiwaniu niedrogich biletów lotniczych dla miłośników podróży

Jeśli w trakcie rozumienia materiału poczujesz się zagubiony, zajrzyj do to artykuł.

Czego szukamy?

Możesz swobodnie korzystać z opisanego tutaj systemu, jak chcesz. Wykorzystałem go na przykład do wyszukiwania weekendowych wycieczek i biletów do mojego rodzinnego miasta. Jeśli poważnie myślisz o znalezieniu dochodowych biletów, możesz uruchomić skrypt na serwerze (proste Serwerza 130 rubli miesięcznie jest do tego całkiem odpowiedni) i upewnij się, że działa raz lub dwa razy dziennie. Wyniki wyszukiwania zostaną przesłane do Ciebie e-mailem. Dodatkowo polecam ustawić wszystko tak, aby skrypt zapisywał plik Excel z wynikami wyszukiwania w folderze Dropbox, co umożliwi przeglądanie takich plików z dowolnego miejsca i o każdej porze.

Python - asystent w wyszukiwaniu niedrogich biletów lotniczych dla miłośników podróży
Nie znalazłem jeszcze taryf z błędami, ale myślę, że jest to możliwe

Przy wyszukiwaniu, jak już wspomniano, stosowana jest „data elastyczna”, skrypt znajduje oferty, które znajdują się w promieniu trzech dni od podanych dat. Choć uruchamiając skrypt wyszukuje oferty tylko w jednym kierunku, łatwo go zmodyfikować tak, aby mógł zbierać dane o kilku kierunkach lotu. Za jego pomocą możesz nawet szukać błędnych taryf, takie znaleziska mogą być bardzo interesujące.

Dlaczego potrzebujesz kolejnego skrobaka sieciowego?

Kiedy po raz pierwszy zacząłem skrobać strony internetowe, szczerze mówiąc, nie byłem tym szczególnie zainteresowany. Chciałem zrobić więcej projektów z zakresu modelowania predykcyjnego, analizy finansowej i ewentualnie z zakresu analizy emocjonalnego zabarwienia tekstów. Okazało się jednak, że bardzo interesujące było wymyślenie, jak stworzyć program zbierający dane ze stron internetowych. Zagłębiając się w ten temat, zdałem sobie sprawę, że web scraping to „silnik” Internetu.

Być może pomyślisz, że to zbyt odważne stwierdzenie. Weź jednak pod uwagę, że Google zaczął od skrobaka sieciowego, który Larry Page stworzył przy użyciu języka Java i Python. Roboty Google eksplorują Internet, starając się zapewnić jego użytkownikom jak najlepsze odpowiedzi na ich pytania. Skrobanie sieci ma nieograniczone zastosowania i nawet jeśli interesuje Cię coś innego w dziedzinie nauki o danych, będziesz potrzebować pewnych umiejętności skrobania, aby uzyskać dane do analizy.

Niektóre z zastosowanych tu technik uważam za cudowne książka o web scrapingu, który niedawno nabyłem. Zawiera wiele prostych przykładów i pomysłów na praktyczne zastosowanie tego, czego się nauczyłeś. Dodatkowo znajduje się bardzo ciekawy rozdział poświęcony omijaniu kontroli reCaptcha. Było to dla mnie nowością, ponieważ nawet nie wiedziałem, że istnieją specjalne narzędzia, a nawet całe usługi rozwiązywania takich problemów.

Czy lubisz podróżować?!

Na proste i raczej nieszkodliwe pytanie postawione w tytule tego rozdziału często można usłyszeć pozytywną odpowiedź, której towarzyszy kilka historii z podróży osoby, której to pytanie zadawano. Większość z nas zgodzi się, że podróżowanie to świetny sposób na zanurzenie się w nowym środowisku kulturowym i poszerzenie horyzontów. Jeśli jednak zapytasz kogoś, czy lubi szukać biletów lotniczych, jestem pewien, że odpowiedź nie będzie tak pozytywna. Tak naprawdę z pomocą przychodzi nam tutaj Python.

Pierwszym zadaniem, jakie musimy rozwiązać na drodze do stworzenia systemu wyszukiwania informacji o biletach lotniczych, będzie wybór odpowiedniej platformy, z której będziemy pobierać informacje. Rozwiązanie tego problemu nie było dla mnie łatwe, ale ostatecznie zdecydowałem się na serwis Kajak. Próbowałem usług Momondo, Skyscanner, Expedia i kilku innych, ale mechanizmy ochrony robotów w tych zasobach były nieprzeniknione. Po kilku próbach, podczas których musiałem radzić sobie z sygnalizacją świetlną, przejściami dla pieszych i rowerami, próbując przekonać systemy, że jestem człowiekiem, zdecydowałem, że Kayak będzie dla mnie najbardziej odpowiedni, pomimo tego, że nawet jeśli załadowanych jest Zbyt wiele stron w krótkim czasie i rozpoczynają się także kontrole. Udało mi się zmusić bota do wysyłania żądań do witryny w odstępach od 4 do 6 godzin i wszystko działało dobrze. Od czasu do czasu podczas pracy z Kayak pojawiają się trudności, ale jeśli zaczną Cię męczyć kontrolami, to musisz albo poradzić sobie z nimi ręcznie, a następnie uruchomić bota, albo poczekać kilka godzin i kontrole powinny ustać. Jeśli zajdzie taka potrzeba, możesz łatwo dostosować kod dla innej platformy, a jeśli to zrobisz, możesz zgłosić to w komentarzach.

Jeśli dopiero zaczynasz przygodę z web scrapingiem i nie wiesz dlaczego niektóre strony internetowe mają z tym problemy, to zanim zaczniesz swój pierwszy projekt w tej dziedzinie, zrób sobie przysługę i wyszukaj w Google słowa „web scraping etykieta” . Twoje eksperymenty mogą zakończyć się wcześniej, niż myślisz, jeśli nierozsądnie wykonasz skrobanie sieci.

Pierwsze kroki

Oto ogólny przegląd tego, co stanie się w naszym kodzie skrobaka sieciowego:

  • Zaimportuj wymagane biblioteki.
  • Otwieranie karty Google Chrome.
  • Wywołaj funkcję uruchamiającą bota, przekazując mu miasta i daty, które będą wykorzystywane przy wyszukiwaniu biletów.
  • Ta funkcja pobiera pierwsze wyniki wyszukiwania, posortowane według najlepszych, a następnie klika przycisk, aby załadować więcej wyników.
  • Inna funkcja zbiera dane z całej strony i zwraca ramkę danych.
  • Dwa poprzednie kroki są wykonywane przy użyciu typów sortowania według ceny biletu (tani) i prędkości lotu (najszybszy).
  • Do użytkownika skryptu wysyłana jest wiadomość e-mail zawierająca zestawienie cen biletów (najtańsze bilety i średnia cena), a ramka danych z informacjami posortowanymi według trzech powyższych wskaźników zapisywana jest w postaci pliku Excel.
  • Wszystkie powyższe czynności wykonywane są cyklicznie po upływie określonego czasu.

Należy zauważyć, że każdy projekt Selenium zaczyna się od sterownika sieciowego. używam Sterownik Chrome, Pracuję w przeglądarce Google Chrome, ale są też inne opcje. Popularne są również PhantomJS i Firefox. Po pobraniu sterownika należy umieścić go w odpowiednim folderze, co kończy przygotowania do jego użycia. Pierwsze linie naszego skryptu otwierają nową kartę Chrome.

Pamiętaj, że w mojej historii nie próbuję otwierać nowych horyzontów w znajdowaniu świetnych okazji na bilety lotnicze. Istnieją znacznie bardziej zaawansowane metody wyszukiwania takich ofert. Chcę tylko zaoferować czytelnikom tego materiału prosty, ale praktyczny sposób rozwiązania tego problemu.

Oto kod, o którym mówiliśmy powyżej.

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)

Na początku kodu widać polecenia importu pakietów, które są używane w całym naszym projekcie. Więc, randint używany do „uśpienia” bota na losową liczbę sekund przed rozpoczęciem nowej operacji wyszukiwania. Zwykle żaden bot nie może się bez tego obejść. Jeśli uruchomisz powyższy kod, otworzy się okno Chrome, którego bot będzie używał do pracy ze stronami.

Zróbmy mały eksperyment i otwórzmy stronę kayak.com w osobnym oknie. Wybierzemy miasto, z którego będziemy latać, miasto, do którego chcemy się dostać, a także termin lotu. Przy wyborze dat należy zwrócić uwagę na zakres +-3 dni. Napisałem kod, biorąc pod uwagę to, co witryna generuje w odpowiedzi na takie żądania. Jeżeli np. będziesz musiał wyszukiwać bilety tylko na określone daty, to z dużym prawdopodobieństwem będziesz musiał zmodyfikować kod bota. Kiedy mówię o kodzie, podaję odpowiednie wyjaśnienia, jeśli jednak poczujesz się zdezorientowany, daj mi znać.

Teraz kliknij przycisk wyszukiwania i spójrz na link w pasku adresu. Powinno być podobne do linku, którego używam w poniższym przykładzie, w którym zadeklarowana jest zmienna kayak, który przechowuje adres URL i metoda jest używana get sterownik sieciowy. Po kliknięciu przycisku wyszukiwania na stronie powinny pojawić się wyniki.

Python - asystent w wyszukiwaniu niedrogich biletów lotniczych dla miłośników podróży
Kiedy użyłem polecenia get więcej niż dwa lub trzy razy w ciągu kilku minut zostałem poproszony o dokończenie weryfikacji za pomocą reCaptcha. Możesz przejść tę kontrolę ręcznie i kontynuować eksperymenty, dopóki system nie zdecyduje się przeprowadzić nowej kontroli. Kiedy testowałem skrypt, wydawało się, że pierwsza sesja wyszukiwania zawsze przebiegała bezproblemowo, więc jeśli chcesz poeksperymentować z kodem, wystarczy okresowo ręcznie sprawdzać i pozwolić kodowi działać, stosując długie odstępy między sesjami wyszukiwania. A jeśli się nad tym zastanowić, jest mało prawdopodobne, aby dana osoba potrzebowała informacji o cenach biletów otrzymywanych w 10-minutowych odstępach między operacjami wyszukiwania.

Praca ze stroną przy użyciu XPath

Otworzyliśmy więc okno i załadowaliśmy witrynę. Aby uzyskać ceny i inne informacje, musimy skorzystać z technologii XPath lub selektorów CSS. Zdecydowałem się pozostać przy XPath i nie czułem potrzeby używania selektorów CSS, ale całkiem możliwe jest działanie w ten sposób. Poruszanie się po stronie za pomocą XPath może być trudne, nawet jeśli użyjesz technik, które opisałem to artykułu, który polegał na skopiowaniu odpowiednich identyfikatorów z kodu strony, zdałem sobie sprawę, że faktycznie nie jest to optymalny sposób dostępu do niezbędnych elementów. Swoją drogą, w to Książka doskonale opisuje podstawy pracy ze stronami wykorzystującymi selektory XPath i CSS. Tak wygląda odpowiednia metoda sterownika internetowego.

Python - asystent w wyszukiwaniu niedrogich biletów lotniczych dla miłośników podróży
Kontynuujmy więc pracę nad botem. Skorzystajmy z możliwości programu i wybierzmy najtańsze bilety. Na poniższej ilustracji kod selektora XPath jest podświetlony na czerwono. Aby obejrzeć kod należy kliknąć prawym przyciskiem myszy na interesującym nas elemencie strony i z menu, które się pojawi wybrać polecenie Sprawdź. Polecenie to można wywołać dla różnych elementów strony, których kod zostanie wyświetlony i podświetlony w przeglądarce kodów.

Python - asystent w wyszukiwaniu niedrogich biletów lotniczych dla miłośników podróży
Wyświetl kod strony

Aby znaleźć potwierdzenie mojego rozumowania o wadach kopiowania selektorów z kodu, zwróć uwagę na następujące cechy.

Oto, co otrzymasz po skopiowaniu kodu:

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

Aby skopiować coś takiego należy kliknąć prawym przyciskiem myszy na interesujący nas fragment kodu i z menu, które się pojawi wybrać polecenie Kopiuj > Kopiuj XPath.

Oto, czego użyłem do zdefiniowania przycisku Najtańszy:

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

Python - asystent w wyszukiwaniu niedrogich biletów lotniczych dla miłośników podróży
Kopiuj polecenie > Kopiuj XPath

Jest całkiem oczywiste, że druga opcja wygląda znacznie prościej. Kiedy jest używany, wyszukuje element a posiadający atrybut data-code, równy price. W przypadku skorzystania z pierwszej opcji następuje przeszukanie elementu id co jest równe wtKI-price_aTabi wygląda ścieżka XPath do elementu /div[1]/div/div/div[1]/div/span/span. Takie zapytanie XPath do strony załatwi sprawę, ale tylko raz. Od razu mogę to powiedzieć id zmieni się przy następnym załadowaniu strony. Sekwencja znaków wtKI zmienia się dynamicznie przy każdym załadowaniu strony, więc kod, który go używa, będzie bezużyteczny po kolejnym przeładowaniu strony. Poświęć więc trochę czasu na zrozumienie XPath. Ta wiedza bardzo Ci się przyda.

Należy jednak zauważyć, że kopiowanie selektorów XPath może być przydatne podczas pracy z dość prostymi witrynami i jeśli czujesz się z tym komfortowo, nie ma w tym nic złego.

Zastanówmy się teraz, co zrobić, jeśli chcesz wyświetlić wszystkie wyniki wyszukiwania w kilku wierszach wewnątrz listy. Bardzo prosta. Każdy wynik znajduje się wewnątrz obiektu z klasą resultWrapper. Ładowanie wszystkich wyników można wykonać w pętli podobnej do pokazanej poniżej.

Należy zaznaczyć, że jeśli rozumiesz powyższe, to powinieneś bez problemu zrozumieć większość kodu, który będziemy analizować. Podczas działania tego kodu uzyskujemy dostęp do tego, czego potrzebujemy (właściwie do elementu, w którym zawinięty jest wynik), korzystając z pewnego rodzaju mechanizmu określania ścieżki (XPath). Odbywa się to w celu pobrania tekstu elementu i umieszczenia go w obiekcie, z którego można odczytać dane (po raz pierwszy użyto flight_containers, Następnie - flights_list).

Python - asystent w wyszukiwaniu niedrogich biletów lotniczych dla miłośników podróży
Wyświetlane są pierwsze trzy linie i wyraźnie widzimy wszystko, czego potrzebujemy. Mamy jednak ciekawsze sposoby pozyskiwania informacji. Musimy pobrać dane z każdego elementu osobno.

Zabrać się do pracy!

Najłatwiejszym sposobem napisania funkcji jest załadowanie dodatkowych wyników i od tego zaczniemy. Chciałbym zmaksymalizować liczbę lotów, o których program otrzymuje informację, nie wzbudzając przy tym podejrzeń w serwisie prowadzącym do kontroli, dlatego przy każdorazowym wyświetleniu strony klikam przycisk Załaduj więcej wyników. W tym kodzie należy zwrócić uwagę na blok try, który dodałem, ponieważ czasami przycisk nie ładuje się poprawnie. Jeśli również to spotkasz, skomentuj wywołania tej funkcji w kodzie funkcji start_kayak, któremu przyjrzymy się poniżej.

# Загрузка большего количества результатов для того, чтобы максимизировать объём собираемых данных
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

Teraz, po długiej analizie tej funkcji (czasami mogę dać się ponieść), jesteśmy gotowi zadeklarować funkcję, która będzie zeskrobywać stronę.

Zebrałem już większość tego, co jest potrzebne w następującej funkcji o nazwie page_scrape. Czasami zwrócone dane ścieżki są łączone, więc używam prostej metody, aby je rozdzielić. Na przykład, gdy używam zmiennych po raz pierwszy section_a_list и section_b_list. Nasza funkcja zwraca ramkę danych flights_dfpozwala to na oddzielenie wyników uzyskanych różnymi metodami sortowania danych i późniejsze ich połączenie.

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

Starałem się nazwać zmienne tak, aby kod był zrozumiały. Pamiętaj, że zmienne zaczynające się od a należą do pierwszego etapu ścieżki i b - do drugiego. Przejdźmy do następnej funkcji.

Mechanizmy wsparcia

Mamy teraz funkcję, która pozwala nam załadować dodatkowe wyniki wyszukiwania i funkcję przetwarzania tych wyników. Ten artykuł mógłby zakończyć się w tym miejscu, ponieważ te dwie funkcje zapewniają wszystko, czego potrzebujesz do zeskrobywania stron, które możesz samodzielnie otworzyć. Nie rozważaliśmy jednak jeszcze niektórych mechanizmów pomocniczych omówionych powyżej. Na przykład jest to kod do wysyłania e-maili i kilku innych rzeczy. Wszystko to można znaleźć w funkcji start_kayak, które teraz rozważymy.

Aby ta funkcja działała potrzebne są informacje o miastach i datach. Korzystając z tych informacji, tworzy łącze w zmiennej kayak, który przenosi Cię na stronę zawierającą wyniki wyszukiwania posortowane według najlepszego dopasowania do zapytania. Po pierwszej sesji skrobania będziemy pracować z cenami w tabeli u góry strony. Mianowicie znajdziemy minimalną cenę biletu i cenę średnią. Wszystko to, wraz z prognozą wystawioną przez serwis, zostanie przesłane e-mailem. Na stronie odpowiednia tabela powinna znajdować się w lewym górnym rogu. Nawiasem mówiąc, praca z tą tabelą może powodować błąd podczas wyszukiwania według dokładnych dat, ponieważ w tym przypadku tabela nie jest wyświetlana na stronie.

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.....')

Przetestowałem ten skrypt przy użyciu konta Outlook (hotmail.com). Nie testowałem go pod kątem poprawnego działania z kontem Gmail, ten system poczty e-mail jest dość popularny, ale możliwych opcji jest wiele. Jeśli korzystasz z konta Hotmail, to aby wszystko działało wystarczy, że w kodzie wpiszesz swoje dane.

Jeśli chcesz zrozumieć, co dokładnie dzieje się w określonych sekcjach kodu tej funkcji, możesz je skopiować i poeksperymentować z nimi. Jedynym sposobem, aby naprawdę go zrozumieć, jest eksperymentowanie z kodem.

Gotowy system

Teraz, gdy już zrobiliśmy wszystko, o czym mówiliśmy, możemy utworzyć prostą pętlę, która wywołuje nasze funkcje. Skrypt żąda od użytkownika danych o miastach i datach. Podczas testowania z ciągłym restartem skryptu jest mało prawdopodobne, że będziesz chciał za każdym razem wprowadzać te dane ręcznie, więc odpowiednie linie na czas testowania można zakomentować, odkomentowując te poniżej, w których znajdują się dane potrzebne skrypt jest zakodowany na stałe.

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.....')

Tak wygląda testowe uruchomienie skryptu.
Python - asystent w wyszukiwaniu niedrogich biletów lotniczych dla miłośników podróży
Uruchomienie testowe skryptu

Wyniki

Jeśli dotarłeś tak daleko, gratulacje! Masz teraz działający skrobak sieciowy, chociaż widzę już wiele sposobów na jego ulepszenie. Można go na przykład zintegrować z Twilio, dzięki czemu zamiast e-maili będzie wysyłać wiadomości tekstowe. Możesz użyć VPN lub czegoś innego, aby jednocześnie otrzymywać wyniki z kilku serwerów. Okresowo pojawia się również problem ze sprawdzeniem użytkownika serwisu, czy jest osobą, ale i ten problem można rozwiązać. W każdym razie teraz masz bazę, którą możesz rozszerzyć, jeśli chcesz. Upewnij się na przykład, że plik Excel zostanie wysłany do użytkownika jako załącznik do wiadomości e-mail.

Python - asystent w wyszukiwaniu niedrogich biletów lotniczych dla miłośników podróży

W ankiecie mogą brać udział tylko zarejestrowani użytkownicy. Zaloguj się, Proszę.

Czy korzystasz z technologii web scrapingu?

  • Tak

  • Nie

Głosowało 8 użytkowników. 1 użytkownik wstrzymał się od głosu.

Źródło: www.habr.com

Dodaj komentarz