当前位置: 首页 > news >正文

Python + requests:从零实现12306自动抢票脚本

简介:面向12306官网购票的自动抢票脚本,专门解决春运、节假日等高峰时段车票秒罄的痛点,通过模拟人工登录、余票查询和提交订单等操作,在放票第一时间完成购票请求,适合有一定Python基础、希望自动化抢票的用户。资源共134个文件,压缩包约55MB,主体为61个py源码与48个pyc编译文件,2个h5文件为验证码识别模型,另有Dockerfile、依赖清单、代理列表、CDN过滤列表及日志等配置,覆盖从环境部署到运行维护的主要环节。已有2009人学习下载,除核心抢票逻辑外,还提供CDN接入、代理配置、模型文件与容器封装,可直接使用也可二次开发。资源内附界面截图与运行日志,便于对照验证脚本状态,整体结构清晰,适合学习自动化购票流程并应用于高峰期抢票场景。 每年春运抢票那几天,朋友圈里哀嚎一片。有人守着12306从早刷到晚,眼瞅着“有票”变“无票”,有人却在放票后几秒内就提交了订单。差别不在手速,而是大部分人还在手动刷新页面,少数人已经用脚本把“查票-选车次-提交订单”这个链路自动化了。

这篇文章想聊的就是自用级别的12306自动抢票脚本。它不是黄牛工具,不碰支付、不碰身份核验,也不保证100%出票,它只是把你在网页上反复刷新的动作,交给程序以更高频率、更稳的节奏去执行。最适合两类人看:一是想解决自己抢票困扰、愿意折腾点代码的技术型旅客;二是对Web自动化、爬虫接口调试感兴趣,想拿真实场景练手的开发者。

1. 动手之前,先把“抢票”这件事拆明白

1.1 12306网页购票的完整业务链路

先别急着写代码,你得搞清楚12306网页购票到底经历了哪些步骤。很多人脚本写不好,是因为只盯着“查票”一个环节,忽略了整条链路。

打开12306官网从登录到支付成功,实际上是这么一串流程:

  • 登录账号,建立会话
  • 进入车票查询页,选择出发地、目的地、日期
  • 查询余票,拿到车次、席别、余票数量列表
  • 点击某趟车的“预订”,进入订单确认页
  • 选择乘客、席别,提交订单
  • 系统处理订单,进入排队或直接出票
  • 出票后跳转支付,完成付款

人工操作的瓶颈在哪里?大多数人以为瓶颈在“手速慢”,其实真正慢的是决策链。你要先看到页面上的余票信息,判断哪趟车合适,再点预订,再选人,再提交。这一串动作下来,快则十几秒,慢则半分钟。在热门线路放票后的头几分钟里,十几秒的差距就是有票和无票的差距。

自动抢票脚本做的工作很纯粹:把“查询余票”和“提交订单”这两个耗时最多的动作自动化。查询交给程序高频轮询,比人眼盯着快;提交订单时把车次、乘客、席别参数直接拼好,比手动点选快。脚本省掉的不是实名制、不是支付,而是你的反应时间和操作时间。

1.2 脚本的能力边界:期望管理比技术更重要

写这个脚本之前,必须把预期摆正。我见过不少第一次接触抢票脚本的人,以为装上就能躺着收票,结果抢不到就骂脚本垃圾。问题往往不是脚本差,而是它的能力边界被误解了。

一个自用脚本能做到的,是这些事:

  • 按设定频率自动查询指定车次的余票,一旦检测到目标席别有余票,立即发起提交
  • 多车次、多席别批量监测,人为手动刷网页根本做不到同时盯几趟车
  • 定时启动,放票前提前登录好、热身好,放票瞬间进入高频查询状态
  • 完整记录日志,方便复盘抢票过程的每一个环节

它做不到的事同样明确:

  • 不能绕过12306的登录验证、实名制校验、支付环节
  • 不能保证提交订单后100%出票,因为提交后还有系统排队和余票竞争
  • 不能突破12306的风控限制,高频请求照样会被限流甚至封禁

把这个边界理清楚,你就能明白,脚本的价值是“在有余票的第一时间替你赶上”这个动作。它优化的是概率,不是创造奇迹。

