ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

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-DDleftTicketDTO.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, paramsparams, timeout10) 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会话管理、反爬策略识别、异常处理、通知推送这些知识点在一个真实场景里串了一遍比刷十篇教程都记得牢。最后提醒几点。脚本仅限自用不要拿它做商业代抢这里面有明确的法律风险不必多言。也不要去研究绕过验证码或其他风控机制的手段个人自用脚本追求的是“合法合规地自动化”而不是“突破系统限制”。技术上保持克制反而能让这个工具长期稳定地为你服务。另外一个小经验脚本真正跑起来之前把所有参数都验证一遍特别是出发站和到达站的电报码、乘客信息格式、目标车次是否在售票期。我见过太多人抢票当天才发现站名电报码写错或者乘客姓名格式不对白白错过了最关键的几分钟。准备充分然后让脚本替你守住那个窗口剩下的交给运气和耐心。祝每一次出发都顺利。本文还有配套的精品资源点击获取
返回列表