简介:大麦网抢票脚本.rar 是一份基于 Python 与 Selenium 的自动化抢票工具,面向具备一定 Python 基础、希望提升大麦网热门演出购票成功率的用户。压缩包体积约 5KB,总共 2 个文件:抢票脚本.py 实现浏览器模拟登录、定时刷新票池、点击购票按钮、填写订单等核心逻辑;user_info.txt 用于存放账号或 Cookie 等敏感信息,需在运行前自行配置并注意隐私安全。该脚本源自知乎项目,原内容经过改动、结构较乱,作者特意提示“以参考为主”,实际使用时应结合最新页面结构和实测教程逐段调试,防止因网站改版或反爬策略失效。资源目前已有 22067 人学习下载,适合想研究 Selenium 自动化流程、学习抢票程序骨架或二次开发购票逻辑的读者,可作为快速入门的示例模板,从中获取定位元素、模拟操作、异常处理等实用思路。
1. Python抢票脚本到底做了什么:把人力操作压缩成毫秒级请求
某票务平台的演出票有多难抢,抢过的人都有体会:倒计时一结束,页面先卡两秒,等你能点进去,所有票档已经变灰。而这份“抢票脚本”的思路不是让手更快,而是把人工刷新、选票、提交订单这一串操作,替换成一段能直接命中下单接口的Python程序。它不是外挂,也不绕过购票规则,只是把操作节奏做到机器级别。这份资源适合两类人:一类是真心想抢票但手速跟不上普通用户,学会改配置就能用;另一类是研究自动化下单与接口逆向的开发者,可以拿它当样本去看登录态维护、请求频控和重试策略。
2. 抢票脚本的核心逻辑:登录态、主循环与下单回退
2.1 登录态:Cookie注入为什么比账密登录稳
先聊第一个绕不开的环节:登录态。票务平台的抢票接口基本都要登录,因为实名制购票要求用户身份绑定在订单上。换句话说,脚本第一步要解决的是“让平台认为你是一个已登录用户”。
常见做法有两种:账号密码登录和扫码登录。账号密码登录最容易写,但翻车率也最高——平台在密码登录环节普遍叠加了滑块验证和异地登录风控,纯requests模拟几乎过不去。扫码登录则更稳:脚本在终端或浏览器窗口打印二维码,你用手机App扫一下,脚本把登录后返回的Cookie落盘存下来,之后的每次请求都带着这份Cookie。我一般会优先选扫码登录还有一个原因:它不碰短信验证码,不会因为同一设备频繁登录而触发风控。
# login.py 扫码登录并把 Cookie 落盘,方便下次直接复用 import time import requests def scan_login(session, login_url): """返回登录后的 Cookie 字典;60 秒内未扫码则抛异常""" resp = session.get(login_url).json() qr_token = resp["qrToken"] # 二维码会话令牌 print("请在手机 App 中扫描以下二维码登录") print_qr(resp["qrContent"]) # qrcode-terminal 库负责在终端画码 for _ in range(60): check = session.post( CHECK_API, json={"token": qr_token}, timeout=3 ).json() if check["status"] == "ok": cookies = session.cookies.get_dict() save_cookies(cookies) # 写成 json 文件,供 buy.py 加载 return cookies time.sleep(1) raise RuntimeError("扫码超时,请重试")这段代码里有几个值得注意的参数:qrToken是扫码会话的标识,qrContent是二维码内容,CHECK_API是轮询确认登录状态的接口地址。真实平台里的接口字段名可能不同,但流程都是“获取二维码 → 轮询 state → 拿到Cookie”的同一套框架。save_cookies在资源包里通常是一个简单的文件写入函数,序列化成JSON后,下次运行直接读文件,避免每次抢票前都掏手机扫码。
Cookie复用不是无损的,它有过期时间,而且一旦账号在另一个设备登录,旧会话会被顶掉。所以脚本里应当有一个load_cookies()加校验的逻辑:如果加载的Cookie请求一个轻量用户信息接口返回未登录,就自动回退到扫码流程。这个细节很多简化版脚本都不处理,导致很多人开抢前才发现登录态失效,属于典型的“毁在起跑线”。
2.2 抢票主循环:时间校准、重试节奏与请求头
抢票脚本真正值钱的部分在主循环。开售瞬间,服务器受理请求的先后可能只差几百毫秒,所以这个循环里藏了三个关键点:时间基准、请求体、重试节奏。
先说时间基准。本机时钟与服务器时钟经常有偏差,直接把配置里的target_time当成开抢点,脚本会提前或推迟发请求。提前发请求会被当成非法访问,推迟发就等于把票让给别人。常见做法是先请求一次平台的服务器时间接口,计算本地与服务器的时钟偏移量,用这个偏移量校准开售时间戳。
# timer.py 计算本机与服务器时钟偏差,避免抢票窗口偏差几百毫秒 import time import requests def get_server_offset(session, time_api): t0 = time.time() * 1000 # 发送前本地时间 server_ms = session.get(time_api, timeout=2).json()["serverTime"] t1 = time.time() * 1000 # 接收后本地时间 network_delay = (t1 - t0) / 2 # 用往返时间估算单程网络延迟 return server_ms - (t0 + network_delay) # 正数=服务器更快,负数=本地更快 def now_corrected(offset_ms): return time.time() * 1000 + offset_ms这里用往返时间的一半估算单程延迟,是最粗糙也最常用的办法。严格一点的做法是连续请求三次取最小往返,但抢票场景里服务器时间接口响应本来就快,一次足够。get_server_offset返回的偏移量加上本地时间,就是修正后的当前时间。我建议在开抢前5分钟校准一次,开抢前10秒再校准一次,因为网络抖动会让第一次的估算偏掉几十毫秒。
主循环本身不复杂,难点在容错。一个能扛住并发返回的循环框架是这样的:
# buy.py 抢票主循环:定时等待、限次重试、失败退避 import time import requests offset = get_server_offset(session, TIME_API) def rush_buy(session, params, start_ms, max_retry=12, interval=0.3): while now_corrected(offset) < start_ms: time.sleep(0.02) # 开售前空转,保持会话不中断 for attempt in range(max_retry): try: resp = session.post( ORDER_API, data=params, headers={"referer": "https://detail.xxx.com/"}, timeout=2, ).json() if resp.get("success"): return resp["data"]["orderId"] # 抢到订单号,进入人工支付阶段 if resp.get("errorCode") == 429: # 被限流,退避时间加长 time.sleep(interval + 0.3) else: # 未开售、库存不足等错误码 time.sleep(interval) except requests.Timeout: time.sleep(interval) # 超时不一定失败,可能订单已创建 return None参数说明:start_ms是校准后的开售时间戳;max_retry建议设置在10到15之间,再多会明显触发风控;interval在0.2到0.5秒之间做随机浮动更贴近真实用户。referer必须带,平台会校验请求来源页,不带Referer的下单请求被识别为脚本的几率非常高。超时分支要特别理解:请求超时并不代表失败,极可能是订单已经创建但响应没传回来,所以超时后不要立刻放弃,等一两秒再查询订单状态。
下单请求的params结构各家不同,但核心字段基本一致:itemId、performId、skuId、buyerIds、count。资源包里通常把这几个字段从配置读入,再拼成请求体,字段顺序有时候也会影响签名校验,所以如果平台返回签名错误,先检查字段顺序和编码格式。
| errorCode | 含义 | 处理策略 |
|---|---|---|
| 429 | 请求频率超限 | 拉长退避,随机延迟 |
| 450 | 未开售 | 继续重试直到开售 |
| 460 | 库存不足 | 快节奏重试,等回流票 |
| 470 | 票档ID失效 | 停止重试,重新抓取skuId |
这张错误码表是我在调试时自己整理的,不同平台码值有差异,但处理思路一样:先看码,再决定是继续重试还是停下来修配置。
2.3 下单之后:脚本只负责创建订单,支付必须留给人工
主循环返回订单号之后,脚本的任务就结束了。很多人会把自动支付也写进去,我强烈不建议。原因有两层:第一,支付环节涉及免密协议、短信验证和支付密码,自动化操作很容易触发支付风控;第二,票务平台的订单创建后一般保留几分钟的支付窗口,留这个时间给人眼确认观演人、场次和票价,是最安全的做法。抢错场次还能取消重来,这个“后悔药”千万别用脚本替你做决定。
# notify.py 抢到订单后的通知入口 def notify(order_id, ticket_info): print(f"\a[抢票成功] 订单号 {order_id}") print(f"场次: {ticket_info['time']} 票档: {ticket_info['sku']} 张数: {len(ticket_info['buyers'])}") print("请在 5 分钟内打开手机 App 完成支付,超时订单自动释放")这个notify函数在资源包里可以替换成任何你喜欢的通知方式:本地响铃、企业微信机器人、Server酱推送都行。核心思路只有一个——把“抢到订单”和“完成支付”分成两个独立动作,前者交给机器,后者保留给人。这也是判断一个抢票脚本是否专业的分水岭:把支付硬写进脚本的,通常是对风控理解不足的产物。
3. 把脚本跑起来:环境、配置与一次干净的模拟
3.1 环境准备:Python版本与依赖安装
资源包里的脚本基于Python。我在3.9和3.11上都跑过,常见的依赖是requests、selenium、qrcode-terminal、retrying。其中requests负责下单请求,selenium是备用的浏览器自动化方案,qrcode-terminal负责在终端打印登录二维码。建议用虚拟环境隔离,避免污染系统Python。
python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install requests selenium qrcode-terminal retrying装完后先验证requests能正常导入,再跑python login.py做一次扫码登录。这一步能提前暴露两个问题:一是终端打印机不支持二维码字符,需要改用浏览器窗口显示二维码登录;二是网络环境访问平台接口超时,需要确认本机网络是否存在代理或防火墙拦截。这两个问题如果等开抢前才发现,基本没有补救时间。
3.2 修改配置:itemId、skuId、buyerIds 从哪来
新手最容易卡在配置上,因为每个ID都不是凭空来的。资源包里通常有一个config.py,参数含义大致如下:
CONFIG = { "item_id": "720123456", # 演出详情页 URL 中 ?id= 后的数字 "sku_id": "1204356789", # 票档 ID,内场/看台各有不同 "perform_id": "7800123", # 场次 ID,一场演出可能有多个开演时间 "buyer_ids": [10023456789, 10023456788], # 观演人 ID,实名购票必须 "target_time": "2025-07-12 10:00:00", # 开售时间,以平台公告为准 "max_retry": 12, "interval": 0.3, "time_api": "https://api.xxx.com/gettimestamp", # 服务器时间接口 "order_api": "https://api.xxx.com/order/create", # 下单接口 }获取这些ID最直接的方式是浏览器开发者工具。打开演出详情页,按F12切到Network面板,随便点一个票档,找到对应的XHR请求,看请求参数里携带的字段:itemId是演出项目,performId是场次,skuId是票档,buyerIds是观演人。平台界面上显示的是“内场1280”或“看台480”,但后端只认一串数字ID。
这里有一个我踩过很多次的坑:skuId不是长期不变的。平台每次开票前都可能重新生成票档ID,昨天抓到的ID,今天开售时可能已经失效。所以开售前10分钟必须重新抓一次。这个习惯救了我不止一次,后面避坑章还会展开说。
3.3 先跑模拟:用本地接口验证主循环逻辑
不要拿真实开售时间做第一次试运行。我每次拿到新脚本,都会先在本地起一个mock服务,模拟平台的下单接口和错误码,把主循环、重试、退避这些逻辑先跑通。这一步能过滤掉大部分低级错误。
# mock_server.py 本地模拟下单接口,前3次返回库存不足 from flask import Flask, request, jsonify app = Flask(__name__) count = 0 @app.route("/order/create", methods=["POST"]) def create_order(): global count count += 1 if count <= 3: # 故意失败几次,验证重试逻辑 return jsonify({"success": False, "errorCode": 460}), 200 return jsonify({"success": True, "data": {"orderId": "TEST2025"}}), 200 if __name__ == "__main__": app.run(port=5001, debug=False)把config里的order_api改成http://127.0.0.1:5001/order/create,再把target_time设成当前时间加30秒,运行主循环。你会看到脚本发4次请求:前3次拿到460错误码后继续重试,第4次拿到订单号。如果输出符合这个预期,说明时间校准、重试节奏、订单返回解析都没有问题;如果中途出现异常,对比日志很容易定位是循环写法还是请求参数的问题。
[00:00:00.120] attempt=1 errorCode=460 库存不足 [00:00:00.430] attempt=2 errorCode=460 库存不足 [00:00:00.740] attempt=3 errorCode=460 库存不足 [00:00:01.050] attempt=4 orderId=TEST2025 抢票成功模拟通过之后再切回真实接口,成功率会高很多。切换时只需要把order_api改回平台地址,同时把target_time改回真实的开售时间。
4. 避坑指南:登录失效、限流、验证码与票档ID变动
4.1 五个高频坑:现象、原因、解决
抢票脚本写出来容易,稳定运行难。我整理了几条最常见的翻车记录,每条都按“现象→原因→解决”来写,你可以直接对照排查。
第一次是本机时间比服务器快了200毫秒。现象是脚本在开售前就发出了请求,平台返回“活动未开始”。原因是我没有做时间校准,直接用本机时间去触发循环。解决方法是跑一次get_server_offset并在主循环里用修正后的时间戳。从那次以后,我每次开售前都会校准两次,一次提前5分钟,一次提前10秒。
第二次是Cookie失效。现象是脚本运行没多久,下单接口返回401未登录。原因是Cookie过期或者账号在其他设备被登录,把当前会话顶掉了。解决方法是每次启动时先加载Cookie并请求一个轻量接口验证登录态,失效就自动退回到扫码登录。这个校验逻辑值得单独写在主函数开头,别省。
第三次是高频请求被限流。现象是前5次请求正常,第6次开始连续返回429。原因是我把重试间隔设成了固定0.2秒,平台的风控引擎识别到了规律性请求。解决方法是把间隔改成随机浮动,用random.uniform(0.2, 0.5)生成每次的退避时间,同时轮换部分请求头字段。
第四次是票档ID变了。现象是脚本报“参数不合法”,而网页端明明还能看到余票。原因是开售当天平台重新生成了skuId,我还在用昨天抓到的旧值。解决方法是开售前10分钟重新抓取一次票档ID,并且把抓取动作写进脚本的启动流程,而不是手工粘贴。
第五次是观演人数量超限。现象是下单失败,提示“该场次每单限购2张”。原因是我在配置里传了4个观演人,而场次规则限制了单笔订单人数。解决方法是先读取场次规则,超限时自动截断到允许的数量,而不是直接报错退出。这条对帮朋友抢票的场景特别实用。
4.2 抢票的“玄学”:接口预热与首次请求成本
除了上面五个可复现的坑,抢票还有一个说不清的部分。同一套代码,有人一次就成,有人一开抢就卡死在第一步。我的观察是,大部分“玄学”其实来自首次请求的成本:开售瞬间,平台会批量推送一大波活动数据,如果你脚本里第一个请求就是下单接口,很容易被当成异常流量。常见做法是提前几秒开始轮询详情页接口,让会话保持活跃,再在开售瞬间切换到下单接口。这个预热动作不保证成功,但不做的话失败概率明显更高。
4.3 验证码出现时怎么办:人工介入窗口
图形验证码和滑块是脚本最头疼的对手。脚本可以识别简单滑块,但平台升级后经常失效。我一般会在脚本里预留一个人工介入窗口:当检测到验证码字段时,脚本停止自动重试,播放提示音并弹出浏览器窗口,等待人在30秒内手动完成验证,验证通过后再继续下单流程。这个做法牺牲了一点速度,但比盲目自动过码稳定得多。自动打码平台也能接,但费用和稳定性是变量,不建议把它作为首选方案。
5. 进阶优化:把脚本变成可复用、可排查的小工具
5.1 日志落盘:下次翻车时不用靠猜
抢完票日志早扔了,追悔莫及。我习惯把每次运行的关键节点写进日志:时间校准偏移量、每次重试的错误码、最终结果。这样下次失败时有据可查,不用对着黑匣子猜。用logging的FileHandler落盘即可,每天一个文件,保留七天。
5.2 随机延迟与请求指纹:别让风控一眼认出你
把固定的重试间隔和请求头参数随机化,是最廉价的风控对抗手段。User-Agent从真实浏览器的UA池里随机选,重试间隔用均匀分布而不是固定值,请求之间偶尔模拟一下鼠标停留的停顿。这个小改动能把被限流的概率降低一截,亲测有效。
5.3 自检清单:开抢前十分钟固定检查项
我最终把抢票流程收敛成一张固定检查单:校准服务器时间、校验并刷新登录态、重新抓取票档ID、启动mock模式验证下单链路、切回真实接口。五个动作,十分钟内完成。如果你拿到这个资源包,先别急着把真实开售时间填进config,第一件事是跑mock服务把主循环走通,再按检查单过一遍配置。我之前吃过一次亏,把票档ID写死在配置文件里,开售当天连报五次“参数不合法”,眼睁睁看着票被抢完。从那以后,每次开抢前我都强制走一遍这张清单,再也不敢省掉重新抓取票档ID那一步。希望帮到你。
本文还有配套的精品资源,点击获取