12306购票流程分析(非抢票)

编程入门 行业动态 更新时间:2024-10-20 07:51:32

12306购票<a href=https://www.elefans.com/category/jswz/34/1770115.html style=流程分析(非抢票)"/>

12306购票流程分析(非抢票)

说在前面
购票流程的关键位置参数encryptedData未分析出来,(吐槽:还是太菜了啊)本文是个人结合网上以及抓包的分析,在此做个记录。有时间再试试encryptedData。
仅供研究学习使用,请勿用于非法用途
 

12306购票流程(20230925)

1 登录过程

1.1 获取验证模式

在预定车票时,点击预定会跳转到登录页面,在登录页输入用户名和密码后,点击登录会发送一个请求,当返回的login_check_code的值为3时,采用的是短信验证。

  • POST接口:
  • 表单:
参数名说明示例
username用户名xxx
appid固定参数otn
_json_att固定空参数

python示例:
import requestsheaders = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"
}
url = ''
data = {"username": user,"appid": "excater"
}
res = session.post(url, headers=headers, data=data)

1.2 获取短信验证码

之后会触发短信验证码验证的界面,需要输入身份证后4位获取短信验证码。

  • POST接口:
  • 表单:
参数名说明示例
appid固定参数otn
username用户名xxx
castNum身份证后4位3333
_json_att固定空参数


python示例:

url = ''
id_4 = input('请输入身份证后4位:')
data = {"appid": "otn","username": user,"castNum": id_4,"_json_att": ""
}
res = session.post(url, headers=headers, data=data)

1.3 登录

获取短信验证码后,才到了真正的登录接口,其中的password参数是加密的,经过分析js,可以确认是sm4加密

  • POST接口:
  • 表单:
参数名说明示例
sessionId固定空参数
sig固定空参数
if_check_slide_passcode_token固定空参数
scene固定空参数
checkMode校验模式(默认0)0
randCode短信验证码854390
username用户名xxx
password@+sm4加密后的密码xxxxxxxxxx
appid固定参数otn
_json_att固定空参数

python示例:

from gmssl import sm4
def _sm4_encode(data, key='tiekeyuankp12306'):sm4Alg = sm4.CryptSM4()  # 实例化sm4sm4Alg.set_key(key.encode(), sm4.SM4_ENCRYPT)  # 设置密钥dateStr = str(data)# print("明文:", dateStr)enRes = sm4Alg.crypt_ecb(dateStr.encode())  # 开始加密,bytes类型,ecb模式enHexStr = str(base64.b64encode(enRes), 'utf-8')# print("密文:", enHexStr)return enHexStrurl = ""login_data = {"sessionId": "","sig": "","if_check_slide_passcode_token": "","scene": "","checkMode": "","randCode": "","username": user,"password": "@" + _sm4_encode(pwd),"appid": "excater"}
response = session.post(url, headers=headers, data=login_data)

1.4 获取tk

当我们登录成功后,会获取到uamtk,但此时还不是放松的时候,在抓包中可以看到后面还有2个post请求,经过分析后可以确认是cookies.tk的必要请求
uamtk接口请求中返回的newapptk就是cookies.tk的值

  • POST接口:
  • 表单:
参数名说明示例
appid固定参数otn
_json_att固定空参数

python示例:

import reurl = ""
data = {"appid": "otn","_json_att": ""
}
res = session.post(url, data=data)
tk = re.findall('"newapptk":"(.*?)"', res.text)[0]

1.5 设置tk

接下来会对接收的newapptk值进行校验,校验正常时服务端会将该cookie设置在Session中(突发奇想:不发送该请求,自己将上面的newapptk添加到cookie中是否可以正常登录)

  • POST接口:
  • 表单:
参数名说明示例
tk获取tk时返回的newapptk8yQK700A2D-IGiOF_aTd8OPo0VPLJKOQaPsxxxxxxxxx
_json_att固定空参数

python示例:

url = ""
data = {"tk": tk,"_json_att": ""
}
response = session.post(url, headers=headers, data=data)

2 余票查询

2.1 车站编码

由于查票、购票时的站点都是采用编码形式的,需要获取车站对应的编码;返回的数据需要用正则提取一下,每条数据之间用|||分隔,数据中的各项内容用|分隔

  • GET接口:.js
    |分割后的数据内容(0开始):
下标内容
1站点名称
2站点编码

