您的位置:首页 > 编程语言 > Python开发

用python 通过12306api抓取列车信息

2017-01-09 17:28 453 查看
参考:

12306官方火车票api

12306火车票查询

PS:本文为学习参考实例。代码与上述大体相同。

首先了解这些查询接口是怎么来的

chrome是个好东西,特别是它的控制台能看到很多细节。

12306网站通过chrome可以看到查询票的api



其中有log? 和 queryA?两种开头的接口,网上介绍log是判断服务是否正常,用queryA进行查询

#在python控制台测试
> import requests
> url = 'https://kyfw.12306.cn/otn/leftTicket/log?leftTicketDTO.train_date=2017-01-11&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=XAY&purpose_codes=ADULT'
# 这里传入了三个变量,
train_date
from_station
to_station
站名为缩写标准,获得方法见后面介绍

r = requests.get(url, verify = False)
#chrome可以看到它用的是get方法, verify为忽略https
解析结果r.text为字典形式的字符串,通过r.json()得到解析后结果,
ticks = r.json()
分析结构, ticks['data']保存所有车次的信息。G653车次的查询结果表示为
g653 = ticks['data'][1]['queryLeftNewDTO']


从结果中提取需要的字段展示

12306查询结果列表的表头包含如下字段:

车次 出发站/到达站 出发时间/达到时间 历时 商务座 特等座 一等座 二等座 高级软卧 软卧 硬卧 软座 硬座 无座 其他 备注

分析g653对应的key分别为:

station_train_code from_station_name/to_station_name start_time/arrive_time lishi/day_difference swz_num tz_num zy_num ze_num gr_num rw_num yw_num rz_num yz_num wz_num qt_num

车次类型分(G,D,Z,T,K)首位不是字母的是普快。

如下代码根据以上通过接口查询的结果,并根据指定列车类型过滤

#代码与参考中大体相同
#coding=utf-8
from prettytable import PrettyTable

class TrainCollection(object):
"""
解析列车信息
"""
header = '序号 车次 出发站/到达站 出发时间/达到时间 历时 商务座  特等座 一等座 二等座 高级软卧 软卧 硬卧 软座 硬座 无座'.split()

def __init__(self, rows, traintypes):
self.rows = rows
self.traintypes = traintypes

def _get_duration(self, row):
"""
获取列车运行时间
这里的row = row['queryLeftNewDTO']
"""
duration = row.get('lishi').replace(':', '小时') + '分'
if duration.startswith('00'):
return duration[4:]
elif duration.startswith('0'):
return duration[1:]
return duration

@property    #将方法trains装饰为属性,之后以属性方式访问
def trains(self):
"""
解析原有采集数据,提取需要的字段保留
"""
result = []
flag = 0
for row in self.rows:
row = row['queryLeftNewDTO']
if row['station_train_code'][0] in self.traintypes or row['station_train_code'].isdigit() and 'P' in self.traintypes:
# 如果全是数字为普快类型,自定义为P
flag += 1
train = [
flag, row['station_train_code'],
'/'.join([row['from_station_name'], row['to_station_name']]),
'/'.join([row['start_time'], row['arrive_time']]),
self._get_duration(row),
row['swz_num'], row['tz_num'],row['zy_num'],
row['ze_num'],row['gr_num'],row['rw_num'],
row['yw_num'],row['rz_num'], row['yz_num'],row['wz_num']
]
result.append(train)
return result

def print_pretty(self):
"""
通过prettytable模块将列表字段美观输出
"""
pt = PrettyTable()
pt._set_field_names(self.header)
for train in self.trains:
pt.add_row(train)
print(pt)


命令行参数方式查询

参考中用来解析命令行参数使用了docopt[介绍]模块。

程序期望的执行方法是输入形如

python tickets.py -gdt beijing shanghai 2016-08-25来查询展示。

首先,需要将参数中的-gdt 转换为内部使用的不带-大写形式的字符列表。

可以接受的列车类型包含types = [‘-d’, ‘-g’, ‘-t’, ‘-z’, ‘-k’, ‘-p’]

train_type = []

for i in types:

if i in args.keys():

train_type.append(i[-1].upper())

其次,参数需要将接收的地名拼音转换为内部的唯一地名代码

站名代码基本不变,参考中介绍了通过一个接口预先导出所有站名代码供程序直接使用。

#coding=utf-8

import requests
import re
from pprint import pprint

def get_stations():
url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8968'
#浏览器中直接输入上面链接不用带version版本,也可以看到
r = requests.get(url, verify = False)
patter = re.compile('([A-Z]+)\|([a-z]+)')
items = dict(re.findall(patter, r.text))
# 将结果中的大写字母代码,车站拼音 反转为 车站拼音->代码的字典结构
stations = dict(zip(items.values(), items.keys()))
pprint(stations, indent=4)
if __name__ == '__main__':
get_stations()


执行脚本并定向到stations.py中,修改其中的字典头部赋值为stations = {…}

这样在之后脚本导入该模块时可以使用其中的字典变量stations.

最终代码:

#coding=utf-8
"""
12306 tickets 查询

Usage:
tickets.py [-gdtkz] <from> <to> <date>

Options:
-h --help   Show this screen.
-g          高铁
-d          动车
-t          特快
-k          快速
-z          直达
-p          普快

Example:
tickets.py  -gdt beijing shanghai 2016-11-12
"""

import requests
from docopt import docopt

from TrainCollection import TrainCollection
from stations import stations

class TicketsSearch(object):
def __init__(self):
"""
通过类的初始化获取脚本命令行参数
"""
self.args = docopt(__doc__)    #通过解析脚本__doc__文本得到参数结构

def _get_traintype(self):
"""
获取列车类型
"""
types = ['-d', '-g', '-t', '-z', '-k', '-p']
train_type = []
for i in types:
if i in self.args.keys():
train_type.append(i[-1].upper())
if train_type:
return train_type
else:
return ['G', 'D', 'T', 'Z', 'K', 'P']

def cli(self):
"""根据变量执行查询"""
from_station = stations.get(self.args['<from>'])
to_station = stations.get(self.args['<to>'])
leave_time = self.args['<date>']

url = 'https://kyfw.12306.cn/otn/leftTicket/queryA?leftTicketDTO.train_date={0}&leftTicketDTO.from_station={1}&leftTicketDTO.to_station={2}&purpose_codes=ADULT'.format(leave_time, from_station, to_station)

#查询
r = requests.get(url, verify=False)
if 'data' not in r.json().keys():
print("查询日期错误,或不再预售期")
exit
traindatas = r.json()['data']

#筛选
traintypes = self._get_traintype()
trains = TrainCollection(traindatas, traintypes)

#打印
trains.print_pretty()

if __name__ == '__main__':
cli = TicketsSearch()
cli.cli()


该查询需要完善的地方:

1. 时间参数的多种格式,这里仅支持2017-01-11格式,应该增加其他类型。同时对于日期不再范围内,需要人为控制,避免报错

2. 站名拼音参数的搜索似乎并非严格,而是有一定模糊,比如beijing shanghai 会包含北京南。看来不用人为进行模糊匹配
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python