Python - 旅行好きな人のための格安航空券を見つけるアシスタント

本日その翻訳を公開するこの記事の著者は、その目的は、航空券の価格を検索する、Selenium を使用した Python での Web スクレイパーの開発について語ることであると述べています。 チケットを検索する場合、柔軟な日付が使用されます (指定された日付に対して +- 3 日)。 スクレイパーは検索結果を Excel ファイルに保存し、検索を実行した人に、見つかった内容の概要を記載した電子メールを送信します。 このプロジェクトの目標は、旅行者が最もお得な情報を見つけられるようにすることです。

Python - 旅行好きな人のための格安航空券を見つけるアシスタント

内容を理解していても迷った場合は、こちらをご覧ください。 この 記事。

私たちは何を探していますか?

ここに記載されているシステムはご自由にお使いください。 たとえば、週末のツアーや故郷へのチケットを探すのに使いました。 収益性の高いチケットを真剣に探している場合は、サーバー上でスクリプトを実行できます (単純な サーバ、月額 130 ルーブルで、これには非常に適しています)、XNUMX 日に XNUMX 回か XNUMX 回実行するようにしてください。 検索結果はメールで送信されます。 さらに、スクリプトによって検索結果を含む Excel ファイルが Dropbox フォルダーに保存されるようにすべてを設定することをお勧めします。これにより、いつでもどこからでもそのようなファイルを表示できるようになります。

Python - 旅行好きな人のための格安航空券を見つけるアシスタント
エラーのある関税はまだ見つかっていませんが、可能性はあると思います

すでに述べたように、検索時には「柔軟な日付」が使用され、スクリプトは指定された日付から XNUMX 日以内のオファーを検索します。 スクリプトを実行すると、一方向のオファーのみが検索されますが、複数の飛行方向のデータを収集できるように変更するのは簡単です。 これを利用すると、間違った関税を探すこともでき、そのような発見は非常に興味深いものになります。

なぜ別の Web スクレイパーが必要なのでしょうか?

Webスクレイピングを始めた当初は、正直あまり興味がありませんでした。 私は、予測モデリング、財務分析、そしておそらくテキストの感情的な色彩を分析する分野で、より多くのプロジェクトをやりたいと思っていました。 しかし、Web サイトからデータを収集するプログラムを作成する方法を理解するのは非常に興味深いことがわかりました。 このトピックを掘り下げていくと、Web スクレイピングがインターネットの「エンジン」であることがわかりました。

これは大胆すぎる発言だと思われるかもしれません。 しかし、Google が、ラリー ペイジが Java と Python を使用して作成した Web スクレイパーから始まったことを考えてみましょう。 Google ロボットはインターネットを探索し、ユーザーの質問に対する最良の答えを提供しようとしています。 Web スクレイピングには無限の用途があり、データ サイエンスの他のことに興味がある場合でも、分析に必要なデータを取得するにはある程度のスクレイピング スキルが必要です。

ここで使用されているテクニックのいくつかは素晴らしいものでした その本 最近取得したWebスクレイピングについて。 学んだことを実際に応用するための簡単な例やアイデアが数多く含まれています。 さらに、reCaptcha チェックのバイパスに関する非常に興味深い章があります。 このような問題を解決するための特別なツールやサービス全体があることさえ知らなかったので、これは私にとってニュースでした。

旅行がすきですか?!

このセクションのタイトルにある単純で無害な質問に対しては、質問された人の旅行からのいくつかの物語を伴う肯定的な答えを聞くことができます。 私たちのほとんどは、旅行が新しい文化環境に浸り、視野を広げる素晴らしい方法であることに同意するでしょう。 しかし、航空券を探すのが好きかと聞かれたら、決して肯定的な答えは出ないと思います。 実際、ここでは Python が役に立ちます。

