1. 程式人生 > >python實現12306車票查詢

python實現12306車票查詢

看到網上有很多火車票查詢的小指令碼,參考一下,發現很多都已經不能再運行了,據說12306介面返回的資料格式更新比較快,這裡自己也寫了一個。

環境

  • Mac osx
  • python3.6
  • pycharm

效果圖

預覽效果

編碼

  1. 安裝指令碼用到的模組

requests, 用於請求12306網站網址

docopt, 解析命令列引數

prettytable, 資料用表格的形式列印在終端

colorama, 為列印在表格中的資料著色

安裝方式,直接用pip命令就好:

pip install requests prettytable docopt colorama

下面先來介紹一下prettytable docopt colorama這三個模組

docopt

python命令列引數解析工具有很多,這裡參考別的查票指令碼用的docopt,為了對這個模組進行了解學習,本篇文章也用了這個模組,首先針對本文,我們要查詢火車票資訊,肯定要輸入出發地點,到達地點,出發日期,以及要查詢的票的種類,於是我們需要的命令列模型如下:

python tickets.py [-gdtrkz]
- 出發地
- 目的地
- 日期
- [-gdtrkz] 車票型別(對應)

看一下下面的程式碼

#coding=utf-8
""" Usage: python tickets.py [-gdtrkz] <from> <to> <date> """ from docopt import docopt arguments = docopt(__doc__) print(arguments)

終端執行上面的程式碼結果如下:
命令列解析

由上面的測試可以看出,docopt能從註釋中的Usage下面的命令解析出一個字典,“[ ]”中的是選項,一般不寫代表全部,寫了代表查詢某一選項對應的資料,
若是選項輸入錯誤,不會丟擲異常,只會出現以下提示:

Usage:
    python tickets.py [-gdtkz] <from
>
<to> <date>

“< >”中的是引數,如本例中的,,,這裡的引數是不能少的,上面提到的選項少了,是可以查詢到資料的,這裡的引數少了,雖然不會報錯,但是不可能有資料。

為了程式碼的可讀性,一般註釋中除了Usage(用法)之外,還有引數說明,使用方式等,比如:

"""
Usage:
    python tickets.py [-gdtrkz] <from> <to> <date>

Options:
    -h,--help   幫助選單
    -g          高鐵
    -d          動車
    -t          特快
    -r          高階軟臥
    -k          快速
    -z          直達

Example:
    #查詢5月10日北京到上海所有車次
    python tickets.py 北京 上海 2017-05-10 
    #查詢5月10日南京到上海的動車和高鐵
    python tickets.py -dg 南京 上海 2017-05-10
"""

prettytable

colorama

這是個對終端輸出的文字進行著色的模組,直接上程式碼圖(官方示例):

colorama使用示例

知道這些用法就足夠本指令碼使用了,模組的介紹到此結束,下面正式進入正題。

網頁介面的獲取

通過chrome監聽點選查詢按鈕後傳送的請求如下

查詢請求

從這個請求我們需要知道,這個一個get請求(嗯,確實是get),請求的地址是:https://kyfw.12306.cn/otn/leftTicket/query ,需要的引數有四個:
- leftTicketDTO.train_date=2017-05-10 車票日期2017-05-10
- leftTicketDTO.from_station=HZH 起點站HZH
- leftTicketDTO.to_station=NJH 終點站NJH
- purpose_codes=ADULT 車票型別ADULT(成人票)

起點站和終點站使用站點的字母縮寫表示的

車站對應的字母縮寫在哪裡????

在剛進入車票查詢頁面時,在頁面載入的js檔案中,有下面的連結

這裡返回的是所有的車站和對應縮寫的資料

import re
import  requests
from pprint import pprint

url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9006'
response = requests.get(url,verify=False)
stations = re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)',response.text)
pprint(dict(stations),indent=4)

通過正則表示式,匹配所有的車站和對應的縮寫,並轉換成字典格式。

因為所有的地點和對應的縮寫,變化的可能性不大,將上面獲取的字典資料儲存為一個檔案,備用。

下面繼續看查詢車票時返回的資料:

通過這個json資料,可以獲取到所有車次的資訊,關鍵在於如何對現有的json進行解析,獲取到所有車次的資訊?ps:網上看了其他人寫的一些查詢車票資訊的指令碼,之前的車票資訊每個欄位對應一個值,標準的json形式:

現在的json資料每列車的資訊都在一串字串中,思來想去,沒有找到什麼好辦法,唯一發現的規律就是每個欄位用“|”分割,現在的做法就是將該段字串以“|”分割成一個列表,從列表中去數需要的欄位對應的位置。

主要程式碼如下:

    def trains(self):
        for raw_train in self.available_trains:
            raw_train_list = raw_train.split('|')
            train_no = raw_train_list[3]
            initial = train_no[0].lower()
            duration = raw_train_list[10]
            if initial in self.options:
                train = [
                    train_no,
                    '\n'.join([Fore.LIGHTGREEN_EX + self.available_place[raw_train_list[6]] + Fore.RESET,
                               Fore.LIGHTRED_EX + self.available_place[raw_train_list[7]] + Fore.RESET]),
                    '\n'.join([Fore.LIGHTGREEN_EX + raw_train_list[8] + Fore.RESET,
                               Fore.LIGHTRED_EX + raw_train_list[9] + Fore.RESET]),
                    duration,
                    raw_train_list[-4] if raw_train_list[-4] else '--',
                    raw_train_list[-5] if raw_train_list[-5] else '--',
                    raw_train_list[-14] if raw_train_list[-14] else '--',
                    raw_train_list[-12] if raw_train_list[-12] else '--',
                    raw_train_list[-7] if raw_train_list[-7] else '--',
                    raw_train_list[-6] if raw_train_list[-6] else '--',
                    raw_train_list[-9] if raw_train_list[-9] else '--',
                ]
                yield train

關於如何從json資料中取出需要的欄位,歡迎有更好方法的朋友留言,感謝!!