項(xiàng)目:機(jī)票數(shù)據(jù)采集
使用模塊:requests(請(qǐng)求模塊),js2py(js執(zhí)行模塊),json(解析json),xpath(解析網(wǎng)頁)。
小編推薦大家可以加我的扣扣群 735934841 。里面有海量視頻教程和學(xué)習(xí)資料免費(fèi)領(lǐng)取,不失為是一個(gè)學(xué)習(xí)的好地方,歡迎你的到來。一起交流學(xué)習(xí)!共同進(jìn)步?。?/b>
項(xiàng)目流程:
分析網(wǎng)站數(shù)據(jù)來源。
編寫爬蟲腳本。
驗(yàn)證數(shù)據(jù)準(zhǔn)確性。
js逆向破解參數(shù)生成。
更換請(qǐng)求參數(shù)城市(飛機(jī)起飛城市和落地城市或日期)測(cè)試結(jié)果是否正常。
1.分析網(wǎng)站數(shù)據(jù)來源
進(jìn)入藝龍機(jī)票列表搜索頁,附上鏈接?http://flight.elong.com/flightsearch/list?departCity=bjs&arriveCity=sha&departdate=2018-12-2?4,鏈接參數(shù)日期自行更改。
一般情況數(shù)據(jù)為調(diào)用接口獲得,或是在頁面中嵌入,這里很明顯是調(diào)用了接口。
F12打開開發(fā)者工具(谷歌瀏覽器),選擇network中的xhr,然后刷新頁面或重新搜索,查看調(diào)用的接口。(這一步也可以使用抓包工具,推薦使用Fiddler,網(wǎng)上有許多漢化版的,看個(gè)人習(xí)慣吧。)
調(diào)用了四個(gè)接口,點(diǎn)擊接口查看返回結(jié)果,確定數(shù)據(jù)來源。
看到出發(fā)機(jī)場(chǎng),航空公司名稱之類的英文,ok,就是這個(gè)了,點(diǎn)擊進(jìn)入Headers。
數(shù)據(jù)來源已經(jīng)確定,下面我們來構(gòu)造爬蟲請(qǐng)求接口。
2.編寫爬蟲腳本
快速上手requests模塊,鏈接已備好http://docs.python-requests.org/zh_CN/latest/user/quickstart.html
直接上代碼(提示:代碼中的請(qǐng)求參數(shù)grabcode的值需要自己抓取,有時(shí)效性,過期無返回結(jié)果導(dǎo)致代碼報(bào)錯(cuò)):
import requests #導(dǎo)入requests模塊
#請(qǐng)求鏈接
url = 'http://flight.elong.com/search/ly/rest/list'
#構(gòu)造請(qǐng)求頭 接口中請(qǐng)求頭有的參數(shù)最好全寫上,之后再了解這些請(qǐng)求頭信息是干什么的,這里不做介紹
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',
}
#請(qǐng)求參數(shù)
data = {
"p": '{"departCode":"bjs","arriveCityCode":"sha","departDate":"2018-12-24","searchType":"0","classTypes":null,"isBaby":0}',
"grabCode":'6793819',
}
#發(fā)起請(qǐng)求
html = requests.post(url, headers=headers,data=data).text
print(html)
有返回結(jié)果并且有數(shù)據(jù)證明我們請(qǐng)求成功了,但是我們還得進(jìn)一步驗(yàn)證數(shù)據(jù)準(zhǔn)確性。
3.驗(yàn)證數(shù)據(jù)是否準(zhǔn)確
使用json進(jìn)一步提取關(guān)鍵數(shù)據(jù)如航班號(hào),最低價(jià)等。
import requests #導(dǎo)入requests模塊
import json #導(dǎo)入json
#請(qǐng)求鏈接
url = 'http://flight.elong.com/search/ly/rest/list'
#構(gòu)造請(qǐng)求頭 接口中請(qǐng)求頭有的參數(shù)最好全寫上,之后再了解這些請(qǐng)求頭信息是干什么的,這里不做介紹
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',
}
#請(qǐng)求參數(shù)
data = {
"p": '{"departCode":"bjs","arriveCityCode":"sha","departDate":"2018-12-24","searchType":"0","classTypes":null,"isBaby":0}',
"grabCode":'9151048',
}
#發(fā)起請(qǐng)求
html = requests.post(url, headers=headers,data=data).text
#json.loads轉(zhuǎn)化json為一個(gè)字典 然后我們可以用字典方法取鍵和值
html = json.loads(html)["flightSelections"]
#創(chuàng)建結(jié)果列表
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)
和網(wǎng)頁價(jià)格對(duì)比:
結(jié)果正確,證明爬取成功。還沒完,上面2,3過程提到grabCode參數(shù)的時(shí)效性,參數(shù)過期會(huì)導(dǎo)致接口無返回結(jié)果,json解析就會(huì)拋出異常。
4.js逆向分析加密請(qǐng)求參數(shù)grabCode的生成
接口請(qǐng)求參數(shù)中的加密參數(shù)都是有跡可循的,前端和后端必須使用相同的加密算法來保證參數(shù)的有效性。
后端代碼我們不可能看得到,所以就要從前端來分析,前端通過js調(diào)用接口,調(diào)用接口的寫法有很多種方式,如原生js,ajax,jquery等。
查找調(diào)用接口js位置:
通過關(guān)鍵字grabCode,來查找js調(diào)用接口的位置。(這里也可以通過其他方法如請(qǐng)求方式Post來搜索位置)
F12打開開發(fā)者工具,使用全局搜索search。
搜索參數(shù)名稱grabCode
找到了,點(diǎn)擊第一個(gè)搜索結(jié)果,進(jìn)入查看js,點(diǎn)擊左下角的圖標(biāo)格式化js。
使用ctrl+f搜索grabCode的位置
很清晰的可以看到這里就是使用了ajax調(diào)用list接口的方法url(接口地址),params(請(qǐng)求參數(shù)),methods(請(qǐng)求方式)。
grabCode的值是調(diào)用了abcdefg函數(shù)。(下面我們可以用js斷點(diǎn)調(diào)試來獲取函數(shù)abcdefg的位置,或是按照剛才的方法使用全局搜索來查找也可以,調(diào)試更方便一點(diǎn))
js斷點(diǎn)調(diào)試:
如圖,在grabCode調(diào)用方法的行標(biāo)點(diǎn)擊,變成藍(lán)色,表示斷點(diǎn)成功,然后刷新頁面。
搜索結(jié)果正在加載被截?cái)?,進(jìn)一步證實(shí)了參數(shù)生成就是調(diào)用函數(shù)abcdefg。
這個(gè)小圖標(biāo)的功能叫”逐語句執(zhí)行“或者叫”逐步執(zhí)行“,這是我個(gè)人理解的一個(gè)叫法,意思就是,每點(diǎn)擊它一次,js語句就會(huì)往后執(zhí)行一句,它還有一個(gè)快捷鍵,F(xiàn)10。
我們點(diǎn)擊一下,發(fā)現(xiàn)剛才斷點(diǎn)的代碼已被執(zhí)行。鼠標(biāo)箭頭懸停在abcdefg函數(shù)上,點(diǎn)擊方法可以直接跳過去。
上圖對(duì)abcdefg函數(shù)做了一個(gè)解析,了解生成過程,總結(jié)一下就是調(diào)用網(wǎng)頁源代碼中的id為tsd的元素的屬性值value,替換字符串中的某個(gè)值,并調(diào)用eval把字符串執(zhí)行。
取消剛才的斷點(diǎn),在如圖所示位置打上新斷點(diǎn),刷新頁面。F10執(zhí)行下一句。
和網(wǎng)頁源代碼對(duì)比一下,ok,正確。
不難看出上面的value值實(shí)際為js代碼,eval函數(shù)會(huì)執(zhí)行這些js代碼。
模擬參數(shù)生成過程:
我們來使用python模擬一下他的過程:1.獲取網(wǎng)頁id==“tsd”的屬性value的值。2.替換字符使用replace("/)^-1/gm", ")&-1")。3.執(zhí)行js代碼。
復(fù)制value的值,可以去網(wǎng)頁,也可以在js中復(fù)制(這里復(fù)制出來的格式會(huì)有錯(cuò)誤,導(dǎo)致js不能執(zhí)行成功,我們直接去網(wǎng)頁抓取好了)。
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)
我們?cè)侔堰@個(gè)封裝成一個(gè)函數(shù)來供第二步進(jìn)行調(diào)用,搜索url中的三字碼和日期可以用一樣的(防止出錯(cuò))。
更換搜索參數(shù)城市三字碼或日期測(cè)試代碼是否能正常運(yùn)行并返回航班及其價(jià)格
下面附上全部代碼,僅供參考學(xué)習(xí)。
import requests #導(dǎo)入requests模塊
import json #導(dǎo)入json
import js2py #導(dǎo)入js執(zhí)行模塊
from lxml import etree #xpath使用lxml的etree解析
def ticket_api(a,b,c):
#請(qǐng)求鏈接
url = 'http://flight.elong.com/search/ly/rest/list'
#構(gòu)造請(qǐng)求頭 接口中請(qǐng)求頭有的參數(shù)最好全寫上,之后再了解這些請(qǐng)求頭信息是干什么的,這里不做介紹
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',
}
#請(qǐng)求參數(shù)
data = {
"p": '{"departCode":"%s","arriveCityCode":"%s","departDate":"%s","searchType":"0","classTypes":null,"isBaby":0}'%(a,b,c),
"grabCode":grabCode(a,b,c),
}
#發(fā)起請(qǐng)求
html = requests.post(url, headers=headers,data=data).text
#json.loads轉(zhuǎn)化json為一個(gè)字典 然后我們可以用字典方法取鍵和值
html = json.loads(html)["flightSelections"]
#創(chuàng)建結(jié)果列表
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)
到這一步,基本上算是完成了。