资讯动态

自动抢券脚本原理与源码详解:抓包、签名、定时、并发抢券实战

发布时间:2026/10/9 18:52:42 来源:尧图企业网站定制
简介自动抢券脚本的可运行源码包适合具备基础前端知识、希望了解浏览器自动化操作的开发者用于解决电商平台活动中的定时抢券需求。包内共3个文件涵盖HTML页面、环境配置与源码管理配置压缩包整体仅6KB结构精简便于直接阅读和二次修改。已有480人学习下载。实现上围绕自动刷新、页面加载、按钮定位与模拟点击等关键步骤展开使用JavaScript结合浏览器自动化方式完成半自动抢券代码示例展示了通过frameset组织页面、按ID和标签名精确定位按钮并特别处理了刷新后控制台代码保留的常见问题。运行结束后会关闭脚本打开的页面降低系统资源占用。对初学者而言这是一份兼具实用性和教学价值的可运行样例既能了解抢券脚本的完整逻辑也能练习DOM操作与自动化脚本的排错思路。1. 自动抢券脚本不是点得比人快是请求发得比人准很多人以为自动抢券就是模拟人手去点按钮拼的是点击速度这是最大的误解。真正在整点秒杀场景里人工点击从看到按钮到触屏反馈至少 200 毫秒而脚本走的是接口直连网络往返一次也就 30 到 80 毫秒本质上是把“看到券→点下去”这个决策链路压缩成了“时间到→发请求”。这份可运行源码的核心不是抢而是把抓包、签名、定时、重试、校验五个环节串成一条流水线。适合谁用想研究接口自动化的人、被整点打卡折磨过的人以及需要批量管理多个账号领权益的运营。先说结论脚本能不能抢到七成在抓包是否精准三成在定时和重试策略跟手速一点关系都没有。2. 抢券脚本的底层逻辑先搞懂领券接口长什么样2.1 人工点击和脚本请求的差别在哪里我先拆一下人工操作的完整流程。打开活动页、等页面渲染、找领券按钮、点击、等接口返回、页面弹窗提示这一套下来至少 3 到 5 秒。而脚本只需要做三件事构造领券请求、在指定时间点发出、解析返回值。省掉的不只是点击那一下还有整个页面的资源加载时间——图片、CSS、JS 文件全部跳过只保留最核心的那个 POST 请求。拿我之前拆过的某电商平台的活动页举例页面里埋了一个领券接口前端 JS 会用当前用户 ID、活动 ID、券模板 ID 和一个写死的密钥拼出签名参数再带上时间戳一起 POST 到服务端。脚本要做的就是用代码模拟这个签名过程然后自己控制请求时机。只要签名算法和参数结构对了服务端根本分不清是人点的还是脚本发的。2.2 用抓包工具定位领券接口的三步操作定位接口不需要什么高深工具浏览器自带的开发者工具就够了。以 Chrome 为例F12 打开 Network 面板勾选 Fetch/XHR 过滤然后在活动页上点一次领券把过滤结果里那个看起来最像领券的请求找出来。一般它长这样URL 里带 coupon、receive、draw 这类关键词Method 是 POST请求头里有当前用户的 Cookie。// 在浏览器控制台里直接看关键请求参数不用翻代码 // 打开活动页 - F12 - Network - Fetch/XHR - 点一次领券按钮 // 选中领券请求在 Headers 里找 Request Headers 和 Query String Parameters // 在 Payload/Request 里找表单数据或 JSON Body我一般会把请求的 URL、请求头里的 Cookie、请求体里的参数全部复制出来存成 JSON 文件作为脚本的配置底座。这里有个容易忽略的点很多活动的领券接口有两次请求第一次是页面初始化时查状态第二次才是真正领券别把第一次当成领券接口否则脚本会一直做无效请求。2.3 签名参数和时间戳的关系几乎所有带反爬的领券接口都会加签名校验。常见做法是后端给前端下发一个固定密钥前端用 MD5 把用户 ID、活动 ID、时间戳拼在一起计算摘要服务端收到后按同样算法重算一遍比对。问题在于这个密钥藏在 JS 文件里需要通过搜索接口 URL 的关键词在 Sources 面板里找到对应的 JS 段。// 在 JS 源码里搜索 sign 关键词通常会看到类似这样的逻辑 // 不同活动算法不同但基本逃不出拼接 摘要运算这个套路 function buildSign(uid, activityId, timestamp) { var rawStr uid activityId timestamp a1b2c3d4; // 最后的字符串就是密钥 return md5(rawStr); // 常见的哈希算法 }拿到密钥和拼接顺序后用 Python 的 hashlib 库就能一板一眼地复刻。这里有个细节值得注意有些接口对时间戳有容错窗口比如前后 5 分钟内有效有些则严格到秒级超过 1 秒就判为非法请求。所以脚本里的时间戳一定要用服务器返回的时间或者本机同步过 NTP 的时间不能用客户端随意改的假时间。3. 源码核心模块拆解定时、请求、并发、日志3.1 项目文件结构与职责划分这份源码包的结构很清晰五个文件各管一段。main.py 是入口负责参数解析和调度sign.py 封装签名算法requester.py 管请求发送和重试scheduler.py 做定时触发logger.py 输出日志。新手拿到手不需要改核心逻辑改 config.json 里的账号信息、活动 ID 和时间参数就能跑起来。# 项目文件结构预览 grab_coupon/ ├── main.py # 程序入口解析命令行参数 ├── config.json # 配置文件账号、活动ID、时间、并发数 ├── sign.py # 签名计算模块从JS里逆向出来的算法 ├── requester.py # 请求发送、重试、异常处理 ├── scheduler.py # 定时触发逻辑到点了启动并发 └── logger.py # 日志输出记录每次请求的结果配置文件是整个脚本的核心调参入口。我习惯把能改的参数全部放在 config.json 里每次活动上线只需要改配置不改代码。特别是活动 ID 和时间戳窗口这两个值活动一变就必须更新写死在代码里会让你每次都要翻源码找。3.2 签名模块复刻前端算法签名模块是整个请求链路里最容易被坑的部分。我把常见做法写成这样先读配置里的 uid、activityId用本地时间生成 timestamp然后按前端 JS 的顺序拼接并做 MD5。注意拼接顺序一定不能改前端是 uid activityId timestamp secretKey你就必须照这个顺序来。import hashlib import time import json # 读配置文件把签名所需的参数全部取出来 with open(config.json, r, encodingutf-8) as f: config json.load(f) def generate_sign(uid: str, activity_id: str, timestamp: str, secret_key: str) - str: 生成接口签名 - uid: 用户ID来自登录后的Cookie或配置 - activity_id: 活动ID从活动页URL或抓包请求体里取 - timestamp: 当前时间戳秒级必须跟请求时间一致 - secret_key: 密钥从前端JS里逆向得到 raw_string uid activity_id timestamp secret_key md5_obj hashlib.md5(raw_string.encode(utf-8)) return md5_obj.hexdigest() # 举例假设从抓包里拿到这几个值 uid config[account][uid] activity_id config[activity][activity_id] secret_key config[activity][secret_key] timestamp str(int(time.time())) sign generate_sign(uid, activity_id, timestamp, secret_key) print(f生成的签名: {sign})这个模块我强调三点。第一MD5 是对拼接后的字符串整体计算不是分段计算第二timestamp 必须在请求发出那一刻才生成提前生成会导致签名过期第三如果接口返回 sign 无效优先检查拼接顺序和密钥这是最常翻车的两处。3.3 请求发送与重试策略请求模块用 requests.Session 复用连接能减少 TCP 握手次数对抢券这种高频场景很有帮助。每个请求带上随机 User-Agent 和一个页面 Referer这算是面向反爬的基操。响应拿到后先判状态码200 再解析 JSON然后看业务码。import requests import random import time from logger import log # 模拟浏览器请求头降低被服务端识别的概率 USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36, Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15, ] def send_grab_request(session, url, headers, params, max_retries5): 发送领券请求带重试机制 - max_retries: 最大重试次数默认5次 - 每次重试间隔从0.5秒开始指数退避 for attempt in range(max_retries): try: resp session.post(url, headersheaders, jsonparams, timeout3) data resp.json() # 业务码为0表示请求成功其他码说明被限流或参数错误 if data.get(code) 0: return data else: log(WARNING, f业务返回异常: {data.get(code)}, 正在第{attempt1}次重试) except Exception as e: log(ERROR, f请求异常: {e}, 正在第{attempt1}次重试) # 指数退避0.5s - 1s - 2s - 4s避免高频触发风控 time.sleep(min(0.5 * (2 ** attempt), 5)) return None关于重试次数我建议别贪多。很多活动的领券接口限制单用户只能领一张重试到第三次还返回“已领取”就该停了继续刷只会增加被拉黑的概率。合理的做法是第一轮多线程抢抢不到就撤退换下一个账号进场。3.4 定时触发把时间精度控到毫秒级定时模块是整点秒杀的关键。Python 的 time.sleep 在 Windows 上精度不够误差可能到几十毫秒所以我更推荐用轮询方式在目标时间前 3 秒开始循环每 10 毫秒取一次当前时间一旦到达预设时间点立刻触发请求。import time import threading from requester import send_grab_request def wait_until(target_timestamp: float): 忙等待到指定时间戳 - target_timestamp: 单位是秒可以用带小数的方式精确到毫秒 while time.time() target_timestamp: # 这里用极短的sleep让出CPU又不至于太粗糙 time.sleep(0.005) def launch_at_target_time(target_time: str, callback): 在指定时刻触发回调函数 - target_time: 格式 2025-05-01 10:00:00.000末尾毫秒 time_obj time.strptime(target_time.split(.)[0], %Y-%m-%d %H:%M:%S) base_ts time.mktime(time_obj) ms_part int(target_time.split(.)[1]) / 1000.0 exact_ts base_ts ms_part log(INFO, f等待到 {target_time} 触发抢券任务) wait_until(exact_ts) log(INFO, 时间到开始发请求) callback()这里有个我踩过的坑很多活动写的是 10:00:00 开抢但服务端实际是以自己的时钟为准偶尔会出现客户端时间已到但服务端还没放量或者反过来。稳妥的做法是在目标时间前 2 秒先发一个预热请求拿到服务器时间算出偏移量再校准本机触发时刻。3.5 并发模块多账号同时抢的线程模型多账号抢券不是简单地开几个线程还要考虑线程安全。每个账号有独立的 Session、独立的请求参数不能共享一个 Session否则 Cookie 串了会全部 401。我的做法是每个账号封装成一个 Job用 ThreadPoolExecutor 并发执行。from concurrent.futures import ThreadPoolExecutor, as_completed from requester import send_grab_request from sign import generate_sign import requests import json # 从配置加载多个账号 with open(config.json, r, encodingutf-8) as f: config json.load(f) def account_job(account, activity): 单个账号的执行任务 - account: 包含 uid, cookie, name - activity: 包含 activity_id, secret_key, url session requests.Session() session.headers.update({Cookie: account[cookie]}) timestamp str(int(time.time())) sign generate_sign( uidaccount[uid], activity_idactivity[activity_id], timestamptimestamp, secret_keyactivity[secret_key] ) params { uid: account[uid], activityId: activity[activity_id], timestamp: timestamp, sign: sign } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Referer: activity[page_url], Origin: activity[origin_url] } result send_grab_request(session, activity[api_url], headers, params) return account[name], result # 并发执行所有账号的抢券任务 def run_all_accounts(accounts, activity): results [] with ThreadPoolExecutor(max_workerslen(accounts)) as pool: futures [pool.submit(account_job, acc, activity) for acc in accounts] for future in as_completed(futures): name, result future.result() results.append((name, result)) log(INFO, f账号 {name} 结果: {result}) return results线程数别无脑开太大。单机并发超过 50 个线程时系统调度本身就会产生竞争网络连接数也容易触达上限。一般 5 到 20 个账号直接用 10 个线程就够再多就建议上多台机器分布执行。4. 避坑指南签名失效、风控拦截、Cookie过期等常见问题4.1 Cookie 过期导致全部请求 401现象脚本刚开始跑一切正常过了几个小时后所有账号突然 401日志里全部是 Unauthorized。 原因大部分平台的 Cookie 有效期在 2 到 24 小时特别是带登录态的关键字段过期后服务端直接拒绝请求。另一种情况是账号在别处登录把当前登录态顶掉了。 解决在脚本入口加一个 Cookie 有效性预检用抓包时发现的用户信息接口发一次 GET 请求如果返回 401 就直接在日志里标红不继续往下跑。及时更新 config.json 里的 Cookie 字段。4.2 签名校验失败返回 403现象请求能发出去服务端返回 403 或 code 为 -1提示 sign 校验失败。 原因三种可能。第一拼接顺序和前端不一致第二密钥不是写死字符串而是动态生成比如用加密算法算出来的第三时间戳精度不一致前端生成的可能是毫秒级 13 位而你用的是秒级 10 位。 解决把前端 JS 里生成 sign 的代码段完整扒下来在 Node 环境里跑一遍用同一个输入验证输出是否一致。如果发现是动态密钥就得用 Python 的 execjs 库直接调那段 JS 逻辑别用 Python 硬写算法。4.3 请求频率过高触发风控封号现象脚本第一次跑没问题第二次跑大批量账号被限制提示“操作频繁”。 原因同一 IP 在短时间内对同一个接口发起高频请求服务端有滑动窗口计数。特别是多账号共用一个出口 IP 时触发阈值很快就会被踩爆。 解决两个手段配合。一是控制并发数到 3 到 5 个每次请求之间加 0.2 到 0.5 秒随机抖动二是准备 3 到 5 个代理 IP 轮流使用避免单 IP 集中请求。注意代理要选稳定的动态短效 IP 在抢券这种毫秒级场景里反而容易掉链子。4.4 本机时间不准导致抢券时机偏移现象脚本在 10:00:00.000 触发返回结果却是“活动未开始”或“券已抢完”但手动点击还能抢到。 原因本机系统时间和服务端时间存在偏差脚本以为到了 10 点实际服务端才 9:59:58。另一种反向偏差是脚本提前了请求被服务端判定为非法时间。 解决在脚本启动时先请求一个业务接口从响应头里获取 Date 字段校准本地时间。我在源码里封装了一个 get_server_time() 函数每次活动前自动校时这个细节能解决很多玄学问题。4.5 验证码或滑块突然出现现象请求返回特殊业务码提示需要滑块验证或短信验证。 原因服务端更新了风控策略在领券接口前加了验证码拦截专门对付脚本。 解决短期解法是换 IP、换 User-Agent模拟真人请求特征长期解法是接入打码平台的 API把验证码图片传到平台上自动识别。但说实话一旦活动方上了这层防护脚本的胜率会大幅下降我一般会评估一下投入产出比不值得的果断放弃。5. 把脚本部署到服务器让它无人值守地跑5.1 在服务器上用 Crontab 管理定时任务本地电脑跑抢券脚本最大的问题是断电和断网部署到服务器才是正经玩法。拿到一台 Linux 服务器后把 Python 环境和依赖装好然后用 Crontab 定时调用。但这里有个细节Crontab 的精度是分钟级的不适合用来做秒级触发它的作用是启动你的脚本脚本内部再用自己的毫秒级定时逻辑去等准确时间点。# 编辑 crontab 定时任务 crontab -e # 每天早上 9:59 执行抢券脚本提前1分钟启动进入等待模式 59 9 * * * cd /opt/grab_coupon /usr/bin/python3 main.py /opt/grab_coupon/logs/run.log 21每次活动开始前 1 分钟启动脚本脚本内部会自己等到目标时刻再发请求。这样做避免了 Crontab 精度不够的问题也给脚本留了加载配置、校时的时间。日志重定向到文件里方便事后排查。5.2 用虚拟环境避免依赖冲突服务器上经常同时跑着多个 Python 任务依赖版本冲突是常有的事。我建议用 venv 给抢券脚本建一个独立环境别直接装到系统 Python 里。requests、execjs 这些库版本升级有时会把接口行为弄坏锁版本号才是可靠的。# 创建虚拟环境并安装依赖 cd /opt/grab_coupon python3 -m venv venv source venv/bin/activate pip install requests2.31.0 execjs1.1.0 PyExecJS # 属性后可以用 pip freeze 导出依赖清单方便迁移 pip freeze requirements.txt5.3 抢到后的结果通知钉钉、企业微信还是邮件脚本跑完后人不可能一直盯着终端。我在源码里嵌了一个通知模块抢券结果通过企业微信群机器人推送抢到了发绿色卡片没抢到发红色告警。这样人在工位上看到群消息就能知道战况不用翻日志。import requests import json # 企业微信群机器人推送消息 def send_webhook_notify(webhook_url: str, content: str, is_success: bool): 推送抢券结果到企业微信群 - webhook_url: 群机器人的webhook地址 - content: 推送内容包含账号名和结果 - is_success: 是否抢到决定样式 payload { msgtype: markdown, markdown: { content: f## 抢券结果通知\n{content}\n 状态{成功 if is_success else 失败} } } resp requests.post(webhook_url, jsonpayload, timeout5) return resp.status_code 200 # 调用示例 webhook config[notification][webhook_url] send_webhook_notify(webhook, 账号A 抢券成功, True)Webhook 推送这块有一点要提醒webhook 地址就是一把钥匙任何人拿到它都能往你的群里发消息。别把它写在公开仓库里建议放在独立的环境变量里读取或者放到一个不入库的配置文件中。6. 进阶技巧三个让脚本更稳的自定义改造第一动态密钥的通用处理方案。很多活动更新后不再用固定密钥而是用 AES 或 RSA 加密Python 硬写算法很容易错。我的习惯是直接把前端那段加密函数抠出来用 PyExecJS 在 Python 里执行 JS 原文输入参数和返回值保持和浏览器环境完全一致。这样做的代价是慢一点但正确性有保障。第二重试策略加一个“成功即停”的逻辑。有些脚本在多线程里只顾发请求不管已经抢到了还在继续发白白增加风控风险。我在 requester 模块里加了个全局标志位任何一个账号抢到之后立即把标志位置位其他线程检测到就自动停止。第三代理池的动态切换。我维护了一个代理列表文件每次请求前从列表头部取一个代理如果连续三次请求失败就自动摘除并换下一个这样能避免某个坏代理拖慢整个抢券节奏。这套逻辑看起来简单实际在多账号场景里效果很明显。说一个我自己吃亏的经历。早先做模拟项目X的时候我写过一个抢兑换码的脚本当时没做代理池单 IP 并发 20 个账号跑了不到两分钟就被服务端把整个 IP 段拉黑了活动没抢到不说正常业务也受了影响。从那以后我每次都强制走一遍“预检 Cookie → 校时 → 限额并发 → 代理轮换”这个流程宁可慢一点也绝不冒进。希望这些经验能帮到你特别是那些想把接口自动化玩明白的新手照着这份源码改一改很快就能搭出自己的第一个抢券工具。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