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

参考:
12306官方火车票api
12306火车票查询

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

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

chrome是个好东西,特别是它的控制台能看到很多细节。
12306网站通过chrome可以看到查询票的api
chrome控制台
其中有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 会包含北京南。看来不用人为进行模糊匹配

发布了27 篇原创文章 · 获赞 3 · 访问量 5万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览