APP下载

基于Server酱的电子客票生成信息推送设计与实现

2021-10-24姚国任

吉林化工学院学报 2021年9期
关键词:码表车次字段

姚国任

(淮南师范学院 计算机学院,安徽 淮南 232038)

随着用户出行服务的需求个性化,电子客票的查询订阅越来越凸显出重要性,尤其表现在法定节假日、高峰春运、集中的寒暑假、旺盛的旅游季.针对出行服务信息的社交媒介化、思维数据化的改变,传统的线下服务逐步被个人的App订阅所取代,私人定制无疑受到了许多人的热捧.很多时候,订阅者在查询余票的时候因检索手段的差异显示的结果也不尽相同,无法订购自己想要的票并非因为客票资源不足,很可能是普通订阅者无法掌握线上数据运营的动力,无法改善现有票源的碎片集成功能[1].铁路实行的是市场化的开放运营方案,从“四纵四横”到“八纵八横”,目的都是避免列车的运力浪费.最佳购票方案一直都是用户通过手动检索进行对比判断,针对大数据时代,个性化的最佳出行方案顺势而行.

鉴于上述情况,一种基于Python爬虫技术[2]提出了很好的解决方案,尤其是在数据挖掘[3]方面精准获取数据,以形成电子客票为目的,利用浏览器的调试工具分析URL种子,以requests获取相关接口,结合脚本语言挖掘车次、日程、余票等信息进行比对、解析,模拟神经网络模型[4]进行优化、拼接、分段处理,将碎片化的余票信息形成数据集合,摸排那种查询无票而实际有票的假信息,为查询或者获取其他相关大数据关联信息[5]争取时间和机会.该方案从数据爬取、解析数据、算法设计、信息推送4个层次线性顺序进行架构.

1 数据爬取

1.1 发起请求

推荐用Google Chrome或者Mozilla Firefox浏览器登录中国铁路12306官网:https://www.12306.cn/index/,借助浏览器自身的DevTools调试插件或者类似与网络爬虫抓包等工具判断当前浏览器窗口的网络请求,以多次查询余票的请求可以判断不是AJAX方式,进而可判定能用Selenium实现浏览器的模拟,利用python语言的第三方库requests的一些函数就能实现爬虫请求[6].

1.2 数据采集

在调试模式下查看请求结果获取查询接口,地址返回的信息属于JSON串,对其他请求没有限制属于典型的Get请求,后期将方便通过python构造这一Get方法,寻找一些有效的request请求,以2021年1月26日查询从合肥到郑州的车次情况为例,以下3项为爬虫的几项关键技术[7].

1.2.1 爬取接口地址

继承了urllib2全部特征的requests库,支持http协议的连接池,支持用cookie维持会话[8],通过分析网页调试模式用requests爬取火车票余票相关信息的接口地址,包括出行日期、用英文字母代码表示出发地与目的地:https://kyfw.12306.cn/otn/leftTicket/queryY?leftTicketDTO.train_date=2021-01-26&leftTicketDTO.from_station=HFH&leftTicketDTO.to_station=ZZF&purpose_codes=ADULT,这个接口地址对于后期的程序设计调试至关重要,地址也会因为日期的更新而刷新生成,上述地址测试时间是2021年1月26日以前的,但2021年1月27日的当日地址用到的是:https://kyfw.12306.cn/otn/leftTicket/queryZ?,通过Preview result展示如图1所示.

图1 Preview result展示

result展示的共38条记录,正好与合肥到郑州(测试时间:2021年1月26日17:20)的车次类型(GC-高铁/城际、D-动车、Z-直达、T-特快、K-快速、其他)显示的38个车次完全吻合.

1.2.2 提取重要参数

利用requests库的requests.head()方法获取HTML网页头部信息,依据Request URL GET请求需要传入4个参数:日期、出发地、目的地、乘客类型正好对应以下字段leftTicketDTO.train_date、leleftTicketDTO.from_station、leftTicketDTO.to_station、purpose_codes,与前台输入查询条件信息完全一致,如图2所示,其中HFH与ZZF信息需要在后面的数据分析后进行解析.

图2 Request URL请求参数

1.2.3 爬取码表信息

爬取车站的码表信息,名称为station_name.js?station_version=1.9183对应的js文件:https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9183,请求该js文件后,在新的页面打开标签链接,获取一部分结果如图2所示,图中码表与12306网站上铁路所有站点正好完全匹配.

