天天看点

python实现12306查询火车票

为方便查询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的查票过程,如下图:

python实现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 |   --   |   --   |   --   |   --   |    --    |  有  |  --  |  无  |  --  |  --  |  --  |
+------+--------+--------+----------+----------+-------+--------+--------+--------+--------+----------+------+------+------+------+------+------+