航空券に関する情報を検索するシステムを構築する上で解決しなければならない最初の課題は、情報を取得する適切なプラットフォームを選択することです。 この問題を解決するのは私にとって簡単ではありませんでしたが、最終的にはKayakサービスを選択しました。 Momondo、Skyscanner、Expedia、その他いくつかのサービスを試しましたが、これらのリソースのロボット保護メカニズムは突破できませんでした。 信号機、横断歩道、自転車に対処し、自分が人間であることをシステムに納得させようと何度か試みた結果、読み込まれているページが多すぎても、Kayak が自分に最も適していると判断しました。すぐにチェックも始まります。 ボットに 4 ~ 6 時間の間隔でリクエストをサイトに送信させることができ、すべてがうまくいきました。 Kayak を使用しているときに問題が発生することがありますが、チェックを要求され始めた場合は、手動で対処してからボットを起動するか、数時間待つとチェックが停止するはずです。 必要に応じて、コードを別のプラットフォームに簡単に適合させることができ、その場合はコメントで報告できます。

Web スクレイピングを始めたばかりで、一部の Web サイトが Web スクレイピングに苦労している理由がわからない場合は、この分野で最初のプロジェクトを始める前に、ぜひ「Web スクレイピング エチケット」という言葉で Google 検索してください。 。 Web スクレイピングを下手に行うと、実験が思ったよりも早く終了する可能性があります。

はじめに

Web スクレイパー コードで何が起こるかについての概要は次のとおりです。

  • 必要なライブラリをインポートします。
  • Google Chrome タブを開く。
  • ボットを開始する関数を呼び出し、チケットの検索時に使用される都市と日付を渡します。
  • この関数は、最初の検索結果を最良の順に取得し、ボタンをクリックしてさらに結果を読み込みます。
  • 別の関数はページ全体からデータを収集し、データ フレームを返します。
  • 前の XNUMX つのステップは、航空券の価格 (安い) と飛行速度 (最速) による並べ替えタイプを使用して実行されます。
  • スクリプトのユーザーにはチケット価格の概要 (最安チケットと平均価格) が記載されたメールが送信され、上記の XNUMX つの指標によって分類された情報を含むデータ フレームが Excel ファイルとして保存されます。
  • 上記のアクションはすべて、指定された期間の後にサイクルで実行されます。

すべての Selenium プロジェクトは Web ドライバーから始まることに注意してください。 私が使う クロームドライバー, 私は 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 新しい検索操作を開始する前に、ボットをランダムな秒数の間「スリープ状態」にするために使用されます。 通常、これなしではボットは XNUMX つもありません。 上記のコードを実行すると、ボットがサイトを操作するために使用する Chrome ウィンドウが開きます。

ちょっと実験して、kayak.com Web サイトを別のウィンドウで開いてみましょう。 出発都市、到着希望都市、飛行日を選択します。 日付を選択するときは、+-3 日の範囲が使用されていることを確認してください。 そうしたリクエストに応えてサイトが何を生み出すかを考慮してコードを書きました。 たとえば、指定した日付のチケットのみを検索する必要がある場合は、ボット コードを変更する必要がある可能性が高くなります。 コードについて話すときは適切な説明をしますが、混乱している場合はお知らせください。

次に、検索ボタンをクリックして、アドレス バーのリンクを確認します。 これは、変数が宣言されている以下の例で使用するリンクと似ているはずです。 kayak、URLを保存し、メソッドが使用されます get ウェブドライバー。 検索ボタンをクリックすると、結果がページに表示されます。

Python - 旅行好きな人のための格安航空券を見つけるアシスタント
コマンドを使用したとき get 数分間に 10 ~ XNUMX 回以上、reCaptcha を使用して認証を完了するよう求められました。 このチェックを手動でパスし、システムが新しいチェックの実行を決定するまで実験を続けることができます。 スクリプトをテストしたところ、最初の検索セッションは常にスムーズに行われたように見えました。そのため、コードを試したい場合は、定期的に手動でチェックし、検索セッションの間隔を長くしてコードを実行するだけで済みます。 そして、考えてみると、検索操作の間に XNUMX 分間隔で受信するチケット価格に関する情報が必要になる可能性は低いでしょう。

XPath を使用したページの操作

