12306 的防护哲学:流程即城墙
12306 是"重 cookie、轻签名"的极致代表:查询和下单接口没有任何客户端签名,但 cookie 体系和请求编排层层嵌套,缺一个环节就返回"网络可能存在问题,请您稍后重试"——这句提示 90% 的情况不是网络问题,是你的流程不对。
核心 cookie 地图
| cookie | 作用 | 种下时机 |
|---|---|---|
JSESSIONID | 会话标识 | 进站即种 |
BIGipServerotn | 负载均衡,决定落到哪台后端 | 进站即种 |
_jc_save_fromStation | 出发站(编码格式:北京,BJP) | 查询页行为 |
_jc_save_toStation | 到达站 | 查询页行为 |
_jc_save_fromDate / _jc_save_toDate | 乘车/查询日期 | 查询页行为 |
tk | 登录票据 | 登录成功 |
RAIL_EXPIRATION / RAIL_DEVICEID | 设备指纹 | 指纹 JS 生成 |
关键认知:leftTicket/query 接口会校验 _jc_save_* 系列 cookie 与查询参数的一致性。不先走查询页流程种 cookie 就直接 POST 查询,必挂。
正确的请求编排
import requests, re, json, time
session = requests.Session()
session.headers['User-Agent'] = 'Mozilla/5.0 ...'
# 1. 进站初始化
session.get('https://kyfw.12306.cn/otn/leftTicket/init')
# 2. 模拟查询页行为,设置上下文 cookie(注意格式:站名,电报码)
session.cookies.set('_jc_save_fromStation', '北京,BJP', domain='kyfw.12306.cn')
session.cookies.set('_jc_save_toStation', '上海,SHH', domain='kyfw.12306.cn')
session.cookies.set('_jc_save_fromDate', '2026-09-01', domain='kyfw.12306.cn')
session.cookies.set('_jc_save_toDate', '2026-09-01', domain='kyfw.12306.cn')
# 3. 查询余票(queryG/queryA/query 端点随版本轮换,都试试)
resp = session.get('https://kyfw.12306.cn/otn/leftTicket/queryG', params={
'leftTicketDTO.train_date': '2026-09-01',
'leftTicketDTO.from_station': 'BJP',
'leftTicketDTO.to_station': 'SHH',
'purpose_codes': 'ADULT',
})下单链路:token 藏在页面里
下单是多步流程,每步的凭证从上一步的响应里取:
submitOrderRequest:用查询结果里的secretStr(urldecode 后按|拆分解出车次信息)initDc页面:响应 HTML 里藏着两个关键变量——var globalRepeatSubmitToken = 'xxx'(防重复提交令牌)和ticketInfoForPassengerForm(座位/票价数据),正则提取getPassengerDTOs:拿乘客列表,带REPEAT_SUBMIT_TOKENcheckOrderInfo:提交选中的乘客confirmSingleForQueue:最终确认,核心是passengerTicketStr和oldPassengerStr两个参数的拼装:
# passengerTicketStr: 座位类型,0,票种,姓名,证件类型,证件号,手机号,N
passenger_ticket_str = f"{seat_type},0,1,{name},1,{id_no},{phone},N"
# oldPassengerStr: 姓名,证件类型,证件号,1_
old_passenger_str = f"{name},1,{id_no},1_"- 轮询
queryOrderWaitTime拿排队结果
精髓:设备指纹与实战坑
RAIL_DEVICEID是指纹 JS 生成的。包含 canvas、ua、屏幕等信息,登录和高频下单时校验。协议模拟从真实浏览器采集后固定,和 ua 成套。- 登录滑块。账号密码登录有滑块验证,识别+轨迹模拟是标配;扫码登录(12306 App)可以绕过,但 App 端是另一套协议。
- "网络可能存在问题"= 流程错误。看到这个提示,按顺序排查:cookie 缺没缺 → 编排顺序对不对 → REPEAT_SUBMIT_TOKEN 是不是用的最新值(每次 initDc 都变)。
- 查询接口端点轮换。
query、queryA、queryG、queryZ历史上轮着用,当前有效端点以init页面里的CLeftTicketUrl变量为准——动态提取,别写死。 - 车站电报码表。
station_name.js这个静态文件里有全量站名-电报码映射,本地缓存一份定期更新。
总结
- 12306 无签名,防护全在 cookie 体系和请求编排
_jc_save_*上下文 cookie 与查询参数必须一致- 下单链路的 token 在 initDc 页面 HTML 里,每轮都变
- "网络可能存在问题"是流程错误的统一话术,按清单排查