ARTICLE DETAIL

资讯详情

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

Selenium 12306 抢票脚本实战:从环境搭建到下单提交的完整指南

Selenium 12306 抢票脚本实战:从环境搭建到下单提交的完整指南 简介这是一份基于Selenium的12306自动抢票脚本完整项目资料面向计算机相关专业的在校学生、教师及企业员工尤其适合用作毕业设计、课程设计、作业或项目初期立项演示也适合具备一定Python基础的小白进阶学习。资源包共18个文件以7个py脚本为核心配合5个xml配置、3个txt说明、1个md文档、1个yaml配置文件及1个iml工程文件压缩包约64KB结构清晰、便于按模块查阅。项目围绕抢票主流程展开涵盖车站信息获取、配置读取、邮件通知与购票逻辑等模块代码经过实际运行测试功能可用并附有详细文档辅助理解。目前已有129人学习下载可作为Selenium自动化实战的参考案例读者既能直接运行体验也能在此基础上修改扩展实现其他自动化功能适合作为毕设、课设或自学练手项目使用。1. 12306 抢票脚本到底在抢什么从 Selenium 驱动浏览器到车票查询接口的真相每年春运前一周后台总会收到类似私信“用 Selenium 写 12306 抢票脚本到底能不能跑通”问的人多了我干脆把过去几年反复调试、翻车、再重构的经验整理成一份可复现的笔记。核心结论先放这里Selenium 抢票脚本能跑通查询和下单流程但真正决定成败的不是代码写得多花哨而是对 12306 页面加载节奏、登录态维持和余票刷新频率的理解。它适合两类人一是想学 Python Selenium 自动化实战的开发者二是需要理解浏览器自动化边界在哪的测试工程师。不适合指望“挂上就能抢到”的人因为 12306 的反爬和风控从来不是靠一个脚本就能绕过的。这篇内容会从环境搭建、登录态保持、余票监控、下单提交四个环节拆开讲每一步都给可抄的代码和参数说明最后收在几个我踩过的坑上。2. 环境准备与 Selenium 驱动 12306 的最小可跑通方案2.1 为什么选 Selenium 而不是直接怼接口很多人第一反应是抓 12306 的查票接口直接发请求但实际操作过的人知道12306 的接口参数带动态签名且频繁请求会触发风控。Selenium 的优势在于它驱动真实浏览器页面渲染、JS 执行、Cookie 管理都由浏览器完成脚本只需要模拟人的操作节奏。常见做法是用 Chrome ChromeDriver配合 Selenium 的 WebDriverWait 做显式等待避免页面没加载完就点按钮导致元素找不到。选版本时注意Selenium 4.x 对 W3C 标准支持更好ChromeDriver 版本必须和本地 Chrome 大版本一致否则启动就报 session not created。我一般会先跑一个最小示例确认环境通了再往上叠业务逻辑。from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC options Options() options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) driver webdriver.Chrome(optionsoptions) driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, {get: () undefined}) }) driver.get(https://kyfw.12306.cn/otn/resources/login.html) wait WebDriverWait(driver, 15) login_btn wait.until(EC.element_to_be_clickable((By.CLASS_NAME, login-hd-account))) print(页面标题:, driver.title) driver.quit()这段代码做了三件事隐藏 webdriver 特征、打开登录页、等待账号登录入口可点击。--disable-blink-featuresAutomationControlled是减少被识别为自动化的关键参数Page.addScriptToEvaluateOnNewDocument在每个页面加载前注入脚本把navigator.webdriver改成 undefined。WebDriverWait的超时设 15 秒是因为 12306 登录页在高峰期加载会超过 10 秒设太短会误报超时。2.2 ChromeDriver 版本对齐与依赖安装的实操命令环境翻车十次有八次是版本不对。先查本地 Chrome 版本再下对应 ChromeDriver。Linux 和 macOS 用命令行查Windows 在浏览器地址栏输入chrome://version看。# 查看 Chrome 版本macOS/Linux google-chrome --version # 或 chromium --version # 安装 Python 依赖 pip install selenium4.15.0 pip install requests pillow # 下载 ChromeDriver 后放到 PATH 或指定路径 # 验证驱动可用 python -c from selenium import webdriver; dwebdriver.Chrome(); print(d.capabilities[browserVersion]); d.quit()参数说明selenium4.15.0是我在多个项目里验证过相对稳定的版本太新的版本偶尔和旧 ChromeDriver 有兼容问题。requests和pillow是为后续处理验证码和通知准备的。验证命令能打印出浏览器版本就说明驱动通了如果报Message: session not created九成是版本不匹配。提示不要把 ChromeDriver 硬编码在代码里用webdriver.Chrome()让它自动从 PATH 找换机器时少改一处。3. 登录态维持与车次查询Selenium 操作 12306 的核心链路3.1 扫码登录与 Cookie 持久化的取舍12306 登录方式有账号密码和扫码两种。账号密码登录会触发滑块验证扫码登录相对省事但二维码有效期短。我一般用扫码登录后把 Cookie 存下来后续查询复用避免每次重新登录。import pickle import time from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def login_and_save_cookie(driver, wait): driver.get(https://kyfw.12306.cn/otn/resources/login.html) # 切换到扫码 tab qr_tab wait.until(EC.element_to_be_clickable((By.CLASS_NAME, login-hd-code))) qr_tab.click() print(请用 12306 App 扫码等待登录完成...) # 等待跳转到个人中心说明登录成功 wait.until(EC.url_contains(https://kyfw.12306.cn/otn/view/index.html)) time.sleep(2) cookies driver.get_cookies() with open(12306_cookies.pkl, wb) as f: pickle.dump(cookies, f) print(Cookie 已保存共, len(cookies), 条) return cookies def load_cookie(driver): driver.get(https://kyfw.12306.cn/otn/resources/login.html) with open(12306_cookies.pkl, rb) as f: cookies pickle.load(f) for c in cookies: driver.add_cookie(c) driver.refresh()逻辑说明扫码后等待 URL 跳到个人中心页这是判断登录成功的可靠信号比等某个元素出现更稳。Cookie 存成 pickle 文件下次启动浏览器后先访问登录页再 add_cookie最后 refresh 让 Cookie 生效。参数上time.sleep(2)是给页面写入 Cookie 留缓冲太短会丢。3.2 车次查询的显式等待与余票解析查询页的核心是填出发站、到达站、日期然后点查询再从结果表格里解析余票。12306 的查询结果表格是动态渲染的必须用显式等待等表格出现。def query_tickets(driver, wait, from_station, to_station, date): driver.get(https://kyfw.12306.cn/otn/leftTicket/init) # 填写出发站 from_input wait.until(EC.presence_of_element_located((By.ID, fromStationText))) from_input.clear() from_input.send_keys(from_station) # 填写到达站 to_input driver.find_element(By.ID, toStationText) to_input.clear() to_input.send_keys(to_station) # 填写日期 date_input driver.find_element(By.ID, train_date) driver.execute_script(arguments[0].value arguments[1], date_input, date) # 点查询 query_btn driver.find_element(By.ID, query_ticket) query_btn.click() # 等结果表格 table wait.until(EC.presence_of_element_located((By.ID, queryLeftTable))) rows table.find_elements(By.TAG_NAME, tr) results [] for row in rows: try: train_no row.find_element(By.CSS_SELECTOR, .number).text seats row.find_elements(By.CSS_SELECTOR, .cdz td) results.append((train_no, [s.text for s in seats])) except Exception: continue return results参数说明fromStationText和toStationText是 12306 查询页的输入框 ID直接 send_keys 会触发下拉联想但这里简化处理实际用的时候要等联想列表出现再选否则可能填错站。日期用execute_script直接赋值因为日期控件是只读的send_keys 写不进去。queryLeftTable是结果表格 ID等它出现再解析行。每行里.number是车次.cdz td是各席别余票单元格。注意查询频率不要高于 5 秒一次否则容易触发 12306 的请求限制页面会返回“操作过于频繁”。4. 余票监控与下单提交Selenium 抢票脚本的最后一公里4.1 轮询查票的间隔控制与异常重试余票监控本质是定时轮询查询接口但用 Selenium 轮询成本高每次都要刷新页面。我的做法是查询到有票后立刻进入下单流程而不是一直刷。轮询间隔设 5 到 8 秒带随机抖动避免固定频率被识别。import random import time def monitor_tickets(driver, wait, from_station, to_station, date, target_train): while True: try: results query_tickets(driver, wait, from_station, to_station, date) for train_no, seats in results: if train_no target_train and any(s not in (--, 无, ) for s in seats): print(发现余票:, train_no, seats) return train_no, seats time.sleep(random.uniform(5, 8)) except Exception as e: print(查询异常重试:, e) time.sleep(10) driver.refresh()逻辑说明random.uniform(5, 8)让每次间隔不一样降低被风控的概率。异常时 refresh 页面再重试因为长时间运行后页面可能失效。target_train是目标车次只盯这一趟减少无效判断。4.2 下单提交的点击链路与确认页处理发现余票后点“预订”按钮进入确认页选乘客、选席别、提交订单。这一步最容易翻车因为确认页元素多加载慢。def submit_order(driver, wait, train_no): # 找到对应车次的预订按钮 book_btn wait.until(EC.element_to_be_clickable( (By.XPATH, f//tr[.//a[contains(text(), {train_no})]]//a[text()预订]) )) book_btn.click() # 等确认页 wait.until(EC.presence_of_element_located((By.ID, confirmPassenger))) # 勾选乘客这里假设乘客已保存在常用联系人 passenger_checkbox wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, #normal_passenger_id input))) if not passenger_checkbox.is_selected(): passenger_checkbox.click() # 提交订单 submit_btn wait.until(EC.element_to_be_clickable((By.ID, submitOrder_id))) submit_btn.click() # 等确认弹窗 confirm_btn wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, #qr_submit_id))) confirm_btn.click() print(订单已提交请尽快支付)参数说明XPath 里用contains(text(), train_no)定位车次行再找该行里的“预订”链接。confirmPassenger是确认页容器 ID等它出现说明页面加载完。乘客勾选框在#normal_passenger_id下提交按钮submitOrder_id最后确认弹窗的按钮qr_submit_id。每一步都用显式等待不用 sleep 硬等。提示下单成功后 12306 会保留 30 分钟支付时间脚本提交完就可以人工接管支付不要试图自动化支付环节。5. 避坑与排查Selenium 抢票脚本最常见的 5 个翻车现场5.1 元素定位不到报 NoSuchElementException现象脚本跑着跑着突然报找不到元素但手动打开页面元素明明在。原因12306 页面有 iframe 嵌套或者元素在动态加载的弹层里直接 find_element 找不到。解决先driver.switch_to.frame()切到对应 iframe或者用 WebDriverWait 等元素可见再操作。我一般会在报错时截图看当时页面到底停在哪。5.2 登录态失效查询页跳回登录页现象Cookie 加载后第一次查询正常过几分钟再查就跳登录。原因12306 的会话有有效期且部分 Cookie 是 HttpOnlypickle 存的时候可能丢。解决不要指望 Cookie 永久有效脚本里加一个检测如果 URL 变成登录页就重新扫码。另外每次查询前先访问一次个人中心页确认登录态。5.3 查询频率过高被限流页面返回空白现象连续查询十几次后结果表格不刷新或者返回“操作过于频繁”。原因请求间隔太短触发风控。解决间隔加到 5 秒以上带随机抖动并且每查 20 次就 refresh 一次页面模拟人工操作节奏。5.4 下单时乘客勾选失败提交按钮灰色现象确认页乘客列表加载出来了但勾选后提交按钮还是灰的。原因乘客信息没同步或者席别没选。解决勾选乘客后等一秒再检查席别下拉框是否已选没选就手动触发 change 事件。提交按钮灰色时不要硬点先看页面有没有提示未选乘客。5.5 ChromeDriver 被识别页面弹验证码现象脚本启动后访问 12306直接弹滑块验证。原因webdriver 特征没藏干净或者 ChromeDriver 版本太旧。解决用--disable-blink-featuresAutomationControlled加 CDP 注入并且 ChromeDriver 版本和 Chrome 大版本对齐。如果还弹就换用 undetected-chromedriver 这类库但注意它更新频繁要锁版本。6. 把抢票脚本跑稳的一个小技巧用日志和截图做黑匣子脚本跑在后台翻车时你不在现场事后全靠猜。我的习惯是给每个关键步骤加日志和截图出问题时能回放。具体做法是封装一个snap函数在查询、下单、提交三个节点各截一张图文件名带时间戳。import os import time def snap(driver, tag): ts time.strftime(%Y%m%d_%H%M%S) path flogs/{tag}_{ts}.png os.makedirs(logs, exist_okTrue) driver.save_screenshot(path) print(f[snap] {tag} - {path})参数说明tag用来区分步骤比如query、confirm、submit。截图存到 logs 目录时间戳避免覆盖。这个习惯帮我定位过好几次问题比如有一次提交按钮一直点不到截图一看是弹了个“请先选择乘客”的提示而日志里只看到点击失败。另外日志里记录每次查询的车次和余票状态方便事后分析哪个时间点放票。我一般用 Python 的 logging 模块输出到文件和控制台级别设 INFO。抢票这件事脚本只是工具真正值钱的是你对放票规律和页面行为的观察。希望帮到你。本文还有配套的精品资源点击获取
返回列表