Selenium实现12306自动抢票:WebDriver参数、验证码与登录态复用指南 简介基于Selenium的12306自动抢票脚本完整资料包面向计算机相关专业学生、教师及企业开发者适合毕业设计、课程设计、项目初期立项演示也可作为Selenium自动化入门进阶的实战案例。压缩包共18个文件以7个Python脚本为核心涵盖配置读取、车站信息获取、余票实时查询、自动下单等关键流程并支持邮件通知与异常处理另有5个XML工程配置、YAML运行参数、3个TXT说明与依赖清单、1个Markdown设计文档结构清晰便于按模块查阅与二次开发。整套资料仅64KB轻量易部署已有129人学习下载。项目代码经导师指导并通过答辩评审评分95分运行稳定、直接可用读者可基于现有逻辑扩展登录验证、多线程抢票等高级功能。文档详细说明环境搭建、使用步骤及常见排错思路能帮助掌握Selenium页面自动化、元素定位及反爬应对策略适合课程设计、作业实战或企业项目预研。1. 12306 自动抢票脚本为什么选 Selenium能解决什么适合谁每逢春运12306 的余票查询和下单页面都会被大量请求顶着跑手动刷新除了熬人命中率全凭手速和运气。用 Selenium 写一套自动抢票脚本核心价值不是“比谁点得快”而是把查询、下单、提交订单这套重复操作稳定地固定下来在放票窗口期替你盯住页面。这个方向适合两类人一类是只有基础 Python 经验、想把自己买票这件事自动化的小团队或个人另一类是已经在用 requests 刷余票但总被验证码和风控卡住的开发者。前者的坑在选择工具后者的坑在思路和运行稳定性。隐患也写在前面这套脚本只建议用于本人账号、个人购票场景不碰商业倒卖不绕过 12306 的乘车人核验和验证机制。抢票的核心不是如何更快而是如何在长时间运行中“不被打断、不误操作”后续章节都围绕这两件事展开。2. 拆解 12306 购票链路Selenium 不是性能最优而是活得最久2.1 一次购票有多少个“关卡”登录、查票、下单、提交、支付12306 的购票流程比一般电商复杂得多。从 Web 页面操作看用户要先过登录关再选出发地、目的地和日期查询列表加载出车次点“预订”后跳转到订单确认页确认乘车人、席别再提交订单提交后系统返回一个排队结果排队成功才弹出支付入口。这一趟下来至少五步每一步之间都有异步加载和状态校验。用 Selenium 自动化时这五步不是顺序执行的代码而是五组“等待 定位 操作”的组合。难的不是点击本身是每一次点击后页面是否真的到达下一状态。12306 前端是典型的单页应用按钮可能因为请求未完成而被禁用弹层可能因为余票紧张而突然出现。写脚本的人如果只按固定节奏 sleep跑十分钟就崩。这里的常见误区是去逆向 12306 的查询接口拿到 JSON 直接解析。接口确实存在但 12306 的请求头和表单里带着动态签名参数依赖页面下发的 key用纯 requests 模拟补全签名的维护成本极高官方前端一更新签名算法可能跟着变。而 Selenium 操作的是完整浏览器JS 生成的签名、指纹、请求头都由浏览器自己处理好脚本只需要关心用户层动作。2.2 为什么不用 requests 写抢票黑匣子签名和浏览器指纹很多从数据采集过来的开发者第一反应是用 requests 打接口速度快、资源省、可以开几十个线程轮询。但 12306 的接口不是公开 API查询和下单的请求体里夹着一套后端校验逻辑前端加密后的参数在黑匣子中生成。抓包能抓到完整的请求和响应但那只是结果生成过程在 JS 混淆代码里。只要有一个参数算不出来请求返回的状态码可能是 200业务逻辑上却告诉你失败。Selenium 换来了一个完整浏览器代价是慢一次查询从发起到结果渲染要一秒多提交订单因为页面弹层和排队可能要两三秒。但抢票场景真正的瓶颈从来不是单次请求速度而是长时间没人值守时脚本还能不能稳定轮询。Selenium 的稳定性来自它操作的是真实 DOM 流页面因后端策略弹出的提示框、验证码脚本都能感知和响应requests 方案遇到“当前访问人数过多”这类提示时只能靠猜。对比维度requests 接口Selenium 浏览器维护成本高前端加密变更即需逆向低跟随页面结构小改风控感知差无法感知页面提示好能读页面状态单次耗时快毫秒级慢秒级上手门槛中需会抓包和签名拼接低会定位元素即可这也决定了这套方案里 Selenium 是主实现requests 只适合做辅助的余票监控不下单。3. 跑通最小抢票脚本WebDriver 参数与查询→下单→提交的主流程3.1 初始化 WebDriver三个必须调对的启动参数Selenium 4 之后启动 Chrome 的推荐做法是使用与浏览器版本匹配的 WebDriver 二进制可以手动下载对应版本也可以用 webdriver_manager 自动匹配。我的建议是手动固定一个 Chrome 版本避免自动更新后驱动失配。初始化时有三个参数直接影响脚本稳定性。第一个是不要开 headless 模式。抢票脚本涉及验证码、弹层和支付跳转无头模式虽然能跑查询但遇到图形验证码时截图画质差人工介入也不方便。第二个是排除自动化检测标志Selenium 默认会在浏览器上暴露 webdriver 属性虽然 12306 不一定每一步都检测但减少暴露面总没坏处。第三个是禁用 GPU 和扩展在 Windows 和 Linux 服务器上这两项能省掉大量不必要的渲染报错。from selenium import webdriver from selenium.webdriver.chrome.options import Options opts Options() # 关闭“Chrome 正受到自动测试软件控制”的提示 opts.add_experimental_option(excludeSwitches, [enable-automation]) # 服务器环境没有 GPU禁用掉可以降低崩溃概率 opts.add_argument(--disable-gpu) # 禁用扩展减少无关请求对页面加载的干扰 opts.add_argument(--disable-extensions) driver webdriver.Chrome(optionsopts) driver.get(https://www.12306.cn/index/)参数说明excludeSwitches 是把自动化标记从 Chrome 的命令行里去掉disable-gpu 在处理本机没有独立显卡的虚拟机上很常见少了它有时会白屏disable-extensions 避免浏览器插件抢占资源。没必要去改 Chrome 的二进制文件那是高风险手段普通购票场景不配拥有。3.2 查询车次与提交订单最小可运行流程跑通流程比优化速度重要。最小脚本只做一件事打开首页后进入余票查询输入出发地、目的地、日期点查询等待车次列表出现。下面给出主流程骨架选择器以当天页面为准12306 改版后你需要重新抓取一次元素。import time from datetime import datetime, timedelta from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 预售期按 15 天算今天加 15 天就是可订的最远车票 travel_date (datetime.now() timedelta(days15)).strftime(%Y-%m-%d) wait WebDriverWait(driver, 20) # 进入购票入口按实际页面按钮文字定位 driver.find_element(By.LINK_TEXT, 购票).click() # 输入出发地输入后等待候选列表完成选中 from_input wait.until(EC.presence_of_element_located((By.ID, fromStation))) from_input.send_keys(北京) wait.until(EC.presence_of_element_located((By.CLASS_NAME, station))).click() # 目的地同理 to_input driver.find_element(By.ID, toStation) to_input.send_keys(上海) wait.until(EC.presence_of_element_located((By.CLASS_NAME, station))).click() # 填日期并查询 date_input driver.find_element(By.ID, train_date) date_input.clear() date_input.send_keys(travel_date) driver.find_element(By.ID, query_ticket).click() # 等待车次列表中的“预订”按钮出现 book_btn wait.until(EC.element_to_be_clickable((By.LINK_TEXT, 预订))) book_btn.click() # 进入订单确认页等待乘车人列表加载 wait.until(EC.presence_of_element_located((By.CLASS_NAME, ticket-list)))逻辑说明这段代码的关键不是每个 selector而是 WebDriverWait。presence_of_element_located 只保证元素存在不保证可点击真正下单时应该用 element_to_be_clickable否则脚本容易在按钮还未被激活时提前点击被 12306 判定为无效操作然后弹回。参数说明WebDriverWait 默认按 0.5 秒轮询检查超时抛异常。抢票场景建议把等待上限从 10 秒放宽到 20 秒尤其在出票高峰页面的网络请求可能长时间 pending等待过短会把一次正常延迟误判成失败。这个最小流程跑通后你手里就已经有一台可以自动查看余票并走到订单确认页的浏览器了。3.3 验证码处理先识别再决定要不要上自定义打码12306 的验证码在登录和提交订单两个环节都可能出现。登录时最常见的是滑块和点选图片提交订单时一般不强制验证码但在高频操作下会触发。很多人一上来就想接一个图像识别模型反而把项目拖垮。常见做法是分级处理登录验证码在无人值守场景下用本机已登录的浏览器会话绕开第 4 章细说提交订单遇到验证码时用短信或人工确认兜底。如果一定要自动识别12306 的点选验证码本质是目标检测问题需要按文字提示找到图片中的对应物体但每次加载的图片集是动态的训练样本收集成本远超脚本本身的开发成本。有人会为验证码单独写一个 Java 服务用 Java 的图形库做边缘检测和模板匹配。这个思路在纯图形验证码时代有效但 12306 现在更依赖语义理解模板匹配效果不稳定。我的建议是验证码模块做到能上报、能切人工就好不要把它当成抢票脚本的核心卖点。脚本真正的核心是把查询到提交的流程在秒级内稳定完成验证码只是偶尔出现的杂音。4. 命中率怎么提登录态复用、轮询节奏与无人值守日志4.1 登录态复用让脚本使用你本机已登录的浏览器会话搭建初期最磨人的是登录环节每次刷新页面都要重新扫码脚本还没开始抢票先被验证码耗死。这里推荐一种稳定的做法启动 Chrome 时指定一个固定的 user-data 目录第一次手动打开浏览器并完成扫码登录之后所有自动化会话都复用这个目录的 Cookie 和登录态。opts.add_argument(--user-data-dir/path/to/12306-profile)加了这个参数后首次运行脚本会打开一个带常驻配置的浏览器窗口你手动登录一次以后每次启动都直接是登录状态。这样做的另一个好处是登录链接附带的滑块验证码也不会因为会话过期频繁出现。参数说明user-data-dir 要指向独立目录不要指向 Chrome 默认的配置目录否则多个浏览器实例会互相锁住。这个参数在 Windows 和 Linux 上都可用路径不要带中文避免解析问题。注意这种复用方式等于把登录态明文放在磁盘里建议该目录只给抢票脚本用机器做好访问控制不要把脚本部署在不信任的公共环境。4.2 刷票节奏轮询间隔、多车次优先和候补兜底抢票脚本的核心矛盾是查得太快容易被风控查得太慢又抢不到首轮放票。有价值的配置不是固定频率而是分时段的节奏。开售前 10 分钟到开售后 5 分钟是峰值窗口此时查询间隔建议在 2 到 5 秒之间余票有变化立即触发下单错过首轮后改为 15 到 30 秒一次慢轮询专门捞别人改签、退票释放的碎片票。多车次优先比单点车次命中率高得多。查询时按发车时间排序把目标车次列表维护成一个数组循环判断每个车次的余票状态一旦某个车次出现可预订的票立即优先提交不要做“先看完所有车次再决定”这种多余动作。候补是 12306 官方的保底机制脚本不要试图替代候补。正确思路是可候补的车次先让用户手动提交候补脚本继续查询其他车次脚本不要自动去点候补按钮候补订单的支付和取消规则比正常订单复杂自动化容易踩坑。4.3 日志与通知脚本跑一夜早上只看结果不看屏幕无人值守的脚本最怕的不是抢不到票而是抢到票了你不知道。日志至少要记录时间、动作、页面状态三件事。时间用来分析哪一轮查询命中了动作记录查询、点击预订、提交订单页面状态记录当前 URL 和是否有弹层排错时能还原现场。通知建议走最原始也最稳定的方式提交订单成功时把页面截图保存到一个固定目录同时发一封带截图的邮件。发邮件用 Python 标准库里的 smtplib 就行不需要引入额外的消息推送 SDK稳定性和 12306 没有相关性不增加风控面。无人值守还有一个很多人忽略的点Chrome 长时间跑会累积内存占用日志里要定期打印 driver.page_source 的长度发现异常就重启浏览器。重启逻辑用最土的办法设定一个计数器每 2000 次查询后主动关闭 driver 再重新初始化比任何内存优化都有效。5. 脚本上线前必看五条真实的抢票翻车与排查记录5.1 查询能出票提交订单时元素突然找不到页面重绘与等待错位现象脚本能查到余票点击“预订”后订单确认页一直点不上“提交订单”有时还会误点到其他元素最后日志里留下一串 TimeoutException。原因12306 的订单确认页是动态渲染的乘车人列表、席别价格都是异步加载。脚本在页面跳转后立刻找按钮此时 DOM 还没有重绘完成元素看起来不存在如果用了错误的显式等待条件等到的是“存在”而非“可点击”同样会提前操作。解决把提交按钮的等待条件从 presence_of_element_located 改成 element_to_be_clickable并加一层 try-except 把超时异常转成“乘车人信息未加载”的明确日志。我一般还会在点击前先判断当前页 URL 中是否包含订单相关路径确认跳转完成后再触发后续动作。5.2 验证码刷十次错八次识别模块和触发时机的问题现象脚本运行中验证码出现的频率比人手操作时高识别率却低到不可用最后被系统判定异常退出。原因验证码识别失败往往不是模型精度问题而是触发了错误类型的验证码。12306 在连续高频操作下会主动升级验证码难度从普通点选变成迷雾、重叠图脚本如果输错一次后立刻再次获取很容易进入升级链路。解决把验证码处理逻辑做成分级退避。第一次验证码失败后等待 5 秒再试连续失败两次转为暂停自动化并通知人工处理。不要在同一会话里连续挑战第四次以上验证码这是风控信号最明显的特征。5.3 脚本总是登录失效Cookie 复用方式不对现象脚本跑两三个小时后就提示重新登录但手动打开浏览器看登录态明明还在。原因登录态维护方式是直接把 Cookie 字符串硬编码在脚本里忽略了 Cookie 有过期时间更关键的是 12306 会把 Session 和浏览器指纹绑定换个 User-Agent 或 Session 都会失效。解决不要手动复制 Cookie使用 user-data-dir 复用完整浏览器配置。这样 Session、本地存储、指纹信息都保持原生状态过期时间也和真实登录一致。如果必须用 Cookie也不要长期复用应每次启动后从浏览器读取最新值写入本地文件作为冷备。5.4 提交订单后卡在排队请求顶掉了上一笔订单现象脚本提交订单后页面进入排队状态几十秒后提示失败再试一次还是失败偶尔还会收到“您有未完成的订单”提示。原因12306 的订单系统允许一个用户在排队队列里保留一个有效订单。脚本前面一次提交已经占住排队位置第二次提交的新请求会先取消旧订单再重新排队如果取消和重提之间延迟较长新的排队就排在了最末位。解决提交订单前先检查页面是否有“存在未完成订单请先取消”的提示如果有不要自动去点取消而是先把状态写进日志并通知人工。自动取消很容易把一张已经排到的票白白放掉。5.5 运行几小时后浏览器无响应页面累积与长跑稳定性现象脚本白天测试一切正常夜里跑四个小时后日志停在一个查询动作上driver 没有任何报错但页面白屏或者无限加载。原因长时间轮询下12306 的页面会不断堆积请求响应数据内存上涨某一次网络波动导致页面加载没有正常结束后续所有等待都挂死Selenium 的等待机制此时不会自动唤醒。解决给循环体加一个全局“看门狗”逻辑统计最近一次页面加载完成的耗时超过 60 秒就主动刷新页面每 500 到 1000 次查询强制重启 driver。这个重启动作配合第 4 章的 user-data-dir重启后登录态不丢损失只有几秒重启时间。6. 最后一关用一张自测清单把脚本从模拟推到真抢上线前别直接拿真票试先走一遍自测清单。以下几点是我跑了几个版本后总结出来的每一条都对应前面章节里的坑。自测项操作通过标准登录态维持脚本启动后检查是否已登录10 分钟内不出现登录弹层查询稳定性连续查询 50 次无超时且无重复点击下单触发用查询到的余票车次走完整下单能到达提交订单确认页验证码降级手动触发一次验证码验证码接人工后 2 分钟内恢复重启恢复手动杀掉浏览器进程再拉起重启后自动恢复查询这五关都过了才算真正到“抢”这一步。抢票当天只做两件事开售前半小时启动脚本观察日志里查询是否正常推进开售瞬间不要手动去刷新页面让脚本自己跑你只盯着通知渠道。我现在的习惯是开售后 5 分钟如果没有任何命中就主动降低查询频率转入捡漏模式而不是跟它较劲。这个方向值得做但请把它定位成“辅助工具”而不是“必中保证”把它当黑匣子去赌你会比春运还焦虑。希望帮到你。本文还有配套的精品资源点击获取