
涉及內(nèi)容:
1、request模擬請(qǐng)求
2、json解析
目標(biāo):
爬取某航線上所有航班價(jià)格信息
核心思想:
通過(guò)頁(yè)面上請(qǐng)求數(shù)據(jù)的API接口,模擬前端請(qǐng)求,獲取json數(shù)據(jù)并將其解析。
Step0. 尋找一個(gè)合適的方法
進(jìn)入攜程機(jī)票的搜索界面,隨便搜索一條航線,跳轉(zhuǎn)后你會(huì)發(fā)現(xiàn)網(wǎng)址一欄變成了:http://flights.ctrip.com/booking/TAO-SJW-day-1.html?DDate1=2017-10-17
很明顯,你搜索的航線是TAO青島到SJW石家莊,起飛時(shí)間2017-10-17,整理一下url的規(guī)則就是:http://flights.ctrip.com/booking/<起飛城市三字碼>-<降落城市三字碼>-day-1.html?DDate1=<起飛日期>
拍腦袋想出來(lái)的方法一:直接通過(guò)頁(yè)面url獲取html代碼,然后對(duì)html進(jìn)行解析,獲取到所需元素。
-> 要用xpath一層層分析網(wǎng)頁(yè)元素,一層層解析下去,好麻煩??!
-> 機(jī)票價(jià)格是Ajax異步請(qǐng)求!頁(yè)面html源碼中沒(méi)有這部分?jǐn)?shù)據(jù)的!就算你有耐心用Xpath定位,也找不到哇!
拍腦袋想出來(lái)的方法二:利用selenium框架,通過(guò)webdriver操縱瀏覽器完成爬取
-> 可以解決方法一中動(dòng)態(tài)頁(yè)面獲取不到源碼的問(wèn)題,用xpath可以定位到元素了。
-> 可是,要是想看每個(gè)航班下所有票價(jià)信息的話,還需要點(diǎn)N次“訂票”按鈕,好麻煩呀!
-> 我用selenium嘗試了一下,發(fā)現(xiàn)如果這個(gè)航線下的航班很多,滾動(dòng)條不往下拉是不會(huì)把后面的航班加載出來(lái)的呀!這可咋整呀我無(wú)法預(yù)判這條航線下有多少航班呀。每條航線都拖動(dòng)滾動(dòng)條3次?5次?10次?太傻了!
……出于以上種種原因
可以用的方法三:我選擇直接利用ajax請(qǐng)求接口,模擬請(qǐng)求,獲取json數(shù)據(jù)
Step1. 找到API的地址
打開(kāi)Chrome瀏覽器的開(kāi)發(fā)者模式,然后重新搜索一條航線。
開(kāi)發(fā)者模式 -> Networks -> XHR里面有一個(gè)耗時(shí)格外長(zhǎng)的請(qǐng)求!

估計(jì)就是你了!點(diǎn)開(kāi)看一下Query String,嗯是航線信息,的確就是這個(gè)請(qǐng)求。獲取這個(gè)請(qǐng)求的地址,也就是Request URL。

把Request URL后面這一大串復(fù)制出來(lái),整理一下:
request_url = 'http://flights.ctrip.com/domesticsearch/search/SearchFirstRouteFlights?' \
+ 'DCity1=' + dcity \ # 起飛城市三字碼
+ '&ACity1=' + acity \ # 降落城市三字碼
+ '&SearchType=S' \
+ '&DDate1=' + ddate \ # 起飛日期
+ '&IsNearAirportRecommond=0' \
+ '&LogToken=8428f186c3504a6ea71158b344a502f5' \
+ '&rk=0.1311117634227288233503' \
+ '&CK=05F016D386A1975EFCF0F1240BA33457' \
+ '&r=0.37113914798207880745716'
request_url后面有奇奇怪怪的四個(gè)字段,LogToken,rk,CK和r,但是我在頁(yè)面源代碼中只找到了rk的定義,沒(méi)有找到另外兩個(gè)值的來(lái)源。嘗試了一下發(fā)現(xiàn)沒(méi)有后面四個(gè)字段,也是可以可以獲取到j(luò)son數(shù)據(jù)的,因此直接忽略。
源碼中找到的rk定義:&rk=' + Math.random()*10+'223600','0.31100000101726617620017');
Step2. 模擬請(qǐng)求
在Chrome中的Request Headers可以看到這個(gè)請(qǐng)求頭有以下信息:

一般情況下模擬請(qǐng)求頭首先要做的就是設(shè)置用戶代理:
ctrip_header = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36'}
有了用戶代理之后,我們嘗試模擬請(qǐng)求:
# coding:utf-8
import urllib2
ctrip_header = {'User-Agent':
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36'}
def search_flight(dcity, acity, ddate):
request_url = 'http://flights.ctrip.com/domesticsearch/search/SearchFirstRouteFlights?' \
+ 'DCity1=' + dcity \
+ '&ACity1=' + acity \
+ '&SearchType=S' \
+ '&DDate1=' + ddate
request = urllib2.Request(request_url, headers=ctrip_header)
response = urllib2.urlopen(request)
return_json = response.read()
print return_json
if __name__ == '__main__':
search_flight('TAO', 'SJW', '2017-10-17')
運(yùn)行結(jié)果:

發(fā)現(xiàn)返回的json當(dāng)中并沒(méi)有想要的數(shù)據(jù),這可能是我們請(qǐng)求的過(guò)程中缺少了某些信息導(dǎo)致的。
查看Request Headers中,除了User-Agent以外還有很多其他的字段,嘗試將這些字段加入ctrip_header
# coding:utf-8
import urllib2
ctrip_header = {'User-Agent':
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36',
'Host': 'flights.ctrip.com',
'Referer': 'http://flights.ctrip.com/booking/TAO-SJW-day-1.html?DDate1=2017-10-16'}
def search_flight(dcity, acity, ddate):
request_url = 'http://flights.ctrip.com/domesticsearch/search/SearchFirstRouteFlights?' \
+ 'DCity1=' + dcity \
+ '&ACity1=' + acity \
+ '&SearchType=S' \
+ '&DDate1=' + ddate
request = urllib2.Request(request_url, headers=ctrip_header)
response = urllib2.urlopen(request)
return_json = response.read()
print return_json
if __name__ == '__main__':
search_flight('TAO', 'SJW', '2017-10-17')
運(yùn)行結(jié)果:

成功了!我本以為需要有Cookies才能請(qǐng)求成功的!
還好不需要Cookies!我還不太會(huì)獲取Cookies,如果一定需要的話,我只能從Chrome里強(qiáng)行復(fù)制一波……(捂臉逃,順便求Cookies偽造的教學(xué))
Step3. 解析json數(shù)據(jù)
首先我們需要將剛剛請(qǐng)求到j(luò)son數(shù)據(jù)轉(zhuǎn)換為可讀的格式,可以用json.loads()這個(gè)函數(shù)將json字符串轉(zhuǎn)換為可讀的字典。即:
return_data = json.loads(return_json, encoding='gbk')
注意前面運(yùn)行結(jié)果可以看出,字符串存在顯示不出來(lái)的情況,這是因?yàn)槲沂褂玫膒ython2.7存在編碼問(wèn)題,需要進(jìn)行編碼轉(zhuǎn)換,即encoding='gbk'
找到所需數(shù)據(jù):
我們需要什么數(shù)據(jù)呢?
所有的航班號(hào),及每個(gè)航班號(hào)下對(duì)應(yīng)的所有價(jià)格。
剛剛我們看的是Headers里的信息,現(xiàn)在切到Preview里。Preview里的信息是格式化的返回json,在這里我們可以很清晰的找到我們需要的數(shù)據(jù)。

可以看到,fis是一個(gè)航班列表,每個(gè)都是一個(gè)json,包含航班信息,航班價(jià)格信息等等等
那么我們只需要提取fis列表中的一些有用的value值就好了。
flight_list = return_data['fis'] # 下挖到航班列表的結(jié)點(diǎn)
flight_nums = len(flight_list) # 航班數(shù)
for i in range(flight_nums):
airline = flight_list[i]['alc'] # 航空公司
flight_no = flight_list[i]['fn'] # 航班號(hào)
price_list = [each['p'] for each in flight_list[i]['scs'] if each['hotel'] is None] # 非飛宿產(chǎn)品價(jià)格
運(yùn)行結(jié)果:

完整代碼:
# coding:utf-8
import urllib2
import json
ctrip_header = {'User-Agent':
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36',
'Host': 'flights.ctrip.com',
'Referer': 'http://flights.ctrip.com/booking/TAO-SJW-day-1.html?DDate1=2017-10-16'}
def search_flight(dcity, acity, ddate):
request_url = 'http://flights.ctrip.com/domesticsearch/search/SearchFirstRouteFlights?' \
+ 'DCity1=' + dcity \
+ '&ACity1=' + acity \
+ '&SearchType=S' \
+ '&DDate1=' + ddate
request = urllib2.Request(request_url, headers=ctrip_header)
response = urllib2.urlopen(request)
return_json = response.read()
# print return_json
return_data = json.loads(return_json, encoding='gbk')
flight_list = return_data['fis']
# print flight_list
flight_nums = len(flight_list)
print '共有航班:', flight_nums, '趟'
for i in range(flight_nums):
airline = flight_list[i]['alc'] # 航空公司
flight_no = flight_list[i]['fn'] # 航班號(hào)
print '攜程', airline, flight_no,
price_list = [each['p'] for each in flight_list[i]['scs'] if each['hotel'] is None] # 非飛宿產(chǎn)品價(jià)格
print price_list
if __name__ == '__main__':
search_flight('TAO', 'SJW', '2017-10-17')
注:代碼十分不規(guī)范,完全沒(méi)寫(xiě)異常處理。近期我會(huì)努力糾正自己的習(xí)慣!