介紹

在本教程中, 我將向您展示如何使用 python 自動瀏覽像 expedia 這樣的網站, 每小時查找航班, 并將您想要的特定路線的最佳航班費率直接發送到您的電子郵件。

最終的結果是這個不錯的電子郵件:

我們將按以下方式開展工作:

  1. 將 python 連接到我們的 web 瀏覽器并訪問網站 (在我們的示例中的 expedia)。
  2. 根據我們的喜好選擇機票類型 (往返、單程等)。
  3. 選擇出發國家/地區。
  4. 選擇到達國家/地區 (如果是往返)。
  5. 選擇出發和返回日期。
  6. 以結構化格式編譯所有可用的航班 (對于那些喜歡做一些探索性數據分析的人!)
  7. 連接到您的電子郵件。
  8. 發送當前小時的最佳費率。

讓我們開始吧!

導入庫

讓我們繼續導入我們的庫:

硒 (用于訪問網站和自動化測試):

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

熊貓 (我們將主要使用熊貓來構建我們的數據):

import pandas as pd

時間和日期時間 (對于使用延遲和返回當前時間, 我們稍后將看到原因):

import time
import datetime

我們需要那些連接到我們的電子郵件和發送我們的消息:

import smtplib
from email.mime.multipart import MIMEMultipart

注意: 我不會去太深入到使用硒的網絡刮擦, 但如果你想一個更詳細的教程刮一般檢查我以前的教程刮使用和網絡刮在一般的第1部分和第2部分.

讓我們開始編碼

連接到 web 瀏覽器

browser = webdriver.Chrome(executable_path='/chromedriver')

這將打開一個空瀏覽器, 告訴您此瀏覽器是由自動測試軟件控制的, 如下所示:

選擇門票

接下來, 我將快速轉到expedia來檢查接口和可供選擇的選項。

我點擊右鍵單擊 + 檢查票證類型 (往返, 單向等), 以查看與之相關的標簽。

正如我們可以看到下面, 它有一個“標簽” 標簽與 “id = 飛行類型往返標簽-hp 飛行”bp.blogspot.com/-SuaMYMOjBqc/XFbGjGbE47I/AAAAAAAAAYU/WRV-yOj2SH8PRDZsX-BltuXEv12hi5p8QCLcBGAs/s1600/Screen%2BShot%2B2019-02-03%2Bat%2B12.45.28%2BPM.png “rel =” nofollow “> >

因此, 我將使用它們來存儲三種不同的票證類型的標記和 id, 如下所示:

#Setting ticket types paths
return_ticket = "http://label[@id='flight-type-roundtrip-label-hp-flight']"
one_way_ticket = "http://label[@id='flight-type-one-way-label-hp-flight']"
multi_ticket = "http://label[@id='flight-type-multi-dest-label-hp-flight']"

然后定義一個函數來選擇票證類型:

def ticket_chooser(ticket):

    try:
        ticket_type = browser.find_element_by_xpath(ticket)
        ticket_type.click()
    except Exception as e:
        pass

上面的順序和我將用于其余代碼的順序相同 (查找標記和 id 或其他屬性, 并定義一個函數來在網頁上進行選擇)。

選擇出發和到達的國家

下面我定義了一個函數來選擇出發的國家。

def dep_country_chooser(dep_country):
    fly_from = browser.find_element_by_xpath("http://input[@id='flight-origin-hp-flight']")
    time.sleep(1)
    fly_from.clear()
    time.sleep(1.5)
    fly_from.send_keys('  ' + dep_country)
    time.sleep(1.5)
    first_item = browser.find_element_by_xpath("http://a[@id='aria-option-0']")
    time.sleep(1.5)
    first_item.click()

我遵循以下邏輯:

  1. 使用元素的標記和屬性查找元素。
  2. 清除在國家/地區字段中寫入的任何值。
  3. 鍵入我想要的國家 (將傳遞到函數中) .sendkeys 使用。
  4. 從下拉菜單中選擇出現的第一個選項 (也使用它的標記和 id, 當下拉菜單出現時, 右鍵單擊 “+ 檢查” 元素即可找到該標記和 id)。
  5. 單擊此首選項。

請注意, 我使用 time.sleep 的是步驟之間的機會, 以便為頁面的元素提供一個機會, 以便在步驟之間進行更新。如果沒有 time.sleep ,有時我們的腳本的作用比頁面加載的速度更快, 從而嘗試訪問尚未加載的元素, 從而導致我們的代碼中斷。

讓我們為抵達國做同樣的事情。

def arrival_country_chooser(arrival_country):
    fly_to = browser.find_element_by_xpath("http://input[@id='flight-destination-hp-flight']")
    time.sleep(1)
    fly_to.clear()
    time.sleep(1.5)
    fly_to.send_keys('  ' + arrival_country)
    time.sleep(1.5)
    first_item = browser.find_element_by_xpath("http://a[@id='aria-option-0']")
    time.sleep(1.5)
    first_item.click()

