1. 程式人生 > >Python爬取12306實現火車票查詢

Python爬取12306實現火車票查詢

介紹:

除了官方的12306網站,其他的很多網站都提供了購買查詢的功能,像攜程的鐵友,途牛等等。這些網站他們盈利大都是通過廣告的收入,以及通過購票帶動的網站內酒店、景點額外收入,他們的網站介面友好型優於12306,使得在購票入口中佔據了一定份額。但是歸根到底,所有購票的APP、網站都是拿的12306的基礎資料,或者說是12306的介面,只是做了前端介面的設計。在程式設計師的眼中,有介面就能創造世界(然而這只是我的一種臆想)。本文就是通過查詢介面,在命令列視窗中實現火車票查詢。  

完整思路和處理過程:

首先要拿到查詢的介面就要採用抓包的方式,常用的抓包工具:瀏覽器自帶的檢查功能,FIddler抓包工具(功能更強大)


但是在URL中的出發地和目的地都是字母,那我們必須要拿到所有的車站資訊列表才能構造URL請求,在網頁Sources和網頁原始碼中分別尋找station的檔案



對station進行解析:parse_station.py 

通過“>”,來重定向輸出的內容,通過“>>”將輸出的內容追加到檔案中,這裡我們通過 $ python parse_station.py > station.py

# coding: utf-8
import re
import requests
from pprint import pprint 


def main():
    
    url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8971'
    # 傳送get請求,不判斷證書
    response = requests.get(url, verify=False)
    # 使用正則表示式提取所有的站點:漢字和大寫代號
    stations = dict(re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)', response.text))
    # 轉換成字典就是為了將漢字站點和字母代號分開且有一一對應關係:鍵-->值
    pprint(stations.keys())
    pprint(stations.values())

if __name__ == '__main__':
    main()

在station.py 中建立鍵值之間的雙向關係

def get_name(telecode):

    return names[telecodes.index(telecode)]

def get_telecode(name):
    return telecodes[names.index(name)]

這裡先對提取資料的程式碼中涉及到的點進行總結:

1.兩種格式化輸出的方式:%和format
In [7]: "%s,%d"%("kzc",18)
Out[7]: 'kzc,18'
In [8]: '{0},{1}'.format('kzc',18)
Out[8]: 'kzc,18'
2.用到的庫

requests:爬蟲必用的傳送請求,獲取HTML網頁內容的庫
docopt:命令列解析工具,可以根據自定義的文件描述,自動生成解析器

prettytable:能讓你的資料像MySQL的命令列顯示資料的格式一樣

colorama:命令列著色庫

# coding: utf-8

"""命令列火車票檢視器:Usage Options為docopt庫固定格式

Usage:
    tickets [-dgktz] <from> <to> <date>

Options:
    -h, --help 檢視幫助
    -d         動車
    -g         高鐵
    -k         快速
    -t         特快
    -z         直達

Examples:
    tickets 上海 北京 2017-10-10
    tickets -dg 成都 南京 2017-10-10
"""

from docopt import docopt
import requests
from prettytable import PrettyTable
from colorama import Fore
import stations

def cli():
    arguments = docopt(__doc__,version='ticket 1.0')
    from_station = stations.get_telecode(arguments.get('<from>'))
    to_station = stations.get_telecode(arguments.get('<to>'))
    date = arguments.get('<date>')
    # 列表推導式,得到的是查詢車次型別的集合
    options = ''.join([key for key,value in arguments.items() if value is True])
    print(options)

    url = ('https://kyfw.12306.cn/otn/leftTicket/query?'
            'leftTicketDTO.train_date={}&'
            'leftTicketDTO.from_station={}&'
            'leftTicketDTO.to_station={}&'
            'purpose_codes=ADULT').format(date,from_station,to_station)

    r = requests.get(url, verify=False)
    # print(r.json())
  #   requests得到的是一個json格式的物件,r.json()轉化成python字典格式資料來提取,所有的車次結果result
    raw_trains = r.json()['data']['result']
    pt = PrettyTable()
    pt._set_field_names("車次 車站 時間 經歷時 一等座 二等座 軟臥 硬臥 硬座 無座".split())
    for raw_train in raw_trains:
        # split切割之後得到的是一個列表
        data_list = raw_train.split("|")
        train_no = data_list[3]
        initial = train_no[0].lower()
        # print(train_no[0])
        # 判斷是否是查詢特定車次的資訊
        if not options or initial in options:
            from_station_code = data_list[6]
            to_station_code = data_list[7]
            from_station_name = ''
            to_station_name = ''
            start_time = data_list[8]
            arrive_time = data_list[9]
            time_duration = data_list[10]
            first_class_seat = data_list[31] or "--"
            second_class_seat = data_list[30] or "--"
            soft_sleep = data_list[23]  or "--"
            hard_sleep = data_list[28] or "--"
            hard_seat = data_list[29] or "--"
            no_seat = data_list[33] or "--"

        pt.add_row([
                   # 對特定文字新增顏色
                   train_no,
                   '\n'.join([Fore.GREEN + stations.get_name(from_station_code) + Fore.RESET, Fore.RED + stations.get_name(to_station_code) +  Fore.RESET]),
                   '\n'.join([Fore.GREEN + start_time + Fore.RESET,Fore.RED + arrive_time +  Fore.RESET]),
                   time_duration,
                   first_class_seat,
                   second_class_seat,
                   soft_sleep,
                   hard_sleep,
                   hard_seat,
                   no_seat
        ])

    print(pt)


if __name__ == '__main__':
    cli()

參考來源:https://www.shiyanlou.com/courses/623/labs/2072/document
最後效果圖:很像火車站電子螢幕的效果