先看预定结果:
ps. 说明我们是完完全全的预定成功了的。
还需要说明的是:这次的预定是使用手动输入验证码,我上一篇文章对自定校验验证码做出了处理,但是效率比较低。在知乎上有为朋友说他正在使用机器学习来跑,但是结果不知道如何,但是我下一次会将两部分整合起来。但是只能用于学习而不能实际抢票。
2017年的计划完成了吗?2018年的计划开始写了吗?新的一年,我们大家一起学python。
好吧,不bb了,看我文章的同学都知道,我比较随意,写的东西并不像作品,更像是大家一起吹牛一样。希望能给大家要给简单的,轻松的学习途径。
今天我想谈谈对12306进行火车票的购买,呀,你居然火车票都不会买哦,是的,我以前是不会的。但是现在我会买简单的火车票了。
ps.下面开始吧
分析购买流程
- 检查用户是都登陆
- 检查用户
- 检查车次是否能被下单(通俗易懂)
- 初始化购票页面
- 获取乘客信息
- 确认订单信息
- 检查车票预定队列
- 下单
下面就来说说每一步吧,可能你们都不需要这些了,只需要源代码就可以完全看懂。但是,我还是说说我的学习过程吧。
检查用户是否登陆
12306这个检查用户是都登陆,我觉得已经是比较好的,就拿我目前的水平来说,下面分析分析。它经历了三个步骤:
login登陆,就是简简单单的带着用户名和密码进行了登陆,但是它返回了一个uuid,我没有仔细的数,不知道是不是uuid,反正就是很长一串字符.我们就姑且叫uuid好了。字段为:uamtk,tk其实就是token的意思。
登陆接口uamtk进行了认证,后面个单词猜不出来是哪个单词。再次返回一个类似新的uuid。字段为:newapptk
uamtk接口uamauthclient再次带着第二部的newapptk再去认证一次客户端。
uamauthclient接口
经过这三部曲,可以说你已经在12306服务器挂上号了。就可以对他干任何可以干的事情。
检查用户
顾名思义就是看看该用户是都进行了登陆,没有参数,就只是简简单单的访问下后台,信息都在cookie和session里面。返回的信息包含在一个flag字段中。
检查用户接口
这是为了防止cookie,session被篡改,但是我觉得也没啥用。呵呵
大家自己仔细想想就应该明白,你就算这次我不篡改,我下个请求改还不是一样的呀。脱裤子放屁-->多此一举
接口返回数据:
{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"flag":true},"messages":[],"validateMessages":{}}
检查下单请求
这个请求的作用是,来判断是的这个车次信息能被你下单么?这个接口的参数就有点多,基本都是跟用户买当前车次的信息。但是会检查是都该用户是否有未完成的订单。
参数信息如下:
"secretStr" 车次,需要进行解码
"train_date": 出发日期
"back_train_date" 返回日期
"tour_flag": "dc" 单程/ 往返(wc)
"purpose_codes": "ADULT" 普通/学生(0X00)
"query_from_stati": 出发车站 ,可以在查询车次接口中得到
"query_to_station": 返回车站, 可以在查询车次接口中得到
"undefined": "" 应该是跟返回数据相关
可以看到,跟哪个用户没有一点信息,只有跟车次的数据。
因为它根本不需要知道谁需要购买这个车次,只需要知道这个车次是都能被人购买即可。
返回结果是要么可以,要么不可以咯,一样的是用一个字段的true/false来表示的。当你有未完成的订单的时候,这里就会被检查出来,提示您有问完成的订单。
接口返回数据:
{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":"N","messages":[],"validateMessages":{}}
初始化购票页面
买票的人都知道,我们点了预定之后会跳转到一个选择用户的页面,这就是初始化购票页面,这个页面做的事情可多了去了。
- globalRepeatSubmitToken 全局token,这一步之后很多请求都会跟这个数据有关
- key_check_isChange 不知道具体意义是什么,但是跟车次信息相关。
initDc接口
处理的核心代码:
def initDc():
"""
在进行点击了预定,初始化订票页面
:return:
"""
print("初始化订单数据")
url = "https://kyfw.12306.cn/otn/confirmPassenger/initDc"
data = {
"_json_att": ""
}
resp = req.post(url, data=data, headers=headers)
# print(resp.text)
# 反回一个验证通过信息
if resp.status_code == requests.codes.ok:
a1 = re.search(r'globalRepeatSubmitToken.+', resp.text).group()
globalRepeatSubmitToken = re.sub(r'(globalRepeatSubmitToken)|(=)|(\s)|(;)|(\')', '', a1)
b1 = re.search(r'key_check_isChange.+', resp.text).group().split(',')[0]
key_check_isChange = re.sub(r'(key_check_isChange)|(\')|(:)', '', b1)
return (globalRepeatSubmitToken, key_check_isChange)
这样我们就拿到了这两个需要用到东西。为后面的进行步骤打了基础。
获取乘客信息
其实就目前来说,这个接口我都是没有处理的,因为这个不是图形化的东西,最重要的是抢票要速度,谁tm还去给你输入用户数据,都是写在代码里面了。所以我就多bb这个接口
获取乘客接口
参数:
data = {
"_json_att": "",
"REPEAT_SUBMIT_TOKEN": REPEAT_SUBMIT_TOKEN 这个参数就是我们在初始化页面的时候获取到的。
}
接口返回:
"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"isExist":true,"exMsg":"","two_isOpenClick":["93","95","97","99"],"other_isOpenClick":["91","93","98","99","95","97"],"normal_passengers":[用户数据],"dj_passengers":[]},"messages":[],"validateMessages":{}}
返回的结果中,用户数据会被使用到。我们这里为了效率,直接先写死的。
确认订单信息
确认订单信息,确认的是用户和车次之间的关联。假如都不知道是那个人和那辆车,那还买个毛线的票是吧。
确认订单和用户接口
参数:
js信息:https://kyfw.12306.cn/otn/resources/merged/passengerInfo_js.js?scriptVersion=1.9058
所有的分析都来自于该js,有兴趣的同学自己跟踪,我就不一一的讲咯。
cancel_flag:2 默认
bed_level_order_num:000000000000000000000000000000 默认
passengerTicketStr: 用户信息
oldPassengerStr:用户数据
tour_flag:dc
randCode: 需要重新获取验证码,为空
whatsSelect:1 是否是常用联系人中选择的需要购买车票的人
_json_att:
REPEAT_SUBMIT_TOKEN:初始化initDc接口中得到的数据
检查车票预定队列
检查预定队列,做过多线程开发的都应该知道,多个线程访问统一资源的时候,会出现线程不安全,所以需要加锁,锁定该资源,在这里的车票就是资源,我们必须确定这个车次还有,才能做订票,不然车都没了,还订个啥。
队列接口
参数:
"train_date": train_data,
"train_no": trick_data[len(trick_data) - 1][2],
"stationTrainCode": trick_data[len(trick_data) - 1][3],
# 1:硬卧 3:硬座
"seatType": "3",
"fromStationTelecode": trick_data[len(trick_data) - 1][6],
"toStationTelecode": trick_data[len(trick_data) - 1][7],
"leftTicket": trick_data[len(trick_data) - 1][12],
"purpose_codes": "00",
"train_location": trick_data[len(trick_data) - 1][15],
"_json_att": "",
"REPEAT_SUBMIT_TOKEN": REPEAT_SUBMIT_TOKEN
这些数据其实都是来自于车次信息,所以我就不想过多的解释了。
请求数据和返回结果:
请求参数:
{'train_date': 'Mon Jan 1 2018 00:00:00 GMT+0800 (中国标准时间)', 'train_no': '78000K95180E',
'stationTrainCode': 'K9518',
'seatType': '3',
'fromStationTelecode': 'GIW',
'toStationTelecode': 'ZIW',
'leftTicket': 'fRHQaP8JPYKHxyvfihr70ZJYDi2VYncf4DPG%2FWI6ZmA4DYyN',
'purpose_codes': '00',
'train_location': 'W2',
'_json_att': '',
'REPEAT_SUBMIT_TOKEN': 'a8faf49bca59d540d55662d1692a7dc4'}
返回结果:
{"validateMessagesShowId":"_validatorMessage",
"status":true,
"httpstatus":200,
"data":{"count":"0",
"ticket":"15",
"op_2":"false",
"countT":"0",
"op_1":"false"},"messages":[],"validateMessages":{}}
最后一梭子,下单
在页面上就是一个确定的事情,这张票就是你的了,当然如果没有了余票,就购买失败了呗。
这里有一点我需要说明的是,乘客数据的生成。
这五个参数中,有两个参数需要注意 passengerTicketStr 与oldPassengersStr
passengerTicketStr 是以下划线"_"分隔当每一个乘客信息组成的字符串,对应每个乘客信息字符串组成如下:
- 座位编号,0,票类型,乘客名,证件类型,证件号,手机号码,保存常用联系人(Y或N)
如:1,0,1,姓名,1(身份证),522.。。,187xxx,Y
同样 oldPassengersStr 也是以下划线"_"分隔每个乘客信息组成的字符串,对应每个乘客信息字符串组成如下:
2.. 乘客名,证件类型,证件号,乘客类型
如:姓名,1,522xxx,1(成人)
在上面的信息中座位编号是指是什么座位类型,硬座,软座...
票类型指的是,成人票,学生票等的编码
证件类型指的是二代身份证,学生证,签证等的编码.
注意:在组合 oldPassengersStr 乘客信息字符串时,未尾会多一个下划线,提交请求是一定要补上。
js中说明的以上数据部分来源:
ticket_type: {adult: "1", child: "2", student: "3", disability: "4"},
ticket_type_name: {"1": "成人票", "2": "孩票", "3": "学生票", "4": "伤残军人票"},
tour_flag: {dc: "dc", wc: "wc", fc: "fc", gc: "gc", lc: "lc", lc1: "l1", lc2: "l2"},
passenger_type: {adult: "1", child: "2", student: "3", disability: "4"},
passenger_card_type: {two: "1", one: "2", tmp: "3", passport: "B", work: "H", hongkong_macau: "C", taiwan: "G"},
request_flag: {isAsync: "1"},
ticket_query_flag: {query_commom: "00", query_student: "0X00"},
seatType: {yz_type: "1"},
special_areas: {lso: "LSO", dao: "DAO", ado: "ADO", nqo: "NQO", tho: "THO"}
请求接口
参数:
data = {
"passengerTicketStr": passengerTicketStr,
"oldPassengerStr": oldPassengerStr,
"randCode": "",
"purpose_codes": "00",
"key_check_isChange": key_check_isChange,
"leftTicketStr": trick_data[len(trick_data) - 1][12],
"train_location": trick_data[len(trick_data) - 1][15],
"choose_seats": "",
"seatDetailType": "000",
"whatsSelect": "1",
"roomType": "00",
"dwAll": "N",
"_json_att": "",
"REPEAT_SUBMIT_TOKEN": REPEAT_SUBMIT_TOKEN
}
参数和返回结果:
传递参数:
{'passengerTicketStr': '1,0,1,姓名,1,522,,N', 'oldPassengerStr': '姓名,1,5221,1_', 'randCode': '', 'purpose_codes': '00', 'key_check_isChange': '675DA8E11714F951FD8026D010742D859C9250E5A20B32604EFF5476', 'leftTicketStr': 'fRHQaP8JPYKHxyvfihr70ZJYDi2VYncf4DPG%2FWI6ZmA4DYyN', 'train_location': 'W2', 'choose_seats': '', 'seatDetailType': '000', 'whatsSelect': '1', 'roomType': '00', 'dwAll': 'N', '_json_att': '', 'REPEAT_SUBMIT_TOKEN': 'a8faf49bca59d540d55662d1692a7dc4'}
返回的结果:
{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"submitStatus":true},"messages":[],"validateMessages":{}}
从上面的结果来看,我们是订票成功了的。
大家一起学习......
有情况简书走一波,小红心的走起来,谢谢!
我必须先给自己点一个,我贱贱的笑了。
最后,奉上我的github地址,该项目在ticket包下,修改conf/conf.ini配置文件之后,直接运行即可。
github