2. 核心链路拆解:查票接口与会话保持是关键

2.1 余票查询接口:一切自动化的起点

12306的余票查询,网页端走的是一个JSON接口,返回的数据结构相对规整。接口地址大概是这个模式:

GET https://kyfw.12306.cn/otn/leftTicket/queryG

关键参数有四个:

  • leftTicketDTO.train_date:乘车日期,格式YYYY-MM-DD
  • leftTicketDTO.from_station:出发站电报码
  • leftTicketDTO.to_station:到达站电报码
  • purpose_codes:购票类型,成人票固定为ADULT

这里必须先说一个容易被忽略的细节:请求参数里的车站名不是中文,而是电报码。比如上海是SHH、北京是BJP、广州是GZQ。你可以在12306的公开静态资源里找到车站电报码的映射文件,也可以自己抓包看浏览器发出的请求,把中文站名转换成电报码再传给接口。第一次写脚本的人大多卡在这个地方,用中文站名直接请求,返回永远是空数据。

2.2 返回数据的字段结构:要会读余票状态

接口返回的JSON结构大致是外层data,里面有result数组,每个元素是一条车次信息,字段用竖线分隔。这里有个反直觉的地方:余票状态不是统一的数字字段,而是在不同位置上的字符串。有的位置是数字,表示余票张数;有的位置是“有”,表示无座票充足;有的位置是“无”,表示已售罄。

具体每个字段在实际抓包中会随版本变化,这也是抢票脚本维护成本最高的地方。你去年写好的字段索引,今年接口一改可能就全废了。我的习惯是拿到返回结果后先完整打印一遍,对照网页上同一趟车显示的余票情况,确认这次接口版本里哪几个字段对应的是硬座、二等座、一等座、无座,再做解析逻辑。

所以建议你把解析函数单独封装成一个模块,接口字段变动时只改一处,不需要动主逻辑。

2.3 会话保持与登录态:绕不开的验证码环节

12306的登录有验证码,而且经常变换类型,最难搞的是点选式验证码。自动识别不是做不到,但成本高、容易被风控盯上,个人脚本完全没必要硬刚。

我的做法是:登录这一步保留人工操作,用浏览器手动扫码或者手动过验证码,登录成功后把Cookie保存到本地文件,脚本每次启动时加载这个Cookie去访问查询接口。

实际操作路径大概是这样的:

  • 用浏览器打开12306登录页,手动完成登录
  • 在开发者工具里把当前会话的Cookie复制出来(重点是tk、当前登录session那一段)
  • 存成文件,脚本启动时读取

需要注意Cookie有效期。12306的Cookie一般能撑一段时间,但过期后查询接口会返回未登录状态的错误码。所以脚本里要加一个会话有效性检查,发现Cookie失效时立即提醒你重新登录,不要傻乎乎地一直空转。

3. 落地实现:Python + requests 的脚本骨架

3.1 技术选型:为什么不用Selenium或Playwright

个人抢票脚本,圈子里最常见的技术方案有两类:一类是Playwright、Selenium这类浏览器自动化框架,另一类是Python + requests直接请求接口。

我推荐后者,理由有两条。第一是速度,requests直接请求接口的效率远高于驱动一个完整浏览器,在放票后的关键几秒里,差几百毫秒可能就是有票和无票的区别。第二是资源占用,一个脚本跑几十个车次的轮询,requests方案占用内存极小,挂在服务器或旧电脑上很轻松。

那浏览器自动化完全没用吗?也不是。它适合干一件事:帮你自动完成登录并导出Cookie。用Playwright打开登录页,你手动过验证码,脚本捕获登录成功后的Cookie,再交给requests主程序使用。这样两边优势都拿到了。

3.2 查询到提交的主循环设计

完整代码贴出来会很长,这里只讲核心逻辑的骨架。主循环其实就是一个词:轮询。

import time import json import requests from datetime import datetime session = requests.Session() session.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" }) session.cookies.update(load_cookie_from_file()) def query_tickets(train_date, from_station, to_station): url = "https://kyfw.12306.cn/otn/leftTicket/queryG" params = { "leftTicketDTO.train_date": train_date, "leftTicketDTO.from_station": from_station, "leftTicketDTO.to_station": to_station, "purpose_codes": "ADULT" } resp = session.get(url, params=params, timeout=10) data = resp.json() if data.get("httpstatus") != 200: log("查询接口返回异常,检查登录态") return [] return parse_ticket_info(data.get("data", {}).get("result", []))

核心循环里要做三件事:查票、判断目标车次与席别是否有票、有票则触发提交逻辑。提交逻辑需要预先准备好乘客信息和车次token,这一步在真实项目中需要抓取预订页面的参数,代码量不小,建议分步实现,先把查询和通知跑通,再逐步加提交。

3.3 轮询频率、随机延时与日志:细节决定成败

轮询频率是个需要反复调优的参数。太频繁,比如1秒一次,短时间就会被12306的限流机制盯上,返回“系统繁忙”甚至封IP;太慢,比如10秒一次,又容易在放票瞬间错过下单窗口。

我的经验值是基础间隔3到5秒一次,每次请求之间加一个0.5到1.5秒的随机延时,让请求节奏看起来更像真人操作。如果你想同时监控多趟车,不要用多线程高频请求去并发,更稳妥的做法是单线程依次请求,在循环里遍历所有目标车次。并发请求看起来快,实际上极大增加被封风险,得不偿失。

日志也非常重要。每个请求的时间点、返回的余票状态、是否触发提交、提交结果,全部记录到本地文件。抢不到票的时候,复盘日志能帮你定位问题出在查询频率、参数构造还是提交逻辑,而不是瞎猜。

3.4 通知机制:别让脚本沉默地跑

脚本查到了票,如果只是在控制台里打一行字,而你正好没盯着,等于白抢。加一个通知机制是自用脚本体验提升最大的一步。

最简单的方案是用Server酱、PushPlus这类微信推送服务,注册一个token,脚本检测到目标余票时给微信推一条消息,内容包括车次、席别、余票数量。也可以用Telegram Bot、企业微信机器人,或者干脆发邮件。我用下来觉得微信推送最方便,毕竟手机不离手,看到推送再决定是否立刻去网页下单。

这里有一个容易忽略的点:脚本检测到有票的时刻,不代表你一定能提交成功。所以通知文案里要把车次、日期、席别、余票状态写清楚,你在手机上扫一眼就能判断值不值得跑回电脑前操作。

4. 实测中踩过的坑:接口变动、限流与候补

4.1 风控与限流:抢票不是频率竞赛

很多人第一次跑脚本,看到查询正常,就习惯性把间隔调到1秒甚至更快,觉得“查得越快,抢得越早”。实测下来这个想法会害了你。

12306对接口请求频率有明显的风控策略。连续高频请求几分钟后,接口可能开始返回错误码,或者直接让你访问超时,严重的整个IP被临时封禁。一旦被封,脚本就彻底失去作用,连正常查询都做不了。

我的实测经验是:把间隔控制在4到5秒,同时监控返回结果,如果连续几次出现异常返回,立刻拉长间隔到10秒以上,让风控降温。抢票是概率游戏,你的脚本只要在放票后的前几分钟保持稳定可用,就比大多数手动刷新的人快了。死了的脚本没有任何意义。

4.2 接口字段变动:维护成本最高的坑

12306的接口结构不是一成不变的。前年返回的字段是一个结构,今年可能就换了。最典型的就是余票信息里的字段顺序和状态值格式。

解决这个问题没有捷径,只有一条:上线前实际抓包验证。每次要抢票的前几天,先拿目标车次的实际数据跑一下解析函数,对照网页显示的余票情况确认字段是否对应。我习惯在脚本里加一个DEBUG模式,把原始返回数据打印到日志,方便快速排查字段问题。真正到抢票那天,不要改任何代码,平时怎么跑的,当天就怎么跑。

4.3 没抢到的时候,脚本还能做什么

抢票不只是放票那一刻的战斗。从放票到发车前,余票是会动态释放的。有人退票、改签,都会让部分车次重新出现余票。这个场景下,脚本的持续监控能力比放票瞬间的高频查询更有价值。

