12306购票流程全解析

顺利的用自制程序抢到回家的火车票,也不枉费我这两周从一个服务器端程序员伪装成客户端程序员利用Chrome DevTools一段一段的挖12306的协议,同时满足了我今年的一个小心愿 – 自制12306程序并且可以实现简单刷票功能。%e8%bd%a6%e7%a5%a8

代码编写的核心使用了 PHP – Client URL Library(Curl)

未研究验证码自动识别,言外之意就是说在Login时需要手动点击验证码,在购票时也需要手动点击输入;

理论上来说这里没有使用什么黑科技(验证码自动识别之类的),只是简化了购票流程;

文章最后更新与2017-01-04,因为12306协议变动比较频繁,若未来协议流程无法与本文对应,请见谅(会尽力保证更新,同时更新日期)。

Step 1 获取cookie并保存,Get

https://kyfw.12306.cn/otn/login/init

唯一需要注意的就是时区,会导致购票异常。一般不会失败,失败了就重试,后续每一步都需要带上cookie内容。

 

Step 2 带上cookie抓取Login验证码,Get

https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=login&rand=sjrand&0.321423424

sjrand后面是一个0-1的随机数,防缓存,建议传上,php一行代码的事情:mt_rand()/mt_getrandmax()。

一般不会失败,失败了就重试。

 

Step 3 验证Login验证码,Post

https://kyfw.12306.cn/otn/passcodeNew/checkRandCodeAnsyn?rand=sjrand&randCode=111,117,31,40

HTTPHEADER设置(“application/x-www-form-urlencoded; charset=utf-8”)

这里麻烦之处是randCode的格式,现在的12306验证码点击区是一个大约290×150像素点的合成图片(未精确测算),由8张子图组成,图片的左上角是坐标0.0,右下角的坐标290×150。

至于这个验证码的点击数据怎么获取,网上很多JS小程序。

回包是Json格式{“validateMessagesShowId”:”_validatorMessage”,”status”:true,”httpstatus”:200,”data”:{“result”:”1″,”msg”:”TRUE”},”messages”:[],”validateMessages”:{}}

建议判断”msg”:”TRUE”,失败跳回step 2。

 

Step 4 验证用户名\密码,Post

https://kyfw.12306.cn/otn/login/loginAysnSuggest?loginUserDTO.user_name=111&userDTO.password=222&randCode=111,117,31,40

HTTPHEADER设置(“application/x-www-form-urlencoded; charset=utf-8”)

接下来是验证用户密码,这里用户名和密码分别使用111、222代替;

回包是Json格式{“validateMessagesShowId”:”_validatorMessage”,”status”:true,”httpstatus”:200,”data”:{“otherMsg”:””,”loginCheck”:”Y”},”messages”:[],”validateMessages”:{}};

建议判断”loginCheck”:”Y”,失败跳回step 2;

这里若提示存在”msg”:”系统繁忙,请稍后再试”有一定几率是IP被封了,需要大约1个小时解封,导致IP被封的原因很多,请求过于频繁,传递了异常参数都有可能。

 

Step 5 正式Login,Post

https://kyfw.12306.cn/otn/login/userLogin?_json_att=

HTTPHEADER设置(“application/x-www-form-urlencoded)

返回的是一个html页面,一般不会失败。

 

Step 6 模拟跳转initMy12306,Get

https://kyfw.12306.cn/otn/index/initMy12306

HTTPHEADER设置(“Content-type:text/html”)

返回的是一个html页面,一般不会失败。

 

Step 7 拉乘客买票验证码,Get(个人觉得这一步无用,但是从协议流程来看12306确实是有拉取)

https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew??module=passenger&rand=randp&0.878574764745

 

Step 8 查询余票第一步,Get

https://kyfw.12306.cn/otn/leftTicket/log?leftTicketDTO.train_date=2017-01-21&leftTicketDTO.from_station=SZQ&leftTicketDTO.to_station=WHN&purpose_codes=ADULT

leftTicketDTO.train_date是购票车次日期,格式为yyyy-mm-dd;

leftTicketDTO.from_station出发地编号,比如:深圳 => SZQ,武汉 => WHN;

