ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Selenium 12306 抢票脚本实战:从登录到下单全流程拆解

2026/10/2 18:37:36 拓冰建站 浏览量
Selenium 12306 抢票脚本实战:从登录到下单全流程拆解 简介这是一份基于Selenium的12306自动抢票脚本完整项目资料面向计算机相关专业学生、教师及企业开发者尤其适合用作毕业设计、课程设计或Python自动化学习案例。项目已通过导师评审答辩成绩达95分代码经过实际运行测试功能可用。压缩包共18个文件约64KB包含7个Python脚本、5个XML配置、3个TXT文本、1个Markdown说明、1个YAML配置及1个IML工程文件覆盖主程序入口、车站信息获取、配置读取、邮件通知、购票逻辑等模块结构清晰便于按功能查阅。目前已有129人学习下载。读者可获得一套可直接运行的抢票脚本源码、详细文档与配置示例理解Selenium在真实场景中的元素定位、页面交互与流程控制思路并能在现有代码基础上修改扩展用于其他自动化任务或作为项目初期立项演示。1. 从一份能跑通的 12306 抢票脚本说起它到底解决了什么每年春运前后后台总有人问我有没有一份能直接跑、结构清晰、还能顺手改的 12306 抢票脚本说实话市面上大部分所谓抢票神器要么是套壳网页要么代码乱到没法读。这份基于 Selenium 的 12306 自动抢票脚本是我近期拆过结构比较完整的一份——它不是玩具 demo而是把登录、查票、下单、通知整条链路都串起来了还配了详细文档和配置文件。它解决的核心问题很具体把手动刷票这件重复劳动交给浏览器自动化。你只需要在config.yaml里填好出发站、到达站、日期、车次偏好和乘客信息脚本就会用 Selenium 驱动真实浏览器完成登录、查询、余票判断和提交订单。适合谁计算机相关专业的毕设/课设学生、想学 Selenium 实战的测试或自动化从业者以及需要一份可二次开发抢票原型的开发者。不适合指望它百分百抢到票的人——它是一套可复现的自动化框架不是玄学外挂。2. Selenium 驱动 12306 的技术选型为什么不用 requests 硬刚接口2.1 选 Selenium 而不是纯接口请求的理由12306 的查询和下单接口背后有一整套风控动态参数、加密签名、行为校验。用 requests 直接怼接口你得逆向它的加密逻辑一旦官方调整参数脚本立刻报废维护成本极高。Selenium 走的是模拟真人操作浏览器的路线页面怎么变脚本就跟着页面元素走抗变更能力更强。常见做法是用 Selenium 打开真实 Chrome让浏览器自己处理 JS 渲染和加密脚本只负责找元素、填表单、点按钮。代价是速度比纯接口慢但换来的是可读性和可维护性。这份脚本正是这个思路——buyTicket/ticket.py里封装了查票和下单逻辑common/get_station.py负责把车站名转成 12306 需要的三字码。2.2 目录结构与模块职责先把资源包拆开看理解每个文件干什么改起来才不慌路径职责main.py程序入口串联登录、查询、下单主流程config.yaml用户配置车站、日期、车次、乘客、通知方式common/get_station.py读取station.txt车站名与三字码互转common/get_config.py解析config.yaml做参数校验buyTicket/ticket.py核心抢票逻辑查询余票、筛选车次、提交订单inform/send_email.py抢到票后邮件通知requirements.txt依赖清单这个分层很清晰配置、工具、业务、通知各管一摊。你要加微信通知或改抢票策略基本只动inform/和buyTicket/不用碰入口。2.3 环境搭建与依赖安装第一步永远是环境。Selenium 对浏览器和驱动版本很敏感版本对不上就是各种报错。# 建议用虚拟环境避免污染全局 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate # 安装依赖 pip install -r requirements.txtrequirements.txt里通常包含selenium、pyyaml、requests等。安装完还要确认 Chrome 浏览器版本并下载匹配的 chromedriver。现在 Selenium 4.x 大多能自动管理驱动但如果你用的是旧版本就得手动把 chromedriver 放到 PATH 里。提示chromedriver 大版本必须和本机 Chrome 主版本号一致差一个大版本就会报SessionNotCreatedException。2.4 配置文件怎么写config.yaml是整个脚本的遥控器参数填错后面全白搭。典型结构如下# 出发与到达车站必须和 station.txt 里的名称一致 from_station: 北京 to_station: 上海 # 出发日期格式固定 train_date: 2025-01-20 # 想抢的车次留空表示所有车次都查 train_numbers: - G101 - G103 # 乘客姓名需与 12306 账号内乘车人一致 passengers: - 张三 # 席别优先级 seat_types: - 二等座 - 一等座 # 邮件通知配置 email: enable: true smtp_server: smtp.qq.com sender: yourqq.com password: 授权码 receiver: yourqq.com参数说明from_station和to_station用中文站名即可脚本内部会通过get_station.py转码train_numbers留空时脚本会遍历当天所有车次命中率更高但查询更慢seat_types按顺序匹配第一个没票就试第二个。password填的是邮箱 SMTP 授权码不是登录密码这点新手最容易翻车。3. 抢票主流程拆解登录、查票、下单三步怎么落地3.1 登录环节扫码还是账号密码12306 现在主推扫码登录账号密码登录经常触发滑块验证。脚本里一般两种都留了口子。扫码登录的好处是不用处理验证码坏处是需要人工扫一次账号密码登录全自动但滑块一出现就得手动介入。from selenium import webdriver from selenium.webdriver.common.by import By import time # 初始化浏览器建议用 Chrome options webdriver.ChromeOptions() # 去掉正受到自动测试软件控制的提示条 options.add_experimental_option(excludeSwitches, [enable-automation]) driver webdriver.Chrome(optionsoptions) # 打开 12306 登录页 driver.get(https://kyfw.12306.cn/otn/resources/login.html) time.sleep(3) # 切换到扫码登录标签页面默认可能是账号登录 qr_tab driver.find_element(By.XPATH, //*[idtoolbar_Div]/div[2]/ul/li[2]/a) qr_tab.click() time.sleep(2) # 此时人工用手机扫码脚本轮询等待登录完成 while True: if index in driver.current_url: print(登录成功) break time.sleep(2)逻辑说明先禁用自动化提示避免被页面识别打开登录页后切到扫码 tab然后轮询 URL 变化判断是否登录成功。参数上time.sleep的秒数是经验值网络慢就调大。这里没有硬编码账号密码是为了避开滑块这个黑匣子——扫码虽然要人工但稳定性高得多。3.2 查票逻辑余票判断与车次筛选登录后进入车票查询页核心是构造查询 URL 或填表单然后解析结果表格。脚本里ticket.py一般会循环查询直到命中目标车次有票。def query_ticket(driver, from_station, to_station, train_date): # 直接拼接查询页 URL带上参数 url ( https://kyfw.12306.cn/otn/leftTicket/init f?linktypeiddcfs{from_station}ts{to_station} fdate{train_date}flagN,N,Y ) driver.get(url) time.sleep(3) # 抓取结果表格的每一行 rows driver.find_elements(By.XPATH, //*[idqueryLeftTable]/tr) result [] for row in rows: try: # 车次号 train_no row.find_element(By.CLASS_NAME, number).text # 各席别余票列顺序固定 seats row.find_elements(By.CLASS_NAME, cdz) result.append({train_no: train_no, seats: [s.text for s in seats]}) except Exception: # 有些行是隐藏的占位行跳过 continue return result逻辑说明fs和ts是出发/到达站的三字码date是日期。解析时用try/except跳过隐藏行这是血泪经验——12306 的表格里混着模板行不处理会直接抛异常。余票文本可能是有、数字或----表示无票判断时要单独处理。3.3 下单提交从点击到确认命中车次后点击预订按钮进入确认页勾选乘客、席别再点提交。这一步最容易出问题的是乘客勾选和席别选择元素定位要准。def submit_order(driver, passenger_name, seat_type): # 点击预订按钮 book_btn driver.find_element(By.XPATH, //a[contains(text(),预订)]) book_btn.click() time.sleep(2) # 勾选乘客通过姓名匹配 passenger_labels driver.find_elements(By.XPATH, //*[idnormal_passenger_id]/li/label) for label in passenger_labels: if passenger_name in label.text: label.click() break # 选择席别 seat_select driver.find_element(By.ID, seatType_1) seat_select.find_element(By.XPATH, f//option[contains(text(),{seat_type})]).click() # 提交订单 submit_btn driver.find_element(By.ID, submitOrder_id) submit_btn.click() time.sleep(2) # 确认弹窗 confirm_btn driver.find_element(By.ID, qr_submit_id) confirm_btn.click()逻辑说明乘客勾选用姓名匹配避免索引错位席别通过option文本匹配最后两步是提交和确认弹窗。参数上seatType_1是席别下拉框的固定 ID不同页面版本可能变跑之前先用开发者工具确认一遍。4. 避坑与常见问题排查这些坑我替你踩过了4.1 元素定位失效报 NoSuchElementException现象脚本跑一半突然报找不到元素昨天还好好的。 原因12306 前端改版元素 ID 或 class 变了或者页面还没加载完脚本就去找了。 解决把time.sleep换成显式等待WebDriverWait并定期用开发者工具核对关键元素定位。别用固定 sleep 硬扛网络一慢就崩。4.2 登录后频繁掉线或跳回登录页现象刚登录成功查票时又被踢回登录页。 原因12306 检测到自动化特征或 cookie 未正确保持。 解决禁用自动化提示excludeSwitches避免高频刷新查询间隔加随机延时。别把循环间隔设成 0.5 秒那是主动送人头。4.3 余票显示有但下单提示无票现象查询明明显示有票点进去提交却失败。 原因余票是缓存数据实际库存已被抢空或席别与乘客类型不匹配。 解决下单失败后不要死循环重试同一车次回到查询步骤重新拉取同时确认乘客类型成人/学生与席别兼容。4.4 邮件通知发不出去现象抢到票了但没收到邮件。 原因SMTP 授权码填错或邮箱未开启 SMTP 服务。 解决登录邮箱后台开启 SMTP用授权码而非登录密码QQ 邮箱还要确认smtp.qq.com的 465 端口可用。4.5 chromedriver 版本不匹配现象启动浏览器直接报SessionNotCreatedException。 原因chromedriver 版本和 Chrome 主版本号不一致。 解决查看 Chrome 版本下载对应大版本的驱动Selenium 4.6 可用 Selenium Manager 自动处理升级依赖即可省心。5. 二次开发与验证把脚本改成你自己的抢票工具5.1 加一个多车次轮询策略原脚本按配置顺序查命中即停。实际抢票时多车次并行监控命中率更高。可以在ticket.py里加一层循环把train_numbers列表逐个查谁先有票就抢谁。def poll_tickets(driver, config): # 按优先级遍历车次命中即返回 for train_no in config[train_numbers]: tickets query_ticket( driver, config[from_station], config[to_station], config[train_date], ) for t in tickets: if t[train_no] train_no and has_seat(t[seats]): return t # 每个车次之间加随机延时降低被风控概率 time.sleep(random.uniform(1, 3)) return None逻辑说明外层遍历目标车次内层判断余票命中就返回。random.uniform(1, 3)是给查询加抖动避免固定频率被识别。参数上train_numbers顺序就是优先级把最想抢的放前面。5.2 验证脚本是否真的在工作改完代码别急着上真实环境先做三步验证第一用非高峰时段的冷门车次跑一遍完整流程确认登录、查询、下单链路通第二把submit_order里的最终提交注释掉只验证到确认页避免误下单第三打开日志或 print确认每一步的元素都定位到了。我一般会在关键节点加print或写日志文件跑完一眼就能看出卡在哪。5.3 一个具体技巧用显式等待替代 sleep固定sleep是脚本最大的不稳定源。把关键等待换成WebDriverWait脚本健壮性会明显提升from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待预订按钮出现最多 10 秒 book_btn WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.XPATH, //a[contains(text(),预订)])) ) book_btn.click()逻辑说明WebDriverWait会轮询直到条件满足或超时比死等固定秒数更聪明。参数10是最大等待秒数网络差可以调到 15。这个改动看着小但能消掉一大半元素找不到的报错。从那以后我每次改完这类自动化脚本都强制先用冷门车次跑一遍全链路再上真实目标——毕竟抢票这事翻车一次可能就错过一整天的放票窗口。希望这份拆解能帮到你把这份资源真正用起来而不是下载完就躺在硬盘里。本文还有配套的精品资源点击获取