实际操作中,我会在放票那天跑一轮高频查询,没成功就换成中等频率的持续监控,一直跑到发车前几个小时。捡漏的成功率虽然不如放票瞬间,但胜在稳定。另外,铁路部门会在特定时间段释放部分候补票额,这个信息在12306页面有提示,可以结合脚本监控的目标车次做调整。

还有一个思路是同时关注“全程票”和“区间票”的逻辑差异。有些车次在发售区间票时显示无票,但全程票可能还有余票,或者反过来。如果你不介意多坐几站,监控时把查询条件放宽到全程票,命中概率会高不少。

5. 写在最后:自用脚本的技术收获与边界

写这个脚本,技术上的收获比抢到票本身更有价值。接口调试、Cookie会话管理、反爬策略识别、异常处理、通知推送,这些知识点在一个真实场景里串了一遍,比刷十篇教程都记得牢。

最后提醒几点。脚本仅限自用,不要拿它做商业代抢,这里面有明确的法律风险,不必多言。也不要去研究绕过验证码或其他风控机制的手段,个人自用脚本追求的是“合法合规地自动化”,而不是“突破系统限制”。技术上保持克制,反而能让这个工具长期稳定地为你服务。

另外一个小经验:脚本真正跑起来之前,把所有参数都验证一遍,特别是出发站和到达站的电报码、乘客信息格式、目标车次是否在售票期。我见过太多人抢票当天才发现站名电报码写错,或者乘客姓名格式不对,白白错过了最关键的几分钟。准备充分,然后让脚本替你守住那个窗口,剩下的交给运气和耐心。

祝每一次出发都顺利。

本文还有配套的精品资源,点击获取

http://www.cnnetsun.cn/news/4346149.html

相关文章:

  • 2026年电脑电源选购指南:从核心参数到16款型号推荐
  • 健身电商小程序
  • 飞轮式卫星姿态控制MATLAB仿真:M文件与Simulink双实现详解
  • SAP S/4HANA ABAP开发实战教程:从核心语法到业务模块的完整学习路线
  • 从零开发TCP/UDP调试工具:核心架构、代码实现与避坑指南
  • 类人机器人灵巧手开发指南:从自由度、力控到仿真与数据闭环
  • CentOS 7 OpenSSH 升级实战:源码编译全流程与踩坑指南
  • DMA固件开发指南:从原理到串口实作
  • Overlay叠加层实战:从《我的世界》终末之诗到直播滚动字幕
  • YOLOv5斗地主牌面识别与安卓端NCNN部署实战
  • 安卓PS5模拟器SharpEmu深度解析:原理、性能与实测
  • 从原理到实战:构建与精调动态压枪系统的完整指南
  • 智慧物流调度架构设计:基于GPIO适配异构电梯的机器人梯控实现
  • Linux 之大文件拆分、合并与校验
  • ur_rtde:UR机器人RTDE实时控制与视觉引导实战解析
  • 从零搭建工业级多模态炼钢大模型:Qwen2.5-VL + LoRA 实战全流程
  • 基于SpringBoot的环保知识普及平台的设计与实现(源码+讲解视频+LW)
  • 蔚来数据分析岗笔试复盘:SQL窗口函数与业务案例实战解析
  • Palantir Study 02|Palantir 产品全景:Gotham、Foundry 等名词归位
  • OpenClaw Mac源码安装指南:开源AI代理框架部署实战
  • 2024秋招小米算法岗笔试全解析:考点题型与备考策略
  • VINS漂移别乱调参,imu-utils标定IMU噪声全流程
  • 15 年前的老笔记本也能用大模型写代码?| 实测 MiniCPM5-1B vs Qwen3.5-0.8B JavaScript 编程能力对比
  • Codex CLI 与中转 API 接入实战:本地部署与模型配置全解析
  • 2026小程序卖货平台搭建哪家好?长期稳定运营的选择方法
  • 大模型应用开发:小白程序员必备,抢占未来先机!
  • Graph Engineering:用图控制Agent执行SOP的工程实践
  • 瑞萨RH850F1L CAN通信驱动开发:从官方示例到实际项目调试指南
  • 管道漏水检测数据集与源码实战:从声学特征到深度学习模型
  • 基于PROSAIL查找表的LAI预测Python脚本实现与验证