安装相关库
(py3env) [root@localhost tickets]# pip install requests prettytable docopt colorama
命令行解析工具docopt (注释一定要在import之前写)
(py3env) [root@localhost tickets]# vim tickets.py
# coding: utf-8
"""火车票查看器
Usage:
tickets [-gdtkz] <from> <to> <date>
Options:
-h,--help 显示菜单
-g 高铁
-d 动车
-t 特快
-k 快速
-z 直达
Example:
tickets 北京 上海 2016-10-10
tickets -dg 成都 南方 2016-10-10
"""
from docopt import docopt
if __name__ == '__main__':
arguments = docopt(__doc__)
print(arguments)
验证命令行解析工具
(py3env) [root@localhost tickets]# python tickets.py -dg 成都 南京 2017-12-10
{'-d': True,
'-g': True,
'-k': False,
'-t': False,
'-z': False,
'<date>': '2017-12-10',
'<from>': '成都',
'<to>': '南京'}
通过Chrome访问12306,在后台查获取到对应访问URL,此时发现URL请求中是站台名称被转换成了字母,通过查看网页源代码发现是一个JS文件,故需要先将JS文件下载到本地
(py3env) [root@localhost tickets]# vim parse_station.py
# coding: utf-8
import re
import requests
from pprint import pprint
url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9035'
# 拉取站台中文与字母对应关系
response = requests.get(url, verify=False)
# 不需要核实打开URL
stations = re.findall(u'([\u4e00-\u9fa5]+)|([A-Z]+)',response.text)
# 提取URL返回JSON中 中文|字母 格式
pprint(dict(stations), indent=4)
# 格式化打印,以空格4为间隔
执行下载
(py3env) [root@localhost tickets]# python parse_station.py > stations.py
/py3env/lib/python3.5/site-packages/urllib3/connectionpool.py:858: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings InsecureRequestWarning)
对站台名称与字母转换文件中添加名称,形成字典方便使用
(py3env) [root@localhost tickets]# vim stations.py
stations = {
'一间堡': 'YJT',
添加一个类用于处理访问参数及表格展示
(py3env) [root@localhost tickets]# cat tickets.py
# coding: utf-8
"""火车票查看器
Usage:
tickets [-cgdtkz] <from> <to> <date>
Options:
-h,--help 显示菜单
-g 高铁
-d 动车
-t 特快
-k 快速
-z 直达
-c 动车组
Example:
tickets 北京 上海 2016-10-10
tickets -dg 成都 南方 2016-10-10
"""
import requests
import json
import sys
from docopt import docopt
from stations import stations
from prettytable import PrettyTable
from colorama import init, Fore
class TrainsCollection:
header = "车次 车站 时间 历时 特等 一等 二等 高级软卧 软卧 动卧 硬卧 软座 硬座 无座 其它".split()
def __init__(self, available_trains, options):
"""查询到的火车班次集合
:param available_trains:一个列表,包含可获得的火车班次,每个火车班次是一个字典
:param options:查询的选项,如高铁,动车,etc
"""
self.available_trains = available_trains
self.options = options
# 将历时转化为小时和分钟的形式
def get_duration(self, raw_train):
duration = raw_train[10].replace(':', '小时') + '分'
if duration.startswith('00'):
return duration[4:]
if duration.startswith('0'):
return duration[1:]
return duration
def check_equals(self, start_station, end_station, test_station):
"""
检查是否是始、过、终
:param start_station: 出发位置
:param end_station: 到达位置
:param test_station: 判断位置
:return: 返回结果
"""
if start_station == test_station:
return '始'
elif end_station == test_station:
return '终'
else:
return '过'
def trains(self):
# 获取每一条数据
for raw_train in self.available_trains:
# 将数据按|分隔
raw_train = raw_train.split('|')
# 获取数组中第4列数据
train_no = raw_train[3]
# 获取车次对应第一个字母
initial = train_no[0].lower()
# 反转station所对应的字典
station_re = dict(zip(stations.values(), stations.keys()))
# 如果没有参数或查询车次首字母在opthons中才会显示
if not self.options or initial in self.options:
# 分别获取车次始发、终点、查询开始站、查询到达站
begin_station = station_re.get(raw_train[4])
end_station = station_re.get(raw_train[5])
from_station = station_re.get(raw_train[6])
to_station = station_re.get(raw_train[7])
# 判断是否为始发、终止、过
begin_flag = self.check_equals(begin_station, end_station, from_station)
end_flag = self.check_equals(begin_station, end_station, to_station)
# 将数据拼接
train = [
train_no,
'\n'.join([Fore.GREEN + begin_flag + ' ' + str(from_station) + Fore.RESET,
Fore.RED + end_flag + ' ' + str(to_station) + Fore.RESET]),
'\n'.join([Fore.GREEN + raw_train[8] + Fore.RESET,
Fore.RED + raw_train[9] + Fore.RESET]),
self.get_duration(raw_train),
# 如果查询到数据不为空就使用否则就是--
# 商务座、特等座
raw_train[32] if raw_train[32] != "" else "--",
# 一等座
raw_train[31] if raw_train[31] != "" else "--",
# 二等座
raw_train[30] if raw_train[30] != "" else "--",
# 高级软卧
raw_train[21] if raw_train[21] != "" else "--",
# 软卧
raw_train[23] if raw_train[23] != "" else "--",
# 动卧
raw_train[33] if raw_train[33] != "" else "--",
# 硬卧
raw_train[28] if raw_train[28] != "" else "--",
# 软座
raw_train[24] if raw_train[24] != "" else "--",
# 硬座
raw_train[29] if raw_train[29] != "" else "--",
# 无座
raw_train[26] if raw_train[26] != "" else "--",
# 其它
raw_train[22] if raw_train[22] != "" else "--",
]
yield train
# 按表格打印
def pretty_print(self):
pt = PrettyTable()
# 设置表格标题
pt._set_field_names(self.header)
# 一行一行打印
for train in self.trains():
pt.add_row(train)
print(pt)
def cli():
"""command-line interface"""
# 将绑定交互参数
arguments = docopt(__doc__)
from_station = stations.get(arguments['<from>'])
to_station = stations.get(arguments['<to>'])
date = arguments['<date>']
#print(arguments)
# 构建URL
url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT'.format(
date, from_station, to_station
)
# 获取参数中的key从arguments中,前提为value为True
options = ''.join([
key for key, value in arguments.items() if value is True
])
r = requests.get(url, verify=False)
# 以防输入过期的时间,如果时间为过期时间就终止查询
available_trains = r.json()['data']['result'] if 'data' in r.json() else print(r.json()['messages']),sys.exit()
TrainsCollection(available_trains, options).pretty_print()
# __name__为__main__时表示程序作为主程序执行,而不是使用import 作为模块导入执行
if __name__ == '__main__':
cli()
展示结果
![这里写图片描述](https://img-blog.csdn.net/20171213173213662?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjcwMDUxNQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)