20015/09/15更新:12306代码已变化,本文代码分析己过期,Github托管软件也失效,感谢支持!

前言

每当春节临近时,因为网络的方便,访问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抓包):

[plain] view plain copy
  1. {
  2. "validateMessagesShowId":"_validatorMessage"
  3. "status":true
  4. "httpstatus":200,
  5. "data":{
  6. "loginCheck":"Y"
  7. },
  8. "messages":[],
  9. "validateMessages":{}
  10. }

这里通过判断data.loginCheck是否为字符串Y判断用户是否可以登录,如不能登录,则显示messages中的内容.

2.当用户登录信息检查成功时,则POST请求https://kyfw.12306.cn/otn/login/userLogin,得到登录请求后的HTML,对应请求的参数为:

[plain] view plain copy
  1. "loginUserDTO.user_name":  // 用户名
  2. "userDTO.password":        // 密码
  3. "randCode":                // 验证码

注:登录图片验证码的获取地址可以从登录页面的HTML中得到为: https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=login&rand=sjrand

3.通过解析获取的HTML可以根据id为login-txt的<span>标签来判断是否登录成功,登录成功的对应的HTML内容为:

[html] view plain copy
  1. <span class="login-txt" style="color: #666666">
  2. <span>意见反馈:
  3. <a class="cursor colorA" href="mailto:12306yjfk@rails.com.cn">
  4. 12306yjfk@rails.com.cn
  5. </a>您好,
  6. </span>
  7. <a id="login_user" href="/otn/index/initMy12306"
  8. class="colorA" style="margin-left:-0.5px;"><span>登录成功用户名</span></a>|
  9. <a id="regist_out" href="/otn/login/loginOut">退出</a>
  10. </span>

失败的内容为:

[html] view plain copy
  1. <span class="login-txt" style="color: #666666">
  2. <span>意见反馈:
  3. <a class="cursor colorA" href="mailto:12306yjfk@rails.com.cn">
  4. 12306yjfk@rails.com.cn
  5. </a>您好,请
  6. </span>
  7. <a id="login_user" href="/otn/login/init"
  8. class="colorA" style="margin-left:-0.5px;">登录</a> |
  9. <a id="regist_out" href="/otn/regist/init">注册</a>
  10. </span>

如上登录成功即可进行下一步的操作:对于车次的查询。

2,车次查询

新版车次预订的查询(这里单指单程票查询)大大减化了请求参数,只接收出发地编码,到达地编码,出发日期与旅客编码四个参数,所有的过滤操作都扔给了前台Javascript,这也说明了车次查询流程的简单,只需请求一个链接地址:

查询车次是通过GET:https://kyfw.12306.cn/otn/leftTicket/query链接获取的,对应的查询参数为(GET请求注意查询参数的顺序):

[plain] view plain copy
  1. leftTicketDTO.train_date=2014-01-23  // 出发日期
  2. leftTicketDTO.from_station=BJP       // 出发站编码
  3. leftTicketDTO.to_station=SHH         // 到达站编码
  4. purpose_codes=ADULT                  // 旅客编码:成人为ADULT,学生为:0X00

对应的获取的JSON信息格式如下:

[plain] view plain copy
  1. {"validateMessagesShowId": "_validatorMessage",
  2. "status": true,
  3. "httpstatus": 200,
  4. "data": [
  5. {"queryLeftNewDTO": {
  6. "train_no": "240000G14104",          // 列车编号
  7. "station_train_code": "G141",        // 车次
  8. "start_station_telecode": "VNP",     // 始发站编码
  9. "start_station_name": "北京南",      // 始发站名
  10. "end_station_telecode": "AOH",       // 终到站编码
  11. "end_station_name": "上海虹桥",      // 终到站名
  12. "from_station_telecode": "VNP",      // 查询输入经过站编码
  13. "from_station_name": "北京南",       // 查询输入经过站名
  14. "to_station_telecode": "AOH",        // 查询输入到站编码
  15. "to_station_name": "上海虹桥",       // 查询输入到站名
  16. "start_time": "14:16",               // 出发时间
  17. "arrive_time": "19:47",              // 到站时间
  18. "day_difference": "0",               // 花费天数
  19. "train_class_name": "",
  20. "lishi": "05:31",                    // 历时
  21. "canWebBuy": "Y",                    // 是否可以预定
  22. "lishiValue": "331",
  23. "yp_info": "O055300094M0933000999174800017",
  24. "control_train_day": "20301231",
  25. "start_train_date": "20140123",
  26. "seat_feature": "O3M393",
  27. "yp_ex": "O0M090",
  28. "train_seat_feature": "3",
  29. "seat_types": "OM9",
  30. "location_code": "P3",
  31. "from_station_no": "01",
  32. "to_station_no": "09",
  33. "control_day": 19,
  34. "sale_time": "1400",                // 出票时间点hhmm
  35. "is_support_card": "1",
  36. "gg_num": "--",
  37. "gr_num": "--",          // 高级软卧座剩余数
  38. "qt_num": "--",          // 其他座剩余数
  39. "rw_num": "--",          // 软卧座剩余数
  40. "rz_num": "--",          // 软座座剩余数
  41. "tz_num": "--",          // 特等座剩余数
  42. "wz_num": "--",          // 无座座剩余数
  43. "yb_num": "--",
  44. "yw_num": "--",          // 硬卧座剩余数
  45. "yz_num": "--",          // 硬座座剩余数
  46. "ze_num": "有",          // 二等座剩余数
  47. "zy_num": "有",          // 一等座剩余数
  48. "swz_num": "17"          // 商务座剩余数
  49. },
  50. "secretStr": "预定请求令牌字符串",
  51. "buttonTextInfo": "预订或开售日期"
  52. },
  53. ..........                       // 省略其它车次,信息同上
  54. ],
  55. "messages": [],
  56. "validateMessages": {}
  57. }

注意这里的 canWebBuy属性,用于标记该趟列车是否可以预订,还有对应列车的 secretStr字符,它用于请求预订确认页面的令牌,

对于其中一直提到的列车站点编码,可以通过请求https://kyfw.12306.cn/otn/resources/js/framework/station_name.js链接,通过得到JS脚本中的station_names变量获取,对应的站点以@字符分隔,而每一个站点信息如下,这里以北京北为例:

[plain] view plain copy
  1. 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中声明的orderRequestDTOticketInfoForPassengerForm两个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个:

[javascript] view plain copy
  1. cancel_flag: "2",                                         // 固定值
  2. bed_level_order_num: "000000000000000000000000000000",    // 固定值
  3. passengerTicketStr: getpassengerTickets(),                // 旅客信息字符串
  4. oldPassengerStr: getOldPassengers(),                      // 旅客信息字符串
  5. tour_flag: ticketInfoForPassengerForm.tour_flag,  // 从ticketInfoForPassengerForm中获取
  6. randCode: $("#randCode").val()                            // 前台输入验证码

这五个参数中,有两个参数需要注意passengerTicketStroldPassengersStr

passengerTicketStr是以下划线"_"分隔当每一个乘客信息组成的字符串,对应每个乘客信息字符串组成如下:

[plain] view plain copy
  1. 座位编号,0,票类型,乘客名,证件类型,证件号,手机号码,保存常用联系人(Y或N)

同样oldPassengersStr也是以下划线"_"分隔每个乘客信息组成的字符串,对应每个乘客信息字符串组成如下:

[plain] view plain copy
  1. 乘客名,证件类型,证件号,乘客类型

在上面的信息中座位编号指的是,一等座、二等座等的编码,从 ticketInfoForPassengerForm.limitBuySeatTicketDTO.seat_type_codes属性中选择获取。

票类型指的是,成人票,学生票等的编码,可以从ticketInfoForPassengerForm.limitBuySeatTicketDTO.ticket_type_codes属性中选择获取。

证件类型指的是二代身份证,学生证,签证等的编码,可以从ticketInfoForPassengerForm.cardTypes属性中选择获取。

最后oldPassengersStr中的乘客类型主要有如下信息:

[javascript] view plain copy
  1. adult: "1",
  2. child: "2",
  3. student: "3",
  4. disability: "4"

取上面对应的数字编码。

注意:在组合oldPassengersStr乘客信息字符串时,未尾会多一个下划线,提交请求是一定要补上,从上也可以看出所有的一些参数都是通过ticketInfoForPassengerForm变量获取的,这也是为什么要事先获取预定确认画面HTML的原因。

2.检查乘合信息合法后,接下来就会结合返回的data.isCheckOrderInfo属性,POST请求:https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue,判断当前乘客是否可以排队,对应的参数如下:

[javascript] view plain copy
  1. train_date: new Date(orderRequestDTO.train_date.time).toString(),  // 列车日期
  2. train_no: orderRequestDTO.train_no,                                // 列车号
  3. stationTrainCode: orderRequestDTO.station_train_code,
  4. seatType: limit_tickets[0].seat_type,                            // 座位类型
  5. fromStationTelecode: orderRequestDTO.from_station_telecode,      // 发站编号
  6. toStationTelecode: orderRequestDTO.to_station_telecode,          // 到站编号
  7. leftTicket: ticketInfoForPassengerForm.queryLeftTicketRequestDTO.ypInfoDetail,
  8. purpose_codes: n,         // 默认取ADULT,表成人,学生表示为:0X00
  9. isCheckOrderInfo: m       // data.isCheckOrderInfo

这里的参数要注意传递列车日期的方式,及座位类型编码,这里选择的是第一个乘客的座位类型编码。最后还要确保orderRequestDTO变量的准确性。

通过返回的JSON信息的data属性值来判断是否允许当前用户进行排队下单,并提示当前的剩余票数。

其中的data属性会包含有两个重要的参数,countTticket,(ticket的格式为:1*****30314*****00001*****00003*****0000的形式):

countT表示的是排队人数,而ticket指的是当前列车对应座位的剩余票数,可以通过https://kyfw.12306.cn/otn/resources/merged/passengerInfo_js.js文件中的function L(l, m) 函数解析获取:

[javascript] view plain copy
  1. function L(l, m) {
  2. rt = "";
  3. seat_1 = -1;
  4. seat_2 = -1;
  5. i = 0;
  6. while (i < l.length) {
  7. s = l.substr(i, 10);
  8. c_seat = s.substr(0, 1);
  9. if (c_seat == m) {
  10. count = s.substr(6, 4);
  11. while (count.length > 1 && count.substr(0, 1) == "0") {
  12. count = count.substr(1, count.length)
  13. }
  14. count = parseInt(count);
  15. if (count < 3000) {
  16. seat_1 = count
  17. } else {
  18. seat_2 = (count - 3000)
  19. }
  20. }
  21. i = i + 10
  22. }
  23. if (seat_1 > -1) {
  24. rt += seat_1
  25. }
  26. if (seat_2 > -1) {
  27. rt += "," + seat_2
  28. }
  29. return rt
  30. }

函数中的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属性判断订单是否以成功提交至服务器,对应的请求参数为:

[javascript] view plain copy
  1. passengerTicketStr: getpassengerTickets(),
  2. oldPassengerStr: getOldPassengers(),
  3. randCode: $("#randCode").val(),
  4. purpose_codes: ticketInfoForPassengerForm.purpose_codes,
  5. key_check_isChange: ticketInfoForPassengerForm.key_check_isChange,
  6. leftTicketStr: ticketInfoForPassengerForm.leftTicketStr,
  7. train_location: ticketInfoForPassengerForm.train_location

这里的参数没有新意,主要是注意获取 ticketInfoForPassengerForm变量的准确性。

4.订单提交至服务器后不一定说明订单已经成功了,还需要GET请求:https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime,判断系统是否已根据提交的订单信息为相应的乘客占位成功,并提示预估出票等待时间,这一步只有一个参数,就是旅行类型,由于我们主要考虑的是单程票,故提交时POST dc就行了,如下:

[javascript] view plain copy
  1. tourFlag: "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订票流程相关推荐

  1. 12306订票流程解析

    粗略的将12306的流程划分为:登录.查询和订票三大模块,下面就这三大模块逐一说明: 1.登录 登录12306请求的URL是:https://kyfw.12306.cn/otn/login/init, ...

  2. 12306订票助手秘笈:2013春节我要回家

    12306订票助手秘笈:2013春节我要回家 又是一年春运时,一票难求让每个漂泊在外的异乡人更添几多思乡情.别急!12306抢票有技巧,本文帮你搜集了12306订票流程及订票技巧方面的文档资料,让我们 ...

  3. 12306订票客户端 FOR .NET 演示项目 【1】项目概况

    **本项目是由木鱼发起的一个基于.NET4.5的12306订票客户端.项目使用C#语言,使用FSLib.Network项目作为网络层访问库,主要用于演示目的. 主要原因是因为很多同学希望能借由1230 ...

  4. 12306订票助手java_开源免费Java Swing C/S版本12306订票助手

    Java Swing C/S版本12306订票助手 本程序完全开放源代码,仅作为技术学习研究交流之用,不得用于任何商业用途:作者不承担任何由此带来的直接或间接责任 特别说明: 整个程序除了速度和效率高 ...

  5. 12306订票助手java_GitHub - boyjunpeng/12306-hunter: 开源免费Java Swing C/S版本12306订票助手...

    Java Swing C/S版本12306订票助手 重要说明:由于12306网站已经全面切换为新版,此版本应用实现已经失效.不再进行更新, 仅保留供参考.感谢关注. 本程序完全开放源代码,仅作为技术学 ...

  6. 12306订票助手java_GitHub - lifes/12306-hunter: 开源免费Java Swing C/S版本12306订票助手

    Java Swing C/S版本12306订票助手 重要说明:由于12306网站已经全面切换为新版,此版本应用实现已经失效.不再进行更新, 仅保留供参考.感谢关注. 本程序完全开放源代码,仅作为技术学 ...

  7. 12306订票助手java_GitHub - zhilongxia/12306-hunter: 开源免费Java Swing C/S版本12306订票助手...

    Java Swing C/S版本12306订票助手 重要说明:由于12306网站已经全面切换为新版,此版本应用实现已经失效.不再进行更新, 仅保留供参考.感谢关注. 本程序完全开放源代码,仅作为技术学 ...

  8. 上海铁路局电话订票和网上订票流程/指南

    上海铁路局电话订票和网上订票流程/指南 一.上海铁路局管辖铁路及主要车站 主要管辖上海市.浙江省.江苏省和安徽省铁路. 主要客运车站:鳌江.苍南.奉化.杭州.杭州南.江山.金华南.金华西.缙云.兰溪. ...

  9. Java Swing C/S版本12306订票助手

    Java Swing C/S版本12306订票助手 https://github.com/xautlx/12306-hunter http://git.oschina.net/xautlx/hunte ...

  10. 12306订票排队系统效率低下 被指存在多处漏洞

    9月18日凌晨消息,铁道部唯一的官方订票网站12306.cn周日完成了新一轮升级,但是多位用户反映,升级后用户购票可能会被强制排队,由于系统存在多处漏洞,排队后购买失败的概率很大. 家住北京亚运村的刘 ...

最新文章

  1. 第二十二讲 对角化分解和幂公式
  2. Design Patterns(二十一):State Pattern--VB代码
  3. 解决yarn build时报错:内存溢出FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed
  4. Android用户界面设计:框架布局
  5. Mark一下 | 当当优惠码,实付满150减30 | + 荐书
  6. recyclerview 分组_Android使用RecyclerView实现跨列分组
  7. 疑似OPPO Find X2外观专利曝光:月亮形相机模组亮了
  8. Spring : @Value注解
  9. JDBC 与 JNDI 这两种连接方式 区别
  10. 如何将矩阵化为约旦标准型_【解题方法】矩阵初等变换的应用
  11. Servlet线程安全问题
  12. Redis实现MongoDB的getlasterror功能
  13. 互联网日报 | 7月11日 星期日 | 市场监管总局禁止虎牙斗鱼合并;北京我爱我家部分门店闭店一周;寒武纪入局自动驾驶芯片...
  14. HTTP请求/响应原理
  15. java jhat_java自带命令行工具jmap、jhat与jinfo的使用实例代码详解
  16. Python 中还藏着这些稀奇古怪的东西...
  17. Unity项目-黑魂复刻(二)玩家控制器(跳跃)
  18. Mysql之半同步复制
  19. 锂离子电池热失控预警资料整理(三)
  20. Android调试常用命令

热门文章

  1. 以大TMS运输配送管理系统
  2. Android项目“error: Apostrophe not preceded by \ (”报错解决方法
  3. smbd - 向客户提供SMB/CIFS服务的服务器
  4. Sisyphus.js 自动保存草稿 用法
  5. 斯托克斯公式_注解_高数
  6. 金融网络安全和反欺诈方法论,金融新兴技术成熟度几何?
  7. 不看你就亏了。。。。
  8. 大学生家乡网页设计作业模板下载 南京城市网页设计作业成品 静态HTML旅游景点网页制作下载_网页设计代码
  9. OpenJudge- 1789:算24
  10. IceSword V1.22 Final 冰刃