Python — помічник у пошуку недорогих авіаквитків для тих, хто любить подорожувати

Автор статті, переклад якої ми публікуємо сьогодні, каже, що її мета – розповісти про розробку веб-скрапера на Python з використанням Selenium, який виконує пошук цін на авіаквитки. Під час пошуку квитків використовуються гнучкі дати (+- 3 дні щодо зазначених дат). Скрапер зберігає результати пошуку в Excel-файлі та відправляє тому, хто його запустив, електронний лист із загальними відомостями про те, що йому вдалося знайти. Завдання цього проекту – допомога мандрівникам у пошуку найбільш вигідних пропозицій.

Python — помічник у пошуку недорогих авіаквитків для тих, хто любить подорожувати

Якщо ви, розбираючись із матеріалом, відчуєте, що загубилися — погляньте на цю статтю.

Що шукатимемо?

Ви вільні скористатися системою, що описується тут так, як вам хочеться. Я, наприклад, застосовував її для пошуку турів вихідного дня та квитків до мого рідного міста. Якщо ви серйозно налаштовані на пошук вигідних квитків, ви можете запустити скрипт на сервері (простий сервер, за 130 рублів на місяць, цілком для цього підійде) і зробити так, щоб він запускався б один-два рази на день. Результати пошуку будуть надходити до вас електронною поштою. Крім того, я рекомендую налаштувати все так, щоб скрипт зберігав Excel-файл з результатами пошуку в Dropbox-папці, що дозволить вам переглядати подібні файли звідки завгодно і в будь-який час.

Python — помічник у пошуку недорогих авіаквитків для тих, хто любить подорожувати
Я поки що не знайшов тарифів з помилками, але вважаю, що це можливо

При пошуку, як було зазначено, використовується «гнучка дата», скрипт знаходить пропозиції, що у межах трьох днів від заданих дат. Хоча під час запуску скрипта виконується пошук пропозицій лише з одному напрямку, його нескладно доопрацювати у тому, щоб міг збирати дані з кількох напрямів польотів. З його допомогою можна навіть шукати помилкових тарифів, подібні знахідки можуть виявитися дуже цікавими.

Навіщо потрібний черговий веб-скрапер?

Коли я вперше зайнявся веб-скрапінгом, мені, щиро кажучи, це було не дуже цікаво. Мені хотілося робити більше проектів у сфері прогнозного моделювання, фінансового аналізу та, можливо, в галузі аналізу емоційного забарвлення текстів. Але виявилося, що це дуже цікаво розбиратися з тим, як створити програму, яка збирає дані з веб-сайтів. У міру того, як я вникав у цю тему, я зрозумів, що саме веб-скрапінг є двигуном інтернету.

Можливо, ви вирішите, що це надто смілива заява. Але подумайте про те, що Google почалася з веб-скрапера, який Ларрі Пейдж створив з використанням Java і Python. Роботи Google досліджували та досліджують інтернет, намагаючись надати своїм користувачам найкращі відповіді на їхні запитання. У веб-скрапінгу є безліч варіантів застосування, і навіть якщо вам, у сфері Data Science, цікаво щось інше, то вам, щоб обзавестися даними для аналізу, знадобляться деякі навички скрапінгу.

Деякі з використаних тут прийомів я знайшов у чудовій книзі про веб-скрапінг, який нещодавно обзавівся. У ній можна знайти безліч простих прикладів та ідей щодо практичного застосування вивченого. Крім того, там є дуже цікавий розділ про обхід перевірок reCaptcha. Для мене це стало новиною, тому що я не знав про те, що існують спеціальні інструменти і навіть цілі сервіси для вирішення подібних завдань.

Любите мандрувати?!

На просте і досить нешкідливе запитання, винесене в заголовок цього розділу, часто можна почути позитивну відповідь, з парою історій з подорожей того, кому її поставили. Більшість із нас погодиться з тим, що подорожі — це чудовий спосіб занурення в нові культурні середовища та розширення власного кругозору. Однак якщо поставити комусь питання про те, чи подобається йому шукати авіаквитки, то я впевнений, що відповідь на нього буде вже далеко не такою позитивною. Власне, тут нам на допомогу приходить Python.

