項目:藝龍國內機票實時數據爬蟲
使用模塊:requests(請求模塊),js2py(js執行模塊),json(解析json),xpath(解析網頁)。
項目流程:
- 分析網站數據來源。
- 編寫爬蟲腳本。
- 驗證數據准確性。
- js逆向破解參數生成。
- 更換請求參數城市(飛機起飛城市和落地城市或日期)測試結果是否正常。
1.分析網站數據來源
進入藝龍機票列表搜索頁,附上鏈接http://flight.elong.com/flightsearch/list?departCity=bjs&arriveCity=sha&departdate=2018-12-24,鏈接參數日期自行更改。
一般情況數據為調用接口獲得,或是在頁面中嵌入,這里很明顯是調用了接口。
F12打開開發者工具(谷歌瀏覽器),選擇network中的xhr,然后刷新頁面或重新搜索,查看調用的接口。(這一步也可以使用抓包工具,推薦使用Fiddler,網上有許多漢化版的,看個人習慣吧。)
調用了四個接口,點擊接口查看返回結果,確定數據來源。
看到出發機場,航空公司名稱之類的英文,ok,就是這個了,點擊進入Headers。
數據來源已經確定,下面我們來構造爬蟲請求接口。
2.編寫爬蟲腳本
快速上手requests模塊,鏈接已備好 http://docs.python-requests.org/zh_CN/latest/user/quickstart.html
直接上代碼(提示:代碼中的請求參數grabcode的值需要自己抓取,有時效性,過期無返回結果導致代碼報錯):
import requests #導入requests模塊 #請求鏈接 url = 'http://flight.elong.com/search/ly/rest/list' #構造請求頭 接口中請求頭有的參數最好全寫上,之后再了解這些請求頭信息是干什么的,這里不做介紹 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36', } #請求參數 data = { "p": '{"departCode":"bjs","arriveCityCode":"sha","departDate":"2018-12-24","searchType":"0","classTypes":null,"isBaby":0}', "grabCode":'6793819', } #發起請求 html = requests.post(url, headers=headers,data=data).text print(html)
有返回結果並且有數據證明我們請求成功了,但是我們還得進一步驗證數據准確性。
3.驗證數據是否准確
使用json進一步提取關鍵數據如航班號,最低價等。
import requests #導入requests模塊 import json #導入json #請求鏈接 url = 'http://flight.elong.com/search/ly/rest/list' #構造請求頭 接口中請求頭有的參數最好全寫上,之后再了解這些請求頭信息是干什么的,這里不做介紹 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36', } #請求參數 data = { "p": '{"departCode":"bjs","arriveCityCode":"sha","departDate":"2018-12-24","searchType":"0","classTypes":null,"isBaby":0}', "grabCode":'9151048', } #發起請求 html = requests.post(url, headers=headers,data=data).text #json.loads轉化json為一個字典 然后我們可以用字典方法取鍵和值 html = json.loads(html)["flightSelections"] #創建結果列表 list = [] for i in html:#遍歷所有航班 if len(i["Segments"]) == 1: #只提取單程,多程排除 flightnumber = i["Segments"][0]["FlightNumber"] price = i["Segments"][0]["Price"] #航班信息字典 item = { "flight": flightnumber, "price": price, } list.append(item) print(list)
和網頁價格對比:
結果正確,證明爬取成功。還沒完,上面2,3過程提到grabCode參數的時效性,參數過期會導致接口無返回結果,json解析就會拋出異常。
4.js逆向分析加密請求參數grabCode的生成
接口請求參數中的加密參數都是有跡可循的,前端和后端必須使用相同的加密算法來保證參數的有效性。
后端代碼我們不可能看得到,所以就要從前端來分析,前端通過js調用接口,最常見的是jquery庫來實現ajax請求。(js原生也可以實現ajax請求)
查找調用接口js位置:
通過關鍵字grabCode,來查找js調用接口的位置。(這里也可以通過其他方法如請求方式Post來搜索位置)
F12打開開發者工具,使用全局搜索search。
搜索參數名稱grabCode
找到了,點擊第一個搜索結果,進入查看js,點擊左下角的圖標格式化js。
使用ctrl+f搜索grabCode的位置
很清晰的可以看到這里就是使用了ajax調用list接口的方法url(接口地址),params(請求參數),methods(請求方式)。
grabCode的值是調用了abcdefg函數。(下面我們可以用js斷點調試來獲取函數abcdefg的位置,或是按照剛才的方法使用全局搜索來查找也可以,調試更方便一點)
js斷點調試:
如圖,在grabCode調用方法的行標點擊,變成藍色,表示斷點成功,然后刷新頁面。
搜索結果正在加載被截斷,進一步證實了參數生成就是調用函數abcdefg。
這個小圖標的功能叫”逐語句執行“或者叫”逐步執行“,這是我個人理解的一個叫法,意思就是,每點擊它一次,js語句就會往后執行一句,它還有一個快捷鍵,F10。
我們點擊一下,發現剛才斷點的代碼已被執行。鼠標箭頭懸停在abcdefg函數上,點擊方法可以直接跳過去。
上圖對abcdefg函數做了一個解析,了解生成過程,總結一下就是調用網頁源代碼中的id為tsd的元素的屬性值value,替換字符串中的某個值,並調用eval把字符串執行。
取消剛才的斷點,在如圖所示位置打上新斷點,刷新頁面。F10執行下一句。
和網頁源代碼對比一下,ok,正確。
不難看出上面的value值實際為js代碼,eval函數會執行這些js代碼。
模擬參數生成過程:
我們來使用python模擬一下他的過程:1.獲取網頁id==“tsd”的屬性value的值。2.替換字符使用replace("/\)\^-1/gm", ")&-1")。3.執行js代碼。
復制value的值,可以去網頁,也可以在js中復制(這里復制出來的格式會有錯誤,導致js不能執行成功,我們直接去網頁抓取好了)。
import requests,js2py from lxml import etree url_list ='http://flight.elong.com/flightsearch/list?departCity=BJS&arriveCity=SHA&departdate=2018-12-24&backdate=&searchType=0' html_list = requests.get(url_list).text html_list = etree.HTML(html_list) js = html_list.xpath('//input[@id="tsd"]/@value')[0] js = js.replace("/\)\^-1/gm", ")&-1") code = js2py.eval_js(js) print(code)
我們再把這個封裝成一個函數來供第二步進行調用,搜索url中的三字碼和日期可以用一樣的(防止出錯)。
更換搜索參數城市三字碼或日期測試代碼是否能正常運行並返回航班及其價格
下面附上全部代碼,僅供參考學習。
import requests #導入requests模塊 import json #導入json import js2py #導入js執行模塊 from lxml import etree #xpath使用lxml的etree解析 def ticket_api(a,b,c): #請求鏈接 url = 'http://flight.elong.com/search/ly/rest/list' #構造請求頭 接口中請求頭有的參數最好全寫上,之后再了解這些請求頭信息是干什么的,這里不做介紹 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36', } #請求參數 data = { "p": '{"departCode":"%s","arriveCityCode":"%s","departDate":"%s","searchType":"0","classTypes":null,"isBaby":0}'%(a,b,c), "grabCode":grabCode(a,b,c), } #發起請求 html = requests.post(url, headers=headers,data=data).text #json.loads轉化json為一個字典 然后我們可以用字典方法取鍵和值 html = json.loads(html)["flightSelections"] #創建結果列表 list = [] for i in html:#遍歷所有航班 if len(i["Segments"]) == 1: #只提取單程,多程排除 flightnumber = i["Segments"][0]["FlightNumber"] price = i["Segments"][0]["Price"] #航班信息字典 item = { "flight": flightnumber, "price": price, } list.append(item) print(list) def grabCode(a,b,c): url_list ='http://flight.elong.com/flightsearch/list?departCity=%s&arriveCity=%s&departdate=%s&backdate=&searchType=0'%(a,b,c) html_list = requests.get(url_list).text html_list = etree.HTML(html_list) js = html_list.xpath('//input[@id="tsd"]/@value')[0] js = js.replace("/\)\^-1/gm", ")&-1") code = js2py.eval_js(js) return code if __name__ == '__main__': a = "bjs" b = "czx" #常州czx,上海sha c = "2018-12-24" ticket_api(a,b,c)
到這一步,基本上算是完成了。
溫馨提示
- 如果您對本文有疑問,請在評論部分留言,我會在最短時間回復。
- 如果本文幫助了您,也請評論關注,作為對我的一份鼓勵。
- 如果您感覺我寫的有問題,也請批評指正,我會盡量修改。
- 本文為原創,轉載請注明出處。
- 本文所有代碼僅供學習參考,在爬取的同時考慮對方的服務器承受能力,適可而止。