python示例:

url = '.js'
res = session.get(url)
text = re.search("'(.*?)'", res.text).group(1)
city_code_list = {i.split('|')[1]: i.split('|')[2] for i in text.split('|||') if i}

2.2 车票查询

经测试,余票查询是可以不登录的。载荷中的出发地与目的地是编码方式的,在此之前需要先获取站点对应的编码

  • GET接口:铁路客户服务中心
  • 载荷:
参数名说明示例
leftTicketDTO.train_date出发日期2023-10-13
leftTicketDTO.from_station出发站点BJP
leftTicketDTO.to_station目的站点SHH
purpose_codes固定参数ADULT

此接口返回的数据以|进行分割后,可以确认的结果目录(从0开始):

下标内容说明
0secretStr提交请求时会用到
1状态用于判断是否可预定
2train_no提交预定请求时会用到
3车次
6出发站名
7到达站名
8出发时间
9到达时间
10历时
12leftTicket提交预定请求与确认配置信息时用到
15train_location提交预定请求与确认配置信息时用到
23软卧数
26无座数
28硬卧数
29硬座数
30二等座
31一等座
32商务座

python示例:

def queryZ(gotime, st_city, ds_city):  # secretStrif (datetime.datetime.strptime(gotime, '%Y-%m-%d')-datetime.datetime.now()).days > 15:logger.error('超出可预定时间,无法查询')quit(401)code_city = {v: k for k, v in city_code.items()}url = ""params = {"leftTicketDTO.train_date": gotime,"leftTicketDTO.from_station": city_code[st_city],"leftTicketDTO.to_station": city_code[ds_city],"purpose_codes": "ADULT"}response = session.get(url, headers=headers, cookies=cookies, params=params)try:result = [i for i in response.json()['data']['result']]parse_format = {0: "secretStr",1: "状态",2: "train_no",3: "车次",6: "出发站名",7: "到达站名",8: "出发时间",9: "到达时间",10: "历时",12: "leftTicket",15: "train_location",23: "软卧",26: "无座",28: "硬卧",29: "硬座",30: "二等座",31: "一等座",32: "商务座"}data_list = [{v: i.split('|')[k] for k, v in parse_format.items()} for i in result]for data in data_list:data['出发站名'] = code_city[data['出发站名']]data['到达站名'] = code_city[data['到达站名']]# logger.info(f'数据提取后:{data_list}')return data_listexcept requests.exceptions.JSONDecodeError:logger.error('[queryZ]requests.exceptions.JSONDecodeError: 请求出现错误,返回数据非json格式')

3 预定

所有预定请求都需要携带登录后的cookies

3.1 提交请求

在选定了车次后,点击预定按钮会提交请求

  • POST接口:
  • 表单:
参数名说明示例
secretStr2.2车票查询时获取的参数uL7wOFr0GvhxkFOHvM64
train_date出发时间2023-10-02
back_train_date购票时间2023-09-18
tour_flag固定参数dc
purpose_codes固定参数ADULT
query_from_station_name出发站点长沙
query_to_station_name目的站点上海
undefined固定空参数

python示例:

def submitOrderRequest(secretStr, gotime, st_city, ds_city):url = ""data = {"secretStr": parse.unquote(secretStr),"train_date": gotime,"back_train_date": ctime,"tour_flag": "dc","purpose_codes": "ADULT","query_from_station_name": st_city,"query_to_station_name": ds_city,"undefined": ""}res = session.post(url, headers=headers, data=data)if res.json()['messages']:logger.warning(res.json()['messages'])return res.json()['messages']else:logger.info('[submitOrderRequest]提交请求成功')

3.2 请求确认预定信息

提交请求后会触发请求的确认,该接口返回的是HTML数据

  • POST接口:
  • 表单:
参数名说明示例
_json_att固定空参数
需要用正则在返回数据中提取两个参数,用于后续的请求参数名说明正则示例
REPEAT_SUBMIT_TOKEN用于多个请求var globalRepeatSubmitToken = '(.*?)'
key_check_isChange用于确认配置信息'key_check_isChange':'(.*?)'

python示例:

def initDc():  # REPEAT_SUBMIT_TOKEN  key_check_isChangeurl = ""data = {"_json_att": ""}text = session.post(url, headers=headers, data=data).textinfo_dict = {"REPEAT_SUBMIT_TOKEN": re.findall("var globalRepeatSubmitToken = '(.*?)'", text)[0],"key_check_isChange": re.findall("'key_check_isChange':'(.*?)'", text)[0]}return info_dict