Першим завданням, яке нам треба вирішити на шляху створення системи пошуку відомостей щодо авіаквитків, буде підбір відповідної платформи, з якої ми братимемо інформацію. Вирішення цього завдання далося мені нелегко, але в результаті я вибрав сервіс Kayak. Я пробував послуги Momondo, Skyscanner, Expedia, та й ще деякі, але механізми захисту від роботів на цих ресурсах були непробивними. Після кількох спроб, у ході яких, намагаючись переконати системи в тому, що я людина, мені довелося мати справу зі світлофорами, пішохідними переходами та велосипедами, я вирішив, що мені найбільше підходить Kayak, незважаючи навіть на те, що й тут, якщо за короткий час завантажити багато сторінок, теж починаються перевірки. Мені вдалося зробити так, щоб бот відправляв запити до сайту в інтервалах від 4 до 6 годин, і все нормально працювало. Періодично складнощі виникають і при роботі з Kayak, але якщо вас починають дошкуляти перевірками, то вам потрібно або розібратися з ними вручну, після чого запустити бот або почекати кілька годин, і перевірки повинні припинитися. Ви, якщо потрібно, цілком можете адаптувати код для іншої платформи, а якщо ви так і зробите, можете повідомити про це в коментарях.

Якщо ви тільки починаєте знайомство з веб-скрапінгом, і не знаєте про те, чому деякі веб-сайти всіма силами з ним борються, то, перш ніж приступати до свого першого проекту в цій галузі, надайте послугу і пошукайте в Google матеріали за словами "web scraping etiquette". Ваші експерименти можуть завершитися швидше, ніж ви думаєте, якщо ви будете займатися веб-скрапінгом нерозумно.

Початок роботи

Ось загальний огляд того, що відбуватиметься в коді нашого веб-скрапера:

  • Імпорт необхідні бібліотеки.
  • Відкривання вкладки Google Chrome.
  • Виклик функції, яка запускає бота, передаючи йому міста та дати, які використовуватимуться для пошуку квитків.
  • Ця функція отримує перші результати пошуку, відсортовані за критерієм найбільшої привабливості (best), та натискає кнопку для завантаження додаткових результатів.
  • Ще одна функція збирає дані з усієї сторінки та повертає кадр даних.
  • Два попередні кроки виконуються з використанням типів сортування за ціною квитків (cheap) та за швидкістю перельоту (fastest).
  • Користувачеві скрипту надсилається електронний лист, що містить коротке зведення про ціни квитків (найдешевші квитки та середня ціна), а кадр даних зі відомостями, відсортованими за трьома вищезгаданими показниками, зберігається у вигляді Excel-файлу.
  • Усі вищеописані дії виконуються у циклі через заданий проміжок часу.

Слід зазначити, що кожен проект Selenium починається з веб-драйвера. Я використовую Chromedriver, працюю з Google Chrome, але є інші варіанти. Популярністю користуються ще PhantomJS та Firefox. Після завантаження драйвера його потрібно помістити у відповідну папку, на цьому підготовка до використання закінчується. У перших рядках нашого скрипта здійснюється відкриття нової вкладки Chrome.

Зважайте на те, що я, у своєму оповіданні, не намагаюся відкрити нові горизонти пошуку вигідних пропозицій на авіаквитки. Існують і набагато просунутіші прийоми пошуку подібних пропозицій. Мені лише хочеться запропонувати читачам цього матеріалу простий, але застосовний на практиці спосіб вирішення цього завдання.

Ось код, про який ми говорили вище.

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)

На початку коду можна побачити команди імпорту пакетів, які використовуються у всьому нашому проекті. Так, randint застосовується для того, щоб бот «засинав» на випадкове число секунд перш ніж почати нову операцію пошуку. Зазвичай без цього не обходиться жоден бот. Якщо запустити вищенаведений код, буде відкрито вікно Chrome, яке буде використовувати бот для роботи з сайтами.

Проведемо невеликий експеримент та відкриємо в окремому вікні сайт kayak.com. Виберемо місто, з якого збираємося летіти, та місто, в яке хочемо потрапити, а також дати польотів. При виборі дат перевіримо, щоб використовувався діапазон +-3 дні. Я написав код з урахуванням того, що видає сайт у відповідь на такі запити. Якщо ж вам, наприклад, потрібно шукати квитки тільки на задані дати, то висока ймовірність того, що вам доведеться доопрацювати код бота. Розповідаючи про код, я роблю відповідні пояснення, але якщо ви відчуєте, що заплуталися, дайте мені знати.

Тепер натискаємо кнопку запуску пошуку і дивимося на посилання в рядку адреси. Вона має бути схожа на те посилання, яке я використовую в прикладі нижче, там, де оголошується змінна kayak, що зберігає URL, і використовується метод get веб-драйвер. Після натискання на кнопку пошуку на сторінці мають з'явитися результати.

Python — помічник у пошуку недорогих авіаквитків для тих, хто любить подорожувати
Коли я використав команду get більше двох-трьох разів протягом кількох хвилин мені пропонували пройти перевірку з використанням reCaptcha. Цю перевірку можна пройти вручну та продовжити експерименти доти, доки система не вирішить влаштувати нову перевірку. Коли я тестував скрипт, виникло таке відчуття, що перший сеанс пошуку завжди проходить без проблем, тому, якщо ви хочете поекспериментувати з кодом, вам доведеться лише періодично вручну проходити перевірку та залишати код виконуватися, використовуючи тривалі інтервали між сеансами пошуку. Та й якщо вдуматися, людині навряд чи знадобляться відомості про ціни на квитки, отримані з 10-хвилинними інтервалами між операціями пошуку.