var station_names ='@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1@bji|北京|BJP|bei-jing|bj|2@bjn|北京南|VNP|beijingnan|bjn|3@bjx|北京西|BXP|beijingxi|bjx|4@gzn|广州南|IZQ|guangzhounan|gzn|5@cqb|重庆北|CUW|chongqingbei|cqb|6@cqi|重庆|CQW|chongqing|cq|7@cqn|重庆南|CRW|chongqingnan|cqn|8@cqx|重庆西|CXW|chongqingxi|cqx|9@gzd|广州东|GGQ|guangzhoudong|gzd|10@sha|上海|SHH|shang-hai|sh|11@shn|上海南|SNH|shanghainan|shn|12@shq|上海虹桥|AOH|shanghaihongqiao|shhq|13@shx|上海西|SXH|shanghaixi|shx|14@tjb|天津北|TBP|tianjinbei|tjb|15@tji|天津|TJP|tianjin|tj|16@tjn|天津南|TIP|tianjin-nan|tjn|17@tjx|天津西|TXP|tianjinxi|tjx|18@xgl|香港西九龙|XJA|hkwestkowloon|xgxjl|19@cch|长春|CCT|changchun|cc|20@ccn|长春南|CET|changchunnan|ccn|21@ccx|长春西|CRT|changchunxi|ccx|22@cdd|成都东|ICW|chengdudong|cdd|23@cdn|成都南|CNW|chengdunan|cdn|24@cdu|成都|CDW|chengdu|cd|25@cdx|成都西|CMW|chengduxi|cdx|26@csh|长沙|CSQ|changsha|cs|27@csn|长沙南|CWQ|changshanan|csn|28@dmh|大明湖|JAK|daminghu|dmh|29@fzh|福州|FZS|fuzhou|fz|30@fzn|福州南|FYS|fuzhounan|fzn|31@gya|贵阳|GIW|guiy-ang|gy|32@gzh|广州|GZQ|guangzhou|gz|33@gzx|广州西|GXQ|guangzhouxi|gzx|34@heb|哈尔滨|HBB|haerbin|heb|35@hed|哈尔滨东|VBB|haerbindong|hebd|36@hex|哈尔滨西|VAB|haerbinxi|hebx|37@hfe|合肥|HFH|hefei|hf|38@hhd|呼和浩特东|NDC|huhehaotedong|hhhtd|39@hht|呼和浩特|HHC|huhehaote|hhht|40@hkd|海口东|HMQ|haikoudong|hkd|42@hko|海口|VUQ|haikou|hk|43@hzd|杭州

2 解析数据

2.1 分析车次

从页面上爬取到的数据,一般都不能作为数据直接使用,都需要进行信息的预处理[9-10],根据前面网页分析的Response的URL与参数,只需要分析返回字段的实际意义,比对Response Json与页面结果,以当前网站查询到G3168次列车为例:

比对1(Response Json)如图3所示.

RoNIVloVyUljqnZQODdWI6GgaXSKYkNlGNUH34ZXGY8CNVoX5tWNohNb87FqR0yxOTrD4fvs56V4%0A0zTphvOEl5TCnHsh8U3oKJTlWfnbz8cgomMViGezz0wTbwXHj3IdQ11oDqIgZ1qeNWA%2FH7d2vXgB%0Acx5CVckoo3VOjlm5BQ0X3JBnBfbdJ%2FsV0yRb31WDTMVfSzi5DH5P%2B%2BD%2FZ6%2BBsdsKETFnvBDKFTS0%0AMe9cHvTbKcSgCku%2F6W9krPfQNAcZtRmThzhCgTY0daebKx0b5pC4snKFEMVk%2FyhkORg8qePXztV7%0AZfkZZw%3D%3D|预订|5i000G316801|G3168|ENH|EAY|ENH|ZAF|0700|1036|0336|Y|CTlQYgG6jmyaVJ%2Fs6SSVvw7gn72UnoTMKDP%2Fr1ulbsyHy%2F19|20210126|3|H3|01|10|1|0|||||||||||有|17|5||O0M090|OM9|0|0||O028950021M0465000179088550005|0|||||1|#1#0

比对2(查询页面)如图5所示.

图5 查询页面信息

比对结果:比对1中的车次(已标注粗体)、车出发时间与到达时间(已标注粗体、倾斜)、日期(已标注粗体)、一等座与二等座有无车票(已标注粗体、倾斜)与比对2返回页面中的字段完全匹配,代码编写解析的时候只需要将Json result用符号“|”切割,这些数据清洗[11]以后才能存储,需要删除一些重复或者无用的数据.

2.2 解析码表

