python實現12306車票查詢
看到網上有很多火車票查詢的小指令碼,參考一下,發現很多都已經不能再運行了,據說12306介面返回的資料格式更新比較快,這裡自己也寫了一個。
環境
- Mac osx
- python3.6
- pycharm
效果圖
編碼
- 安裝指令碼用到的模組
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
這是個對終端輸出的文字進行著色的模組,直接上程式碼圖(官方示例):
知道這些用法就足夠本指令碼使用了,模組的介紹到此結束,下面正式進入正題。
網頁介面的獲取
通過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資料中取出需要的欄位,歡迎有更好方法的朋友留言,感謝!!