Робота зі сторінкою з використанням XPath

Отже, ми відкрили вікно та завантажили сайт. Для того, щоб отримати відомості про ціни та іншу інформацію, нам потрібно скористатися технологією XPath або CSS-селекторами. Я вирішив зупинитися на XPath і не відчув потреби застосування CSS-селекторів, але цілком можливо працювати і так. Переміщення по сторінці з використанням XPath може виявитися непростою справою, і навіть якщо ви скористаєтеся тими методами, які я описував у цій статті, де застосовувалося копіювання відповідних ідентифікаторів із коду сторінки, я зрозумів, що це, насправді, не є оптимальним способом звернення до необхідних елементів. До речі, у цій Книзі можна знайти чудовий опис основ роботи зі сторінками з використанням XPath та CSS-селекторів. Ось як виглядає відповідний метод веб-драйвера.

Python — помічник у пошуку недорогих авіаквитків для тих, хто любить подорожувати
Отже, продовжуємо роботу над ботом. Скористайтеся можливостями програми для вибору найдешевших квитків. На наступному зображенні червоним виділено код селектора XPath. Для того щоб переглянути код, потрібно клацнути правою кнопкою миші по цікавому для вас елементу сторінки і в меню вибрати команду Переглянути код (Inspect). Цю команду можна викликати для різних елементів сторінки, код яких виводитиметься і виділятиметься у вікні перегляду коду.

Python — помічник у пошуку недорогих авіаквитків для тих, хто любить подорожувати
Перегляд коду сторінки

Щоб знайти підтвердження моїм міркуванням про недоліки копіювання селекторів з коду, зверніть увагу на такі особливості.

Ось що виходить при копіюванні коду:

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

Для того щоб скопіювати щось подібне, потрібно клацнути правою кнопкою миші по ділянці коду, що вас цікавить, і вибрати в меню, що з'явилося, команду Copy > Copy XPath.

Ось що я використав для визначення кнопки Cheapest:

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

Python — помічник у пошуку недорогих авіаквитків для тих, хто любить подорожувати
Команда Copy > Copy XPath

Очевидно те, що другий варіант виглядає набагато простіше. При його використанні виконується пошук елемента a, який має атрибут. data-code, рівний price. При використанні першого варіанта здійснюється пошук елемента id якого дорівнює wtKI-price_aTab, при цьому XPath-шлях до елемента виглядає як /div[1]/div/div/div[1]/div/span/span. Подібний XPath-запит до сторінки зробить свою справу, але лише один раз. Я просто зараз можу сказати, що id зміниться під час наступного завантаження сторінки. Послідовність символів wtKI динамічно змінюється при кожному завантаженні сторінки, в результаті код, в якому вона використовується після чергового перезавантаження сторінки виявиться марним. Тому приділіть деякий час, щоб розібратися з XPath. Ці знання будуть вам хорошу службу.

Однак треба відзначити, що копіювання XPath-селекторів може стати в нагоді при роботі з досить простими сайтами, і якщо вас це влаштовує, нічого поганого в цьому немає.

Тепер подумаємо про те, як бути, якщо потрібно отримати всі результати пошуку в декількох рядках у списку. Дуже просто. Кожен результат знаходиться всередині об'єкта з класом resultWrapper. Завантаження всіх результатів може бути виконано в циклі, що нагадує те, що буде показано нижче.

Треба зазначити, що якщо вам зрозуміло вищесказане, то ви без проблем повинні зрозуміти більшість коду, який ми розбиратимемо. У ході роботи цього коду до того, що нам потрібно (фактично це елемент, в який обернуть результат), ми звертаємося з використанням якогось механізму для вказівки шляху (XPath). Робиться це для того, щоб отримати текст елемента і помістити його в об'єкт, з якого можна зчитувати дані (спочатку використовується flight_containers, потім - flights_list).

Python — помічник у пошуку недорогих авіаквитків для тих, хто любить подорожувати
Виводяться перші три рядки, і ми можемо чітко бачити все, що нам потрібно. Однак у нас є й цікавіші способи отримання інформації. Нам потрібно брати дані з кожного елемента окремо.

За роботу!

Найлегше написати функцію для завантаження додаткових результатів, тому з неї і почнемо. Мені хотілося б максимізувати кількість перельотів, відомості про які отримує програма, і при цьому не викликати у сервісу підозри, що призводять до перевірки, тому я одного разу натискаю на кнопку Load more results кожного разу, коли відображається сторінка. У цьому коді варто звернути увагу на блок try, що я додав через те, що іноді кнопка нормально не завантажується. Якщо ви також з цим зіткнетеся - закоментуйте виклики цієї функції в коді функції start_kayakми розглянемо нижче.

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