3.3 获取乘客信息

在进入选座界面后,需要选择乘客

  • POST接口:
  • 表单:
参数名说明示例
_json_att固定空参数
REPEAT_SUBMIT_TOKEN3.2请求确认预定信息获取a14fe59b302c8bf7b7901aee95811111

返回的乘客列表在data.normal_passengers中,其中订单需要用到的项有:

参数名说明示例
passenger_name姓名张三
passenger_id_type_code身份认证代码1
passenger_id_no身份证333***333
mobile_no手机号159***3333
allEncStr确认订单信息与确认配置信息用到639...
passenger_type是否成人1

python示例:

def getPassengerDTOs(info_dict):  # passengerTicketStr oldPassengerStrurl = ""data = {"_json_att": "","REPEAT_SUBMIT_TOKEN": info_dict["REPEAT_SUBMIT_TOKEN"]}res = session.post(url, headers=headers, data=data)return res.json()['data']['normal_passengers']

3.4 确认订单信息

在选择乘客后点击确认,会进行订单校验

  • POST接口:
  • 表单:
参数名说明示例
cancel_flag固定参数2
bed_level_order_num固定参数000000000000000000000000000000
passengerTicketStr乘客信息O,0,1,xxx,333333,159333,N,43253...
oldPassengerStr乘客信息xxx,1,333***333,1_
tour_flag固定参数dc
whatsSelect固定参数1
sessionId固定空参数
sig固定空参数
scene固定参数nc_login
_json_att固定空参数
REPEAT_SUBMIT_TOKEN3.2请求确认预定信息获取a14fe59b302c8bf7b7901aee95811111

python示例:

def checkOrderInfo(info_dict, pass_info):url = ""name = pass_info['passenger_name']id_type = pass_info['passenger_id_type_code']id_no = pass_info['passenger_id_no']mob_no = pass_info['mobile_no']enc_str = pass_info['allEncStr']pass_type = pass_info['passenger_type']data = {"cancel_flag": "2","bed_level_order_num": "000000000000000000000000000000","passengerTicketStr": f"O,0,1,{name},{id_type},{id_no},{mob_no},N,{enc_str}","oldPassengerStr": f"{name},{id_type},{id_no},{pass_type}_","tour_flag": "dc","whatsSelect": "1","sessionId": "","sig": "","scene": "nc_login","_json_att": "","REPEAT_SUBMIT_TOKEN": info_dict["REPEAT_SUBMIT_TOKEN"]}response = session.post(url, headers=headers, data=data)

3.5 提交预定请求

订单校验完成会提交预定请求

  • POST接口:
  • 表单:
参数名说明示例
train_date出发日0时的中国标准时间Mon Oct 02 2023 00:00:00 GMT+0800 (中国标准时间)
train_no2.2车票查询获取6c000G13420K
stationTrainCode2.2车票查询获取G1342
seatType固定参数O
fromStationTelecode2.2车票查询获取(出发)CWQ
toStationTelecode2.2车票查询获取(目的)AOH
leftTicket2.2车票查询获取foZ%2BJb5xBT%2By%2BAqnZJZxzd3B2QGfJ7pWZhXMm7vJO7ncjrkD
purpose_codes固定参数00
train_location2.2车票查询获取QX
_json_att固定空参数
REPEAT_SUBMIT_TOKEN3.2请求确认预定信息获取a14fe59b302c8bf7b7901aee95811111

python示例:

def getQueueCount(gotime, ticket, info_dict):GMT_FORMAT = '%a %b %d %Y %H:%M:%S GMT+0800 (中国标准时间)'url = ""data = {"train_date": datetime.datetime.strptime(gotime, "%Y-%m-%d").strftime(GMT_FORMAT),"train_no": ticket["train_no"],"stationTrainCode": ticket["车次"],"seatType": "O",  # 固定值"fromStationTelecode": city_code[ticket["出发站名"]],"toStationTelecode": city_code[ticket["到达站名"]],"leftTicket": ticket["leftTicket"],"purpose_codes": "00",  # 固定值"train_location": ticket["train_location"],"_json_att": "","REPEAT_SUBMIT_TOKEN": info_dict["REPEAT_SUBMIT_TOKEN"]}response = session.post(url, headers=headers, data=data)

3.6 确认配置信息

页面手动点击的最后一个,确认配置信息

  • POST接口:铁路客户服务中心
  • 表单:
参数名说明示例
passengerTicketStr乘客信息O,0,1,xxx,333333,159333,N,43253...
oldPassengerStr乘客信息xxx,1,333***333,1_
purpose_codes固定参数00
key_check_isChange3.2请求确认预定信息获取B0426EDD36A030B0FCB98DDDF816BCF3305170B8121EE9E749C11111
leftTicketStr2.2车票查询获取foZ%2BJb5xBT%2By%2BAqnZJZxzd3B2QGfJ7pWZhXMm7vJO7ncjrkD
train_location2.2车票查询获取QX
choose_seats固定空参数
seatDetailType固定参数000
is_jy固定参数N
is_cj固定参数Y
encryptedDatajs加密参数(貌似 json_ua )vYcLvqvAAUYBTH...
whatsSelect固定参数1
roomType固定参数00
dwAll固定参数N
_json_att固定空参数
REPEAT_SUBMIT_TOKEN3.2请求确认预定信息获取a14fe59b302c8bf7b7901aee95811111

python示例:

def confirmSingleForQueue(ticket, info_dict, pass_info):url = ""name = pass_info['passenger_name']id_type = pass_info['passenger_id_type_code']id_no = pass_info['passenger_id_no']mob_no = pass_info['mobile_no']enc_str = pass_info['allEncStr']pass_type = pass_info['passenger_type']data = {"passengerTicketStr": f"O,0,1,{name},{id_type},{id_no},{mob_no},N,{enc_str}","oldPassengerStr": f"{name},{id_type},{id_no},{pass_type}_","purpose_codes": "00","key_check_isChange": info_dict["key_check_isChange"],"leftTicketStr": ticket["leftTicket"],"train_location": ticket["train_location"],"choose_seats": "","seatDetailType": "000","is_jy": "N","is_cj": "Y","encryptedData": "xxxx","whatsSelect": "1","roomType": "00","dwAll": "N","_json_att": "","REPEAT_SUBMIT_TOKEN": info_dict["REPEAT_SUBMIT_TOKEN"]}response = session.post(url, headers=headers, data=data)

4 排队等待

4.1 排队(未排到)

确认配置信息后,会进行排队等待,需要通过返回的data.waitTime确定需要等待的时间(当waitTime为-1时等待结束)

  • GET接口:
  • 载荷:
参数名说明示例
random毫秒时间戳1695223890986
tourFlag固定参数dc
_json_att固定空参数
REPEAT_SUBMIT_TOKEN3.2请求确认预定信息获取a14fe59b302c8bf7b7901aee95811111

python示例:

def queryOrderWaitTime(info_dict):  # orderSequence_nourl = ""while True:logger.info('[queryOrderWaitTime]排队等待中...')params = {"random": f"{int(time.time()*1000)}","tourFlag": "dc","_json_att": "","REPEAT_SUBMIT_TOKEN": info_dict["REPEAT_SUBMIT_TOKEN"]}res = session.get(url, headers=headers, params=params)orderId = res.json()['data']['orderId']if orderId:return orderIdlogger.info('[queryOrderWaitTime]未获得orderId,正在进行新一次请求')

4.2 排队(已排到)

在等待时间结束后,通过排队等待的接口,获取data.orderId,用于后续的操作

5 出票

等待结束就可以出票了

  • POST接口:
  • 表单:
参数名说明示例
orderSequence_no4.2排队获取EC26727456
_json_att固定空参数
REPEAT_SUBMIT_TOKEN3.2请求确认预定信息获取a14fe59b302c8bf7b7901aee95811111

python示例:

def resultOrderForDcQueue(orderId, info_dict):url = ""data = {"orderSequence_no": orderId,"_json_att": "","REPEAT_SUBMIT_TOKEN": info_dict["REPEAT_SUBMIT_TOKEN"]}response = session.post(url, headers=headers, data=data)if response.json()['data']['submitStatus']:logger.info('[resultOrderForDcQueue]已成功预定,您可以登录后台支付了')

更多推荐

12306购票流程分析(非抢票)

本文发布于:2023-12-07 07:51:51,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1670590.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:流程   非抢票

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!