涉及內(nèi)容:
1舵盈、request模擬請求
2、json解析
目標:
爬取某航線上所有航班價格信息
核心思想:
通過頁面上請求數(shù)據(jù)的API接口桩了,模擬前端請求,獲取json數(shù)據(jù)并將其解析夕土。
Step0. 尋找一個合適的方法
進入攜程機票的搜索界面馆衔,隨便搜索一條航線瘟判,跳轉后你會發(fā)現(xiàn)網(wǎng)址一欄變成了:http://flights.ctrip.com/booking/TAO-SJW-day-1.html?DDate1=2017-10-17
很明顯,你搜索的航線是TAO青島到SJW石家莊角溃,起飛時間2017-10-17拷获,整理一下url的規(guī)則就是:http://flights.ctrip.com/booking/<起飛城市三字碼>-<降落城市三字碼>-day-1.html?DDate1=<起飛日期>
拍腦袋想出來的方法一:直接通過頁面url獲取html代碼,然后對html進行解析减细,獲取到所需元素匆瓜。
-> 要用xpath一層層分析網(wǎng)頁元素,一層層解析下去未蝌,好麻煩巴灾ā!
-> 機票價格是Ajax異步請求萧吠!頁面html源碼中沒有這部分數(shù)據(jù)的左冬!就算你有耐心用Xpath定位,也找不到哇纸型!
拍腦袋想出來的方法二:利用selenium框架拇砰,通過webdriver操縱瀏覽器完成爬取
-> 可以解決方法一中動態(tài)頁面獲取不到源碼的問題,用xpath可以定位到元素了狰腌。
-> 可是除破,要是想看每個航班下所有票價信息的話,還需要點N次“訂票”按鈕琼腔,好麻煩呀瑰枫!
-> 我用selenium嘗試了一下,發(fā)現(xiàn)如果這個航線下的航班很多展姐,滾動條不往下拉是不會把后面的航班加載出來的呀躁垛!這可咋整呀我無法預判這條航線下有多少航班呀。每條航線都拖動滾動條3次圾笨?5次教馆?10次?太傻了擂达!
……出于以上種種原因
可以用的方法三:我選擇直接利用ajax請求接口土铺,模擬請求,獲取json數(shù)據(jù)
Step1. 找到API的地址
打開Chrome瀏覽器的開發(fā)者模式板鬓,然后重新搜索一條航線悲敷。
開發(fā)者模式 -> Networks -> XHR里面有一個耗時格外長的請求!
估計就是你了俭令!點開看一下Query String后德,嗯是航線信息,的確就是這個請求抄腔。獲取這個請求的地址瓢湃,也就是Request URL理张。
把Request URL后面這一大串復制出來,整理一下:
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
后面有奇奇怪怪的四個字段绵患,LogToken
雾叭,rk
,CK
和r
落蝙,但是我在頁面源代碼中只找到了rk
的定義织狐,沒有找到另外兩個值的來源。嘗試了一下發(fā)現(xiàn)沒有后面四個字段筏勒,也是可以可以獲取到json數(shù)據(jù)的移迫,因此直接忽略。
源碼中找到的rk
定義:&rk=' + Math.random()*10+'223600','0.31100000101726617620017');
Step2. 模擬請求
在Chrome中的Request Headers可以看到這個請求頭有以下信息:
一般情況下模擬請求頭首先要做的就是設置用戶代理:
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'}
有了用戶代理之后奏寨,我們嘗試模擬請求:
# 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')
運行結果:
發(fā)現(xiàn)返回的json當中并沒有想要的數(shù)據(jù)起意,這可能是我們請求的過程中缺少了某些信息導致的。
查看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')
運行結果:
成功了!我本以為需要有Cookies才能請求成功的套菜!
還好不需要Cookies亲善!我還不太會獲取Cookies,如果一定需要的話逗柴,我只能從Chrome里強行復制一波……(捂臉逃蛹头,順便求Cookies偽造的教學)
Step3. 解析json數(shù)據(jù)
首先我們需要將剛剛請求到json數(shù)據(jù)轉換為可讀的格式,可以用json.loads()
這個函數(shù)將json字符串轉換為可讀的字典戏溺。即:
return_data = json.loads(return_json, encoding='gbk')
注意前面運行結果可以看出渣蜗,字符串存在顯示不出來的情況,這是因為我使用的python2.7存在編碼問題旷祸,需要進行編碼轉換耕拷,即encoding='gbk'
找到所需數(shù)據(jù):
我們需要什么數(shù)據(jù)呢?
所有的航班號托享,及每個航班號下對應的所有價格骚烧。
剛剛我們看的是Headers里的信息,現(xiàn)在切到Preview里闰围。Preview里的信息是格式化的返回json赃绊,在這里我們可以很清晰的找到我們需要的數(shù)據(jù)。
可以看到羡榴,fis
是一個航班列表碧查,每個都是一個json,包含航班信息校仑,航班價格信息等等等
那么我們只需要提取fis列表中的一些有用的value值就好了么夫。
flight_list = return_data['fis'] # 下挖到航班列表的結點
flight_nums = len(flight_list) # 航班數(shù)
for i in range(flight_nums):
airline = flight_list[i]['alc'] # 航空公司
flight_no = flight_list[i]['fn'] # 航班號
price_list = [each['p'] for each in flight_list[i]['scs'] if each['hotel'] is None] # 非飛宿產(chǎn)品價格
運行結果:
完整代碼:
# 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'] # 航班號
print '攜程', airline, flight_no,
price_list = [each['p'] for each in flight_list[i]['scs'] if each['hotel'] is None] # 非飛宿產(chǎn)品價格
print price_list
if __name__ == '__main__':
search_flight('TAO', 'SJW', '2017-10-17')
注:代碼十分不規(guī)范者冤,完全沒寫異常處理。近期我會努力糾正自己的習慣档痪!