Python:爬蟲助你回家,12306餘票監測!
寫在前面
一年一度的春運即將來臨,各位看官回家的票有沒有買好呢?反正小編已經按捺不住激動的心情,開始蠢蠢欲動了。但是作為技術控,就應該有技術控的搶票姿態,鑑於12306逆天的驗證碼,小編放棄了控制12306自動搶票的騷操作,開始走向自動餘票提醒:有餘票=>微信推送餘票資訊的道路。
正文
以徐州到滕州為例,我們想爬取1月26號的餘票資訊,登入12306官網查詢餘票頁面(https://kyfw.12306.cn/otn/leftTicket/init),在下圖所示的紅框內輸入出發地、目的地以及出發日期。
由於餘票資訊是非同步載入的,我們需要通過谷歌瀏覽器開發者工具找到中間請求的URL,可以發現我們需要的資訊就在
https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date=2019-01-14&leftTicketDTO.from_station=XCH&leftTicketDTO.to_station=TXK&purpose_codes=ADULT 下的result裡面。
下面我們運用selenium庫驅動PhantomJS來獲取網頁原始碼資訊,其中selenium是一個自動化測試工具,大家可以通過pip install下載安裝。利用它可以驅動瀏覽器執行特定動作,同時還可以獲取瀏覽器當前呈現的原始碼,做到“可見可爬”,迴避了各種反爬措施。最近小編在學習這個庫,所以暫且用它牛刀小試一下。由於selenium不自帶瀏覽器,所以我們這裡選擇小巧的無介面瀏覽器PhantomJS
首先介紹一下,所需要載入的模組。
Selenium庫:模擬瀏覽器執行的庫。
Pandas庫:Python科學計算庫。
Json庫:解析JSON後將其轉為Python字典或者列表。
Wxpy庫:可用來實現各種個人微訊號的自動化操作,我們這裡用來實現微信自動傳送餘票資訊。
time模組: Python標準庫中的模組,用來實現時間間隔控制,迴圈獲得餘票資訊。
載入程式如下:
fromselenium importwebdriver
importpandas aspd
importjson
importnumpy asnp
fromwxpy import*
importtime
下面開始進入正題,第一步爬取餘票資訊,還是以徐州到滕州為例。首先獲取網頁原始碼,程式如下:
driver = webdriver.PhantomJS( 'D:webdriverphantomjs-2.1.1-windowsbinphantomjs.exe')
#phantomjs所在位置
url= 'https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date='+ '2019-01-14'+ '&leftTicketDTO.from_station='+ 'XCH'+ '&leftTicketDTO.to_station='+ 'TXK'+ '&purpose_codes=ADULT'
driver.get(url)html=driver.page_sourcedriver.quit() #關閉phantomjs
最終獲取的文字資訊如下:
可以發現,我們需要的資訊以json資料的形式存在,把網頁的標籤刪除,就可以直接將json資料轉換成字典的形式。程式如下:
html = html.replace( '''<html><head></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">''', '') .replace( '</pre></body></html>', '') #刪除網頁標籤資訊
html=json.loads(html) #剩下的網頁原始碼是json資料,json.loads將json格式資料轉換為字典
這樣就把網頁的資訊,儲存到了名為html的字典中,而餘票資訊(即上面原網頁的表格內容)就被已列表的形式,存在了result中。
自然,我們接下來需要做的就是將餘票資訊從result中提取出來。通過觀察上圖能發現,車次資訊清晰地用“|”分隔開,這裡就可以通過split函式將字串分割,再根據資訊位置,提取我們需要的資訊。
程式如下:
data={
'車次': '',
'始發站': '',
'終點站': '',
'出發時間': '',
'到達時間': '',
'全程時間': '',
'商務座': '',
'一等座': '',
'二等座': '',
'高階軟臥': '',
'軟臥': '',
'動臥': '',
'硬臥': '',
'軟座': '',
'硬座': '',
'無座': ''} #生成一個空的字典,將我們需要的資料資訊定義為鍵名
result=pd.DataFrame(data,index=[ 0]) #將字典轉成一個dataframe,方便資料處理 fori inhtml[ 'data'][ 'result']: #我們需要的資料在html字典鍵名為result中 item = i.split( '|') #用"|"進行分割data[ '車次'] = item[ 3] #車次在3號位置data[ '始發站'] = item[ 6] #始發站資訊在6號位置data[ '終點站'] = item[ 7] #終點站資訊在7號位置data[ '出發時間'] = item[ 8] #出發時間資訊在8號位置data[ '到達時間'] = item[ 9] #抵達時間在9號位置data[ '全程時間'] = item[ 10] #經歷時間在10號位置data[ '商務座'] = item[ 32] oritem[ 25] # 特別注意:商務座在32或25位置data[ '一等座'] = item[ 31] #一等座資訊在31號位置data[ '二等座'] = item[ 30] #二等座資訊在30號位置data[ '高階軟臥'] = item[ 21] #高階軟臥資訊在31號位置data[ '軟臥'] = item[ 23] #軟臥資訊在23號位置data[ '動臥'] = item[ 27] #動臥資訊在27號位置data[ '硬臥'] = item[ 28] #硬臥資訊在28號位置data[ '軟座'] = item[ 24] #軟座資訊在24號位置data[ '硬座'] = item[ 29] #硬座資訊在29號位置data[ '無座'] = item[ 26] #無座資訊在26號位置df1=pd.DataFrame(data,index=[ 0]) #將賦值後的字典轉換成datafram,命名為df1frames=[result,df1] #result,df1構成一個列表,再使用concatresult = pd.concat(frames,axis= 0, ignore_index= True) #將兩個列表按行合併,然後衝著索引 columns=[ '車次', '始發站', '終點站', '出發時間', '到達時間', '商務座', '一等座', '二等座', '高階軟臥', '軟臥', '動臥', '硬臥', '軟座', '硬座', '無座']result=result.reindex(columns=columns) #將result的列名按columns排列,我們想先看到車次和時間嘛.
通過資料清洗,原網頁表格資訊就規整的放到了名為result的dataframe中。如下圖所示:
這樣就完成了1月26號滕州到徐州的餘票資訊的爬取,但是大家需要查詢的出發地、目的地各不相同,所以為了方便使用和程式移植,我們可以把上述過程,封裝到一個函式中。
我們重新看上面的URL。如下圖表示的那樣,URL中trian_data=後接出發時間,from_station=後接出發城市城市程式碼(XCH是徐州的城市程式碼),to_station=後接目的城市程式碼(TXK是滕州的城市程式碼)。我們只要把這三個資訊改成函式引數,就可以方便地查詢不同出發地、目的地以及出發時間的餘票資訊了。
關於城市和程式碼的對照表,將其儲存在了一個名為地名程式碼對照表.txt中,並將其上傳到雲中,可以將其讀取到python,轉換成字典形式,程式如下:
f = open( '地名程式碼對照表.txt', 'r') #開啟txt
dict1 = eval(f.read()) #將txt載入成字典形式
f.close() #關閉txt
最終字典dict1中的檔案形式如下,鍵名為城市名,鍵值為城市程式碼。
這樣,我們就可以直接通過輸入城市的名字,來提取對應的程式碼資訊,比如要提取滕州的城市程式碼,直接輸入:
dict1['滕州']
就能提取相應的城市程式碼:
以上就解決了封裝函式的所有障礙,直接將出發城市、目的城市、出發日期設成引數即可。我們將函式名命名為get_news(start,end,day)。Start,end,day對應上述引數,返回值為一各包含所有列車餘票資訊的dataframe 。
程式如下:
defget_news(start,end,day):driver = webdriver.PhantomJS( 'D:webdriverphantomjs-2.1.1-windowsbinphantomjs.exe') url= 'https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date='+day.title() + '&leftTicketDTO.from_station='+dict1[start.title()] + '&leftTicketDTO.to_station='+dict1[end.title()] + '&purpose_codes=ADULT'driver.get(url) html=driver.page_source html = html.replace( '''<html><head></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">''', '').replace( '</pre></body></html>', '') html=json.loads(html) data={
'車次': '',
'始發站': '',
'終點站': '',
'出發時間': '',
'到達時間': '',
'全程時間': '',
'商務座': '',
'一等座': '',
'二等座': '',
'高階軟臥': '',
'軟臥': '',
'動臥': '',
'硬臥': '',
'軟座': '',
'硬座': '',
'無座': ''} result=pd.DataFrame(data,index=[ 0])
fori inhtml[ 'data'][ 'result']: item = i.split( '|') #用"|"進行分割data[ '車次'] = item[ 3] #車次在3號位置data[ '始發站'] = item[ 6] #始發站資訊在6號位置data[ '終點站'] = item[ 7] #終點站資訊在7號位置data[ '出發時間'] = item[ 8] #出發時間資訊在8號位置data[ '到達時間'] = item[ 9] #抵達時間在9號位置data[ '全程時間'] = item[ 10] #經歷時間在10號位置data[ '商務座'] = item[ 32] oritem[ 25] # 特別注意:商務座在32或25位置data[ '一等座'] = item[ 31] #一等座資訊在31號位置data[ '二等座'] = item[ 30] #二等座資訊在30號位置data[ '高階軟臥'] = item[ 21] #高階軟臥資訊在31號位置data[ '軟臥'] = item[ 23] #軟臥資訊在23號位置data[ '動臥'] = item[ 27] #動臥資訊在27號位置data[ '硬臥'] = item[ 28] #硬臥資訊在28號位置data[ '軟座'] = item[ 24] #軟座資訊在24號位置data[ '硬座'] = item[ 29] #硬座資訊在29號位置data[ '無座'] = item[ 26] #無座資訊在26號位置df1=pd.DataFrame(data,index=[ 0]) frames=[result,df1] result = pd.concat(frames,axis= 0, ignore_index= True) columns=[ '車次', '始發站', '終點站', '出發時間', '到達時間', '商務座', '一等座', '二等座', '高階軟臥', '軟臥', '動臥', '硬臥', '軟座', '硬座', '無座'] result=result.reindex(columns=columns) returnresult driver.quit()
下面,呼叫函式查詢武漢到滕州的1月26號火車票的情況:
a=get_news( '武漢', '滕州', '2019-01-26') #將函式返回結果賦給a
最終輸出結果如下,1月26日從武漢到滕州有一班高鐵和一班火車,並已全部告罄。想哭~~~