简介:这是一份面向Python爬虫与自动化爱好者的「大麦网自动抢票工具」源码包,适合具备一定编程基础、希望研究浏览器自动化与购票流程模拟的开发者参考学习。资源围绕大麦网票务场景,涉及网络请求模拟、页面元素定位、JavaScript动态数据解析、多线程并发请求、验证码识别、IP代理池及异常重试等常见技术点,可用于理解自动抢票的实现思路与工程结构。压缩包共6个文件,包含1个py主程序、1个json配置文件、1个md说明文档及3个png示意图,整体约24KB,体积轻量便于快速阅读与二次调整。目前已有32970人学习下载,热度较高。通过阅读源码与配置示例,读者可掌握抢票脚本的模块划分、参数配置方式与运行逻辑,并在此基础上按自身需求修改演出信息、票档偏好等设置,同时留意平台使用条款与个人信息安全,理性评估自动化工具的合规边界。
1. 抢票工具的真实门槛:从“手动刷新”到“脚本托管”的认知差
很多人第一次接触大麦网自动抢票工具,是卡在“手动点不过别人”这个痛点上。手动刷新页面、选场次、点座位、提交订单,整套动作下来最快也要七八秒,而热门场次开票后一秒内就可能售罄。自动抢票工具的核心逻辑,是用脚本代替人手完成“监控余票→自动提交订单”这条链路,把响应时间压到毫秒级。它适合两类人:一是对某场演出有明确刚需、愿意花时间配置环境的从业者;二是想借这个场景练手自动化工具链的开发者。但必须先把预期摆正——工具解决的是“手速和流程自动化”,不解决“票量供给”和“平台风控”这两个根本变量。下面按实际拆包和复现的顺序,把这份资源从环境搭建到参数调优讲透。
2. 环境搭建与依赖安装:Python 版本、浏览器驱动与目录结构
2.1 为什么选 Python + Selenium 而不是纯 requests
抢票场景和普通爬虫最大的区别在于:下单链路依赖登录态、验证码交互和动态渲染的座位图。纯 requests 虽然快,但需要逆向签名参数和加密逻辑,维护成本极高,平台一改就废。Selenium 走的是真实浏览器内核,能复用已登录的 Cookie,对前端改版的容忍度更高。常见做法是 Python 3.8~3.10 配 Selenium 4.x,浏览器驱动用与本地 Chrome 版本严格匹配的 chromedriver。我一般会把驱动放在项目根目录的drivers/下,避免 PATH 污染。
# 创建虚拟环境,隔离依赖 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate # 安装核心依赖 pip install selenium==4.15.0 pip install requests==2.31.0 pip install pillow==10.1.0这里锁版本是有血泪经验的:Selenium 4.11 之前对Service对象的写法不同,网上很多老教程直接webdriver.Chrome(executable_path=...)在新版会报废弃警告甚至报错。pillow用于处理验证码截图裁剪,requests用于部分接口的轻量轮询。装完后用下面这段代码验证驱动是否可用:
from selenium import webdriver from selenium.webdriver.chrome.service import Service # 指向本地驱动路径,避免版本不匹配 service = Service(executable_path="./drivers/chromedriver") options = webdriver.ChromeOptions() # 关闭“正受到自动测试软件控制”提示条 options.add_experimental_option("excludeSwitches", ["enable-automation"]) driver = webdriver.Chrome(service=service, options=options) driver.get("https://www.damai.cn") print(driver.title) driver.quit()逻辑说明:Service对象是 Selenium 4 的标准写法,excludeSwitches用来去掉顶部自动化提示,减少被前端检测到的特征。参数上,executable_path必须指向与 Chrome 主版本号一致的驱动,比如 Chrome 是 120.x,驱动也得是 120.x。如果打印出标题就说明环境通了,打印空白或抛SessionNotCreatedException,九成是驱动版本对不上。
2.2 目录结构与配置文件分离
资源包解压后,建议按下面结构整理,不要把账号密码硬编码在脚本里:
damai_ticket/ ├── config/ │ └── setting.yaml # 账号、场次、票档、时间 ├── drivers/ │ └── chromedriver ├── core/ │ ├── login.py # 登录与 Cookie 持久化 │ ├── monitor.py # 余票监控 │ └── submit.py # 下单提交 ├── utils/ │ └── captcha.py # 验证码处理 └── main.py # 入口调度配置文件用 YAML 的好处是可读性强,改场次不用动代码:
# setting.yaml account: username: "your_phone" password: "your_password" target: item_url: "https://detail.damai.cn/item.htm?id=xxxxx" session: "2024-12-31 19:30" price: 680 quantity: 1 timing: start_time: "2024-12-25 12:00:00" advance_seconds: 2参数说明:advance_seconds是提前量,表示在开票时间前多少秒开始高频轮询,设 2 秒是常见值,设太大反而增加被限流概率。price必须和页面票档文字完全一致,差一个字都会选不中。
3. 登录态保持与余票监控:Cookie 复用和轮询频率控制
3.1 扫码登录 + Cookie 序列化
大麦的登录环节有滑块和短信验证,脚本硬刚验证码性价比极低。更稳的做法是首次手动扫码登录,然后把 Cookie 存到本地,后续启动直接加载。这样既绕开了验证码,又保留了真实登录态。
import pickle import time from selenium import webdriver from selenium.webdriver.common.by import By def login_and_save_cookie(driver, cookie_path="./config/cookie.pkl"): driver.get("https://passport.damai.cn/login") # 留出时间手动扫码,终端会提示 print("请在 30 秒内完成扫码登录...") time.sleep(30) # 登录成功后跳转到目标页,确认登录态 cookies = driver.get_cookies() with open(cookie_path, "wb") as f: pickle.dump(cookies, f) print(f"已保存 {len(cookies)} 条 Cookie") def load_cookie(driver, cookie_path="./config/cookie.pkl"): driver.get("https://www.damai.cn") with open(cookie_path, "rb") as f: cookies = pickle.load(f) for c in cookies: # 部分字段不能直接 add,需要剔除 c.pop("sameSite", None) c.pop("expiry", None) try: driver.add_cookie(c) except Exception as e: print(f"跳过一条 Cookie: {e}") driver.refresh()逻辑说明:get_cookies()拿到的是列表,每条含 name、value、domain 等字段。add_cookie对sameSite和expiry格式敏感,直接塞会抛InvalidCookieDomainException,所以先 pop 掉。加载完必须refresh()让 Cookie 生效。注意 Cookie 有有效期,一般几小时到几天,过期后重新扫码即可。
3.2 余票监控的轮询策略
监控环节最容易翻车的地方是无脑高频请求。平台对同一账号的请求频率有阈值,超过就弹验证或直接限流。合理的做法是分层轮询:开票前 5 分钟用 1 秒间隔,开票前 10 秒切到 0.3 秒,开票后如果没抢到再退回 1 秒。
import time from datetime import datetime def monitor_ticket(driver, target_time, advance=2): while True: now = datetime.now() delta = (target_time - now).total_seconds() if delta > 300: interval = 1.0 elif delta > 10: interval = 0.5 elif delta > -advance: interval = 0.3 else: interval = 1.0 # 检查“立即购买”按钮是否可点 try: btn = driver.find_element(By.CSS_SELECTOR, ".buy-btn") if btn.is_enabled(): print(f"[{now}] 检测到可购买,触发下单") return True except Exception: pass time.sleep(interval)参数说明:advance控制提前量,delta是距离开票的秒数。interval最小 0.3 秒是经验值,再低容易触发风控。is_enabled()判断按钮状态比判断文字更可靠,因为大麦在未开票时按钮是置灰的。如果一直检测不到,先确认item_url是否指向正确场次,再看页面是否改版导致选择器失效。
4. 下单提交与验证码处理:选择器定位、异常重试与人工兜底
4.1 票档和数量选择的选择器写法
进入下单页后,要按配置选票档、选数量、点提交。这一步的坑在于票档文字经常带“¥”和“元”后缀,直接匹配会失败。稳妥做法是用 XPath 的contains做模糊匹配。
from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def select_price_and_submit(driver, price, quantity): wait = WebDriverWait(driver, 5) # 模糊匹配票档文字,兼容“¥680”“680元”等写法 price_xpath = f"//div[contains(@class,'sku-item')][contains(.,'{price}')]" price_el = wait.until(EC.element_to_be_clickable((By.XPATH, price_xpath))) price_el.click() # 选择数量,通常是一个数字列表 qty_xpath = f"//div[contains(@class,'quantity')]//*[text()='{quantity}']" qty_el = wait.until(EC.element_to_be_clickable((By.XPATH, qty_xpath))) qty_el.click() # 提交订单 submit_btn = wait.until(EC.element_to_be_clickable( (By.CSS_SELECTOR, ".submit-order-btn"))) submit_btn.click() print("已提交订单,请尽快在 App 内完成支付")逻辑说明:WebDriverWait配合element_to_be_clickable比find_element更稳,因为下单页元素是异步渲染的。contains(.,'680')里的点号表示匹配该节点下所有文本,能兼容嵌套结构。数量选择用text()精确匹配数字,避免误点到“1”以外的元素。提交后脚本不负责支付,支付必须在官方渠道手动完成,这是底线。
4.2 验证码的两种处理路径
下单环节可能触发滑块或图形验证码。资源包里通常带一个captcha.py,思路是截图后裁剪验证码区域,再走人工识别或第三方打码接口。我的建议是:滑块优先用 Selenium 的ActionChains模拟拖拽,图形码则截图保存后弹窗让人工输入,不要硬接打码平台,成本和封号风险都不划算。
from selenium.webdriver.common.action_chains import ActionChains import time def slide_verify(driver, slider_selector, distance=300): slider = driver.find_element("css selector", slider_selector) ActionChains(driver).click_and_hold(slider).perform() # 分多段移动,模拟人手加速度 for i in range(5): ActionChains(driver).move_by_offset(distance / 5, 0).perform() time.sleep(0.05) ActionChains(driver).release().perform() time.sleep(1)参数说明:distance是滑块需要拖动的像素距离,不同分辨率下不一样,需要实测。分 5 段移动是为了模拟加速度曲线,一次性拖到底会被判定为机器。如果拖完提示失败,先检查distance是否准确,再看是否需要加入上下抖动。
4.3 异常重试与日志记录
抢票过程中网络抖动、元素未加载、弹窗遮挡都会导致失败。给关键步骤包一层重试装饰器,并把每次失败的原因写进日志,方便复盘。
import functools import logging logging.basicConfig(filename="run.log", level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") def retry(times=3, delay=0.5): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): for i in range(times): try: return func(*args, **kwargs) except Exception as e: logging.warning(f"{func.__name__} 第{i+1}次失败: {e}") time.sleep(delay) logging.error(f"{func.__name__} 重试{times}次仍失败") return None return wrapper return decorator逻辑说明:retry装饰器对下单、选票档这类幂等性不强的操作要慎用,因为重复点击可能生成多笔订单。建议只对“查找元素”类操作加装饰器,提交动作只执行一次。日志里记录时间戳和失败原因,事后能看出是选择器问题还是网络问题。
5. 避坑与常见问题排查:封号、选择器失效、时间同步与多开
5.1 账号被限流或弹验证
现象:脚本运行几分钟后,页面开始频繁弹滑块,或者直接提示“操作过于频繁”。原因:轮询间隔太短,或者同一 IP 下多个账号并发。解决:把最小间隔调到 0.5 秒以上,单账号单 IP 运行,必要时在开票前 10 分钟才启动脚本,不要全天挂着。
5.2 选择器突然失效
现象:昨天还能跑,今天报NoSuchElementException。原因:平台前端改版,class 名或 DOM 结构变了。解决:把选择器集中写在配置文件里,改版时只改一处;优先用contains和相对路径,少用绝对路径和动态生成的 class。
5.3 本地时间与服务器时间偏差
现象:明明卡着点启动,却总是慢半拍。原因:本地系统时间没同步,偏差几百毫秒就足以错过。解决:启动前用 NTP 校准系统时间,或者在脚本里先请求一次平台接口,用响应头里的时间做基准。
5.4 多开窗口导致 Cookie 串号
现象:同时跑两个场次,结果登录态互相覆盖。原因:多个 Chrome 实例共用同一个用户数据目录。解决:每个实例指定独立的user-data-dir,Cookie 文件也按场次分开命名。
options.add_argument("--user-data-dir=./profiles/session_a")5.5 支付环节超时
现象:订单提交成功,但没在时限内支付,票被释放。原因:脚本提交后没有提醒,人没盯着。解决:提交成功后用声音或桌面通知提醒,支付必须在官方 App 或网页手动完成,脚本不碰支付接口。
6. 进阶调优:用 CDP 降低特征、用异步日志做复盘
把基础流程跑通后,真正拉开差距的是“不被识别”和“可复盘”这两件事。Selenium 默认会暴露navigator.webdriver等特征,常见做法是通过 Chrome DevTools Protocol 注入脚本抹掉。下面这段在driver.get之前执行:
# 通过 CDP 注入,隐藏自动化特征 driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { "source": """ Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); window.chrome = { runtime: {} }; """ })逻辑说明:addScriptToEvaluateOnNewDocument保证每次新文档加载前都执行这段脚本,把webdriver属性改成 undefined,并补一个空的chrome.runtime。这不是万能的,平台还会看鼠标轨迹、请求头顺序等,但能过滤掉大部分基础检测。参数上不需要改,直接照抄即可。
另一个进阶点是日志复盘。把每次运行的轮询次数、检测到可购买的时间点、提交耗时写进结构化日志,跑几次后就能看出瓶颈在哪。我一般用下面这个格式:
| 字段 | 含义 | 示例 |
|---|---|---|
| poll_count | 总轮询次数 | 142 |
| first_hit | 首次检测到可购买的时间 | 12:00:00.312 |
| submit_cost | 从点击到提交完成耗时 | 0.85s |
| result | 结果 | success / timeout |
如果first_hit比开票时间晚很多,说明轮询间隔还是太大;如果submit_cost超过 2 秒,多半是页面元素加载慢,可以提前把下单页在后台标签页预加载。
最后说个习惯:我每次改完配置,都会先用一个冷门场次做全流程演练,确认登录、监控、选档、提交、日志五个环节都正常,再切到目标场次。抢票这件事没有后悔药,开票那几秒的容错全靠前期的反复验证。希望帮到你。
本文还有配套的精品资源,点击获取