前言
每当春节临近时,因为网络的方便,访问12306购买火车票回家过年成了很多人的首选。但由于12306的种种不给力,给那些在官网刷票的人带来了很多的不便。从2011年未12306上线起,连续几年回家我都是靠网上购票,今年也不例外;我记得11年时我使用的是官网直接购票,到了12年则使用了新出的木鱼抢票助手,而今年我用了360与猎豹两款主流抢票浏览器,还发动了几位朋友一起帮忙,才买到了一张差强人意的票,现在感觉买票是越来越困难。而就在前几天媒体还曝出了商业黄牛使用假器10分钟钞杀1000多张票的新闻,让人吃惊不已。于是就萌生了自己写一个抢票应用的念头,最开始设想的就是本地桌面应用,而非浏览器插件,个人觉得本地应用始终比浏览器插件敏捷,因为本地应用可以精确稳定的请求有用的链接,过滤图片和CSS等前台无用请求,可以节省网络消耗时间。于是我花了一段时间将12306的整体订票流程解析了一遍,其间还经历了一次12306的改版,幸好主体流程改动不是很大,终算有点收获。
粗略的将12306的流程划分为:登录、查询和订票三大模块,下面就这三大模块逐一说明:
1.登录
登录12306请求的URL是:https://kyfw.12306.cn/otn/login/init,可以使用Firbug抓取一下它的请求头,得到的response响应内容如下:
从中可以看到Set-Cookie信息,也就是说,如果想要登录就必须先请求https://kyfw.12306.cn/otn/login/init这个链接,以获取服务端设置的Cookie信息,而有了该Cookie信息就可以将其保存,以备下步的请求使用。
再来分析一下它的页面HTML与其对应处理登录的Javascript脚本文件(https://kyfw.12306.cn/otn/resources/merged/login_js.js),得到如下流程:
1.用户点击登录提交时先要验证请求一下:https://kyfw.12306.cn/otn/login/loginAysnSuggest链接,用于判断当前网络环境是否可以登录,得到JSON数据(通过Firebug抓包):
-
{
-
"validateMessagesShowId":"_validatorMessage"
-
"status":true
-
"httpstatus":200,
-
"data":{
-
"loginCheck":"Y"
-
},
-
"messages":[],
-
"validateMessages":{}
-
}
这里通过判断data.loginCheck是否为字符串Y判断用户是否可以登录,如不能登录,则显示messages中的内容.
2.当用户登录信息检查成功时,则POST请求https://kyfw.12306.cn/otn/login/userLogin,得到登录请求后的HTML,对应请求的参数为:
-
"loginUserDTO.user_name"://用户名
-
"userDTO.password"://密码
-
"randCode"://验证码
3.通过解析获取的HTML可以根据id为login-txt的<span>标签来判断是否登录成功,登录成功的对应的HTML内容为:
-
<spanclass="login-txt"style="color:#666666">
-
<span>意见反馈:
-
<aclass="cursorcolorA"href="mailto:12306yjfk@rails.com.cn">
-
12306yjfk@rails.com.cn
-
</a>您好,
-
</span>
-
<aid="login_user"href="/otn/index/initMy12306"
-
class="colorA"style="margin-left:-0.5px;"><span>登录成功用户名</span></a>|
-
<aid="regist_out"href="/otn/login/loginOut">退出</a>
-
</span>
失败的内容为:
-
<spanclass="login-txt"style="color:#666666">
-
<span>意见反馈:
-
<aclass="cursorcolorA"href="mailto:12306yjfk@rails.com.cn">
-
12306yjfk@rails.com.cn
-
</a>您好,请
-
</span>
-
<aid="login_user"href="/otn/login/init"
-
class="colorA"style="margin-left:-0.5px;">登录</a>|
-
<aid="regist_out"href="/otn/regist/init">注册</a>
-
</span>
如上登录成功即可进行下一步的操作:对于车次的查询。
2,车次查询
新版车次预订的查询(这里单指单程票查询)大大减化了请求参数,只接收出发地编码,到达地编码,出发日期与旅客编码四个参数,所有的过滤操作都扔给了前台Javascript,这也说明了车次查询流程的简单,只需请求一个链接地址:
查询车次是通过GET:https://kyfw.12306.cn/otn/leftTicket/query链接获取的,对应的查询参数为(GET请求注意查询参数的顺序):
-
leftTicketDTO.train_date=2014-01-23//出发日期
-
leftTicketDTO.from_station=BJP//出发站编码
-
leftTicketDTO.to_station=SHH//到达站编码
-
purpose_codes=ADULT//旅客编码:成人为ADULT,学生为:0X00
对应的获取的JSON信息格式如下:
-
{"validateMessagesShowId":"_validatorMessage",
-
"status":true,
-
"httpstatus":200,
-
"data":[
-
{"queryLeftNewDTO":{
-
"train_no":"240000G14104",//列车编号
-
"station_train_code":"G141",//车次
-
"start_station_telecode":"VNP",//始发站编码
-
"start_station_name":"北京南",//始发站名
-
"end_station_telecode":"AOH",//终到站编码
-
"end_station_name":"上海虹桥",//终到站名
-
"from_station_telecode":"VNP",//查询输入经过站编码
-
"from_station_name":"北京南",//查询输入经过站名
-
"to_station_telecode":"AOH",//查询输入到站编码
-
"to_station_name":"上海虹桥",//查询输入到站名
-
"start_time":"14:16",//出发时间
-
"arrive_time":"19:47",//到站时间
-
"day_difference":"0",//花费天数
-
"train_class_name":"",
-
"lishi":"05:31",//历时
-
"canWebBuy":"Y",//是否可以预定
-
"lishiValue":"331",
-
"yp_info":"O055300094M0933000999174800017",
-
"control_train_day":"20301231",
-
"start_train_date":"20140123",
-
"seat_feature":"O3M393",
-
"yp_ex":"O0M090",
-
"train_seat_feature":"3",
-
"seat_types":"OM9",
-
"location_code":"P3",
-
"from_station_no":"01",
-
"to_station_no":"09",
-
"control_day":19,
-
"sale_time":"1400",//出票时间点hhmm
-
"is_support_card":"1",
-
"gg_num":"--",
-
"gr_num":"--",//高级软卧座剩余数
-
"qt_num":"--",//其他座剩余数
-
"rw_num":"--",//软卧座剩余数
-
"rz_num":"--",//软座座剩余数
-
"tz_num":"--",//特等座剩余数
-
"wz_num":"--",//无座座剩余数
-
"yb_num":"--",
-
"yw_num":"--",//硬卧座剩余数
-
"yz_num":"--",//硬座座剩余数
-
"ze_num":"有",//二等座剩余数
-
"zy_num":"有",//一等座剩余数
-
"swz_num":"17"//商务座剩余数
-
},
-
"secretStr":"预定请求令牌字符串",
-
"buttonTextInfo":"预订或开售日期"
-
},
-
..........//省略其它车次,信息同上
-
],
-
"messages":[],
-
"validateMessages":{}
-
}
注意这里的canWebBuy属性,用于标记该趟列车是否可以预订,还有对应列车的secretStr字符,它用于请求预订确认页面的令牌,
对于其中一直提到的列车站点编码,可以通过请求https://kyfw.12306.cn/otn/resources/js/framework/station_name.js链接,通过得到JS脚本中的station_names变量获取,对应的站点以@字符分隔,而每一个站点信息如下,这里以北京北为例:
-
bjb|北京北|VAP|beijingbei|bjb|0
用于提取其中有用的信息是:北京北与VAP,使用查询北京北的编码就是VAP,其它站点的解析同理。
如上即可以查询指定出发地与到达地的车次预定信息,紧接着进行预订流程的分析。
3,车票预订
在12306的解析中,就属车票预订的解析最为费神,也是最核心的一个流程,我现在只掌握了成人单程票的预订流程,其他的比如返程,学生票等都还没有分析出来,如下讲解的就是关于成人单程票的预定基本流程:
3.1,获取预定确认页面
车票预定首先要请求获取车票的预订确认页面,如下流程图所示:
分析:该流程是在用户单击车次的“预订”按钮时触发的,如图所示,获取预订确认页面,先要判断用户是否登录,POST请求的地址是:https://kyfw.12306.cn/otn/login/checkUser,这个请求无参数,然后通过判断得到的JSON信息中的data.flag属性是否为true判断用户是否已登录,接着再根据对应列车查询时所获得的secretStr字符与用户输入的查询信息POST请求https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest,判断用户是否可以访问预定确认画面,通过得到JSON信息的status属性判断是否允许访问,如果为true说明可以访问,最后依据旅行类型为单程(dc)POST跳转获取单程车票的预订确认画面:https://kyfw.12306.cn/otn/confirmPassenger/initDc。如果登录用户不进行上述判断,直接POST请求https://kyfw.12306.cn/otn/confirmPassenger/initDc提示非法请求,只有成功获取预订确认页面后才能进行下一步的操作。
注:该流程可以查看对应JS脚本:https://kyfw.12306.cn/otn/resources/merged/queryLeftTicket_end_js.js,function
L(b4, bX)方法获知。
从请求订单的确认画面还可以得到获取当前登录用户常用联系人的链接地址为:https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs。
3.2,预订提交
在车票的预定提交之前必先要获取预定确认画面的原因是因为预订确认HTML中声明的orderRequestDTO与ticketInfoForPassengerForm两个Javascript变量,含有预订提交的时的必需参数信息,下面就预订提交给出粗略的流程分析图,如下:
注:图片可以右击后查看大图,该流程对应的JS文件地址为:https://kyfw.12306.cn/otn/resources/merged/passengerInfo_js.js
分析:如上图显示了车票预定提交的大体流程,可以依据请求的链接数将其分为四大块:
1.检查用户选择的乘客信息的合法性,POST请求:https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo,通过分析得到的JSON中的data.submitStatus属性是否为true判断,同时这一步的JSON信息中还会包含有一个data.isCheckOrderInfo属性将会作为下一步判断当前用户是否可排队请求的参数。对应请求参数有如下5个:
-
cancel_flag:"2",//固定值
-
bed_level_order_num:"000000000000000000000000000000",//固定值
-
passengerTicketStr:getpassengerTickets(),//旅客信息字符串
-
oldPassengerStr:getOldPassengers(),//旅客信息字符串
-
tour_flag:ticketInfoForPassengerForm.tour_flag,//从ticketInfoForPassengerForm中获取
-
randCode:$("#randCode").val()//前台输入验证码
这五个参数中,有两个参数需要注意passengerTicketStr与oldPassengersStr:
passengerTicketStr是以下划线"_"分隔当每一个乘客信息组成的字符串,对应每个乘客信息字符串组成如下:
-
座位编号,0,票类型,乘客名,证件类型,证件号,手机号码,保存常用联系人(Y或N)
同样oldPassengersStr也是以下划线"_"分隔每个乘客信息组成的字符串,对应每个乘客信息字符串组成如下:
在上面的信息中座位编号指的是,一等座、二等座等的编码,从ticketInfoForPassengerForm.limitBuySeatTicketDTO.seat_type_codes属性中选择获取。
票类型指的是,成人票,学生票等的编码,可以从ticketInfoForPassengerForm.limitBuySeatTicketDTO.ticket_type_codes属性中选择获取。
证件类型指的是二代身份证,学生证,签证等的编码,可以从ticketInfoForPassengerForm.cardTypes属性中选择获取。
最后oldPassengersStr中的乘客类型主要有如下信息:
-
adult:"1",
-
child:"2",
-
student:"3",
-
disability:"4"
取上面对应的数字编码。
注意:在组合oldPassengersStr乘客信息字符串时,未尾会多一个下划线,提交请求是一定要补上,从上也可以看出所有的一些参数都是通过ticketInfoForPassengerForm变量获取的,这也是为什么要事先获取预定确认画面HTML的原因。
2.检查乘合信息合法后,接下来就会结合返回的data.isCheckOrderInfo属性,POST请求:https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue,判断当前乘客是否可以排队,对应的参数如下:
-
train_date:newDate(orderRequestDTO.train_date.time).toString(),//列车日期
-
train_no:orderRequestDTO.train_no,//列车号
-
stationTrainCode:orderRequestDTO.station_train_code,
-
seatType:limit_tickets[0].seat_type,//座位类型
-
fromStationTelecode:orderRequestDTO.from_station_telecode,//发站编号
-
toStationTelecode:orderRequestDTO.to_station_telecode,//到站编号
-
leftTicket:ticketInfoForPassengerForm.queryLeftTicketRequestDTO.ypInfoDetail,
-
purpose_codes:n,//默认取ADULT,表成人,学生表示为:0X00
-
isCheckOrderInfo:m//data.isCheckOrderInfo
这里的参数要注意传递列车日期的方式,及座位类型编码,这里选择的是第一个乘客的座位类型编码。最后还要确保orderRequestDTO变量的准确性。
通过返回的JSON信息的data属性值来判断是否允许当前用户进行排队下单,并提示当前的剩余票数。
其中的data属性会包含有两个重要的参数,countT与ticket,(ticket的格式为:1*****30314*****00001*****00003*****0000的形式):
countT表示的是排队人数,而ticket指的是当前列车对应座位的剩余票数,可以通过https://kyfw.12306.cn/otn/resources/merged/passengerInfo_js.js文件中的function
L(l, m) 函数解析获取:
-
functionL(l,m){
-
rt="";
-
seat_1=-1;
-
seat_2=-1;
-
i=0;
-
while(i<l.length){
-
s=l.substr(i,10);
-
c_seat=s.substr(0,1);
-
if(c_seat==m){
-
count=s.substr(6,4);
-
while(count.length>1&&count.substr(0,1)=="0"){
-
count=count.substr(1,count.length)
-
}
-
count=parseInt(count);
-
if(count<3000){
-
seat_1=count
-
}else{
-
seat_2=(count-3000)
-
}
-
}
-
i=i+10
-
}
-
if(seat_1>-1){
-
rt+=seat_1
-
}
-
if(seat_2>-1){
-
rt+=","+seat_2
-
}
-
returnrt
-
}
函数中的l指的就是ticket,而m指的是第一位乘客所选择的座位编号。
如果计算的余票信息还有剩余,则会提示用户点击确认按进行订单的提交请求,如果没有充实的票,则会提示用户选择其它车次,处理该请求的方法详情见https://kyfw.12306.cn/otn/resources/merged/passengerInfo_js.js文件中的function
M(n, m) 方法。
3.当提示的有充足的余票,且用户点击了确定按钮,则接下来会POST请求:https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue,进行单程票(dc)类型的排队下单操作,通过判断返回的JSON信息data.submitStatus属性判断订单是否以成功提交至服务器,对应的请求参数为:
-
passengerTicketStr:getpassengerTickets(),
-
oldPassengerStr:getOldPassengers(),
-
randCode:$("#randCode").val(),
-
purpose_codes:ticketInfoForPassengerForm.purpose_codes,
-
key_check_isChange:ticketInfoForPassengerForm.key_check_isChange,
-
leftTicketStr:ticketInfoForPassengerForm.leftTicketStr,
-
train_location:ticketInfoForPassengerForm.train_location
这里的参数没有新意,主要是注意获取ticketInfoForPassengerForm变量的准确性。
4.订单提交至服务器后不一定说明订单已经成功了,还需要GET请求:https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime,判断系统是否已根据提交的订单信息为相应的乘客占位成功,并提示预估出票等待时间,这一步只有一个参数,就是旅行类型,由于我们主要考虑的是单程票,故提交时POST
dc就行了,如下:
这一步占位的操作在12306的官网中是将其封装在了一个名为OrderQueueWaitTime的对象中,可以解压https://kyfw.12306.cn/otn/resources/merged/passengerInfo_js.js文件获知,对应的如果判断系统占位成功,将会从返的JSON信息中获取data.orderId属性,即为下单成功时的订单号。
如上4次请求就可以准确的模拟出12306官网订单提交的整套流程,其中其实还忽略了验证码的获取与判断操作,而这一步仅仅是判断验证码的合法性,与主体流程无关。对应订单确定页面的验证码获取链接为:https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=passenger&rand=randp,从中与登录页面的验证码链接对比,可知新版12306的验证码管理统一为了一个方法,登录与订单确认的验证码链接只是传递的module和rand参数不一样而已。
4,结束语:
根据上面的操作,基本可以全程模拟官网的订单操作,编写出一个属于自己的抢票助手。在写这篇文章时,我一直在想这样做是否有意义,因为12306随时都有可能变更,由于23:00点~07:00点的维护时间段的设置,也许今天写出来的东西明天马上就会失效过期。但仔细考虑后还是打算将他分享出来,就当是一种学习吧。同时在这里公布GitHub上使用Python3编写的一个订票项目源码:https://github.com/lzqwebsoft/trainticket,对应window下独立运行exe文件下载地址为:https://code.google.com/p/lzqwebsoft-projects/source/browse/#svn%2Ftrunk,软件运行效果如下:
分享到:
相关推荐
《12306订票助手.NET版:深入解析与技术要点》 12306订票助手.NET版是一款专为用户简化12306官网购票流程的应用程序,其核心目标是提供便捷、高效的火车票预订服务。在这款应用的背后,蕴含了丰富的.NET框架技术和...
【12306订票助手源码】是一款曾经流行的应用程序,主要用于帮助用户自动查询和预订中国铁路12306官网上的火车票。由于铁路系统的不断升级和安全策略的加强,这款助手可能已经无法正常使用,但它依然具有很高的学习...
总结来说,12306订票助手——51gohome是一款为方便用户在12306平台上购票而设计的软件,通过自动化和优化购票流程,提升购票效率。用户在使用时需要注意软件的合法性、安全性和兼容性,确保购票过程顺利。同时,2345...
总的来说,12306订票工具是利用技术手段优化购票流程的尝试,它体现了信息技术在日常生活中发挥的积极作用。通过自动化和智能化,这款工具降低了用户购票的复杂度,提升了购票体验。然而,任何工具的使用都需谨慎,...
总的来说,12306订票助手结合了网络爬虫、数据解析、自动化控制、并发处理等多种技术,为用户提供了便捷的火车票预订体验。然而,用户在使用过程中,应关注软件的安全性和合法性,以免带来不必要的麻烦。
12306订票系统自动登录是一种技术手段,它旨在简化用户在12306中国铁路客户服务中心网站上购票的过程。12306是中国官方的火车票预订平台,由于其高流量和安全需求,通常会设置复杂的验证码机制以防止恶意机器人或...
"网络订票系统(20120111100606).rar"可能是一个早期版本的12306订票系统源码或工具,供研究和参考使用。 总的来说,12306自动订票系统结合了网络编程、数据处理、自动化控制等多种技术,旨在为用户提供便捷高效的...
- **Ticket12306**:这可能是项目的主程序或核心模块,包含了12306订票功能的具体实现。 - **Waner8.Common**:这可能是一个公共类库,封装了一些通用的工具函数或服务,供项目其他部分使用。 - **AutoUpdater**:...
总的来说,"C#12306一键订票源码"项目涵盖了C#语言基础、网络编程、图形用户界面设计、API接口调用等多方面知识,对于想要深入理解C#开发和12306订票流程的开发者来说,这是一个极具价值的学习资源。同时,通过改进...
这个版本主要是针对订票流程的持续优化和改进,特别是在春运期间,为了满足大量旅客购票需求,软件进行了频繁的更新以提高其性能和用户体验。 描述中的“继续完善订票流程”意味着这个版本主要的更新内容是优化了...
在C#的12306订票源码中,以下几个关键知识点值得深入探讨: 1. **网络爬虫**:由于12306网站的动态加载和反爬虫策略,开发者可能使用了如HtmlAgilityPack或Jsoup这样的库来解析HTML页面,获取火车班次、余票等信息...
《12306自动火车订票系统:深入解析与应用》 12306自动火车订票系统,作为一款高效便捷的购票工具,深受广大旅客的喜爱。它旨在帮助用户自动化地完成火车票的查询、预定和购买过程,极大地减轻了在节假日等高峰期...
《My12306购票系统:一个Java Web开发实例详解》 My12306购票系统是一款基于Java Web技术构建的在线票务预订平台,它利用了一系列现代Web开发技术,如JSP(JavaServer Pages)、Servlet、HTML5、CSS、JSON、Ajax...
《12306简易订票程序免费版》是一款针对中国铁路12306官网的辅助工具,旨在简化用户购票流程,提高购票效率。这款软件允许用户在登录后便捷地选择乘车人、出发站、到达站,并输入验证码进行自动查询和预订。一旦发现...
【C# 12306一键订票源码】是一个使用C#编程语言开发的程序,其核心功能是自动识别验证码并实现一键预订火车票。这个程序的目的是提高购票效率,尤其在节假日高峰期,能极大地方便用户快速地完成购票操作。其成功率...
《12306(客户版):便捷的火车票预订工具详解》 12306(客户版)是一款专为用户提供便捷火车票预订服务的桌面应用程序。它通过模拟HTTP请求的方式,优化了购票流程,有效减少了用户在购票过程中的等待时间,使广大...