選擇出發和返回日期

出發日期:

def dep_date_chooser(month, day, year):

    dep_date_button = browser.find_element_by_xpath("http://input[@id='flight-departing-hp-flight']")
    dep_date_button.clear()
    dep_date_button.send_keys(month + '/' + day + '/' + year)

非常直的前進:

  1. 像以前一樣在網頁上查找元素。
  2. 清除以前編寫的內容。
  3. 用在函數中輸入的月、日和年份作為參數填充元素 + 日期格式的斜杠。

返回日期:

def return_date_chooser(month, day, year):
    return_date_button = browser.find_element_by_xpath("http://input[@id='flight-returning-hp-flight']")

    for i in range(11):
        return_date_button

返回 _ date _ 需結束. 發送 _ key (月 + “/” + day + “/”/”年)

對于返回日期, 清除所寫內容由于某種原因而不起作用 (可能是由于頁面有此自動填充不允許我覆蓋它 .clear() )

我解決這個問題的方法是使用 Keys.BACKSPACE 它, 它只是告訴 python 單擊后空間 (刪除日期字段中寫的任何內容)。我把它放在一個 for 循環中, 單擊后空間 11次, 以刪除字段中日期的所有字符。

獲取結果

定義將單擊搜索按鈕的函數。

def search():
    search = browser.find_element_by_xpath("http://button[@class='btn-primary btn-action gcw-submit']")
    search.click()
    time.sleep(15)
    print('Results ready!')

在這里, 最好使用15秒左右的長延遲, 以確保在我們繼續下一步之前加載所有結果。

生成的網頁如下 (標記了我感興趣的字段):

編譯數據

我們將使用此序列來編譯我們的數據:

  1. 首先, 創建一個熊貓數據框架來保存我們的數據。
  2. 為要存儲在列表中的所有飛行屬性 (在上圖中突出顯示) 創建變量。
  3. 查找屬性的所有元素 (例如, 所有出發時間)。
  4. 將它們存儲在我們創建的相關變量中作為列表。
  5. 將所有這些列表并排放在我們的數據框架中的列中。
  6. 將數據框架保存到 excel 工作表 (以防我們稍后分析此數據)。

下面是代碼:

df = pd.DataFrame()
def compile_data():
    global df
    global dep_times_list
    global arr_times_list
    global airlines_list
    global price_list
    global durations_list
    global stops_list
    global layovers_list


    #departure times
    dep_times = browser.find_elements_by_xpath("http://span[@data-test-id='departure-time']")
    dep_times_list = [value.text for value in dep_times]


    #arrival times
    arr_times = browser.find_elements_by_xpath("http://span[@data-test-id='arrival-time']")
    arr_times_list = [value.text for value in arr_times]


    #airline name
    airlines = browser.find_elements_by_xpath("http://span[@data-test-id='airline-name']")
    airlines_list = [value.text for value in airlines]


    #prices
    prices = browser.find_elements_by_xpath("http://span[@data-test-id='listing-price-dollars']")
    price_list = [value.text.split('$')[1] for value in prices]


    #durations
    durations = browser.find_elements_by_xpath("http://span[@data-test-id='duration']")
    durations_list = [value.text for value in durations]


    #stops
    stops = browser.find_elements_by_xpath("http://span[@class='number-stops']")
    stops_list = [value.text for value in stops]


    #layovers
    layovers = browser.find_elements_by_xpath("http://span[@data-test-id='layover-airport-stops']")
    layovers_list = [value.text for value in layovers]


    now = datetime.datetime.now()
    current_date = (str(now.year) + '-' + str(now.month) + '-' + str(now.day))
    current_time = (str(now.hour) + ':' + str(now.minute))
    current_price = 'price' + '(' + current_date + '---' + current_time + ')'
    for i in range(len(dep_times_list)):
        try:
            df.loc[i, 'departure_time'] = dep_times_list[i]
        except Exception as e:
            pass
        try:
            df

