前几天看了一个爬取12306来获得火车票信息的教程,发现12306官网的存储车票信息的 Json 数据格式已经变了,导致这篇教程的代码已经没法继续使用了,因此我针对新的格式重新进行了解析,最后达到了目的。在此记录一下整个过程。

01/11/2018 更新:12306 更改了保存着余票信息的网址,有同学反映之前的代码运行会出错,于是我修改了一下代码,现在可以正常运行了。最新的代码在 GitHub 上,地址在文末倒数第二行。

先看一下最终效果吧

最终效果

只需要输入查询细节,就可以输出你想查询的车票信息,而且界面一目了然。

接口设计

用户在使用这个工具的时候,需要输入1.车次类型2.始发站3.终点站以及4.日期。火车有很多类型,可以大致分为如下几种:

-g 高铁

-d 动车

-t 特快

-k 快车

-z 直达

我们需要的接口就是刚刚提到的 4 种,因此接口看起来应该是这个样子

$ python tickets.py [-gdtkz] from to date

其中,tickets.py 是这个程序的名字,-gdtkz 是车次类型,from 是始发站,to 是终点站,date 是日期,用户在使用时需要填入这几个信息。

需要的库

requests 使用 Python 访问 HTTP 资源

docopt Python3 命令行解析工具

prettytable 格式化信息打印工具,见过过 MySQL 打印数据的界面吧

colorama 命令行着色工具

最方便的下载方式还是pip,如果觉得pip的下载速度太慢可以参考这篇文章解决:更换 pip 源

解析参数

# coding: utf-8

"""命令行火车票查看器

Usage:

tickets [-gdtkz]

Options:

-h,--help 显示帮助菜单

-g 高铁

-d 动车

-t 特快

-k 快速

-z 直达

Example:

tickets 武汉 上海 2017-11-20

tickets -dg 北京 南京 2017-11-20

"""

from docopt import docopt

def cli():

"""command-line interface"""

arguments = docopt(__doc__)

print(arguments)

if __name__ == '__main__':

cli()

上面的程序中,docopt会根据我们在程序开头定义的格式自动解析出参数并返回一个字典,也就是arguments,然后打印出这个字典的内容。

运行一下这个程序,比如查询一下11月20号从武汉到十堰的动车和快车,可以得到解析的结果如下所示,这和我们的接口是对应的

演示

获取数据

整个过程的关键是从 12306 获取数据和解析数据。

打开 12306 官网,点击“余票查询”,进入如下网页

余票查询

随便查询一下车票,比如我查一下 11 月 20 号从武汉到十堰的票,如图

随便查询

然后进入开发者模式下的 Network 页面,如图所示(我的浏览器是 Chrome,不同浏览器的进入方法可能不一样,不清楚的可以百度)

开发者模式-Network

再点击一次查询按钮,会发现 Network 页面有所变化,点击如图所示的项目,然后进入右边显示的 Request URL

URL

你看到应该是如下图所示的一团杂乱无章的数据

杂乱无章的数据

其实这是 Json 格式的数据,里面其实保存了我们查询的车次的所有车票的信息,我们的任务就是想办法把它们提取出来并显示出来。

我们先看看刚才的 URL:

不难发现几个关键信息:

train_date=2017-11-20 这是我刚才查询的日期

from_station=WHN 这是始发站

to_station=SNN 这是终点站

其中始发站和终点站的名字是用大写字母组成的代号代替的,然而用户输入的是汉字,我们需要找到汉字和代号的对应关系。查看一下网页的源代码,搜索 station_version 关键字,找到如下位置

station_version

打开这个链接,你会发现一个惊喜

station_version

这里面存储了全国的城市代号,接下来我们写一个脚本,把城市和代号以字典的形式存入一个 Python 文件

新建 parse_station.py 文件,并写入以下代码

import re

import requests

from pprint import pprint

url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8971'

response = requests.get(url, verify=False)

stations = re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)', response.text)

pprint(dict(stations), indent=4)

这里用到了正则表达式,通过正则表达式把所有汉字和后面紧跟着的字母解析出来。

运行这个脚本,它将以字典的形式返回所有车站和代号, 并将结果保存到到 stations.py 文件中

$ python3 parse_station.py > stations.py

打开stations.py文件,看起来是这样的(因为这个字典没有名字,所以 Pycharm 发出了 warning,所以界面看起来黄黄的...)

stations.py

给这个字典命名为 stations,最终stations.py看起来是这样的

stations.py

现在,用户输入车站的中文名,我们就可以直接从这个字典中获取它的字母代码了:

...

from stations import stations

def cli():

"""command-line interface"""

arguments = docopt(__doc__)

from_station = stations.get(arguments[''])

to_station = stations.get(arguments[''])

date = arguments['']

# 构建 URL

url = 'https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date={}&leftTicketDTO.from_station=' \

