首頁?學習  »   正文

如何用Python抓取最便宜的機票信息(上)

如何用Python抓取最便宜的機票信息(上)

簡單地說

這個項目的目標是為一個特定的目的地建立一個web scraper,它將運行和執行具有靈活日期的航班價格搜索(在您首先選擇的日期前后最多3天)。它保存一個包含結果的Excel,并發送一封包含快速統計信息的電子郵件。顯然,目的是幫助我們找到最好的交易!

實際應用取決于您。我用它搜索假期和離我的家鄉最近的一些短途旅行!

如果你非常認真的對待,您可以在服務器上運行腳本(一個簡單的Raspberry Pi就可以了),并讓它每天啟動一兩次。把結果郵寄給你,我建議將excel文件保存到Dropbox文件夾中,這樣你就可以隨時隨地訪問它了。

如何用Python抓取最便宜的機票信息(上)

它會搜索“靈活日期”,因此它會在你首先選擇的日期之前和之后的3天內查找航班。盡管該腳本一次只能運行一對目的地,但您可以輕松地對其進行調整,以便在每個循環中運行多個目的地。您甚至可能最終發現一些錯誤票價…這太棒了!

另一個scraper

當我第一次開始做一些web抓取時,我對這個主題不是特別感興趣。但是我想說!如果我想做更多的項目,包括預測模型、財務分析,或許還有一些情緒分析,但事實證明,弄清楚如何構建第一個web爬蟲程序非常有趣。在我不斷學習的過程中,我意識到網絡抓取是互聯網“工作”的關鍵。

您可能認為這是一個非常大膽的說法,但是如果我告訴您谷歌是由一個用Java和Python構建的web scraper開始的呢?它爬行,而且依然如此,整個互聯網試圖為你的問題提供最好的答案。web抓取有無數的應用程序,即使您更喜歡數據科學中的其他主題,您仍然需要一些抓取技巧來獲取數據。

我在這里使用的一些技術來自于我最近買的一本很棒的書,《Web Scraping with Python》它涵蓋了與web抓取相關的所有內容。書中有大量簡單的例子和大量的實際應用。甚至有一個非常有趣的章節是關于解決reCaptcha檢查的,這讓我大吃一驚——我不知道現有的工具甚至服務來處理它!

“你喜歡旅行嗎?”

這個簡單而無害的問題通常會得到一個積極的答案,然后會有一兩個關于先前冒險的故事。我們大多數人都會同意旅行是體驗新文化和開闊視野的好方法。但如果問題是“你喜歡搜索機票的過程嗎?”,我敢肯定人們的反應不會那么熱烈……

Python救援.

第一個挑戰是選擇從哪個平臺獲取信息。這有點兒難,但我還是選擇了Kayak。我嘗試了Momondo、Skyscanner、Expedia和其他一些網站,但這些網站上的reCaptchas非常殘忍。在“你是人類嗎”的檢查中,我嘗試了幾次選擇交通燈、人行橫道和自行車后,我得出結論,Kayak是我最好的選擇,只是當你在短時間內加載了太多頁面,它會發出安全檢查。我設法讓機器人每隔4到6小時查詢一次網站,一切正常。這里或那里可能偶爾會有一個小問題,但如果您開始獲得reCaptcha檢查,要么手動解決它們并在此之后啟動機器人,或者等待幾個小時,它會重置。您可以隨意將代碼調整到另一個平臺,歡迎您在評論部分與我們分享!

如果你剛接觸網絡抓取,或者你不知道為什么有些網站要花很長時間來阻止它,請在編寫第一行代碼之前幫你自己一個大忙。谷歌“網頁抓取禮儀”。如果你像個瘋子一樣開始抓,你的努力可能比你想象的要快得多。

系緊你的安全帶……

大人不記小人過

在導入并打開chrome選項卡之后,我們將定義一些將在循環中使用的函數。結構的構思大致是這樣的:

  • 一個函數將啟動bot,聲明我們要搜索的城市和日期
  • 該函數獲取第一個搜索結果,按“最佳”航班排序,然后單擊“加載更多結果”
  • 另一個函數將抓取整個頁面,并返回一個dataframe
  • 對于“便宜”和“最快”排序類型,它將重復步驟2和步驟3
  • 將向您發送一封電子郵件,其中簡要總結了價格(最便宜和平均價格),并將包含這三種排序類型的數據框保存為excel文件
  • 前面的所有步驟都在循環中重復,循環每X小時運行一次。

每個Selenium項目都從一個WebDriver開始。我正在使用Chromedriver,但是還有其他的選擇。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

# Change this to your own chromedriver path!
chromedriver_path = 'C:/{YOUR PATH HERE}/chromedriver_win32/chromedriver.exe'

driver = webdriver.Chrome(executable_path=chromedriver_path) # This will open the Chrome window
sleep(2)

這些是我將用于整個項目的包。我將使用randint使bot在每次搜索之間的睡眠時間是隨機的。這通常是任何bot都必須具備的特性。如果運行前面的代碼,應該會打開一個Chrome窗口,bot將在其中導航。

所以讓我們做一個快速測試,在另一個窗口上訪問kayak.com。選擇您想要往返的城市和日期。在選擇日期時,請確保選擇“+-3天”。我在編寫代碼時考慮了結果頁面,所以如果只想搜索特定的日期,很可能需要做一些調整。我會試著在整篇文章中指出這些變化,但如果你卡住了,請在評論中留言給我。

點擊搜索按鈕,在地址欄中找到鏈接。它應該類似于我下面使用的鏈接,我將變量kayak定義為url,并從webdriver執行get方法。您的搜索結果應該出現。

