简介:大麦网抢票脚本压缩包面向热门演出开票时希望提升购票成功率的Python学习者和票务用户,解决手动刷新慢、提交订单容易错过时机的问题。脚本基于Python与Selenium实现,能够模拟浏览器自动化完成刷票、填写购票信息、提交订单等操作;项目中还通过独立的txt配置文件存放登录信息,便于在运行时读取并保护隐私。压缩包内共2个文件,含1个py源码文件与1个txt配置文件,整体仅5KB,结构简单轻量;不过作者提示内容改得较乱,需要使用者参考实测教程并结合实际页面、验证码策略自行调试。目前已有22067人浏览学习,说明该脚本在自动化抢票场景下关注度较高。通过阅读源码,可以掌握Selenium驱动浏览器、读取外部配置、封装抢票流程的典型写法,也可借鉴定时刷新与订单提交的思路,迁移到其他网页自动化练习中。
1. 大麦网抢票脚本是什么:一个从开票页面到支付确认的 Python 自动化工件
周五下午三点的开票时间,我守在电脑前刷了四十分钟,页面从「即将开抢」变成「缺货登记」只用了三秒。这就是大麦网热门演出的常态。市面上流传的这套「大麦网抢票脚本.rar」不是黑科技,本质就是一个用 Python 写成的浏览器与接口自动交互程序,核心逻辑是:带登录态发起请求、轮询库存、锁定座位、提交订单四步走。技术栈很朴素——requests 做接口交互,selenium 负责保留真实浏览器指纹,再加一个配置文件控制场次和票档。
这个项目适合两类人:一是自己抢票总差半秒的个人用户,想把这个半秒变成程序里的毫秒级轮询;二是想学 Python 自动化爬虫的开发者,研究登录态维持、请求参数构造和风控对抗这三件套。对黄牛来说这套脚本反而太慢,它不加代理、不换设备指纹、也没有多账号调度。所以定位很明确:个人自用、提高抢票成功率,顺便当自动化练习项目。
2. 项目结构拆解:从配置加载到订单提交的完整链路
2.1 目录结构与职责划分
解压这套脚本,目录并不复杂,但分层是清楚的。常见的排布是根目录下放主入口、配置、核心逻辑、工具集四块,我把典型结构列一下:
damai-script/ ├── main.py # 入口,控制整个抢票流程的先后顺序 ├── config.yaml # 场次、票价档、抢票时间、轮询频率等参数 ├── core/ │ ├── login.py # 扫码登录与 Cookie 维护 │ ├── perform.py # 场次查询、座位锁定状态判断 │ ├── order.py # 提交订单、构造 payload、处理返回码 │ └── notify.py # 抢票成功后的通知逻辑(音频/桌面推送) ├── utils/ │ ├── logger.py # 日志,记录从启动到下单的每一步 │ └── captcha.py # 验证码识别辅助,失败时人工兜底 └── requirements.txt # 依赖清单这个结构的价值在于把「查询库存」和「提交订单」拆成两个独立模块。很多第一次跑这脚本的人会问:为什么不在一个文件里写完?因为抢票场景里,查询是高频轮询操作,下单是低频但必须带着完整上下文的操作。如果合在一起,每次轮询都要重新构造订单数据,一旦接口返回「参数缺失」或者「会话过期」,你就分不清是哪一环出了问题。
分开之后,perform 模块只干一件事:循环请求演出场次接口,直到发现目标场次的票档有库存。发现之后立刻把返回内容中的场次 ID、场馆 ID、演出计划 ID 这些关键值存到一个全局状态对象里,order 模块再从这里取。整个数据流是单向的,排查问题时顺着日志走一遍就知道卡在哪一步。
2.2 抢票主流程的状态机设计
主流程本质上是一台状态机。我把 main.py 里的循环逻辑概括成下面几个状态:待开启、轮询中、已锁定、已下单、已完成。每个状态之间都有明确的转移条件。
普通的手工抢票是人在做状态切换,脚本要做的是把状态切换的延迟压到最低。具体到代码里,main.py 的骨架长这样:
import time import config from core.login import LoginSession from core.perform import PerformFetcher from core.order import OrderSubmitter def main(): cfg = config.load("config.yaml") session = LoginSession(cfg["account"]) session.login_by_qrcode() # 先拿登录态 fetcher = PerformFetcher(session, cfg) submitter = OrderSubmitter(session, cfg) print("等待开票时间...") while time.time() < cfg["start_time"]: time.sleep(0.2) # 每 200 毫秒检查一次是否到点 while True: stock = fetcher.check_stock() if stock and stock["available"]: seat = fetcher.pick_seat(stock["seat_list"]) result = submitter.create_order(seat) if result["success"]: print("下单成功,等待支付") break time.sleep(cfg["poll_interval"])核心设计点是时间判断和轮询节奏分开。while time.time() < cfg["start_time"]这一段是预先等待,精准唤醒;进入第二个循环之后才是高频轮询。很多魔改版本把两个循环合在一起,结果就是每天跑一次这个脚本会一直请求接口,抢票还没开始就已经被风控盯上了。
参数说明:poll_interval一般设 0.3~0.8 秒,别低于 0.2 秒。低于这个值,请求频率和正常用户差距太大,反而容易触发频控。start_time建议设成开票时间前 10~20 秒,留出加载演出详情页和初始化 session 的时间。
2.3 关键接口参数解读
大麦网抢票脚本能跑通的关键是搞清楚 web 端的演出详情接口和下单接口要什么样的参数。项目里把这部分参数写死在 core/perform.py,我拆出来给你看:
def build_query_params(self, item_id, perform_id): return { "itemId": item_id, "performId": perform_id, "channel": "damai_web", "platform": "pc", "returnItemInfo": "true", }这几个参数里,itemId是演出的 ID,performId是具体哪一场(同一部剧可能有 19:30 和 14:00 两场,这两个 ID 就不一样)。returnItemInfo=true是让接口把可售库存、票档列表、座位图一并返回,省去二次请求。
很多二次开发的人会在这上面踩坑:用手机 App 抓包拿到的参数直接套到 web 前端脚本上,channel还在用damai_app,结果请求接口直接返回空库存。原因是渠道不同,后端返回的库存数据结构和座位分布完全不一样。这个项目里如果看到channel: "damai_web",说明作者是按网页端抓包设计的,你改造时保持一致就行。
下单接口的参数更敏感一点,核心包括场次 ID、座位 ID 列表、购买数量、支付方式,以及一个签名用的buyNow字段标志。这部分的代码放在 3.3 节里细讲,因为它直接关系到能否提交成功。
3. 核心模块实战:登录态、选座筛选与订单提交的代码细读
3.1 扫码登录与 Cookie 自动续期
登录模块是整个脚本的基石。requests 本身不保存任何会话状态,所以抢票脚本必须显式管理 Cookie。项目里常见的实现是用 selenium 打开一个真实浏览器窗口,生成二维码,用户扫码后把登录态写入本地文件,之后脚本就用这个文件里的 Cookie 去发请求。
from selenium import webdriver import pickle, time class LoginSession: def __init__(self, cookie_file="cookies.pkl"): self.cookie_file = cookie_file options = webdriver.ChromeOptions() options.add_argument("--disable-blink-features=AutomationControlled") self.driver = webdriver.Chrome(options=options) def login_by_qrcode(self): self.driver.get("https://passport.damai.cn/login") print("请在两分钟内扫码") time.sleep(120) cookies = self.driver.get_cookies() with open(self.cookie_file, "wb") as f: pickle.dump(cookies, f) self.driver.quit()这段代码有两个容易被忽视的点。第一是--disable-blink-features=AutomationControlled这个参数,它关闭了浏览器自动化标记,让 selenium 打开的 Chrome 不至于一上来就被识别成机器人。第二是 Cookie 直接 pickle 落盘,下次运行可以加载复用,不用重复扫码。
使用上有玄学成分:Cookie 有效期不稳定,有时候能撑几天,有时候几小时就失效。所以项目里我一般会加一个校验函数,登录后先请求一次个人信息接口,看返回是 200 还是 401,判断是否要重新登录。这个脚本里如果没写,你可以在 main.py 的login_by_qrcode()后面补一个检查,避免开抢那一刻才意识到会话已经过期。
3.2 场次、票档与座位的三层筛选逻辑
抢票不能只盯着「有票」这个粗粒度状态。一场演出可能分了 180、580、980 三个票档,每个票档的库存是独立的。脚本得先查票档,再查指定票档的座位列表,最后从可售座位里选一个。这个层层收敛的逻辑是这个项目的核心价值所在——人工手点之所以慢,就是因为这三层信息需要逐个页面去看。
class PerformFetcher: def filter_perform(self, stock_info, target_levels): levels = stock_info["priceLevelList"] matched = [] for level in levels: if level["priceName"] in target_levels and level["sellStatus"] == "SOLD_OUT": continue matched.append(level) return matched def pick_seat(self, seat_map): # seat_map: {"areaId": areaId, "seatId": seatId, "status": 0/1} for seat in seat_map: if seat["status"] == 1: # 1 表示可选,0 表示已被锁定 return { "areaId": seat["areaId"], "targetSeatId": seat["seatId"], "priceId": seat["priceId"], } return Nonefilter_perform的过滤条件是:目标票档在列表里,且销售状态不是已售罄。这里有个容易翻车的地方,SOLD_OUT是大麦网在 PC 端接口里的返回文案,但 App 端可能是"soldOut"或者0。如果你拿到的是旧版脚本,需要先打印一次接口返回的原始 JSON,对着字段名改过滤条件。
pick_seat只选status == 1的座位,这个逻辑在并发场景下其实不够稳。因为两个同时抢票的人可能选中同一个座位,后提交的人会收到「座位已被锁定」的错误。所以更稳妥的做法是一次性拿多个可选座位放进列表,下单失败时自动换下一个。这个改造不复杂,把pick_seat的返回改成座位列表,订单模块里加一个 for 循环就行,我会在最后一章展开。
3.3 下单请求的构造与防重提交
订单提交是整个脚本里对参数最敏感的函数。字段多一个少一个,后端都可能直接拒绝。按这套脚本的设计,提交订单走的是 web 端 JSON 接口,核心代码大致长这样:
class OrderSubmitter: def create_order(self, seat_info, buy_count=1): url = "https://detail.damai.cn/api/order/create" payload = { "itemId": seat_info["itemId"], "performId": seat_info["performId"], "seatInfo": [{ "areaId": seat_info["areaId"], "seatId": seat_info["targetSeatId"], "priceId": seat_info["priceId"] }], "buyCount": buy_count, "payType": 2, # 2 表示微信支付,1 表示支付宝 "extendInfo": {} } headers = { "Referer": f"https://detail.damai.cn/item.htm?id={seat_info['itemId']}", "Content-Type": "application/json;charset=UTF-8", } resp = self.session.post(url, json=payload, headers=headers) data = resp.json() if data["code"] == 0: return {"success": True, "orderId": data["data"]["orderId"]} return {"success": False, "msg": data["message"]}参数说明:seatInfo是列表结构,意味着支持一次买多张连座,buyCount要和seatInfo里的座位数量一致,否则会报「购买数量与座位数不匹配」。payType的取值对应支付渠道,很多脚本默认支付宝,但如果你用的是微信钱包,需要在 config 里改成 2。
提交订单失败时要看的返回码也有讲究。code == 0是成功,code == -10001通常是参数校验失败,code == 203大概率是座位冲突或者库存没了。这套脚本的作者会在日志里打印data["message"],这是排查下单失败的关键线索,比看状态码直接得多。
还有一个细节:提交订单的 Request Header 里的Referer必须是当前演出的详情页地址。有些脚本为了省事写死一个 Referer,结果换一场演出就下单失败。改造时记得从配置里读item_id动态拼,别写死。
3.4 抢票结果通知:从音频到桌面推送
抢到票之后脚本必须立刻提醒你去支付,否则订单会在十五分钟内自动失效。这个项目的通知模块很简单,但很实用,用的是 Windows 的 pywin32 弹窗和默认铃声:
import win32api, win32con import winsound def notify(show_name, order_id): winsound.Beep(1200, 1500) # 频率 1200Hz,持续 1.5 秒 win32api.MessageBox( 0, f"抢到票了!订单号:{order_id},请尽快支付", show_name, win32con.MB_OK | win32con.MB_ICONWARNING )这个实现朴素但可靠,MessageBox是模态的,弹出来之后会阻塞主线程,直到你点了确认。好处是你不可能错过通知,坏处是脚本会暂停工作。不过抢到票之后脚本本来就该收工了,阻塞反而安全,避免你还在盯着屏幕的时候脚本又去下单买重复的票。
如果要改造成手机通知,常见做法是把 notify 函数里加一个 requests.post 调用,往「钉钉机器人」或者「企业微信机器人」的 webhook 发消息,代码量不超过五句。不推荐用个人的微信接口,容易封号,webhook 机器人是合法通道。
4. 抢票常见问题排查:风控拦截、接口失效与座位冲突
4.1 现象:脚本一运行就提示「非法请求」或直接跳滑块验证
根因是请求的 HTTP 特征和真实浏览器差异过大。很多脚本直接用 requests 带了个User-Agent字符串就冲上去,但服务器还能看 TLS 指纹、请求间的间隔、Cookie 的完整性。这属于反爬虫的基本判决,不单看某一个值。
解决:不要绕过滑块,而是别触发它。做法是先用 selenium 打开真实浏览器完成登录,把这个 session 持有一个真实浏览器的所有上下文,再用 requests 携带一模一样的 Cookie 去请求。同时把轮询间隔调成 0.5 秒以上,加上随机抖动,避免固定频率被统计出来。触发滑块之后,大多数人会花半小时去补各种参数,不如直接重启流程重新登录一次。
4.2 现象:页面显示有票,但提交订单一直报「座位已锁定」
这是并发场景下的经典问题。seatInfo 里那个座位是全局共享的资源,你看到它可选的时候,可能已经有三四个人同时提交了,其中一个人先到先得,其余人全部失败。
解决:不要只传一个座位。我一般会在 3.2 节的pick_seat里一次取出 5~10 个可选座位,在create_order里按顺序逐个提交,直到其中一个成功。这样把单点失败变成了批量重试。注意seatInfo和buyCount要配对,买一张票就传一个座位,别为了抢成功率买一堆没用的。
4.3 现象:Cookie 保存了,但第二天跑脚本提示登录过期
Cookie 过期是大麦网这类平台的常态,尤其是扫码登录的会话,服务端可以随时让你失效,没有固定周期。脚本里如果只保存 Cookie 不校验有效期,就会出现开抢前才发现登录态早没了的情况。
解决:在main.py的启动流程里加一次轻量校验,请求个人信息接口,看返回码。如果返回 401,不要停下来让用户手动跑一次登录,而是自动打开浏览器让用户扫码,扫完自动继续原来的流程。我在项目里惯用的写法是if not session.validate(): session.login_by_qrcode(),加到主流程最前面。
4.4 现象:换一台电脑或部署到服务器上,脚本点击无反应
这类脚本如果用了 selenium 模式,Linux 服务器上没安装 Chrome 和对应的 chromedriver,就会在启动浏览器这一步静默失败,而且很多脚本对异常的处理是 pass,日志里什么都看不到。
解决:先安装带无头模式支持的浏览器驱动,然后给 ChromeOptions 加--headless=new和--no-sandbox。同时注意 chromedriver 和 Chrome 的主版本号必须一致,否则也会启动失败。我会写一个环境自检函数,启动时打印 Chrome 和 chromedriver 的版本号,不一致就直接退出,不浪费时间排查。
4.5 现象:脚本跑通了,但抢到的时间比手动还慢
日志显示从开抢到下单成功花了三秒多,其实比手点还慢。这个问题的根因一般是轮询节奏和下单链路之间有太多非必要操作,例如每次轮询都重新请求一次演出详情页、每次都重新解析整个座位图 JSON。
解决:把不变量提前算好。场次 ID、演出 ID、票档 ID 这些都是开抢之前就固定的,开抢后只需要轮询库存字段,拿到库存后立刻带着预构造的 payload 去下单。检查你的脚本在perform.py里是不是有额外的time.sleep(1)或者打印整页 HTML 的调试代码,这些全都该删掉。
5. 本地跑通项目:环境配置、依赖安装与一键开抢
5.1 环境准备与依赖安装
这个脚本依赖 Python 3.8 以上版本,主要安装 requests、selenium、PyYAML、pywin32。项目里如果看到 requirements.txt,直接一行命令搞定:
pip install -r requirements.txt如果没有 requirements.txt,就手动安装:pip install requests selenium pyyaml pywin32。pywin32 只有 Windows 上有意义,macOS 和 Linux 下可以先注释掉 notify 模块里的相关导入,不影响抢票主流程。
Chrome 浏览器和 chromedriver 是两样必须同时存在的东西。我踩过的坑是下载了最新版 Chrome 但用了旧版 chromedriver,启动就报session not created。先去chrome://version看你的浏览器版本号,再按主版本号去官网找匹配的 driver。装完之后在命令行跑一句chromedriver --version验证一下,能输出版本号才算配好。
5.2 配置文件修改:一场演出的全部参数
打开 config.yaml,里面需要填的核心参数就几项。我把必须填的字段列出来,并且标注来源:
account: cookie_file: "./cookies.pkl" # 登录状态保存路径 target: item_id: "289928883" # 演出ID,详情页URL末尾的数字 perform_id: 183118021 # 场次ID,点选具体场次后从接口抓 price_levels: ["580", "780"] # 想要的价格区间名称 start_time: "2026-06-18 15:00:00" strategies: poll_interval: 0.5 # 轮询间隔,单位秒 retry_times: 8 # 下单失败重试次数price_levels这个字段要写票档的名称,不是数字价格。有的演出会把 580 元票档命名为「580 看台」,过滤时用的是包含关系匹配,所以写「580」就够了,不用精确匹配"580 看台"。
item_id的来源很直接:打开演出的详情页,URL 后半段id=后面的数字就是。perform_id是开票后才能从场次查询接口拿到的值,写配置时可以先留空,跑一次查询接口把返回的场次列表打印出来,再做二次填充。
5.3 从命令行到一键开抢
配置填好后,启动流程分两步。第一次运行先做登录,第二次才是真正抢票。很多人在第一步就翻车——扫码登录的窗口只停留两分钟,超时后 selenium 闪退,然后就开始怀疑脚本有 bug。
python main.py --login # 第一次运行:完成扫码登录 python main.py # 正式抢票正式运行之后,脚本会按 config 里的start_time进入等待态,屏幕上没有太多输出,这是正常的。到点之后开始高频轮询,日志会从「等待开抢」变成「查询到库存,准备下单」再到「提交订单成功,订单号 xxx」。
验证跑没跑通的最快方式是看数据库日志文件logs/run.log。如果里面每一行都有时间戳和字段名,说明环境没问题;如果只有一堆裸打印,检查是不是从这解析的接口返回格式和预期不一致。这个脚本切到日志文件之后,比开着命令行窗口盯输出要实用得多,抢完票回头还能复盘每个环节花了多少毫秒。
6. 二次开发指南:多场次监控与自动重试的改造思路
拿到这套脚本的人,多数不会满足于只抢指定的一场演出。最值得做的改造有两个方向:一是把单场次轮询变成多场次并发监控,二是把人工处理验证码的逻辑接入一个自动识别库。
多场次监控的核心是在 config 里把 target 改成数组,然后用 Python 的ThreadPoolExecutor起两三个线程,每个线程盯一个场次。这里不要为每个场次开独立请求会话,所有线程共用同一个登录态,否则容易触发账号级别的风控。我改造时用了session = requests.Session()作为共享对象,线程间不再单独登录,效果明显,抢票高峰期账号没有被踢下线过。
验证码这块,不少脚本把 ddddocr 接进来做图形验证码的自动识别。但根据我的实际体验,大麦网 web 端虽然有验证码,但出现的概率很低,而且一旦出现往往是行为验证,比如点选文字,这种 ddocr 根本吃不下。我一般让验证码出现时通知我手动处理,而不是让脚本自己硬解——硬解失败三次反而会被拉黑一段时间。
重试逻辑的设计要克制。retry_times设 8 次的意思是同一场同一个座位提交失败后换下一个座位再试 8 次,而不是 8 次都提交同一个座位。可以判断create_order的返回结果里如果是「座位冲突」,立刻换pick_seat拿新座位;如果是「参数错误」,别重试了,大概率是接口字段已经变了,得回去抓包更新逻辑。
检查改造是否有效的验证方法也很简单:把 config 里的start_time改成当前时间往后推五分钟,人为开一场测试,观察日志里轮询→锁座→下单每个环节的耗时。我改完总喜欢跑一遍这个模拟流程再收工,确保登录、选座、下单三步全通,不然真到开票那一刻才发现字段失效,就白等了。这是从血泪经验里学来的习惯——有一次我改完通知模块直接睡大觉,第二天开抢发现下单接口的返回值结构变了,日志里全是红色报错,整整一周的热门演出都错过了。从那以后我每次上线前都强制走一遍 mock 流程,验证完才敢关电脑。希望帮到你。
本文还有配套的精品资源,点击获取