簡單的Python學(xué)習(xí),用Python完成一個(gè)12306余票查詢~
Python之12306余票查詢
參考資料來自Python 實(shí)現(xiàn)火車票查詢工具
需要用到的第三方庫
requests,使用 Python 訪問 HTTP 資源的必備庫。docopt,Python3 命令行參數(shù)解析工具。prettytable, 格式化信息打印工具,能讓你像 MySQL 那樣打印數(shù)據(jù)。
效果圖

分析12306接口請求



在網(wǎng)頁檢查器中我們刷新網(wǎng)頁,可以觀察到下面這個(gè)接口~

單獨(dú)復(fù)制url打開就能發(fā)現(xiàn)這就是12306余票數(shù)據(jù)請求接口,分析一下這個(gè)接口的類型是Get,請求參數(shù)是
leftTicketDTO.train_date: 2018-10-30
leftTicketDTO.from_station: BJP
leftTicketDTO.to_station: LYF
purpose_codes: ADULT
hoho~,萬里長征第一步走的還是比較順利的,下面就用我們的python程序來請求這個(gè)接口看看~
Requests請求接口
import requests
url = 'https://kyfw.12306.cn/otn/leftTicket/queryA?leftTicketDTO.train_date=2018-10-30&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=LYF&purpose_codes=ADULT'
r= requests.get(url)
print(r.json())
關(guān)于requests庫,我剛接觸python覺得應(yīng)該是現(xiàn)在網(wǎng)絡(luò)交互中必選庫吧,上述代碼就請求12306的接口并且輸出結(jié)果為json
{
"data":{
"flag":"1",
"map":{
"BXP":"北京西",
"GLF":"關(guān)林",
"LLF":"洛陽龍門",
"LYF":"洛陽"
},
"result":Array[18]
},
"httpstatus":200,
"messages":"",
"status":true
}
數(shù)據(jù)結(jié)構(gòu)如上,我們只需要result中的結(jié)果就ok,python也可以很方便的解析json這一點(diǎn)我覺得比java舒服一些。
r= requests.get(url)
print(r.json()['data']['result'])
我們現(xiàn)在已經(jīng)有了數(shù)據(jù),接下來則是把我們轉(zhuǎn)換過的結(jié)果顯示出來,萬里長征第二步~
PrettyTable庫
PrettyTable 是python中的一個(gè)第三方庫,可用來生成美觀的ASCII格式的表格,十分實(shí)用。以下為官方介紹:
A simple Python library for easily displaying tabular data in a visually appealing ASCII table format.PrettyTable is a simple Python library designed to make it quick and easy to represent tabular data in visually appealing ASCII tables. It was inspired by the ASCII tables used in the PostgreSQL shell psql. PrettyTable allows for selection of which columns are to be printed, independent alignment of columns (left or right justified or centred) and printing of “sub-tables” by specifying a row range.
使用方法也是非常簡單,先寫個(gè)小小的Demo
pt = PrettyTable()
pt.field_names=['測試1','測試2','測試3','測試4','測試5']
pt.add_row(['測試結(jié)果1','測試結(jié)果2', '測試結(jié)果3', '測試結(jié)果4','測試結(jié)果5'])
print(pt)
+-----------+-----------+-----------+-----------+-----------+
| 測試1 | 測試2 | 測試3 | 測試4 | 測試5 |
+-----------+-----------+-----------+-----------+-----------+
| 測試結(jié)果1 | 測試結(jié)果2 | 測試結(jié)果3 | 測試結(jié)果4 | 測試結(jié)果5 |
+-----------+-----------+-----------+-----------+-----------+
通過field_names設(shè)置元數(shù)據(jù)(其實(shí)并不知道怎么形容。。。
然后不斷的add_row添加行,那么我們的12306也就可以這樣寫啦,
# coding: utf-8
import requests, datetime
from prettytable import PrettyTable
def _get_time(from_time, trains_time):
try:
time1 = datetime.datetime.strptime(from_time, "%H:%M")
time2 = datetime.datetime.strptime(trains_time, "%H:%M")
except ValueError as e:
return '異常時(shí)間啦'
hour = 0
if time1.minute + time2.minute >= 60:
hour = 1
if time1.hour + time2.hour + hour > 24:
return "次日到達(dá)"
else:
return "今日到達(dá)"
def _get_seat_count(count):
if not str(count).strip():
return '--'
else:
return count
url = 'https://kyfw.12306.cn/otn/leftTicket/queryA?leftTicketDTO.train_date=2018-10-30&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=LYF&purpose_codes=ADULT'
r = requests.get(url)
results = r.json()['data']['result']
header = '車次 車站 時(shí)間 歷時(shí) 商務(wù)座特等座 一等座 二等座 高級軟臥 軟臥 動臥 硬臥'.split()
pt = PrettyTable()
pt._set_field_names(header)
for raw_train in results:
trains_info = str(raw_train).split('|')
from_station = trains_info[6]
to_station = trains_info[7]
from_time = trains_info[8]
to_time = trains_info[9]
trains_time = trains_info[10]
pt.add_row([trains_info[3], from_station + "\n" + to_station, from_time + "\n" + to_time,
trains_time + "\n" + _get_time(from_time, trains_time),
_get_seat_count(trains_info[32]), _get_seat_count(trains_info[31]),
_get_seat_count(trains_info[30]), _get_seat_count(trains_info[21]),
_get_seat_count(trains_info[23]), _get_seat_count(trains_info[33]),
_get_seat_count(trains_info[28])])
print(pt)
+-------+----------+-------+----------+--------------+--------+--------+----------+------+------+------+
| 車次 | 車站 | 時(shí)間 | 歷時(shí) | 商務(wù)座特等座 | 一等座 | 二等座 | 高級軟臥 | 軟臥 | 動臥 | 硬臥 |
+-------+----------+-------+----------+--------------+--------+--------+----------+------+------+------+
| G427 | 北京西 | 06:20 | 03:26 | 11 | 2 | 有 | -- | -- | -- | -- |
| | 洛陽龍門 | 09:46 | 今日到達(dá) | | | | | | | |
| G651 | 北京西 | 06:58 | 04:07 | 有 | 有 | 有 | -- | -- | -- | -- |
| | 洛陽龍門 | 11:05 | 今日到達(dá) | | | | | | | |
| K269 | 北京西 | 07:00 | 10:47 | -- | -- | -- | -- | 有 | -- | 有 |
| | 洛陽 | 17:47 | 今日到達(dá) | | | | | | | |
| K507 | 北京西 | 07:12 | 10:05 | -- | -- | -- | -- | 10 | -- | 有 |
| | 洛陽 | 17:17 | 今日到達(dá) | | | | | | | |
| K817 | 北京西 | 08:01 | 11:11 | -- | -- | -- | -- | 9 | -- | 有 |
| | 洛陽 | 19:12 | 今日到達(dá) | | | | | | | |
| G671 | 北京西 | 08:16 | 04:12 | 9 | 有 | 有 | -- | -- | -- | -- |
| | 洛陽龍門 | 12:28 | 今日到達(dá) | | | | | | | |
| G307 | 北京西 | 09:38 | 04:09 | 8 | 有 | 有 | -- | -- | -- | -- |
| | 洛陽龍門 | 13:47 | 今日到達(dá) | | | | | | | |
| G655 | 北京西 | 09:43 | 04:17 | 19 | 有 | 有 | -- | -- | -- | -- |
| | 洛陽龍門 | 14:00 | 今日到達(dá) | | | | | | | |
| G429 | 北京西 | 10:45 | 04:01 | 7 | 2 | 有 | -- | -- | -- | -- |
| | 洛陽龍門 | 14:46 | 今日到達(dá) | | | | | | | |
| K267 | 北京西 | 13:22 | 11:23 | -- | -- | -- | -- | 無 | -- | 有 |
| | 關(guān)林 | 00:45 | 今日到達(dá) | | | | | | | |
| G673 | 北京西 | 14:35 | 04:17 | 8 | 有 | 有 | -- | -- | -- | -- |
| | 洛陽龍門 | 18:52 | 今日到達(dá) | | | | | | | |
| G661 | 北京西 | 14:48 | 04:09 | 3 | 無 | 有 | -- | -- | -- | -- |
| | 洛陽龍門 | 18:57 | 今日到達(dá) | | | | | | | |
| G663 | 北京西 | 15:45 | 03:54 | 8 | 有 | 有 | -- | -- | -- | -- |
| | 洛陽龍門 | 19:39 | 今日到達(dá) | | | | | | | |
| T55 | 北京西 | 15:50 | 08:47 | -- | -- | -- | -- | 3 | -- | 2 |
| | 洛陽 | 00:37 | 今日到達(dá) | | | | | | | |
| Z75 | 北京西 | 15:57 | 07:05 | -- | -- | -- | -- | 2 | -- | 無 |
| | 洛陽 | 23:02 | 今日到達(dá) | | | | | | | |
| G665 | 北京西 | 16:00 | 04:11 | 7 | 有 | 有 | -- | -- | -- | -- |
| | 洛陽龍門 | 20:11 | 今日到達(dá) | | | | | | | |
| T231 | 北京西 | 18:50 | 08:03 | -- | -- | -- | -- | 無 | -- | 18 |
| | 洛陽 | 02:53 | 次日到達(dá) | | | | | | | |
| K1363 | 北京西 | 22:06 | 09:38 | -- | -- | -- | -- | 無 | -- | 有 |
| | 洛陽 | 07:44 | 次日到達(dá) | | | | | | | |
+-------+----------+-------+----------+--------------+--------+--------+----------+------+------+------+
12306的接口格式我個(gè)人覺得有點(diǎn)非人類…本例用的組合方法可能并不是最優(yōu)方法,歡迎大家提供更好的方法~,萬里長征三步走完啦,最后一步就是如何動態(tài)的調(diào)整出發(fā)地和目的地。
docopt
一個(gè)參數(shù)解析庫,可以看下Python 參數(shù)解析庫 docopt 簡單使用圖文教程。
利用這個(gè)庫的便利性我們可以很方便的提取出想要的參數(shù)信息,
"""命令行火車票查看器
Usage:
tickets [-gdtkz] <from> <to> <date>
Options:
-h,--help 顯示幫助菜單
-g 高鐵
-d 動車
-t 特快
-k 快速
-z 直達(dá)
Example:
tickets 北京 上海 2016-10-10
tickets -dg 成都 南京 2016-10-10
"""
def cil():
"""command-line interface"""
arguments = docopt(__doc__)
from_station = arguments['<from>']
to_station = arguments['<to>']
date = arguments['<date>']
print("from_station:" + from_station + "\nto_station:" + to_station + "\ndate:" + date)
if __name__ == '__main__':
cil()
python tickets_test.py -dg 上海 洛陽 2018-10-31
from_station:上海
to_station:洛陽
date:2018-10-31
這里其實(shí)還有一個(gè)問題就是我們提取出來的都是中文,而12306接口需要的是縮寫,這里我們可以通過12306的另一個(gè)接口解決~
import re, requests
from pprint import pprint
url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9069'
response = requests.get(url)
stations = re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)', response.text)
pprint(dict(stations), indent=4)
python parse_station.py > stations.py
stations = {'一間堡': 'YJT',
'一面坡': 'YPB',
'一面山': 'YST',
'七臺河': 'QTB',
'七甸': 'QDM',
'七營': 'QYJ',
'七里河': 'QLD',
'萬樂': 'WEB',
'萬發(fā)屯': 'WFB',
'萬寧': 'WNQ',
'萬州': 'WYW',
'萬州北': 'WZE',
'萬年': 'WWG',
'萬源': 'WYY',
'三義井': 'OYD',
'三井子': 'OJT',
'三亞': 'SEQ',
'三元區(qū)': 'SMS',
'三關(guān)口': 'OKJ',
'三十家': 'SRD',
'三十里堡': 'SST',
'三原': 'SAY',
'三合莊': 'SVP',
.....
}
所有的條件已經(jīng)集齊,我們走最后一步吧~
汽車人合體
def cil():
"""command-line interface"""
arguments = docopt(__doc__)
from_station = stations.get(arguments['<from>'])
to_station = stations.get(arguments['<to>'])
date = arguments['<date>']
print("from_station:" + from_station + "\nto_station:" + to_station)
# 構(gòu)建URL
url = 'https://kyfw.12306.cn/otn/leftTicket/queryA?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT'.format(
date, from_station, to_station
)
r = requests.get(url, verify=False)
results = r.json()['data']['result']
header = '車次 車站 時(shí)間 歷時(shí) 商務(wù)座特等座 一等座 二等座 高級軟臥 軟臥 動臥 硬臥'.split()
pt = PrettyTable()
pt._set_field_names(header)
for raw_train in results:
trains_info = str(raw_train).split('|')
from_station = trains_info[6]
to_station = trains_info[7]
from_time = trains_info[8]
to_time = trains_info[9]
trains_time = trains_info[10]
pt.add_row([trains_info[3], from_station + "\n" + to_station, from_time + "\n" + to_time,
trains_time + "\n" + _get_time(from_time, trains_time),
_get_seat_count(trains_info[32]), _get_seat_count(trains_info[31]),
_get_seat_count(trains_info[30]), _get_seat_count(trains_info[21]),
_get_seat_count(trains_info[23]), _get_seat_count(trains_info[33]),
_get_seat_count(trains_info[28])])
print(pt)
if __name__ == '__main__':
cil()
以上是本次Demo的完整調(diào)用,作為一個(gè)Android開發(fā)我覺得python還是挺有意思的~