基于 Python 3 + Selenium/Playwright 实现的的自动预约 12306 火车票的全流程实战爬虫教程
每逢春运、国庆或暑运,火车票“一票难求”的现象总是如约而至。即便 12306 平台已推出「候补抢票」功能,并上线人脸核验 App,仍然难以完全缓解旅客的购票压力。人们迫切希望能够掌握一套完整的自动化购票解决方案,包括预约提醒、实时余票监控、快速下单提交以及一键支付等功能,以提升购票效率和成功率。
看完本文你会学到:
- 账号扫码登录 → Cookies/Token 本地持久化
- 动态查询余票 + 正则过滤车次
- 自动填充乘客信息、席别、日期
- 抢票失败自动重试 / 候补提单
- 支持 CLI、Web 面板、无人值守守护脚本
多端支持:Selenium + ChromeDriver(兼容 GUI 调试)或 Playwright + Chromium(无头 + asyncio)。
一、技术选型与环境搭建
| 层次 | 推荐技术 | 说明 |
|---|---|---|
| 浏览器自动化 | Selenium 4.20+ / Playwright 1.44+ | Selenium 生态最成熟;Playwright 原生 async,滑块模拟更自然 |
| 浏览器内核 | Chrome / Edge 121+ 或 Chromium 无头 | 版本需与驱动对应;Playwright 自带包 |
| 语言 & 运行时 | Python ≥ 3.10 | 区分 sync / async 版本 |
| 验证码识别 | ddddocr v2 + PaddleOCR | 图片点选、逻辑判断、滑块缺口定位 |
| 并发调度 | asyncio / 多进程 + Redis 队列 | 秒级轮询余票、任务分发 |
| 持久化 | SQLite / PostgreSQL | Cookies、订单状态历史 |
| 部署 | Docker 24、Kubernetes、frp 内网穿透 | 一键打包,支持云函数定时执行 |
依赖安装
ython -m venv venv && source venv/bin/activate
pip install selenium==4.20.0 webdriver-manager==4.0.1 \
playwright==1.44.0 ddddocr==2.1.7 paddlepaddle==2.6.3 \
rich loguru aiofiles uvicorn fastapi redis
playwright install # 自动下载 Chromium
二、登录难点:验证码与滑块双校验
图片点选验证码
12306 登录页的图片验证码包含「8 选 1-4 」的点击题(如“请点选所有的 飞机 / 公交车 / 交通灯”)。其挑战流程:
- 浏览器加载验证码背景与前景图(多块拼图)。
- JS 对点击坐标进行 AES 加密后发往
/passport/captcha/captcha-check。 - 服务端解析坐标并与题库标签比对。
破解思路:
- 标准做法是调用 OCR 服务(如超级鹰、汉王、人识别平台)。
- 开源方案 ddddocr 在点选验证码的准确率 > 94%。
- 对应代码参见
captcha_solver.py。
滑块验证码
自 2024 年起,12306 登录与提单阶段均可能触发滑块。滑块元素为 canvas 动态绘制,并在 JS 层对拖动轨迹进行速度—时间曲线和随机抖动校验;若判定轨迹过于线性或时间过短,即失败。
绕过方法:
- 用 Selenium/Playwright 获取滑块与缺口图片,OpenCV 模板匹配计算
distance。 - 利用贝塞尔曲线插值生成“人类”轨迹(含匀加速、减速、微停顿)。
- Playwright 的
page.mouse.move(x, y, steps=n)原生支持分步拖拽,模拟更平滑。
完整实现见 slider_solver.py,后文给出源码。
三、模块化项目结构
rail_12306/ ├── core/ │ ├── browser.py # 浏览器工厂(Selenium / Playwright) │ ├── captcha_solver.py # 点选验证码 & OCR │ ├── slider_solver.py # 滑块拖拽轨迹 │ ├── api_client.py # 12306 REST 封装 │ ├── order_worker.py # 下单、排队、结果轮询 │ └── watcher.py # 余票监控 & 消息推送 ├── cli.py # 命令行入口 ├── config.toml # 用户配置:账号、乘客、首选车次 ├── requirements.txt └── Dockerfile
设计要点:分层解耦;核心 API 不依赖具体浏览器;余票监控可在纯
requests环境运行,无需 GUI。
四、核心代码逐行详解(Selenium 版本)
提示:篇幅有限,下文节选关键函数;完整源码另附 GitHub 链接与注释行号,可一键 clone 体验。
浏览器初始化
# core/browser.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
from loguru import logger
def create_driver(headless: bool = False) -> webdriver.Chrome:
opts = Options()
if headless:
opts.add_argument('--headless=new')
# 反检测
opts.add_argument('--disable-blink-features=AutomationControlled')
opts.add_argument('--disable-gpu')
opts.add_argument('--window-size=1366,768')
drv = webdriver.Chrome(
service=webdriver.chrome.service.Service(ChromeDriverManager().install()),
options=opts
)
# 注入 JS 去除 navigator.webdriver
drv.execute_cdp_cmd(
"Page.addScriptToEvaluateOnNewDocument",
{
"source": """
Object.defineProperty(navigator, 'webdriver', {get: () => undefined})
"""
},
)
logger.success("ChromeDriver 启动完毕")
return drv
扫码登录 + Cookies 持久化
# core/login.py
import json, time, pathlib
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from .browser import create_driver
from loguru import logger
COOKIE_FILE = pathlib.Path("cookies.json")
def scan_login(driver=None, timeout=120):
driver = driver or create_driver()
driver.get("https://kyfw.12306.cn/otn/resources/login.html")
logger.info("请在弹出的 12306 页面完成扫码登录…")
WebDriverWait(driver, timeout).until(
EC.url_contains("loginSuccess")
)
logger.success("扫码成功,开始保存 Cookies")
json.dump(driver.get_cookies(), COOKIE_FILE.open("w", encoding="utf-8"))
driver.quit()
def load_cookies_session(driver):
if not COOKIE_FILE.exists():
raise RuntimeError("未检测到 cookies.json,请先扫码登录")
for c in json.load(COOKIE_FILE.open()):
c.pop("sameSite", None) # Selenium 兼容字段
driver.add_cookie(c)
driver.refresh()
余票查询接口封装
官方前端调用 /otn/leftTicket/queryA (或 queryX)接口,关键参数为 train_date, from_station, to_station, purpose_codes=ADULT。JSON 响应中字段 "ypInfoDetail" 即座席余票字符串;解析可参考社区协议分析 github。
# core/api_client.py
import requests, time
from typing import List, Dict
class TicketAPI:
HOST = "https://kyfw.12306.cn"
def __init__(self, cookie_jar: requests.cookies.RequestsCookieJar):
self.sess = requests.Session()
self.sess.cookies = cookie_jar
self.sess.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
})
def query_left_ticket(self, date: str, from_code: str, to_code: str) -> List[Dict]:
url = f"{self.HOST}/otn/leftTicket/queryA"
params = {
"leftTicketDTO.train_date": date,
"leftTicketDTO.from_station": from_code,
"leftTicketDTO.to_station": to_code,
"purpose_codes": "ADULT"
}
r = self.sess.get(url, params=params, timeout=10, verify=False)
r.raise_for_status()
return r.json()["data"]["result"]
下单核心流程
/otn/leftTicket/submitOrderRequest—— 提交订单请求/otn/confirmPassenger/initDc—— 初始化订单页面/otn/confirmPassenger/checkOrderInfo—— 检查乘客、席别/otn/confirmPassenger/confirmSingleForQueue—— 加入排队/otn/confirmPassenger/queryOrderWaitTime?random=—— 轮询排队结果/otn/confirmPassenger/resultOrderForDcQueue—— 获取订单号- 支付:跳转
/otn/payorder/init,扫描云闪付/支付宝
注意:步骤 3/4/5 在 2025 年增加了 RSA 公钥加密,需抓包提取
encSecKey算法,或者复用前端 JS (Playwrightevaluate注入执行)而非纯 requests。
五、Playwright + asyncio 方案(无头 & 单进程百万协程)
与 Selenium 相比,Playwright 启动更快、滑块拖拽更像真人,而且支持同一进程多浏览器实例。主流程:
# cli.py 节选
import asyncio
from playwright.async_api import async_playwright
from core.login_async import ensure_login
from core.watcher_async import watch_and_book
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context()
await ensure_login(context) # 复用 cookies 或扫码
await watch_and_book(context) # 余票监控 + 下单
print("任务结束,关闭浏览器")
if __name__ == "__main__":
asyncio.run(main())
轨迹生成器(贝塞尔曲线)
def bezier_curve(start, end, duration=1.2, steps=25):
xs, ys = start
xe, ye = end
c1 = (xs + (xe - xs) * 0.3, ys + 15) # 控制点
c2 = (xs + (xe - xs) * 0.6, ye - 15)
for i in range(steps + 1):
t = i / steps
xt = (
(1 - t) ** 3 * xs +
3 * (1 - t) ** 2 * t * c1[0] +
3 * (1 - t) * t ** 2 * c2[0] +
t ** 3 * xe
)
yt = (
(1 - t) ** 3 * ys +
3 * (1 - t) ** 2 * t * c1[1] +
3 * (1 - t) * t ** 2 * c2[1] +
t ** 3 * ye
)
yield xt, yt
Playwright 拖拽:
await page.mouse.move(xs, ys)
await page.mouse.down()
for x, y in bezier_curve((xs, ys), (xe, ye)):
await page.mouse.move(x, y)
await page.mouse.up()
六、接口层封装:排队 & 结果查询
排队等待接口返回 waitTime, waitCount。若排队失败或返回 -1,需重新提交或进入候补。同时可推送钉钉/飞书机器人告警。
def poll_queue(order_id: str, max_sec: int = 120):
for _ in range(max_sec):
rsp = api.query_wait_time(order_id)
if rsp["status"] == True and rsp["data"]["waitCount"] == 0:
return True
time.sleep(1)
return False
七、异常与反爬机制对策
| 场景 | 触发条件 | 应对方案 |
|---|---|---|
| 502/503 白屏 | 高并发、IP 被限流 | 随机省网出口 IP,或限速重试 |
| 登录后“当前访问过于频繁”提示 | Cookies 使用次数过多 | 浏览器池 + 动态 UA |
| 滑块二次校验 | 拖拽轨迹不自然/加速度异常 | 贝塞尔 & 抖动;时间 > 800 ms |
| 您的订单尚未完成 | 排队过长,座席锁定失败 | 切换其他席别 / 启动候补 |
| account has been frozen | 批量账号同 IP | 多地域 Serverless、短信验证码登录 |
八、高并发抢票:队列、协程与分布式
在“大年 29” 的零点开售场景,列车秒光,需要亚秒级提交订单。
- 协程模型:
asyncio方案轻量,每秒可发起 2 000+ 余票查询。 - Redis 布隆过滤器:防止重复查询同车次同时间。
- Kafka + Celery:主控只负责推送“可抢车次”消息,Worker 池执行下单。
- etcd / ZooKeeper 分布式锁:确保单用户同时仅一次排队,防止账号冻结。
九、 Docker 化部署
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN apt-get update && apt-get install -y wget gnupg && \
pip install -r requirements.txt && playwright install --with-deps
CMD ["python", "cli.py"]
部署到 k8s 或 Cloud Run,只需配置 Secret 注入 cookies.json 与 config.toml,即可水平扩展。
十、法律 / 合规风险与替代方案
- 违反服务条款:批量抢票脚本易触发 12306 安全风控,账号可能被冻结,甚至触及治安管理处罚法。
- 数据安全:Cookies、身份证号、手机号均为敏感信息。使用加密卷、KMS 管理。
- 推荐替代:官方候补、官方小程序“智能通知”;或购买经授权的第三方“速订+保险”服务,避免灰色操作。
总结
本文从 验证码识别 → 浏览器控制 → REST 接口逆向 → 并发调度 全链路演示自动预约 12306 车票,实现了可运行的 CLI + 监控守护脚本。你可以按需切换 Selenium 或 Playwright,引入 AI 轨迹合成、分布式排队等高级技巧,将脚本进化为专业级 “抢票 SaaS”。
以上关于基于 Python 3 + Selenium/Playwright 实现的的自动预约 12306 火车票的全流程实战爬虫教程的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » 基于 Python 3 + Selenium/Playwright 实现的的自动预约 12306 火车票的全流程实战爬虫教程
微信
支付宝