在爬取map过程中的“合肥”字段用“HFH”表示,“郑州”字段用“ZZF”表示,探究图2中的码表,排除无效的消息,发现车站站点的基本规律是“汉字+|+三位的英文字符”,利用正则表达式表示为“([u4e00-u9fa5]+)|([A-Z]+)”,其中“u4e00-u9fa5”是汉字unicode编码的范围,通过解析后获取的码表就有6236条记录,截取一部分如图6所示.

北京北|VAP北京东|BOP北京|BJP北京南|VNP北京西|BXP广州南|IZQ重庆北|CUW重庆|CQW重庆南|CRW重庆西|CXW广州东|GGQ……

部分代码实现如下所示:

def station_info():

#中国铁路12306官网的城市名称与城市代码对应js文件的url:

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

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

#“u4e00-u9fa5”为汉字unicode码范围

sn=u'([u4e00-u9fa5]+)|([A-Z]+)' #按正则表达式进行匹配:

result=re.findall(sn,rs.text)

stationinfo=dict(result)

return stationinfo

3 系统设计方案

3.1 实现算法

该设计思想是通过算法选择时间最短、距离最短、降低换乘次数的最优出行路线供查询者参考.模拟一条线路,始发站标识为T1,终点站标识为Tn,在始发站与终点站之间站点线性依次标识为T1、T2、T3、……、Tn,站点匹配成码表后将建立数据库,具体策略如下所示:

(1)建立码表链接

将T1->T2、T1->T3、T1->T4、T1->T5、……、T1->Tn-1、T1->Tn标识为S12、S13、S14、S15、……、S1(n-1)、S1n,信息包括是否有车次、剩余票数、无票等字段.

依次建立T2->T3、T2->T4、T2->T5、T2->T6、……、T2->Tn-1、T2->Tn标识为S23、S24、S25、S26、……、S2(n-1)、S2n,信息依然包括是否有车次、剩余票数、无票等字段.

用同样办法建立始发站是T3、T4、T5、……Tn-1、Tn的码表链接.

建立Tn-1->Tn的标识为S(n-1)n,包含信息同上.

(2)数据拼接

第1次站点换乘:在有票源的前提下,判断S1i+Sin是否可行,设Ti是同一列车换乘中转站点对应的中转码表.

第2次站点换乘:在有票源的前提下,判断S1i+Sij+Sjn是否可行,设Ti、Tj是列车换乘中转站点对应的中转码表.

用同样的办法多个站点换乘:在有票源的前提下,判断S1i+Sij+ Sjk+……+Svn是否可行,设Ti、Tj、Tk、……、Tv是列车换乘中转站点对应的中转码表.

(3)在上述执行过程中无法满足的情况下,考虑换位换乘,即购买同一车次的中转票,将Sa标识(商务座/特等座)、Sb标识(一等座)、Sc标识(二等座)、Sd标识(硬座)、Se标识(无座),在数据拼接的过程中实现换座切换,可能实现的是:Sa1i+Sbij+Scjk+……+Sdvn,其中Ti、Tj、Tk、……、Tv是列车换乘中转站点对应的中转码表.

(4)在上述执行(3)过程中无法满足的情况下,可以考虑多购买1~2个站点,即有1~2个站点的重叠,比如Sa1(i+1)与Sb(i-1)j就会最少有一个站点的重复,可能实现的是:

Sa1(i+1)+Sb(i-1)j+……Sckv +Sdvn,Ti、Tj、Tk、Tv等是列车中转站点对应的码表.

(5)在上述执行(4)过程中无法满足的情况下,从理论上可以考虑利用最短距离的补票换乘,即先购买一张站票,可以利用最少重复站的换乘,比如Sa1(i-1)+S b(i-1)(i+1)+Sc(i+1)j+……+Sdkv+ Sdvn.

(6)算法中始终以直达票为首先,同车换乘为优选,每个算法方案均是递进的关系,在(5)的算法无法递进的时候,就要考虑2车或者更多次列车的换乘方案,换乘后再用上述的同车换乘实现整个流程的分段.

以上算法也是有缺陷的,在拼接的过程中未将列车晚点等不确定因素考虑进去.

3.2 部分重点设计代码

3.2.1 构造API URL

生成可查询的URL是整个程序的入口与关键,所有的城市名称按照字典的方式设计,按照{城市名称:城市代码}生成,将城市名称转换生成城市代码,构造API URL如下: url=('https://kyfw.12306.cn/otn/leftTicket/queryY?'

#该地址会因为网站改版或者日期的变化会动态刷新:

'leftTicketDTO.train_date={}&' # train_date为列车的出发时间;

'leftTicketDTO.from_station={}&' # train_date为出发站的城市代码;

'leftTicketDTO.to_station={}&' # train_date为到达站的城市代码;

'purpose_codes=ADULT').format(date,from_station,to_station).

