9

12306购票流程全解析

 3 years ago
source link: https://www.lanindex.com/12306%e8%b4%ad%e7%a5%a8%e6%b5%81%e7%a8%8b%e5%85%a8%e8%a7%a3%e6%9e%90/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

12306购票流程全解析

2016/12/23 · Leave a comment

顺利的用自制程序抢到回家的火车票,也不枉费我这两周从一个服务器端程序员伪装成客户端程序员利用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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK