简介:面向哔哩哔哩会员购用户的自动抢票辅助工具,基于Rust开发并具备跨平台GUI界面,专为CP漫展、演唱会等热门票务设计。无论是CP31、BW还是其他会员购活动,都能通过定时抢票与捡漏两大核心功能提升购票成功率;用户可自定义抢票时间与规则,软件自动执行抢票操作,同时兼顾执行效率与运行稳定性。压缩包共15个文件,整体大小38.74MB,内含可直接运行的登录、滑块验证、抢票等exe程序,以及配套Python源码、配置文件、说明文档和界面截图,满足不同层级的用户需求——新手可一键启动,开发者可研究源码进行定制。目前已有386人学习下载,适合经常参与B站会员购活动的粉丝,也适合对自动化抢票实现原理感兴趣的Python开发者参考。
1. B站会员购抢票脚本.zip:手点抢不到时,这个压缩包到底能不能信
你大概也见过这种场面:一场热门演唱会的票在 B站会员购上开售,你提前五分钟就守着页面,倒计时一跳零,你鼠标点下去,页面转圈三秒,然后弹出来“已售罄”或“缺票登记”。同一时间,黄牛和手快的用户已经把票扫空了。手点一次请求从浏览器渲染到提交,少说也要两三秒,而脚本可以在倒计时归零的瞬间把请求发出去,延迟压缩到几百毫秒以内。这就是“B站会员购抢票脚本.zip”这类压缩包存在的全部理由。
这类 zip 包通常是从技术交流群、论坛帖子或网盘里流出来的,命名很直接,里面一般装着油猴脚本、Python 脚本,运气不好还可能会混进一个伪装成脚本的可执行文件。对于刚接触的人来说,它像是一个黑匣子:你只知道它能抢票,却不知道解压之后它在你电脑上做了什么。这篇文章就围绕这个标题把整条链路讲清楚:拿到 zip 之后先做什么检查,浏览器脚本和 Python 直连两条路线分别怎么落地,参数怎么调,以及最常见的翻车现场有哪些。
这个方向适合两类人:一类是想抢演出票、漫展票、限量周边的普通用户,想在不写复杂代码的前提下把抢购成功率提上去;另一类是已经在写爬虫或自动化脚本的开发者,想从油猴脚本升级到相对可控的直连方案。先说明边界:脚本能提升的是“提交请求的速度”和“操作稳定性”,它不能保证你一定抢到,也不能绕过平台的风控验证。下面从解压前的安全检查开始讲。
2. 解压前先把 zip 拆开看:脚本能跑的前提是包本身没问题
2.1 别双击解压:先用 file 命令确认压缩包里装的是什么
从网上下来的抢票脚本 zip,最常见的坑不是脚本写错,而是压缩包里混着不该有的东西。我见过有人把一个 3MB 的“抢票脚本.zip”解压之后,里面是一个 .exe 和一个 .bat,没有任何 Python 或 JS 源码。双击 exe 之后,脚本没跑起来,电脑倒是被装了一堆推广软件。
在你解压之前,先用 file 命令看整体类型,再用 unzip -l 看文件清单。
file 抢票脚本.zip unzip -l 抢票脚本.zip unzip -p 抢票脚本.zip 文件名.py | head -n 50file 命令输出的是这个 zip 的真实格式信息,如果显示 “Zip archive data” 那是正常的,要是显示 “PE32 executable” 或者 “DOS batch file”,那这个“zip”实际上是个程序,直接停手。unzip -l 列出包内所有文件,这一步能帮你快速判断里面是源码、目录结构,还是躺着奇怪的 exe、scr、vbs、dll。第三行命令是把某个文件内容直接打印出来,不解压也能先看源码头部。
我一般会先看文件列表里有没有“后缀名和实际内容不符”的文件。比如一个叫 readme.txt 的文件,大小却有 2MB,这不是正常文档,八成是个伪装的东西。判断标准很简单:抢票脚本的逻辑无论如何都写在文本源码里,如果压缩包里没有任何源码文件,全是可执行文件,那就没有必要继续了。
2.2 zip 伪加密与密码移除:这个参数位改一下就能绕过
很多从群里流出来的抢票 zip 带密码,分享者故意设置加密防止白嫖。但有一种情况叫“伪加密”,它不是真正用 AES 或 ZipCrypto 算法把内容加密了,而是只把 zip 头里的加密标志位改成 1,让解压工具误以为文件有密码。
zip 格式里每个文件头都有一段 general purpose bit flag,其中的第 0 位标记“本文件已加密”。伪加密的原理是把这个 flag 置 1,但实际上文件内容没有做任何加密运算。所以解压工具会弹窗要密码,但直接用十六进制编辑器把标志位改回 0,文件就能正常解出来。
识别伪加密用 zipinfo 看一下:
zipinfo -v 抢票脚本.zip输出里每个文件会显示 “file security status” 和加密方式。如果显示 “encrypted” 但加密算法列是 “unknown” 或者 “zipcrypto” 的同时文件大小和原始文件完全一致,就可以怀疑是伪加密。用 7-Zip 打开,有些伪加密包在 7-Zip 里直接能读,7-Zip 会忽略错误的加密标志位尝试列出内容。
如果你确实需要去掉一个真正有弱密码的 zip 的密码,常见做法是用 hashcat 或 fcrackzip 做字典攻击。fcrackzip 的使用很简单:
fcrackzip -D -p /usr/share/wordlists/rockyou.txt -u 抢票脚本.zip-D 表示字典模式,-p 指定字典文件,-u 表示通过解压来验证密码是否正确。
不过这里有一条血泪经验:来历不明的抢票 zip 如果加了密码,不要去破解它,直接删掉。分享者给一个破解门槛极高、内容完全不可见的脚本压缩包,这本身就违背了“分享脚本”的逻辑。伪加密在恶意样本里非常常见,攻击者故意让压缩包打不开,诱导你去搜索“zip密码移除”工具,而这类工具本身可能就是投放木马的载体。
2.3 解压到隔离目录,观察是否有多余动作
确认包内是源码后,也先别在主力环境里解压。用沙箱、虚拟机或者至少隔离目录解压,然后跑一遍基础的行为观察。
mkdir -p ~/sandbox/bsky-grab && unzip 抢票脚本.zip -d ~/sandbox/bsky-grab cd ~/sandbox/bsky-grab && find . -type f | sort python3 -m http.server 8899解压之后先看目录结构,再启动一个本地 HTTP 服务,把目录暴露在浏览器里,用无痕窗口访问本机地址,人工看一下页面效果。这样可以避开在本机环境直接运行带来的风险。
更稳妥的方式是把源码从头读一遍,重点看三件事:有没有出现外部 URL 字符串、有没有调用系统命令、有没有把 cookie 或本地文件往外传。抢票脚本必然要登录 B站,所以脚本会读取你的 cookie,这是合理的;但读取 cookie 之后往一个陌生的域名发送,那就是偷数据了。用 grep 把可疑模式扫出来:
grep -rE "https?://[^'\"]+" --include="*.py" --include="*.js" . grep -rE "eval\(|exec\(|subprocess|os\.system" --include="*.py" . grep -rE "document\.cookie|localStorage|sessionStorage" --include="*.js" .第一行列出代码里出现的所有 URL,第二行扫 Python 脚本里的动态执行和系统命令调用,第三行扫 JS 里对 cookie 和本地存储的读写。抢票脚本读 cookie 可以理解,但读完之后把 cookie 拼进一个外部请求里,这就是妥妥的账号盗窃行为。到这里,压缩包本身安全了吗,可以开始动手改造脚本了。
3. 先跑通浏览器油猴方案:改动最小、最容易复现的抢票脚本
3.1 为什么优先选油猴而不用 Python 直连
对于大多数“想抢票但不想逆向接口”的用户,我建议第一条路走油猴脚本。B站会员购的所有接口都依赖登录态和风控参数,Python 脚本直连需要自己处理签名、cookie 有效期、风控指纹,这些对新手来说是绝对的大坑。而油猴脚本跑在浏览器里,天然继承了浏览器的完整登录态,脚本只需要做一件事:在正确的时机点击正确的按钮。
油猴脚本的另一个优势是安装成本低。下载一个 .js 文件,油猴面板里新建脚本粘贴进去,刷新页面就能生效,不需要任何第三方依赖。Python 方案要装 requests、要处理证书、要模拟环境,任何一个环节出问题都会导致脚本废掉。
那油猴方案的劣势是什么?是它仍然受浏览器渲染进程的调度影响。页面卡顿、定时器延迟、验证码弹窗都会拖慢节奏。所以油猴脚本适合抢购并发量中等、页面结构变化不频繁的场景;如果你要抢的是那种开售一秒就没了的热门票,可能需要升级到 Python 直连。
3.2 油猴脚本的最小实现:监听倒计时与自动点击
一个合格的 B站会员购抢票脚本,核心逻辑只有三个部分:轮询商品状态、倒计时归零时触发点击、点击后监测是否有验证码或风控弹窗。下面是一个可以跑通的最小示例。
// ==UserScript== // @name B站会员购抢购助手(最小版) // @match https://*.bilibili.com/* // @grant GM_log // @run-at document-idle // ==/UserScript== (function () { 'use strict'; const CONFIG = { checkIntervalMs: 200, // 轮询间隔 preClickMs: 200, // 提前量,一般不用 buttonText: '立即购买' // 按钮文字,按页面实际文案调整 }; let clicked = false; function findBuyButton() { const all = document.querySelectorAll('button, .buy-btn, [class*="buy"]'); return Array.from(all).find(el => el.textContent.includes(CONFIG.buttonText)); } function tryClick() { if (clicked) return; const btn = findBuyButton(); if (btn && !btn.disabled) { btn.click(); clicked = true; GM_log('[grab] clicked at ' + new Date().toISOString()); } } function checkCountdown() { const countdownEl = document.querySelector('.countdown, [class*="countdown"]'); if (countdownEl) { const text = countdownEl.textContent.replace(/\s/g, ''); if (/^0(时|:)|^00:00/.test(text)) { tryClick(); } } else { // 页面没有倒计时,直接尝试点击 tryClick(); } } setInterval(checkCountdown, CONFIG.checkIntervalMs); })();这段脚本的逻辑是每 200 毫秒检查一次页面是否存在倒计时和购买按钮,当倒计时归零或按钮变为可点击时,立即执行 click。clicked 标志位用于防止重复点击。
参数调整方面,checkIntervalMs 不建议低于 100,否则定时器频繁触发反而会造成页面卡顿;preClickMs 是提前量参数,默认 200,会被倒计时文本解析干扰,实际上不建议开启。按钮文字匹配是这套方案最大的弱点:B站会员购页面改版后,按钮文案可能变成“立即抢购”或“马上抢”,改成对应文案即可。
3.3 验证码与风控提示怎么处理:别写绕过代码
油猴脚本执行点击之后,页面大概率会出现两种情况:一是跳转到订单确认页,脚本使命完成;二是弹验证码或风控提示。很多人在这里开始动歪心思,想用脚本自动识别验证码或模拟滑动。我的建议是:不要写任何绕过验证码的代码。
原因有两层。一层是平台风控识别验证码绕过的能力很强,一旦侦测到自动化轨迹,轻则验证失败,重则账号被风控。另一层是,验证码本身充当了“人类操作背书”的作用,人工在验证码上点一下反而能证明这个账号操作正常。所以脚本的正确策略是:点击按钮后,脚本负责把页面滚动到验证码位置并聚焦,剩下的人工完成。
油猴脚本方案稳定跑通之后,你会发现瓶颈依然存在:你只能抢当前页面正在卖的东西。如果你要同时监控多个商品,或者页面加载慢导致点击时按钮还没绑定事件,油猴方案就不够用了。这就是下一章讲 Python 直连接口的原因。
4. 进阶:Python 脚本直连创建订单接口,保留 cookie 会话
4.1 抓包定位关键请求:先看 F12,再写代码
Python 直连方案的核心不是写代码,而是先搞清楚会员购下单的请求链路。用浏览器开发者工具打开 Network 面板,勾选 Preserve log,然后手动走一遍购买流程:进入商品页 -> 点击购买 -> 确认订单 -> 取消支付。全程记录下这几个关键请求的 URL、请求方法、请求头和请求体。
我一般会重点抓三个接口。第一个是商品详情接口,返回库存、价格、开售时间;第二个是创建订单接口,提交商品 ID、SKU ID 和数量;第三个是订单确认接口,校验订单信息。这三个接口的路径结构通常是 /x/xxx/detail、/x/xxx/order/create 之类的形式,具体以你抓到的包为准,不要去盲猜。
抓包时注意一个细节:创建订单请求里有几个签名参数,比如 ts、sign 之类。B站的风控体系对这些参数做了加密,硬逆向的工作量大。常见的绕行方案是:用浏览器执行创建订单的 JS,让浏览器把签名生成好,Python 只负责发送最后的确认请求。这个方案不算完美,但能把复杂度控制在可维护范围内。
4.2 Python 最小实现:抢票主循环与参数说明
下面给出一个框架性的 Python 脚本,核心是维护一个带 cookie 的 Session,轮询库存并尝试创建订单。
import time import requests session = requests.Session() session.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://www.bilibili.com/", }) # 从浏览器开发者工具中复制你登录后的 cookie session.cookies.update({ "SESSDATA": "你的SESSDATA", "bili_jct": "你的bili_jct", "DedeUserID": "你的DedeUserID", }) CONFIG = { "item_id": "商品ID", "sku_id": "SKU_ID", "buy_count": 1, "check_interval_sec": 0.3, "order_timeout_sec": 10, } def fetch_stock(): # 详情接口返回库存信息 url = "https://api.bilibili.com/x/xxx/detail" params = {"item_id": CONFIG["item_id"], "sku_id": CONFIG["sku_id"]} resp = session.get(url, params=params, timeout=CONFIG["order_timeout_sec"]) resp.raise_for_status() data = resp.json() return data.get("data", {}) def create_order(): url = "https://api.bilibili.com/x/xxx/order/create" payload = { "item_id": CONFIG["item_id"], "sku_id": CONFIG["sku_id"], "count": CONFIG["buy_count"], } resp = session.post(url, json=payload, timeout=CONFIG["order_timeout_sec"]) resp.raise_for_status() return resp.json() def main(): order_ok = False while not order_ok: try: stock = fetch_stock() if stock and stock.get("stock_count", 0) > 0: result = create_order() if result.get("code") == 0: print("订单创建成功:", result.get("data")) order_ok = True else: print("创建订单失败:", result) else: print("暂无库存,继续轮询...") except requests.RequestException as exc: print("请求异常:", exc) time.sleep(CONFIG["check_interval_sec"]) if __name__ == "__main__": main()这段代码的逻辑是:每隔 0.3 秒请求一次商品详情接口,当库存大于 0 时立即提交创建订单请求,成功则退出循环。Session 对象自动维持 cookie,不需要手动拼接 Cookie 头。
参数说明:check_interval_sec 设置的是轮询频率,我习惯在 0.3 附近,低于 0.1 容易触发风控;order_timeout_sec 是单次请求超时时间,建议不小于 5 秒,因为抢购高峰期服务端响应慢;buy_count 固定为 1,不建议调大,催生囤货对账号没好处。
这个框架代码直接跑大概率会失败,因为接口路径和签名参数都未补全。这正是前面强调“先抓包再写代码”的原因。把抓到的真实 URL 替换进去,把签名参数按照你在浏览器里看到的方式补上,这个脚本才有实际战斗力。
4.3 两种方案的边界:油猴不一定输,Python 不一定赢
做一个直接对比,方便你在投入时间前想清楚选哪条路。
| 维度 | 油猴脚本 | Python 直连 |
|---|---|---|
| 登录态 | 自动继承浏览器 | 需要手动复制 cookie |
| 签名参数 | 由页面 JS 生成 | 需要逆向或复用浏览器 |
| 请求时机 | 依赖页面渲染和定时器 | 可精确到毫秒发出 HTTP 请求 |
| 风控风险 | 更低,行为接近真人 | 更高,请求频率和指纹容易被识别 |
| 维护成本 | 页面改版后改个选择器 | 接口调整后要重新抓包 |
| 适合场景 | 单商品、临时用 | 多商品监控、需要组合操作 |
油猴方案输在“点击这个动作本身需要页面元素存在”,但赢在“浏览器环境天然可信”。Python 方案赢在可以直接对接口发请求,不依赖页面加载,但代价是要自己处理签名和风控。
对于大多数只抢一两场票的用户,油猴脚本足够用。对于想长期监控多个商品甚至写价格监控的人来说,Python 方向更有价值。但无论哪条路,都会遇到下面这些烦人的问题。
5. 抢票脚本的常见翻车现场与排查清单
5.1 现象一:脚本在别人电脑上能跑,自己一运行就报错
原因是这个。抢票脚本的依赖环境比想象的敏感。油猴脚本要求浏览器版本不能太老,还会被扩展商店的权限模型影响;Python 脚本则卡在第三方库版本上。最常见的是 requests 版本过低导致 TLS 握手失败,或者 Python 版本低于 3.8 无法解析某些语法。
解决思路是先做最小验证。油猴脚本先打开 B站任意页面,确认扩展图标亮了,控制台里能用 GM_log 输出。Python 脚本先单独跑一条最简单的接口请求,确认能拿到 200 响应,再跑完整逻辑。
python -c "import requests; print(requests.__version__)" python -V这个命令检查 Python 和 requests 版本。如果 requests 是 2.20 以下,升级到新版即可。环境问题是最容易解决的,也是最容易让人白忙一晚上的。
5.2 现象二:用脚本抢到了票,但支付超时
原因不在脚本,而在支付环节。订单创建成功后一般有 15 到 30 分钟的支付窗口,但很多人把脚本挂在后台,自己没盯着页面,等看到提醒时订单已经自动关闭了。另一种情况是支付页面加载慢,脚本在跳转支付时被浏览器拦截。
解决做法是:在脚本里增加声音或浏览器通知提醒。油猴脚本可以用 Notification API 发桌面通知,Python 脚本可以用最简单的控制台弹窗提醒。
if (Notification.permission === 'granted') { new Notification('抢到了', { body: '请尽快完成支付' }); }这段代码加在点击成功之后。支付窗口是脚本无法代劳的,任何声称“自动支付”的脚本都要警惕,它很可能在收集你的支付信息。
5.3 现象三:请求频率不高,账号还是被风控了
原因通常是环境指纹问题,而不是频率问题。Python 脚本的请求头如果缺少一些浏览器默认会带的字段,比如 Accept-Language、Sec-Fetch-Mode,服务端就能判断这不是浏览器发的。而且数据中心 IP 或者宽带 IP 的段如果被标记过,请求再少也会被风控。
解决做法有两个方向。一是在 headers 里补齐浏览器特征字段,把 F12 里复制出来的完整请求头原样贴进脚本。二是降低抢购前试探请求的频率。我见过有人写了个“提前十分钟开始刷详情接口”的循环,开售前就把 IP 权重刷低了。正确的做法是开售前一分钟内再启动轮询,前几十秒只在本地做倒计时,不发出任何请求。
5.4 现象四:倒计时归零了,脚本却没触发
原因是本地时间和服务器时间不一致。B站会员购的倒计时以服务器时间为准,但页面展示的倒计时往往基于你本机时间计算。本机时间快了或慢了一秒,就会导致脚本提前或延后触发。
解决做法是在脚本里面直接请求服务器时间接口,和本地时间做差,然后以校正后的时间作为触发基准。油猴脚本里可以这样处理:
const serverTime = await fetch('https://api.bilibili.com/x/xxx/time').then(r => r.json()); const offset = serverTime.data.now - Date.now();拿到 offset 之后,把倒计时判断逻辑里的时间全部加上这个偏移量。这个坑很隐蔽,本地时间误差个几百毫秒就可能导致抢购失败,而且是脚本代码层面看不出问题的类型。
5.5 现象五:脚本里藏着“更新逻辑”在偷数据
这是最严重的一类情况。有些分享出来的抢票脚本,源码写得像模像样,但里面有一段“自动检查更新”的逻辑,实际上是把 cookie、手机号这类信息 POST 到脚本作者的服务器上。它伪装成获取新版本配置,实际干的是数据收集。
排查方法在前面 2.3 节已经讲过:grep 扫所有 URL,重点看有没有往非 bilibili.com 域名发送的请求。真正的抢票脚本只需要访问 B站域名,出现任何第三方域名都有问题。而且这类脚本往往会在 readme 里写着“必须联网获取配置才能使用”,这个说辞就是提示风险的红旗。
6. 让脚本稳定工作:限速、重试策略与可观测性
过了翻车期,脚本能跑通了,接下来要考虑的是怎么让它稳定工作。我的经验是三个词:限速、重试、日志。
限速不是越慢越好,而是要有节奏。抢购前用低频轮询探路,比如 500 毫秒一次;开售瞬间可以提高到 200 毫秒一次,持续 10 秒;如果 10 秒内没抢到,把频率降回 500 毫秒,继续拉长到 1 秒,避免在高峰期持续高频请求。这个策略可以用一句话概括:快在关键瞬间,慢在平时。重试要区分错误类型。网络超时可以立即重试,但业务错误比如“库存不足”“订单频繁创建”不应该重试,因为重试也没用,反而增加风控风险。我习惯给重试加上上限,超过 5 次进入冷却,冷却 30 秒之后再继续。
日志是最后一道后悔药。建立一个简单的日志文件,记录每次请求的时间、状态码、响应耗时和返回内容,判断脚本行为是否有异常。没有日志的脚本出了问题只能靠猜,有了日志可以直接定位是网络问题、风控问题还是接口参数问题。
import logging logging.basicConfig( filename='grab.log', level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s' ) logging.info('start grab item_id=%s sku_id=%s', CONFIG['item_id'], CONFIG['sku_id'])这套三件套用上之后,脚本就不再是“碰运气跑一次”的黑匣子了。你每天打开日志,就能看到它在几点几分做了什么,有没有异常。我自己在这条路上踩过的最大一次教训,就是第一版脚本没做限速,把轮询间隔写成 50 毫秒,结果开售后十分钟账号被风控锁定二十四小时,完美错过了整场演出。后来老老实实把限速和重试策略加上,反而稳定多了。这行字写给你,希望帮到你。
本文还有配套的精品资源,点击获取