3.2.2 设计获取列车车次信息

以从合肥到郑州为例

deftrain_query(url,text):

try:

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

#查询json信息data字段result值

train_infos=rs.json()['data']['result']

for i in train_infos:

db=i.split('|') # 遍历所有列车信息;

train_no=db[3] # 车次代码 ;

from_station_code=db[6] # 出发站;

from_station_name=text['合肥'];

to_station_code=db[7] # 达到站;

to_station_name=text['郑州'];

starttime=db[8] # 发站时间;

arrivetime=db[9] # 到站时间;

fulltime=db[10] # 发站与到站时间间隔;

firstseat=db[31] or '--' # 一等座剩余信息;

secondseat=db[30] or '--' # 二等座剩余信息;

softsleeper=db[23] or '--' # 软卧剩余信息;

hardsleeper=db[28] or '--' # 硬卧剩余信息;

hardseat=db[29] or '--' # 硬座剩余信息;

noseat=db[26] or '--' # 显示无座信息;

info=( '查询车次:{} 始发站:{} 终点站:{} 始发时间:{}抵达时间:{} 历时:{} 座位剩余情况: 一等座剩余:「{}」 二等座剩余:「{}」 软卧剩余:「{}」 硬卧剩余:「{}」 硬座剩余:「{}」 无座:「{}」 '.format(train_no,from_station_name,to_station_name,starttime,arrivetime,fulltime,firstseat,secondseat,softsleeper,hardsleeper,hardseat,noseat)) # 供查询显示的信息.

3.2.3 实现刷新频率程序部分代码如下:

text=station_info()

print(text)

url= urlinfo_query(text) #调用生成可查询的URL.

#循环查询,查询终止条件为查到必须有的车次,

while True:

time.sleep(1) #查票刷新频率

if train_query(url,text):

break

4 信息推送

12306购票成功后,信息通知渠道有很多,手机短信、腾讯QQ、个人邮箱、微信等,但是尚未购票成功的客户想通过提前查询余票的结果推送功能却不具备,借助第三方一款方便使用的工具Server酱即可满足很好的推送功能,Server酱即可实现程序员与服务器之间的通信[12].

Server酱(ServerChan)本身就是一个拥有GET接口可编程的接收器,信息可以通过微信推送至客户,鉴于Server酱SCKEY与UserID一对一的关系,如果实现一对多信息的服务推送就必须要用到PushBear,相对于Server酱也就是高级版本.ServerChan配置过程需要3个步骤:(1)登录步骤:注册GitHub账号,获取1个SCKEY,SCKEY将在发送信息的页面使用.(2)绑定步骤:单击按钮“微信推送”,扫码关注即可完成绑定请求.(3)信息发送步骤:向URL页面发送Get或者Post请求,而URL将接受sendkey、text、desp3个参数,其中sendkey参数为通道,属于必填写项;text参数为消息标题,属于长度不超过256的必填项;desp参数为信息的内容,可以为空,支持MarkDown.以上实时查询余票信息后,可以调用Server酱,推送至客户的微信,部分定义代码实现如下:

def send_Information(title,info):

url='https://pushbear.ftqq.com/sub?sendkey=此处为个人注册后生成的SCKEY值&text=%s&desp=%s'%(title,info)

requests.get(url)

5 实验测试

图7 控制台运行结果

图8 Server酱推送信息

测试结果:控制台输出的结果与Server酱公众号推送的信息完全一致,而这两个结果与图9网页实际查询的结果也是完全一致的.

图9 网页查询结果

6 结 论

该系统设计基于python的爬虫技术,经过算法筛选,用Python的Requests模块与JSON解析方法[13-14]爬取了电子客票的相关信息.调用Server酱推送功能获取了最佳出行方案,解决了官网中以5 s作为刷新的固定频率,可以实时获取想要的数据,事实证明该方案行之有效,对其他相关系统有一些应用参考价值[15-17],但系统设计还有一些可以改进的地方,比如没有通过更换USER-Agent或者IP代理进行防爬处理,算法设计中模糊了车次晚点的实际情况等.

猜你喜欢

码表车次字段
图书馆中文图书编目外包数据质量控制分析
调度集中系统车次号技术的研究
动车所车次号处理逻辑存在问题分析与对策
iGPSPORTiGS618智能GPS码表测评
皱皱眉头就是一首诗
廉价亲民黑鸟单车BB10 GPS码表评测
CTC系统自动变更折返车次号功能的实现
CNMARC304字段和314字段责任附注方式解析
轻松上手 码表踏频组
无正题名文献著录方法评述