'{}&leftTicketDTO.to_station={}&purpose_codes=ADULT'.format(date, from_station, to_station)

回想一下我们的最终目的是从 Json 数据中解析出车票的信息,我们先向存储 Json 数据的 URL 发送请求:

...

import requests

def cli():

...

# 添加verify=False参数不验证证书

r = requests.get(url, verify=False)

print(r.json())

这里打印出了 Json 数据,的确是杂乱无章的,下一步就进行解析。

解析数据

仔细观察和对比 Json 数据和 12306 网站上显示的车票信息,可以发现所有的车票信息都存储在 r.json()["data"]["result"] 下,并且存储的形式是 Python 中的列表,一个车次对应列表中的一个元素,这个元素是一个特别长的字符串,但是里面却有我们需要的所有信息,包括始发站,终点站,开车时间,到达时间,总时间,以及各个座位的车票是否有剩余,下面用红框框住的是其中一个车次的数据

json

这里面除了两段很长的貌似没有意义的字符串,剩余的信息都用 | 隔开了,剩下的工作就是遍历这个列表里的所有元素,并针对每个元素进行解析。

class TrainsCollection:

header = '车次 车站 时间 历时 商务特等座 一等座 二等座 高级软卧 软卧 硬卧 硬座 无座'.split()

def __init__(self, available_trains, station_map, options):

"""查询到的火车班次集合

:param available_trains: 一个列表, 包含着所有车次的信息

:param station_map: 一个字典,包含不同代号对应的站点

:param options: 查询的选项, 如高铁, 动车, etc...

"""

self.available_trains = available_trains

self.station_map = station_map

self.options = options

def geturation(self, duration):

duration = duration.replace(':', '小时') + '分'

if duration.startswith('00'):

return duration[4:]

if duration.startswith('0'):

return duration[1:]

return duration

@property

def trains(self):

for raw_train in self.available_trains:

# 利用正则表达式得到列车的类型

train_type = re.findall('[\u4e00-\u9fa5]+\|\w+\|(\w)', raw_train)[0].lower()

if train_type in self.options and '售' not in raw_train and '停运' not in raw_train:

station = re.findall('(\w+)\|(\w+)\|\d+:', raw_train)[0] # 元组,保存始发站和终点站的代号

s_station = station[0] # 始发站的代号

e_station = station[1] # 终点站的代号

train = [

# 车次

re.findall('[\u4e00-\u9fa5]+\|\w+\|(\w+)', raw_train)[0],

# 始发站和终点站

'\n'.join([Fore.MAGENTA+self.station_map[s_station]+Fore.RESET,

Fore.BLUE+self.station_map[e_station]+Fore.RESET]),

# 发车时间和到站时间

'\n'.join([Fore.MAGENTA+re.findall('\|(\d+:\d+)', raw_train)[0]+Fore.RESET,

Fore.BLUE+re.findall('\|(\d+:\d+)', raw_train)[1]+Fore.RESET]),

self.geturation(re.findall('\|(\d+:\d+)', raw_train)[-1]), # 行驶总时间

re.findall('(\d){8}\|(\w*\|){18}(\w*)', raw_train)[0][-1], # 商务特等座

re.findall('(\d){8}\|(\w*\|){17}(\w*)', raw_train)[0][-1], # 一等座

re.findall('(\d){8}\|(\w*\|){16}(\w*)', raw_train)[0][-1], # 二等座

re.findall('(\d){8}\|(\w*\|){7}(\w*)', raw_train)[0][-1], # 高级软卧

re.findall('(\d){8}\|(\w*\|){9}(\w*)', raw_train)[0][-1], # 软卧

re.findall('(\d){8}\|(\w*\|){14}(\w*)', raw_train)[0][-1], # 硬卧

re.findall('(\d){8}\|(\w*\|){15}(\w*)', raw_train)[0][-1], # 硬座

re.findall('(\d){8}\|(\w*\|){12}(\w*)', raw_train)[0][-1] # 无座

]

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)

我们封装一个类专门用来解析数据,这个类对传来的列表进行遍历,并用正则表达式解析每一个元素,然后把这些信息存储在列表train中,最后再通过prettytable库将所有信息有序的打印出来。

在原教程中,车票的信息是存储在 12306 网站中的字典里的,因此解析十分方便,然而后来 12306 将车票信息的存储格式改为了列表,使得信息的提取变难了,但是只要将正则表达式正确运用,依然可以解析出我们想要的信息,只不过比字典要麻烦一些而已。

显示结果

最后,我们将上述过程进行汇总并将结果输出到屏幕上:

def cli():

"""command-line interface"""

arguments = docopt(__doc__)

from_station = stations.get(arguments[''])

to_station = stations.get(arguments[''])

date = 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)

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']

station_map = r.json()['data']['map']

TrainsCollection(available_trains, station_map, options).pretty_print()

