12306 火车票余票查询脚本开发
12306 余票查询开发初衷
可能很多同学有疑惑都2024年了,怎么还需要自己开发这个余票查询功能呢?毕竟当前市面太多自动化抢票工具了,只需要填写基本信息就可以完成从抢票到下单等一系列操作,确实,现在有很多抢票软件做的都很完美,但是有一个痛点目前是我遇到的,那就是
1.我想查例如 北京—> 石家庄的票,在这样的大型节假日的时候,我们都会选择抢票,但是抢票软件都是选择你关注的的,虽然抢票软件可以自动抢新开列车,但是如果新开的列车不是你所期望的时间,这时间把钱付了,你还要退票,那么就要面临手续费!
2.另一方面,我想看当前那些有余票的,我就需要每次打开软件去查询,没有一个很方便的直接告诉我,哪列车当前有票!会感觉很麻烦,如果有一个程序可以帮我自动查询然后告诉我,那么就很完美了!
那么我开发的这个脚本就可以完美的解决上述的两个问题,但是目前仅限于查询余票,其余还是做不到的!后续功能可以慢慢加上!
12306 网站接口剖析
如果我们想查询余票,各位同学肯定会想到的时候通过接口查询,那么我们首先观察12306 网站在查询的时候都用了那些接口呢?假如我们这时候查询 北京—> 石家庄 2024年10月9题的火车票,通过查询NetWork面板我们发现一共请求了2个接口!
那么这两个接口具体是干嘛的呢? 下面我们就先剖析第一个接口
这个url_1 实现了我们的查询功能就是查询北京—>石家庄 10月09日的票,可以看返回体:
1 |
|
上述的返回体中我截取了result中的部分数据,因为返回太多了我们只需要对一个观察就可以找到具体的规律,此时我们看到
1 |
|
首先是一堆乱码,后面我们看到有中文的预定,然后我们继续观察看到有 K7725 这个火车编号的 信息,然后后面后有部分时间信息,以及后续的票务信息:|||||||有||有|有|||||, 那么有人要问了这些|
表示什么呢?先不管,这里我们认为是票务的信息,那么我们发现也就这些是我们能看懂的,那么我们怎么利用现有信息来实现余票查询呢?别着急后续继续解释。
url_2 是查询中转的接口,也就是查询北京—>石家庄 10月09日的可以乘坐哪些中转的车,一起看返回体:
1 |
|
这里返回的信息也依旧截取了部分返回信息,那么可以看到这个返回体更加清晰,描述了中转车量的信息,那么由于我们这里暂时不考虑中转,首先就关心这个接口了!
针对url_1 接口的返回体解析
那么从上面我们看到了返回信息里面包含了乘车的编号信息,票务信息,那么就可以实现查询了,首先我们观察第一个也是上面遗留的问题:|||||||有||有|有|||||
, 这些 |
表示什么意思?下面我来解释一下,通过观察 我们对比该车次的返回在12306页面的表达看
观察上述信息发现,硬卧 二等卧、 硬座、 无座 均是有,而其他类型的例如 一等座、二等座 二等包座等 都是 --
,那么现在很明显了,|
表示的是无,有票以及票有数据,则是显示 有或者具体数据,那么此时我们已经解刨这个接口完毕了,找到了我们想要的信息,那么接下来的难点就是如何从这些返回体中取出想要的数据!
编写返回体提取票务信息逻辑
首先观察返回信息:
1 |
|
我们看到在上面中有一个预定的汉字,那么我就可以先以 预定
进行分割,此时可以获取后面的信息,那么就是
1 |
|
那么我们拿到信息如下:
1 |
|
那么我们继续观察发现在上述信息中有一个|Y|,继续观察其他的发现要不就是|Y|要不就是|N|,那么就可以按照这个进行分割。
1 |
|
也就得到了
1 |
|
此时拿到了具体的车次信息,那么想要后续的信息就可以取
1 |
|
那么拿到的是:
1 |
|
通过观察,在|20241008| 前面的乱码发现基本上长度一致,那么我就可以指定长度取截取
至此,我们实现了信息的采集和分割,那么我们能实现拿到具体车次,是否有票这些信息。
判断信息中是否有票
那么拿到数据之后,我们就要判断是否有票,我们可以根据是否有有以及是否有数字存在即可!
1 |
|
函数通过检查是否有数字以及是否有有,返回布尔值。
组装返回信息
目前计划是通过邮件将信息返回,那么此时已经可以判断车次,是否有票,我们就需要通过将我们拿到的数据进行重写组装,然后将其转化为一个html格式的表格便于我们查看,具体代码:
1 |
|
上面的函数接收一个list,然后进行转化生成一个html信息。
邮件发送
我们拿到html信息后需要第一时间通知给我,那么就要用到邮件通知,下面是邮件通知代码
1 |
|
这里我们声明是一个协程方法也就是发送邮件是异步的,我无需等待邮件发送后才执行下一个逻辑。
一些固定规律的解决
在继续查看返回体的时候发现里面会有固定的 W M F 的返回信息,此处为
1 |
|
也就是我想去掉 |1030W0| 后面的信息由于后续都是无规律的,但是肯定会有M W F 出现,那么我们可以获取当前位置然后进行分割,代码如下:
1 |
|
整体规划
到现在我们已经拿到了具体的信息,那么就可以规划我们的具体实现,首先我们可以支持 邮件多人发送,请求12306延迟设置防止IP访问频繁被封,用户关注的火车信息,假如我关注的车次只想他有票的时候才会通知也行支持多个车次的设置,假如我们不想一直接收邮件的发送,那么我们可以设置邮件接收的延迟时间,目前支持设置分钟,小时级别的延迟。
那么上面就是我们要实现的功能,下面就是已经实现的代码:
1 |
|
多线程和多进程的选择
刚开始我选择的是多线程,但是在实践中发现在传递信息的时候会发生 A 线路的信息 流转到了 B 线路中,后来查询资料发现,多线程的内存信息共享的可能会发生数据错乱,但是多进程是内存独立的不共享,此时在实现的时候选择了 进程池来实现!
1 |
|
上述代码采用了进程池的方式进行请求,最大设置了2个,可以自由设置,通过submit 将任务提交返回一个futures对象,然后我们需要看是否每个都完成了调用as_completed这个方法即可!
效果查看
运行后如下:
可以发现我们实现了我们上述的所有功能,下面看邮件效果:
可以看到邮箱收到了我们的邮件以及票务信息!
总结
通过编写这个脚本我也学到了很多知识,最大就是利用了进程池来实现,也感受到了多进程和多线程在使用上的差距,那么源码已经共享到github 地址是:https://github.com/dreamshao/12306
在使用过程中,如果无法查询可以将接口请求的最新cookie替换一下本地接口请求的cookie,如果有更好的建议或者有疑问可以联系我哦!