为方便查询12306的火车票信息,在此用python写了一个小工具,主要用到了docopt、requests、re、prettytable几个模块。其中:
docopt模块作用是创建一个简洁漂亮的命令行交互界面,区别于sys.argv;
requests模块作用是实现http请求,区别于urllib、urllib2等模块;
re模块作用是正则表达匹配,用于匹配中文字符;
prettytable模块作用是创建一个简单直视的表格,用于打印火车票信息。
1.docopt命令行交互
"""
Usage:
12306.py <from> <to> <date>
12306.py (-h|--help)
Options:
-h,--help show this help
Example:
12306.py 青岛 苏州北 2016-10-05
"""
from docopt import docopt
if __name__ == "__main__":
argu = docopt(__doc__)
print argu
以上就是我们脚本的命令行交互信息,当执行命令格式有误时,会打印此信息。
如:
root@yanggd-OptiPlex-:~/python# python py 青岛
Usage:
py <from> <to> <date>
py (-h|--help)
root@yanggd-OptiPlex-:~/python# python py -h
Usage:
py <from> <to> <date>
py (-h|--help)
Options:
-h,--help show this help
Example:
py 青岛 苏州北 --
注意:
(1).Usage和Options之间必须有空行,否则Options中的内容会全部当做变量
(2).Usage中命令行格式必须是此命令的全部运行方式,否则当运行到没有定义的格式时会打印help信息
(3).Options中的可选参数后面应该用2个以上的空格,不要用tab,否则会报错
(4).Options中的可选参数应该空2个格,否则会有问题
2.http请求
通过分析12306的查票过程,如下图:
从上图我们可以得到以下信息:
访问链接为”https://kyfw.12306.cn/otn/leftTicket/log?leftTicketDTO.train_date=2016-10-14&leftTicketDTO.from_station=QDK&leftTicketDTO.to_station=OHH&purpose_codes=ADULT“,对应的站点信息为QDK、QHH,可见站点信息被对应的站点代码所代替,并且数据响应返回的格式为json。
再从站点源码中仔细查看,发现站点对应的代码链接”https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8955“。
因此,我们需要先获得每个站点对应的代码信息:
station_code_url = "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8955"
#去除https访问的警告信息
requests.packages.urllib3.disable_warnings()
r = requests.get(station_code_url, verify=False)
station_code_html = r.text
station_code = re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)', station_code_html)
station_code_dict = dict(station_code)
source = station_code_dict.get(argu['<from>'].decode("utf-8"))
des = station_code_dict.get(argu['<to>'].decode("utf-8"))
其中,我们利用re,通过中文字符对应的unicode字符为’([\u4e00-\u9fa5]+)|([A-Z]+)’来正则匹配站点对应代码的中文字符,然后将站点、代码初始化成字典的格式。
注意:由于站点以unicode字符存于字典中,因此当我们从命令行中读取到中文站点时,需要先decode成unicode的格式,才能在字典中匹配到对应的站点,进而得到站点的代码。
3.打印车票信息
由于查询到的数据以json格式输出,因此我们需要requests模块获取到json串,然后通过prettytable来格式化输出车票信息,方便我们能够直视
query_url = "https://kyfw.12306.cn/otn/leftTicket/queryT?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT".format(date, source, des)
table = PrettyTable(["车次", "出发站", "到达站", "出发时间", "到达时间", "历时", "商务座", "特等座", "一等座", "二等座", "高级软卧", "软卧", "硬
卧", "软座", "硬座", "无座", "其他"])
r2 = requests.get(query_url, verify=False)
for info in r2.json()["data"]:
detail = info["queryLeftNewDTO"]
table.add_row([detail["station_train_code"], detail["start_station_name"], detail["to_station_name"], detail["start_time"], detail["arrive_time"], detail["lishi"], detail["swz_num"], detail["tz_num"], detail["zy_num"], detail["ze_num"], detail["gr_num"], detail["rw_num"], detail["yw_num"], detail["rz_num"], detail["yz_num"], detail["wz_num"], detail["qt_num"]])
#打印车次信息
print table
最后的代码为:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
"""
Usage:
12306.py <from> <to> <date>
12306.py (-h|--help)
Options:
-h,--help show this help
Example:
12306.py 青岛 苏州北 2016-10-05
"""
import re
import requests
from docopt import docopt
from prettytable import PrettyTable
if __name__ == "__main__":
argu = docopt(__doc__)
station_code_url = "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8955"
#去除https访问的警告信息
requests.packages.urllib3.disable_warnings()
r = requests.get(station_code_url, verify=False)
station_code_html = r.text
#过滤中文字符
station_code = re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)', station_code_html)
station_code_dict = dict(station_code)
#获取站点的代码
source = station_code_dict.get(argu['<from>'].decode("utf-8"))
des = station_code_dict.get(argu['<to>'].decode("utf-8"))
date = argu['<date>']
query_url = "https://kyfw.12306.cn/otn/leftTicket/queryT?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT".format(date, source, des)
table = PrettyTable(["车次", "出发站", "到达站", "出发时间", "到达时间", "历时", "商务座", "特等座", "一等座", "二等座", "高级软卧", "软卧", "硬
卧", "软座", "硬座", "无座", "其他"])
r2 = requests.get(query_url, verify=False)
for info in r2.json()["data"]:
detail = info["queryLeftNewDTO"]
table.add_row([detail["station_train_code"], detail["start_station_name"], detail["to_station_name"], detail["start_time"], detail["arrive_time"], detail["lishi"], detail["swz_num"], detail["tz_num"], detail["zy_num"], detail["ze_num"], detail["gr_num"], detail["rw_num"], detail["yw_num"], detail["rz_num"], detail["yz_num"], detail["wz_num"], detail["qt_num"]])
#打印车次信息
print table
查询结果为:
[email protected]-OptiPlex-380:~/python# python 12306.py 青岛 北京 2016-10-10
+------+--------+--------+----------+----------+-------+--------+--------+--------+--------+----------+------+------+------+------+------+------+
| 车次 | 出发站 | 到达站 | 出发时间 | 到达时间 | 历时 | 商务座 | 特等座 | 一等座 | 二等座 | 高级软卧 | 软卧 | 硬卧 | 软座 | 硬座 | 无座 | 其他 |
+------+--------+--------+----------+----------+-------+--------+--------+--------+--------+----------+------+------+------+------+------+------+
| G178 | 青岛 | 北京南 | 07:02 | 11:51 | 04:49 | 4 | -- | 无 | 有 | -- | -- | -- | -- | -- | -- | -- |
| G180 | 青岛 | 北京南 | 07:10 | 12:01 | 04:51 | 1 | -- | 无 | 有 | -- | -- | -- | -- | -- | -- | -- |
| G182 | 青岛 | 北京南 | 08:20 | 12:58 | 04:38 | 8 | -- | 无 | 有 | -- | -- | -- | -- | -- | -- | -- |
| G184 | 青岛 | 北京南 | 09:29 | 14:18 | 04:49 | 5 | -- | 有 | 有 | -- | -- | -- | -- | -- | -- | -- |
| G186 | 青岛 | 北京南 | 10:22 | 15:14 | 04:52 | 13 | -- | 有 | 有 | -- | -- | -- | -- | -- | -- | -- |
| G188 | 青岛 | 北京南 | 11:27 | 16:22 | 04:55 | 2 | -- | 无 | 有 | -- | -- | -- | -- | -- | -- | -- |
| G190 | 青岛 | 北京南 | 12:14 | 17:05 | 04:51 | 6 | -- | 12 | 有 | -- | -- | -- | -- | -- | -- | -- |
| G192 | 青岛 | 北京南 | 12:32 | 17:17 | 04:45 | 17 | -- | 有 | 有 | -- | -- | -- | -- | -- | -- | -- |
| G194 | 青岛 | 北京南 | 14:30 | 19:18 | 04:48 | 18 | -- | 有 | 有 | -- | -- | -- | -- | -- | -- | -- |
| G196 | 青岛 | 北京南 | 16:00 | 20:40 | 04:40 | 有 | -- | 有 | 有 | -- | -- | -- | -- | -- | -- | -- |
| G198 | 青岛 | 北京南 | 17:11 | 22:02 | 04:51 | 20 | -- | 有 | 有 | -- | -- | -- | -- | -- | -- | -- |
| G200 | 青岛 | 北京南 | 18:35 | 23:38 | 05:03 | 3 | -- | 14 | 有 | -- | -- | -- | -- | -- | -- | -- |
| Z8 | 青岛北 | 北京 | 21:00 | 06:10 | 09:10 | -- | -- | -- | -- | -- | 有 | -- | 无 | -- | -- | -- |
+------+--------+--------+----------+----------+-------+--------+--------+--------+--------+----------+------+------+------+------+------+------+