Тепер, після тривалого розбирання цієї функції (іноді я можу і захопитися), ми готові до того, щоб оголосити функцію, яка займатиметься скрапінгом сторінки.

Я вже зібрав більшу частину того, що потрібно, у наступній функції, яка називається page_scrape. Іноді дані про етапи шляху, що повертаються, виявляються об'єднаними, для їх поділу я використовую простий метод. Наприклад, коли вперше користуюся змінними section_a_list и section_b_list. Наша функція повертає кадр даних flights_dfЦе дозволяє нам розділити результати, отримані при використанні різних методів сортування даних, а пізніше - об'єднати їх.

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

Я постарався називати змінні так, щоб код виявився зрозумілим. Пам'ятайте про те, що змінні, що починаються з a відносяться до першого етапу шляху, а b - До другого. Переходимо до наступної функції.

Допоміжні механізми

Тепер ми маємо функцію, яка дозволяє завантажувати додаткові результати пошуку та функцію для обробки цих результатів. Цю статтю на цьому можна було б закінчити, оскільки ці дві функції дають все необхідне для скрапінгу сторінок, які можна відкривати самостійно. Але ми поки що не розглянули деякі допоміжні механізми, про які говорили вище. Наприклад, це код для надсилання електронних листів і ще деякі речі. Все це можна знайти у функції start_kayak, яку ми зараз розглянемо.

Для роботи цієї функції потрібні відомості про міста та дати. Вона, використовуючи ці відомості, формує посилання у змінній kayak, яка використовується для переходу на сторінку, на якій будуть знаходитись результати пошуку, відсортовані за їх найкращою відповідністю запиту. Після першого сеансу скрапінгу ми попрацюємо з цінами, що у таблиці, у верхній частині сторінки. А саме, знайдемо мінімальну ціну квитка та середню ціну. Все це, разом з прогнозом, що видається сайтом, буде надіслано електронною поштою. На сторінці відповідна таблиця має бути у лівому верхньому кутку. Робота з цією таблицею, до речі, може викликати помилку під час пошуку з використанням точних дат, оскільки у разі таблиця на сторінці не виводиться.

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

Я протестував цей скрипт за допомогою облікового запису Outlook (hotmail.com). Я не перевіряв його на правильність роботи з обліковим записом Gmail, ця поштова система дуже популярна, але є безліч можливих варіантів. Якщо ж ви використовуєте обліковий запис Hotmail, то вам, для того, щоб все запрацювало, достатньо ввести в код свої дані.

Якщо ви хочете розібратися з тим, що саме виконується в окремих ділянках коду цієї функції, ви можете скопіювати їх та поекспериментувати з ними. Експерименти з кодом - це єдиний спосіб, як слід його зрозуміти.

Готова система

Тепер, коли зроблено все те, що ми говорили, ми можемо створити простий цикл, у якому викликаються наші функції. Скрипт запитує у користувача дані про міста та дати. При тестуванні з постійним перезапуском скрипту вам навряд чи захочеться щоразу вводити ці дані вручну, тому відповідні рядки, на час тестування, можна закоментувати, розкоментувавши ті, що йдуть нижче за них, у яких жорстко задані потрібні скрипту дані.

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

Ось як виглядає тестовий запуск сценарію.
Python — помічник у пошуку недорогих авіаквитків для тих, хто любить подорожувати
Тестовий запуск скрипту

Підсумки

Якщо ви дісталися до цього моменту – прийміть вітання! Тепер у вас є робочий веб-скрапер, хоча я вже бачу безліч шляхів його покращення. Наприклад, його можна інтегрувати з Twilio, щоб він замість електронних листів надсилав би текстові повідомлення. Можна скористатися VPN або ще чимось для того, щоб одночасно отримувати результати з кількох серверів. Є ще й періодично проблема з перевіркою користувача сайту на те, чи є він людиною, але і цю проблему можна вирішити. У будь-якому випадку тепер у вас є база, яку ви, за бажання, можете розширювати. Наприклад, зробити так, щоб Excel-файл відправлявся користувачеві у вигляді вкладення в електронний лист.

Python — помічник у пошуку недорогих авіаквитків для тих, хто любить подорожувати

Тільки зареєстровані користувачі можуть брати участь в опитуванні. Увійдіть, будь ласка.

Чи використовуєте ви технології веб-скрапінгу?

  • Так

  • Ні

Проголосували 8 користувачів. Утримався 1 користувач.

Джерело: habr.com

Додати коментар або відгук