其中,我们通过colorama库为站点和时间信息添加了颜色,使结果看起来更加舒服。

全部代码

由于stations.py中的字典很长,所以就不在这里将所有代码贴出来了,感兴趣的可以到 Github 上下载查看:Python3 实现火车票查询工具

python识别火车票二维码_Python3 实现查询火车票工具相关推荐

  1. python识别发票二维码_Python 实现二维码生成和识别

    今天突然想给自己自己做个头像,然后还是二维码的形式,这样只要扫一扫就可以访问我的主页.然后就开始自己的苦逼之路... 其实实现二维码java,c#,C++等都可以实现:由于自己正在学python,所以 ...

  2. 用python识别条形码,二维码并且定位标注

    原理很简单用OpenCV处理图片pyzbar识别条形码 python里面有一个包pyzbar可以用识别二维码和条形码,我们再结合OpenCV处理图片的功能就可以标注出二维码位置内容 我们生活中的常见的 ...

  3. python识别发票二维码_python如何实现二维码的生成和识别

    安装: pip install myqr pip install pyzbr pip install pil 报错请切换 pip install pillow # 扫描二维码,直接访问words指定的 ...

  4. python识别火车票二维码_Python实现查询12306火车票信息

    例子来源于马哥的公众号,看了几遍,有些地方存在些疑问,然后就自己查找些资料,重写的一下,但是对于获取到的信息,并不能有效的解析出来,而且对于中文字符处理,并不是很好,请大神指教下!谢过! 1.接口设置 ...

  5. python实现二维码识别软件_OpenCV和Zbar两个Python模块实现二维码和条形码识别

    在我们的日常生活中,处处可见条形码和二维码. 在以前,我们去逛书店时,或者你现在随手拿起你身边的一本书,你肯定能看到书本的封页后面印有一排黑色线条组成的标签,也就是条形码:你去你们学校的自助机上借书还 ...

  6. 数字图像处理二维码识别 python+opencv实现二维码实时识别

    数字图像处理二维码识别 python+opencv实现二维码实时识别 特点: (1)可以实现普通二维码,条形码: (2)解决了opencv输出中文乱码的问题 (3)增加网页自动跳转功能 (4)实现二维 ...

  7. python 检查图品二维码,识别图片二维码

    代码识别图片二维码方法 大体分两种 1.查资料 理解二维码生成原理,自己搬砖解码 2.是有类库(第三方包) 二维码中包含的信息: 空白区(边界) 位置探测区 分隔符 定位图形 矫正图形 格式信息 版本 ...

  8. 如何用python制作动态二维码,提升表白成功率?

    来源:凹凸数据 本文约1000字,建议阅读5分钟. 本文教你用python制作动态二维码,助你表白成功! 关注数据派THU(DatapiTHU)后台回复"20200520"获取完整 ...

  9. Android 识别图片二维码

    zxing依赖: compile 'cn.yipianfengye.android:zxing-library:2.2' 初始化:private String SAVE_PIC_PATH = Envi ...

最新文章

  1. 一个WEB网站高并发量的解决方案
  2. php elasticsearch ik,elasticsearch和analysis-ik的安装使用
  3. 数字新写法3_000_000,简单明了
  4. One order deletion tool
  5. 云服务远程登录---设置安全组
  6. 华北计算机研究所分房,请教公安部第一研究所这样分房合理吗??
  7. View 绘制体系知识梳理(4) 绘制过程之 Layout 详解
  8. mariadb密码问题
  9. 详解python运行三种方式_详解python运行三种方式
  10. java----JUnit
  11. 365Key今天不能用了,感觉不爽
  12. 准双向口和KELL C头文件已经处理
  13. 漏洞CNNVD-201805-248的复现与提权
  14. SpringMVC工作原理详细讲解
  15. java线程之读写锁
  16. 未知的软件异常0xc0000409解决办法
  17. mysql取第一行数据_select取第一行数据
  18. 管网模型(julia)
  19. 易语言python识别图片验证码_图片识别-打码平台-打码网站-识别验证码-图鉴网络科技有限公司...
  20. Feedback from Microsoft

热门文章

  1. 南阳理工628解题报告(小媛在努力)
  2. win10直接打开服务器文档,win10如何打开数据库服务器
  3. dell台式机进入安全模式_戴尔电脑如何进入安全模式
  4. 题目 1546: 班级排名
  5. 文章数据分析与自动分类
  6. Python 代码理解 polygon.py
  7. AVL右旋转思路分析与图解
  8. Windows系统下将MAC系统的可执行dmg文件转换为ISO文件 安装到虚拟机的MAC系统上
  9. 2022.09 青少年Python等级考试(六级) 编程题部分
  10. 编写三个系别的学生类:英语系,计算机系,文学系(要求通过继承学生类) [选做题] (java)