そこで、ウィンドウを開いてサイトをロードしました。 価格やその他の情報を取得するには、XPath テクノロジまたは CSS セレクターを使用する必要があります。 私は XPath を使い続けることにし、CSS セレクターを使用する必要性を感じませんでしたが、そのように作業することは十分に可能です。 XPath を使用してページ内を移動するのは難しい場合があり、たとえ「」で説明したテクニックを使用したとしても この この記事では、ページのコードから対応する識別子をコピーする必要がありましたが、実際、これが必要な要素にアクセスする最適な方法ではないことに気付きました。 ちなみに、 この この本では、XPath および CSS セレクターを使用したページ操作の基本について優れた説明が提供されています。 対応する Web ドライバー メソッドは次のようになります。

Python - 旅行好きな人のための格安航空券を見つけるアシスタント
それでは、ボットの作業を続けましょう。 プログラムの機能を使用して、最も安いチケットを選択してみましょう。 次の図では、XPath セレクター コードが赤色で強調表示されています。 コードを表示するには、関心のあるページ要素を右クリックし、表示されるメニューから [検査] コマンドを選択する必要があります。 このコマンドはさまざまなページ要素に対して呼び出すことができ、そのコードがコード ビューアで表示および強調表示されます。

Python - 旅行好きな人のための格安航空券を見つけるアシスタント
ページコードを表示

コードからセレクターをコピーすることの欠点についての私の推論の裏付けを見つけるために、次の機能に注目してください。

コードをコピーすると次のようになります。

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

このようなものをコピーするには、興味のあるコードのセクションを右クリックし、表示されるメニューから [コピー] > [XPath のコピー] コマンドを選択する必要があります。

「最安」ボタンを定義するために使用したものは次のとおりです。

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

Python - 旅行好きな人のための格安航空券を見つけるアシスタント
コピーコマンド > XPathのコピー

XNUMX 番目のオプションの方がはるかに単純であることは明らかです。 使用すると、属性を持つ要素 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 - 旅行好きな人のための格安航空券を見つけるアシスタント
最初の XNUMX 行が表示され、必要なものがすべてはっきりとわかります。 しかし、情報を入手するためのもっと興味深い方法があります。 各要素から個別にデータを取得する必要があります。

仕事を始める!

関数を作成する最も簡単な方法は、追加の結果を読み込むことなので、そこから始めます。 検査につながるサービスに疑惑を抱かせることなく、プログラムが情報を受け取るフライトの数を最大化したいので、ページが表示されるたびに [結果をさらに読み込む] ボタンを XNUMX 回クリックします。 このコードでは、ブロックに注意する必要があります。 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 - XNUMX番目へ。 次の関数に進みましょう。

サポートメカニズム

追加の検索結果をロードできる関数と、それらの結果を処理する関数が追加されました。 これら XNUMX つの関数は、自分で開くことができるページをスクレイピングするために必要なものをすべて提供するため、この記事はここで終了することもできます。 しかし、上で説明した補助メカニズムの一部についてはまだ検討していません。 たとえば、これは電子メールやその他のことを送信するためのコードです。 これらすべては関数で見つけることができます 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 - 旅行好きな人のための格安航空券を見つけるアシスタント
スクリプトのテスト実行

結果

ここまで進んだなら、おめでとうございます! これで Web スクレイパーが機能するようになりましたが、これを改善する方法はすでにたくさんあります。 たとえば、Twilio と統合して、電子メールの代わりにテキスト メッセージを送信することができます。 VPN などを使用して、複数のサーバーから同時に結果を受信できます。 また、サイトのユーザーが本人であるかどうかを確認する際に定期的に問題が発生しますが、この問題も解決できます。 いずれにせよ、これで、必要に応じて拡張できる基盤ができました。 たとえば、Excel ファイルが電子メールの添付ファイルとしてユーザーに送信されるようにします。

Python - 旅行好きな人のための格安航空券を見つけるアシスタント

登録ユーザーのみがアンケートに参加できます。 ログインお願いします。

Webスクレイピング技術を使用していますか?

  • はい

  • ノー

8 人のユーザーが投票しました。 1 ユーザーが棄権しました。

出所: habr.com

コメントを追加します