Python爬蟲(chóng) —— 攜程機(jī)票數(shù)據(jù)

攜程機(jī)票搜索頁(yè)面

涉及內(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è)字段,LogTokenrk,CKr,但是我在頁(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í)慣!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評(píng)論 19 139
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 12,425評(píng)論 6 13
  • iOS開(kāi)發(fā)系列--網(wǎng)絡(luò)開(kāi)發(fā) 概覽 大部分應(yīng)用程序都或多或少會(huì)牽扯到網(wǎng)絡(luò)開(kāi)發(fā),例如說(shuō)新浪微博、微信等,這些應(yīng)用本身可...
    lichengjin閱讀 4,040評(píng)論 2 7
  • 1 前言 作為一名合格的數(shù)據(jù)分析師,其完整的技術(shù)知識(shí)體系必須貫穿數(shù)據(jù)獲取、數(shù)據(jù)存儲(chǔ)、數(shù)據(jù)提取、數(shù)據(jù)分析、數(shù)據(jù)挖掘、...
    whenif閱讀 18,313評(píng)論 45 523
  • 1、感恩父母給予我生命。 2、感恩祖輩將我養(yǎng)育。 3、感恩母親、祖母為家里的巨大付出。 4、感恩妻子一直的支持、包...
    朱曉軍閱讀 233評(píng)論 0 2

友情鏈接更多精彩內(nèi)容