如何用Python抓取最便宜的機票信息(上)

每當我在幾分鐘內使用get命令超過兩三次時,都會出現reCaptcha檢查。實際上,您可以自己解決reCaptcha,并在下一次出現之前繼續進行您想要的測試。從我的測試來看,第一次搜索似乎總是沒問題的,所以如果您想要擺弄代碼,并且讓代碼在它們之間有很長的間隔時自動運行,那么實際上需要您自己來解決這個難題。你真的不需要10分鐘更新這些價格,對吧?

每個XPath都有它的陷阱

到目前為止,我們打開了一個窗口,得到了一個網站。為了開始獲取價格和其他信息,我們必須使用XPath或CSS選擇器。我選擇了XPath,并不覺得有必要將其與CSS混合使用,但是完全可以這樣做。使用XPath導航網頁可能會讓人感到困惑,即使使用我曾經使用的直接從inspector視圖中使用“復制XPath”技巧,我也意識到這并不是獲得所需元素的最佳方法。有時,這種聯系是如此具體,以至于很快就會過時。《用Python進行Web抓取》一書出色地解釋了使用XPath和CSS選擇器導航的基礎知識。

如何用Python抓取最便宜的機票信息(上)

接下來,讓我們使用Python選擇最便宜的結果。上面代碼中的紅色文本是XPath選擇器,如果在任何地方右鍵單擊網頁并選擇“inspect”,就可以看到它。再次單擊右鍵要查看代碼的位置,并再次檢查。

如何用Python抓取最便宜的機票信息(上)

為了說明我之前對從檢查器復制路徑的缺點的觀察,請考慮以下差異:

1 # This is what the copy method would return. Right click highlighted rows on the right side and select "copy > Copy XPath"
//*[@id="wtKI-price_aTab"]/div[1]/div/div/div[1]/div/span/span2 # This is what I used to define the "Cheapest" button
cheap_results = ‘//a[@data-code = “price”]’

第二種選擇的簡單性顯而易見。它搜索具有屬性data-code = price的元素a。第一個選項查找id等于wtKI-price_aTab的元素,并遵循第一個div元素、四個div和兩個span。這次會成功的。我現在就可以告訴您,id元素將在下次加載頁面時更改。每次頁面加載時,字母wtKI都會動態變化,所以只要頁面重新加載,您的代碼就沒用了。花點時間閱讀一下XPath,我保證會有回報。

如何用Python抓取最便宜的機票信息(上)

不過,使用復制方法可以在不那么“復雜”的網站上工作,這也很好!

?基于上面顯示的內容,如果我們想在列表中以幾個字符串的形式獲得所有搜索結果,該怎么辦?其實很簡單。每個結果都在一個對象中,這個對象的類是“resultWrapper”。獲取所有結果可以通過像下面這樣的for循環來實現。如果您理解了這一部分,您應該能夠理解接下來的大部分代碼。它基本上是指向您想要的東西(結果包裝器),使用某種方式(XPath)獲得文本,并將其放置在可讀對象中(首先使用flight_containers,然后使用flights_list)。

如何用Python抓取最便宜的機票信息(上)

前3行顯示出來,我們可以清楚地看到我們需要的所有內容,但是我們有更好的選擇來獲取信息。我們需要分別刮取每個元素。

準備起飛吧!

最容易編寫的函數是加載更多的結果,所以讓我們從這里開始。我想在不觸發安全檢查的情況下最大化我的航班數量,所以每次顯示頁面時,我都會在“加載更多結果”按鈕中單擊一次。惟一的新特性是try語句,我添加它是因為有時按鈕加載不正確。如果它也對你起作用,只需在我將在前面展示的Start-Kayak函數中對其進行注釋。

# Load more results to maximize the scraping
def load_more():
try:
more_results = '//a[@class = "moreButton"]'
driver.find_element_by_xpath(more_results).click()
# Printing these notes during the program helps me quickly check what it is doing
print('sleeping.....')
sleep(randint(45,60))
except:
pass

現在,經過長時間的介紹(我有時會忘乎所以!),我們已經準備好定義將實際擦除頁面的函數。

我已經編譯了下一個函數page-scrape中的大部分元素。有時,元素返回插入第一和第二條腿信息的列表。我使用了一個簡單的方法來分割它們,例如在第一個section_a_list和section_b_list變量中。該函數還返回一個dataframe 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] # This is to separate the two flights
section_b_list = sections_list[1::2] # This is to separate the two flights

# if you run into a reCaptcha, you might want to do something about it
# you will know there's a problem if the lists above are empty
# this if statement lets you exit the bot or do something else
# you can add a sleep here, to let you solve the captcha and continue scraping
# i'm using a SystemExit because i want to test everything from the start
if section_a_list == []:
raise SystemExit

# I'll use the letter A for the outbound flight and B for the inbound
a_duration = []
a_section_names = []
for n in section_a_list:
# Separate the time from the cities
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:
# Separate the time from the cities
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]
# Separating the weekday from the day
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]

# getting the prices
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))

# the stops are a big list with one leg on the even index and second leg on odd index
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]

# this part gets me the airline company and the departure and arrival times, for both legs
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])
# split the hours and carriers, between a and b legs
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") # so we can know when it was scraped
return flights_df

我試著把名字寫清楚。記住,變量a與行程的第一段相關,b與第二段相關。轉到下一個函數。

等等,還有更精彩的嗎?!我們明天見~

 

歡迎關注ATYUN官方公眾號,商務合作及內容投稿請聯系郵箱:[email protected]

發表評論