之前写过用Python查询火车票的小程序,后来不可以用了,是由于12306网站改版,导致一些参数发生了变化,今天又做了一下,调试成功了,现在把修改的过程记录一下。
这次主要的代码是使用了实验楼的一个教程案例,对其中的部分参数进行了修改:
1、获取站名的URL地址中的station_version调整为最新的1.8997
2、余票查询的地址也变了,具体可以看下面的代码
3、请求返还的数据结构也发生一些变化,数据放在json字符串的data里面,并且是一个list,list中的元素是每趟火车的信息(这是字典结构),而对我们有用的火车信息放在queryLeftNewDTO里面
在这个小程序中需要使用的库有:
requests、docopt、colorama、prettytable
对于具体的获取地址、参数等,在此不赘述,可以参考实验楼的教程,或者在网上搜一下。
#parse_stations.py
# 通过以下语句生成车站代码:python3 parse_stations.py > stations.py
# 获取车站的编码
import re
import requests
from pprint import pprint
# pprint 提供了打印出任何Python数据结构类和方法
# pprint.pprint(object,stream=None,indent=4, width=80, depth=None)
# 输出格式的对象字符串到指定的stream,最后以换行符结束
url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8997'
response = requests.get(url, verify=False)
stations = re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)', response.text)
pprint(dict(stations), indent=4)
# coding: utf-8
# tickets.py
"""命令行火车票查看器
Usage:
tickets [-dgktz] <from> <to> <date>
Options:
-h, --help 查看帮助
-d 动车
-g 高铁
-k 快速
-t 特快
-z 直达
Examples:
tickets 上海 安阳 2016-10-10
tickets -dg 上海 安阳 2016-10-10
"""
import requests
from docopt import docopt
from prettytable import PrettyTable
from colorama import init, Fore
from stations import stations
init() # init(autoreset=True) 添加了这个参数之后,对于输出的颜色就不需要每次都Fore.RESET
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.get('lishi').replace(':', '小时') + '分'
if duration.startswith('00'): # 如果字符串是以00开头的则返回TRUE
return duration[4:]
if duration.startswith('0'):
return duration[1:]
return duration
@property
def trains(self):
for raw_train in self.available_trains:
raw_train = raw_train.get('queryLeftNewDTO')
if raw_train is not None and raw_train != '':
train_no = raw_train['station_train_code']
initial = train_no[0].lower()
if not self.options or initial in self.options:
train = [
train_no,
'\n'.join([Fore.GREEN + raw_train['from_station_name'] + Fore.RESET,
Fore.RED + raw_train['to_station_name'] + Fore.RESET]),
'\n'.join([Fore.GREEN + raw_train['start_time'] + Fore.RESET,
Fore.RED + raw_train['arrive_time'] + Fore.RESET]),
self._get_duration(raw_train),
raw_train['zy_num'],
raw_train['ze_num'],
raw_train['rw_num'],
raw_train['yw_num'],
raw_train['yz_num'],
raw_train['wz_num'],
]
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>']
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
)
options = ''.join([
key for key, value in arguments.items() if value is True
])
r = requests.get(url, verify=False)
available_trains = r.json()['data']
TrainsCollection(available_trains, options).pretty_print()
if __name__ == '__main__':
cli()
下面是查询的结果:
查询结果
在后续的调试过程还发现了一个有意思的地方,就是当我修改目的地为昆山的时候,发现有历时近100个小时,这应该是程序的bug,没有对这个预售的状态进行判断,在返回的数据结果中buttonTextInfo的值一般是‘预售’,我估计可以根据这个值判断一下。
程序bug
另外对于结果的排序也没有做,我觉得可以再完善一下,后面有时间了可以再补充一下。
这个方法不是很完美:
print(pt.get_string(sortby='历时',reversersort=False))
网友评论