leftTicketDTO.to_station到达地编号;

purpose_codes,ADULT表示成人乘客,学生票用什么定义未挖掘。

回包Json格式{“validateMessagesShowId”:”_validatorMessage”,”status”:true,”httpstatus”:200,”messages”:[],”validateMessages”:{}}

判断”status”:true,失败了就跳到Step 8(还是这一步)

 

Step 9 查询余票第二步,Get

https://kyfw.12306.cn/otn/leftTicket/queryA?leftTicketDTO.train_date=2017-01-21&leftTicketDTO.from_station=SZQ&leftTicketDTO.to_station=WHN&purpose_codes=ADULT

参数和Step 8是一样的;

回包Json格式所有车次的列表train_info(太长略),没有余票就跳到Step 8,建议sleep1~3秒再跳转;

注意:这步有可能回包为空,导致Json解析异常,若异常就跳到Step 8;

这个url会偶尔变更,开发第一周url是https://kyfw.12306.cn/otn/leftTicket/queryX?……

当station_train_code符合我们车次,并且有票,从Json结果中挖取我们需要的数据:

secretStr,train_no,start_station_telecode,end_station_telecode,yp_info,location_code

其中yp_info需要做urlencode。

 

Step 10 验证登录,Post

https://kyfw.12306.cn/otn/login/checkUser?_json_att=

HTTPHEADER设置(“application/x-www-form-urlencoded; charset=utf-8”)

回包Json格式{“validateMessagesShowId”:”_validatorMessage”,”status”:true,”httpstatus”:200,”data”:{“flag”:true},”messages”:[],”validateMessages”:{}}

建议同时验证”status”:true,”flag”:true,否则返回step 1,需要重新登录

 

Step 11 预提交订单,Post

https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest?secretStr=secretStr&train_date=2017-01-21&back_train_date=2016-12-23&tour_flag=dc&purpose_codes=ADULT&query_from_station_name=深圳&query_to_station_name=武汉&undefined=

HTTPHEADER设置(“application/x-www-form-urlencoded; charset=utf-8”)

secretStr是在Step 9中Json取值secretStr;

train_date是购票车次日期,与Step 8中的leftTicketDTO.train_date一致;

back_train_date是回程日期,格式填对即可yyyy-mm-dd;

tour_flag=dc,表示单程

purpose_codes=ADULT,表示成人

回包Json格式{“validateMessagesShowId”:”_validatorMessage”,”status”:true,”httpstatus”:200,”data”:”N”,”messages”:[],”validateMessages”:{}}

验证”status”:true,失败重试3次后建议返回step 1

 

Step 12 模拟跳转页面InitDc,Post

https://kyfw.12306.cn/otn/confirmPassenger/initDc?_json_att=

HTTPHEADER设置(“application/x-www-form-urlencoded”)

回包html格式,需要利用正则挖取数据:

“/var globalRepeatSubmitToken = ‘(.*?)’;/”  => SubmitToken

“/’key_check_isChange’:'(.*?)’/” => key_check_isChange

 

Step 13 常用联系人确定,Post

https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs?_json_att=&REPEAT_SUBMIT_TOKEN=SubmitToken

HTTPHEADER设置(“application/x-www-form-urlencoded; charset=utf-8”)

REPEAT_SUBMIT_TOKEN就是在Step 12中挖出来的SubmitToken;

回包是Json格式的联系人列表,解析联系人的信息,在乘车人符合passenger_name时需要关心这几个数据:

passenger_id_type_code,passenger_id_no,mobile_no,passenger_type

 

Step 14 拉乘客买票验证码,Get

https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew??module=passenger&rand=randp&0.6352423422

 

Step 15 购票人确定,Post

https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo?cancel_flag=2&bed_level_order_num=000000000000000000000000000000&passengerTicketStr=str1_str2&
oldPassengerStr=str3_str4_&tour_flag=dc&randCode=&_json_att=&REPEAT_SUBMIT_TOKEN=SubmitToken

HTTPHEADER设置(“application/x-www-form-urlencoded; charset=utf-8”)

座位编号(seatType)参考:

‘硬卧’ => ‘3’,
‘软卧’ => ‘4’,
‘二等座’ => ‘O’,
‘一等座’ => ‘M’,
‘硬座’ => ‘1’,

passengerTicketStr组成的格式:seatType,0,票类型(成人票填1),乘客名,passenger_id_type_code,passenger_id_no,mobile_no,’N’

多个乘车人用’_’隔开

oldPassengerStr组成的格式:乘客名,passenger_id_type_code,passenger_id_no,passenger_type,’_’

多个乘车人用’_’隔开,注意最后的需要多加一个’_’。

回包是Json格式,需要关心的是”status”:true,失败返回step 8;

还需要关心”ifShowPassCode”:”Y”,若是”Y”表示后续要校验验证码,”N”则不用校验。

 

Step 16 准备进入排队,Post

https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount?train_date=Sat Jan 21 2017 00:00:00 GMT+0800 (中国标准时间)&train_no=train_no&stationTrainCode=station_train_code&seatType=seatType&fromStationTelecode=start_station_telecode&
toStationTelecode=end_station_telecode&leftTicket=yp_info&purpose_codes=00&train_location=location_code&_json_att=&REPEAT_SUBMIT_TOKEN=SubmitToken

HTTPHEADER设置(“application/x-www-form-urlencoded; charset=utf-8″)

记得使用urlencode转换一次

上面很多参数都是在Step 9中取得的,可以一一对应。

至于这一步为何在输入验证码前面不得而知,但是确实官网的流程是这样。

回包是Json格式,可以看到当前的票数”ticket”:”xxx”与正在排队人数”count”:”xxx”

判断”status”:true,若失败建议返回step 1

 

若Step 15中的”ifShowPassCode”:”Y”,那么多了输入验证码这一步,Post

https://kyfw.12306.cn/otn/passcodeNew/checkRandCodeAnsyn?randCode=131,117,371,40&rand=randp&_json_att=&REPEAT_SUBMIT_TOKEN=SubmitToken

HTTPHEADER设置(“application/x-www-form-urlencoded; charset=utf-8”)

输错了就重新拉取验证码然后进入这一步

 

Step 17 确认购买,Post

https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue?passengerTicketStr=str1_str2&oldPassengerStr=str3_str4_&&randCode=131,117,371,40&purpose_codes=00&key_check_isChange=key_check_isChange
&leftTicketStr=yp_info&train_location=location_code&choose_seats=&seatDetailType=000&roomType=00&dwAll=N&_json_att=&REPEAT_SUBMIT_TOKEN=SubmitToken

HTTPHEADER设置(“application/x-www-form-urlencoded; charset=utf-8″)

参数都是前面可以挖的。

randCode这个参数,若”ifShowPassCode”:”N”填空,若为”Y”填入和上一步同样的randCode。

返回是Json格式{“validateMessagesShowId”:”_validatorMessage”,”status”:true,”httpstatus”:200,”data”:{“submitStatus”:true},”messages”:[],”validateMessages”:{}}

验证”submitStatus”:true进行下一步

主要注意的是,这一步会出现各种异常“出票错误”,可能是真的没票,可能是参数传的有问题,可能是没有登录态了…我暂时的处理方式直接回到step 1

 

Step 18 快成功了!每隔4秒循环Post

https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime?random=time&tourFlag=dc&_json_att=&REPEAT_SUBMIT_TOKEN=SubmitToken

random参数是当前秒数*1000+毫秒数

返回是Json格式{“validateMessagesShowId”:”_validatorMessage”,”status”:true,”httpstatus”:200,”data”:{“queryOrderWaitTimeStatus”:true,”count”:0,”waitTime”:17,”requestId”:6217964314520123645,”waitCount”:366,”tourFlag”:”dc”,”orderId”:null},”messages”:[],”validateMessages”:{}}

需要关心的是”waitCount”,如果数量小于Step 16中的”ticket”那么买到票的几率就很大了。

若”orderId”有实际值表示成功!

 

(全文结束)

 


转载文章请注明出处:漫漫路 - lanindex.com

Leave a Comment

Your email address will not be published.

25 Trackbacks