12306改版之后简单抢票软件的实现(二)完结

    上一篇文章讲完了12306网站模拟登陆的部分,看这里 12306改版之后简单抢票软件的实现

    现在把后面的步骤全部分析一下。

本文作者 http://www.cnblogs.com/russellwang,转载请标明出处

    登录完成要选择买票人的信息,那么怎么获得账户中常用联系人的信息呢?访问这个地址:https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs

访问这个页面需要两个参数(@code_flyer提醒这里可能不需要参数,暂时没验证),

    说一下第二个参数,REPEAT_SUBMIT_TOKEN是从提交订单后选择乘客信息的页面中获得的,这个页面在后面还会用到,先给出地址:https://kyfw.12306.cn/otn/confirmPassenger/initDc,页面头部有一段js,找到globalRepeatSubmitToken对应的值就是需要的参数

 

    获得用户信息后可以在界面上展示出来,供用户选择,或者直接提供用户输入信息的地方,只需要姓名、身份证号、手机号就可以了,这里要求输入的买票人信息是账号中已经认证过的人,12306做了实名认证了。

本文作者 http://www.cnblogs.com/russellwang,转载请标明出处

    准备步骤都完成了,下面应该就是查询余票了,查票的地址和参数如下:https://kyfw.12306.cn/otn/leftTicket/queryT?leftTicketDTO.train_date=2015-02-16&leftTicketDTO.from_station=SZH&leftTicketDTO.to_station=XCH&purpose_codes=ADULT

    参数分别代表出发日期,出发站,到达站,车票类型,复杂一点的就是出发站、到达站的代号问题了,这也难不倒我们勤劳的猿类,查询页面 https://kyfw.12306.cn/otn/leftTicket/init 加载的时候引入了一个站点名称拼音和代号对应的js文件,

    访问这个地址:https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8241,密密麻麻的站点和代号啊,看得我眼花缭乱的,赶紧找到你在的城市吧!我先拿前三个数据来说明一下数据的格式,

1 @bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1@bji|北京|BJP|beijing|bj|2

    每条记录以"@"开头,剩下的用"|"来分隔,全大写的代号就是我们在查询余票时用到的出发站、到达站编号,一开始就把这些信息保存起来,查询时直接使用是不是可以快那么一点点呢?

    差一点忘记了,查询之前需要先设置cookie值,记录查询信息,需要设置的cookie字段如下图所示:

    其中JSESSIONID和BIGipServerotn在上篇文章登录时已经获得了,剩下的几个字段就是你查询车票用到的信息,_jc_save_fromStationh和_jc_save_toStation将要查询站点的中文名和编号按照特定方式编码组成,中文名转为Unicode值并转为全大写,然后将"\"替换为"%",后面拼接站点对应编号即可。

    下面来看查询余票返回的结果,数据为json格式,为了方便展示,特意找了一个只有一辆列车的区间:昆明--徐州,其他区间返回的数据格式也是类似的。