loc [i, ‘ 航空公司 ‘] = 航空公司 _ list [i] 除了例外作為 e: 通過嘗試: df. loc [i, ‘ 期限 ‘] = 持續時間 _ list [i] 除了例外 e: 通過嘗試: df. loc [i, ‘ 停止 ‘] = 停止 _ list [i] 除了例外作為 e: 通過嘗試: df. loc [i, ‘ layovers ‘ = layovers _ list [i] 除了例外作為 e: 通過嘗試: df. loc [i, str (當前價格)] = 價目表 _ list [i] 除了例外作為 e: 通行證 print (“excel 工作表已創建!”)

值得一提的是, 對于價格列, 每次使用此代碼段運行代碼時, 我都會重命名它:

now = datetime.datetime.now()
current_date = (str(now.year) + '-' + str(now.month) + '-' + str(now.day))
current_time = (str(now.hour) + ':' + str(now.minute))
current_price = 'price' + '(' + current_date + '---' + current_time + ')'

這是因為我希望有列的標題, 說明該特定運行的當前時間, 以便以后能夠看到價格如何隨著時間的推移而變化, 以防我想這樣做。

設置我們的電子郵件功能

在這一部分中, 我將設置三個函數:

  • 一個連接到我的電子郵件。
  • 一個用于創建消息。
  • 最后一個實際發送它。

首先, 我還需要將我的電子郵件登錄憑據存儲在兩個變量中, 如下所示:

#email credentials
username = '[email protected]'
password = 'XXXXXXXXXXX'

連接

def connect_mail(username, password):
    global server
    server = smtplib.SMTP('smtp.outlook.com', 587)
    server.ehlo()
    server.starttls()
    server.login(username, password)

創建消息

#Create message template for email
def create_msg():
    global msg
    msg = '\nCurrent Cheapest flight:\n\nDeparture time: {}\nArrival time: {}\nAirline: {}\nFlight duration: {}\nNo. of stops: {}\nPrice: {}\n'.format(cheapest_dep_time,
                       cheapest_arrival_time,
                       cheapest_airline,
                       cheapest_duration,
                       cheapest_stops,
                       cheapest_price)

在這里, 我使用占位符 {} “創建消息, 以便在每次運行期間傳入的值。

此外, 這里使用的變量, cheapest_arrival_time cheapest_airline 如, 等等, 將被定義后, 當我們開始運行我們的所有函數, 以保持每個特定運行的值。

發送消息

def send_email(msg):
    global message
    message = MIMEMultipart()
    message['Subject'] = 'Current Best flight'
    message['From'] = '[email protected]'
    message['to'] = '[email protected]'

    server.sendmail('[email protected]', '[email protected]', msg)

讓我們運行我們的代碼!

現在我們將最終運行我們的函數。我們將使用下面的邏輯。

數據刮除部分:

  1. 訪問我們的 expedia 鏈接, 并睡 5秒, 以允許加載頁面。
  2. 選擇 “僅限航班”, 因為我目前對航班加酒店等其他優惠不感興趣。
  3. 運行我們的機票選擇功能的回程機票。
  4. 運行我們的出發國家選擇器 (為開羅, 因為這是我目前所在的地方)。
  5. 運行我們的到達國家選擇器 (讓我們做紐約)。
  6. 運行我們的出發日期選擇器 (最好在您的月份或日期之前放置零, 例如, 1月為 01, 因為這是 expedia 使用的格式)。
  7. 運行返回日期選擇器。
  8. 運行我們的搜索和編譯功能。

電子郵件部分:

  1. 訪問我們的 datframe 的第一行, 因為通常情況下, 第一班航班是 expedia 上最便宜、最好的, 但如果我們想走得更遠, 我們可以按最低價格進行過濾, 得到那一行

)

  • 運行我們的電子郵件功能以創建郵件、連接和發送電子郵件。
  • 最后, 我們將數據框架保存到 excel 工作表中, 并在 3600秒 (1小時) 內休眠。

    此循環將每小時運行 8次, 因此將運行8小時。你可以根據自己的喜好調整時機。

    for i in range(8):    
        link = 'https://www.expedia.com/'
        browser.get(link)
        time.sleep(5)
    
        #choose flights only
        flights_only = browser.find_element_by_xpath("http://button[@id='tab-flight-tab-hp']")
        flights_only.click()
    
        ticket_chooser(return_ticket)
    
        dep_country_chooser('Cairo')
    
        arrival_country_chooser('New york')
    
        dep_date_chooser('04', '01', '2019')
    
        return_date_chooser('05', '02', '2019')
    
        search()
    
        compile_data()
    
        #save values for email
        current_values = df.iloc[0]
    
        cheapest_dep_time = current_values[0]
        cheapest_arrival_time = current_values[1]
        cheapest_airline = current_values[2]
        cheapest_duration = current_values[3]
        cheapest_stops = current_values[4]
        cheapest_price = current_values[-1]
    
    
        print('run {} completed!'.format(i))
    
        create_msg()
        connect_mail(username,password)
        send_email(msg)
        print('Email sent!')
    
        df.to_excel('flights.xlsx')
    
        time.sleep(3600)

    現在, 我將得到這封電子郵件每小時在接下來的 8小時:

    我也有這個整潔的 excel 表與所有的航班, 它將不斷更新每小時與一個新的列為當前價格:

    現在, 您可以通過應用許多其他想法來進一步解決這一問題, 例如:

    • 訪問多個網站, 并從每個網站向自己發送當前的最佳價格。
    • 運行多個日期范圍的循環, 并檢查哪些日期給出了網站上的最佳價格。
    • 檢查每家航空公司的價格如何隨時間變化。

    如果你有其他想法, 不要猶豫分享!

    就是這樣!我希望你發現它有用。

    Comments are closed.