{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":[{"queryLeftNewDTO":{"train_no":"800000K4920F","station_train_code":"K492","start_station_telecode":"KMM","start_station_name":"昆明","end_station_telecode":"JNK","end_station_name":"济南","from_station_telecode":"KMM","from_station_name":"昆明","to_station_telecode":"XCH","to_station_name":"徐州","start_time":"12:40","arrive_time":"02:59","day_difference":"2","train_class_name":"","lishi":"38:19","canWebBuy":"IS_TIME_NOT_BUY","lishiValue":"2299","yp_info":"1027853214407805000910278503273048850106","control_train_day":"20201231","start_train_date":"20150202","seat_feature":"W3431333","yp_ex":"10401030","train_seat_feature":"3","seat_types":"1413","location_code":"M1","from_station_no":"01","to_station_no":"27","control_day":59,"sale_time":"1430","is_support_card":"0","gg_num":"--","gr_num":"--","qt_num":"--","rw_num":"9","rz_num":"--","tz_num":"--","wz_num":"有","yb_num":"--","yw_num":"有","yz_num":"有","ze_num":"--","zy_num":"--","swz_num":"--"},"secretStr":"MjAxNS0wMi0wMiMwMCNHNzI5OCMwMjozMyMyMDoyMSM1NTAwMEc3Mjk4MDAjT0hII1VVSCMyMjo1NCPoi4%2Flt57ljJcj5b6Q5bee5LicIzAyIzA4I08wMjQ5MDAwNTlNMDQxOTAwMDUwTzAyNDkwMzAwMCNIMSMxNDE4OTk2MjQ0NjgxIzA5MEI3MkQ5NEFDQTgyQzI3MjM2MTc5RTE5ODA3RDQ2NkUxRDAxNjY2MDM1MzVFQzE0QTZFMUQ4","buttonTextInfo":"预订"}],"messages":[],"validateMessages":{}}

    慢慢解析这个数据,我们能够看到车次 station_train_code:K492,座位信息:"gg_num":"--","gr_num":"--","qt_num":"--","rw_num":"9","rz_num":"--","tz_num":"--","wz_num":"有","yb_num":"--","yw_num":"有","yz_num":"有","ze_num":"--","zy_num":"--","swz_num":"--",这些信息包括了座位类型和剩余数量,遇到“有”或者数字的座位代表我们可以预订的,其他的应该是没有这种类型的座位或无票。筛选车票的逻辑可以根据自己的需要进行定制了,可以预先定义好需要哪个车次什么类型的座位,发现能预定的车票赶紧下手,到下一步去提交订单吧,如果没有合适的车票你可以在查询筛选一下,这就在不停的刷票了。这里还有一个secretStr是比较重要的,待会提交订单的时候要用到,所以先把这个值保存起来。

本文作者 http://www.cnblogs.com/russellwang,转载请标明出处

    下面的这段内容是在12306改版之后新添加进去的,原理和登录时一样,增加了动态的参数名和值来判断在提交订单时是不是抢票插件的请求,还是看余票查询页面https://kyfw.12306.cn/otn/leftTicket/init,新加载脚本的地方在这

    同样访问里面的内容,获得key的值并计算value值,计算方法参考上一票文章,12306改版之后简单抢票软件的实现。后面提交订单的时候能用到这个key跟value值。

    筛选完能预订的列车,12306网站还做了一个检验https://kyfw.12306.cn/otn/login/checkUser,验证你有没有登录的,这个时候你要是没有登录那就悲剧了,想想先你刷了半天的票,突然发现一张能购买的了,这时候告诉你“去登陆”,跟这张票说byebye吧。前面我们是先登录然后查询余票的,所以检查是否登录这一步是可以跳过去的,直接进入下一步,提交订单。

    提交订单的地址:https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest,这里的参数用到了前面保存的车票secretStr和计算的key、value值,参数见下图:

    这里需要根据返回的结果判断是否成功,成功之后12306网站会跳转到选择乘客、输入验证码的页面https://kyfw.12306.cn/otn/confirmPassenger/initDc,这个页面在前面获得常用联系人的地方用到过。在这里我们从页面代码中重新获取一下globalRepeatSubmitToken的值,方法在文中开头处那一段,这个值在检查确认订单的时候用到,同样在这个页面获得key_check_isChange的值,用正则来匹配,对应的正则表达式为 'key_check_isChange':'(.*?)',自己验证一下,保存这个值后面使用。在程序中我们需要将前面准备好的用户信息模拟提交一下,并查看返回的结果是否成功。具体来分析一下,首先要处理的还是新增加的验证,同登录步骤和上面的提交订单步骤,要先获得key和value值,这里就不在赘述。然后请求一个新的验证码,和登陆之前的验证码地址是相同的,但是参数有所区别,

,注意这里的参数,将验证码ocr自动识别或手动输入之后最好先提交给12306后台验证一下,这一步可以跳过去直接到下一步检查确认订单,但是如果验证码错误的话也不能蒙混过关,检查订单时还是会校验的。

    下面这一步是检查确认订单,请求地址是https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo,传入的参数比较多,下图所示

    分开来讲这几个参数,cancel_flag,bed_level_order_num,tour_flag,_json_att 用图中固定值即可,randCode是上一步请求的验证码,NTkzODQy这一对值是验证是否是抢票助手用的key,value值,前面已获得,REPEAT_SUBMIT_TOKEN的值也在页面https://kyfw.12306.cn/otn/confirmPassenger/initDc中重新获得了,就剩下passengerTicketStr和oldPassengerStr这是根据乘客的用户信息拼接出来的,参考一段别人的代码(hncdyj)

 1 public static String getOldPassengerStr(List<UserInfo> userInfo) {
 2     String oldStrs = "";
 3     for (int i = 0; i < userInfo.size(); i++) {
 4         String oldStr = userInfo.get(i).getName() + "," + userInfo.get(i).getCardType() + "," + userInfo.get(i).getCardID() + "," + userInfo.get(i).getType();
 5         oldStrs += oldStr + "_";
 6     }
 7     return oldStrs;
 8 }
 9 public static String getPassengerTicketStr(List<UserInfo> userInfo) {
10     String oldStrs = "";
11     for (int i = 0; i < userInfo.size(); i++) {
12         String oldStr = "";
13         if ("WZ" == userInfo.get(i).getSeatType()) {
14         } else {
15             oldStr = userInfo.get(i).getSeatType();
16         }
17         String bR = oldStr + ",0," + userInfo.get(i).getTickType() + "," + userInfo.get(i).getName() + "," + userInfo.get(i).getCardType() + "," + userInfo.get(i).getCardID() + ","
18         + (userInfo.get(i).getPhone() == null ? "" : userInfo.get(i).getPhone()) + ",N";
19         oldStrs += bR + "_";
20     }
21     return oldStrs.substring(0, oldStrs.length() - 1);
22 }

结果大概是这个样子的 

passengerTicketStr-->1(座位类型),0,1(车票类型),张三(乘客姓名),1(证件类型),320xxxxxx(身份证号),151xxxx(手机号),N
oldPassengerStr-->张三(乘客姓名),1(证件类型),320xxxxxx(身份证号),1_

可以自己买票时抓包验证,这里给出座位类型和代号的对应关系:商务座(9),特等座(P),一等座(M),二等座(O),高级软卧(6),软卧(4),硬卧(3),软座(2),硬座(1),无座(1)(修正为1,感谢@code_flyer的提醒),可以根据自己的需要预先设置要买的座位类型,根据查询结果判断对应的类型是否有票。

这一步提交成功根据返回的信息判断确认定但是否通过,常见的错有“非法请求”,“输入的验证码错误”等。

本文作者 http://www.cnblogs.com/russellwang,转载请标明出处

通过之后离成功就很近啦,来看一下排队人数的情况吧,地址:https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount,参数

    要查询的车次和leftTicket信息在查询余票时得到的信息里面有,不超过排队人数的情况下就进行下面一步吧

    确认单笔订单 https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue

  

    这些参数都比较简单了,前面都用到过了或者已经保存下来准备这里使用的,train_location在查询余票返回的结果里。

    后面还可以查询出票等待时间,https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime?random=1419117908672&tourFlag=dc&_json_att=&REPEAT_SUBMIT_TOKEN=xxxxxxxxxxxxxx,参数请做相应替换,random对应的应该是当前时间。

本文作者 http://www.cnblogs.com/russellwang,转载请标明出处

    最后还需要补充一点知识,12306返回的信息有些是gzip压缩格式的,可以根据返回信息头中的Content-Encoding来判断,读取这些返回的信息流时需要用到GZIPInputStream,否则得到的是一堆乱码。

    OK,到这里整个流程走完了,应该能买到你心仪的火车票了,也算是了却了一桩心事。

    买票的全部流程简单介绍了一下,可能不够清晰,但是有心人根据这些分析应该能做出一个简单的抢票软件了,如果你用自己做的抢票软件买到了票,请到这里来分享一下。在做的过程中有什么疑问也欢迎到这里来提问,我会尽量及时帮助解答,不一定所有的问题都能解决。

    空余的时间还做了一个抢小米的软件,现在抢火车票的文章告一段落了,闲下来的时候把抢小米的过程也分析一下。

    感谢你看到了这里!欢迎留言讨论,也希望你帮我点一下“推荐” 

    

    博文作者:russellwang
    本文版权归作者和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作!
posted @ 2014-12-21 12:45  russellwang  阅读(31125)  评论(67编辑  收藏  举报