简介这是一份面向抢票自动化学习者的实战脚本资源基于 Selenium 实现可在 Android 与 iOS 设备上运行用于在大麦网完成门票抢购并走通支付宝支付流程同时支持日志记录与滑块验证处理适合具备一定 Python 基础、希望研究移动端自动化与验证码应对思路的开发者参考。压缩包共 3 个文件约 6KB包含 1 个 py 主脚本、1 个 ini 配置文件与 1 个 md 说明文档分别承担核心逻辑、参数配置与使用说明结构精简、便于快速理解整体流程。目前已有 974 人学习下载说明该方向关注度较高。读者可从中获取移动端自动化抢票的完整实现框架、配置项组织方式、日志与滑块验证的处理思路以及支付环节的衔接逻辑适合作为自动化测试与脚本编写的学习案例也可在此基础上按自身需求调整配置与流程。1. 脚本、大麦、bp 回流与支付一条链路里的四个关键节点抢票脚本跑通登录只是起点真正决定成败的是「bp 回流 支付」这一段。大麦的下单链路里bp业务处理负责库存校验与订单生成回流指的是下单请求被风控拦截或库存不足后流量重新回到选座/确认页面的过程而支付则是最后一道有时限的关卡。很多人脚本能抢到票却卡在支付超时问题往往出在回流处理没做对。这篇笔记面向已经能跑通登录和选座、但下单成功率低或支付总超时的开发者把 bp 回流的触发条件、参数构造、支付链路对接和常见翻车点拆开讲。适合有 Python 或 Node.js 基础、了解 HTTP 抓包、想把这套链路做稳的人。2. bp 回流机制拆解请求从哪来、回哪去2.1 bp 接口在下单链路里的位置大麦的下单流程大致是选座/选票 → 提交订单bp 接口→ 库存锁定 → 生成待支付订单 → 跳转支付。bp 接口通常是一个 POST 请求携带演出 ID、场次 ID、票档 ID、数量、用户 token 等参数。服务端收到后先做风控校验再查库存库存够就锁库存并返回订单号库存不够或风控命中就返回错误码。回流的意思是当 bp 接口返回「库存不足」或「请求过于频繁」时脚本不能直接放弃而要把这次请求的上下文选中的票档、数量、当前场次重新送回选座页面等待下一次库存释放或风控窗口过去后再提交。这个「送回」的动作就是回流。没有回流逻辑的脚本一次失败就退出成功率自然低。常见做法是维护一个状态机idle → selecting → submitting → reflow → submitting。每次 bp 返回非成功码根据错误码决定是重试、回流还是换票档。错误码里FAIL_SYS_TRAFFIC_LIMIT这类要退避FAIL_BIZ_ITEM_NO_STOCK这类要回流等库存。2.2 回流触发条件与状态机设计回流不是无脑重试。触发条件一般有三类库存不足、风控限流、订单冲突同一用户已有未支付订单。三类对应的处理策略不同。库存不足回流到选座页重新拉取票档库存接口等有库存再提交。风控限流不能立刻重试要按指数退避比如 1s、2s、4s、8s同时换设备指纹或降低频率。订单冲突先查未支付订单能支付就支付不能支付就取消再重新下单。下面是一个简化的状态机实现用 Python 写import time import random class OrderStateMachine: def __init__(self, max_reflow5, base_delay1.0): self.state idle self.reflow_count 0 self.max_reflow max_reflow self.base_delay base_delay def submit_bp(self, payload): 模拟 bp 接口调用返回 (code, data) # 实际替换为 requests.post(bp_url, jsonpayload, headersheaders) code random.choice([SUCCESS, NO_STOCK, TRAFFIC_LIMIT, ORDER_CONFLICT]) return code, {order_id: 12345} if code SUCCESS else {} def run(self, payload): while self.state ! done and self.reflow_count self.max_reflow: if self.state in (idle, reflow): self.state submitting code, data self.submit_bp(payload) if code SUCCESS: self.state done return data elif code NO_STOCK: self.state reflow self.reflow_count 1 time.sleep(self.base_delay * (2 ** self.reflow_count)) elif code TRAFFIC_LIMIT: self.state reflow self.reflow_count 1 time.sleep(self.base_delay * (2 ** self.reflow_count) random.uniform(0, 0.5)) elif code ORDER_CONFLICT: self.state reflow # 先查未支付订单这里省略查询逻辑 time.sleep(self.base_delay) return None这段代码里max_reflow控制最大回流次数base_delay是退避基数。NO_STOCK和TRAFFIC_LIMIT都走指数退避但TRAFFIC_LIMIT额外加随机抖动避免固定间隔被识别。ORDER_CONFLICT不增加退避倍数因为要先处理已有订单。参数怎么调max_reflow一般设 3 到 5太多会拖到支付超时base_delay从 0.5s 到 1s 起步根据实际返回的Retry-After头调整。如果接口返回里有retry_after字段优先用它。2.3 回流时的参数复用与刷新回流不是把原请求原样再发一次。有几个参数必须刷新时间戳、签名、请求 IDtraceId或requestId、设备指纹相关字段。签名通常是对参数按字典序拼接后做 MD5 或 HMAC时间戳变了签名就得重算。import hashlib import time import uuid def build_signed_payload(base_params, secret): params dict(base_params) params[timestamp] str(int(time.time() * 1000)) params[requestId] uuid.uuid4().hex # 按 key 字典序拼接 sorted_items sorted(params.items()) sign_str .join(f{k}{v} for k, v in sorted_items) secret params[sign] hashlib.md5(sign_str.encode()).hexdigest() return paramstimestamp用毫秒级requestId每次必须唯一sign的拼接规则要以实际抓包为准——有的接口是keyvalue用连接有的是直接拼接 value。签名算错是最常见的 403 来源抓包对比一次就能确认。回流时还要注意 cookie 和 token 的有效期。如果回流次数多、耗时长token 可能过期需要在回流前检查 token 剩余有效期快过期就先刷新。3. 支付链路对接从待支付订单到支付成功3.1 支付接口的调用时序与参数bp 接口返回订单号后下一步是调支付接口。大麦的支付一般走支付宝或微信脚本能做的通常是拉起支付链接或生成支付二维码然后由用户扫码或跳转完成。支付接口调用时序创建支付单 → 获取支付参数payUrl或qrCode→ 轮询支付结果 → 支付成功回调。创建支付单的请求一般携带orderId、payChannelALIPAY或WECHAT、returnUrl等。返回的支付参数里支付宝通常是payUrl或表单 HTML微信是codeUrl或prepayId。import requests def create_payment(order_id, channel, session): url https://mtop.damai.cn/h5/mtop.trade.order.create/1.0/ payload { orderId: order_id, payChannel: channel, # ALIPAY / WECHAT returnUrl: https://m.damai.cn/order/detail, } headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X), Referer: https://m.damai.cn/, } resp session.post(url, datapayload, headersheaders) data resp.json() if data.get(ret) and SUCCESS in data[ret][0]: return data[data].get(payUrl) or data[data].get(codeUrl) return NonepayChannel决定后续支付方式returnUrl是支付完成后跳回的页面。返回的payUrl可以直接生成二维码codeUrl是微信的二维码链接。注意session要复用登录后的 cookie否则会返回「用户未登录」。3.2 支付超时与订单状态轮询待支付订单有时限一般 15 分钟。脚本要在超时前完成支付否则订单取消库存释放前面抢的票就没了。轮询支付结果用订单查询接口间隔 2 到 3 秒不要频繁查。import time def poll_payment_status(order_id, session, timeout900): start time.time() while time.time() - start timeout: resp session.get( https://mtop.damai.cn/h5/mtop.trade.order.detail/1.0/, params{orderId: order_id}, ) data resp.json() status data.get(data, {}).get(orderStatus) if status PAID: return True if status CLOSED: return False time.sleep(2.5) return Falsetimeout设 900 秒对应 15 分钟orderStatus的取值以实际接口为准常见有WAIT_PAY、PAID、CLOSED。轮询间隔别低于 2 秒否则容易触发限流。3.3 支付回调与本地状态同步支付成功后大麦服务端会收到支付平台的异步通知更新订单状态。脚本这边不能只依赖轮询还要处理「轮询还没查到但支付已完成」的情况。常见做法是轮询到PAID后再调一次订单详情确认同时把本地状态标记为已支付避免重复发起支付。如果脚本是多进程或多线程的本地状态同步要用文件锁或 Redis 锁防止两个进程同时对同一订单发起支付。血泪经验曾经因为两个线程同时调支付接口一个成功一个失败失败的那个把订单状态覆盖成「支付中」导致后续查询一直卡住。4. 避坑与排查回流和支付里最容易翻车的五件事4.1 回流次数过多导致 token 过期现象脚本回流几次后bp 接口返回「用户未登录」或 401。原因回流耗时超过 token 有效期或者回流过程中 cookie 被服务端刷新但脚本没更新。解决在回流循环里每次提交前检查 token 剩余有效期低于 60 秒就先调刷新接口同时用session.cookies自动更新 cookie不要手动固定。4.2 签名参数顺序错误导致 403现象bp 接口一直返回 403 或FAIL_SYS_ILLEGAL_ACCESS。原因签名拼接时参数顺序和抓包不一致或者漏了某个隐藏参数如_csrf或umid。解决用抓包工具对比一次成功请求和脚本请求的完整参数列表逐字段核对签名函数里把参数按 key 排序后打印出来和抓包结果比对。4.3 支付链接生成后未及时拉起现象订单创建成功但支付链接生成后用户没及时扫码订单超时取消。原因脚本生成payUrl后没有及时通知用户或者二维码有效期短。解决生成支付链接后立刻通过本地弹窗、声音或消息推送提醒二维码有效期一般 5 分钟超时后要重新调创建支付单接口。4.4 轮询频率过高触发限流现象订单查询接口返回FAIL_SYS_TRAFFIC_LIMIT后续查询全部失败。原因轮询间隔太短比如 0.5 秒一次。解决间隔调到 2 到 3 秒并在返回限流错误时退避 10 秒再查如果订单状态长时间不变可以适当拉长间隔到 5 秒。4.5 多线程并发下单导致订单冲突现象同一用户同时提交多个 bp 请求返回FAIL_BIZ_ORDER_CONFLICT。原因大麦限制同一用户同时只能有一个待支付订单。解决在脚本层面加锁确保同一用户同一时间只有一个下单流程如果已经有待支付订单先查订单状态能支付就支付不能支付就取消再重新下单。5. 进阶用回流日志和支付成功率做链路调优回流和支付跑通之后下一步是调优。我一般会在脚本里加两个日志回流日志和支付日志。回流日志记录每次 bp 请求的requestId、返回码、回流次数、耗时支付日志记录订单号、支付渠道、创建时间、支付完成时间、轮询次数。这两个日志能直接告诉你瓶颈在哪。比如回流日志里如果NO_STOCK占比高但TRAFFIC_LIMIT很少说明库存释放窗口没抓准可以调整回流间隔从指数退避改成固定短间隔加随机抖动。如果TRAFFIC_LIMIT占比高说明请求频率太高要拉长退避基数或换设备指纹。支付日志里如果「创建支付单到支付完成」的平均耗时超过 5 分钟说明用户扫码不及时可以在生成支付链接后加一个倒计时提醒或者自动切换支付渠道。如果轮询次数超过 100 次还没支付成功大概率是订单已经取消但脚本没识别要检查orderStatus的判断逻辑。下面是一个简单的日志分析脚本统计回流返回码分布和支付耗时import json from collections import Counter def analyze_reflow_log(path): codes Counter() with open(path) as f: for line in f: record json.loads(line) codes[record[code]] 1 total sum(codes.values()) for code, count in codes.most_common(): print(f{code}: {count} ({count/total*100:.1f}%)) def analyze_payment_log(path): durations [] with open(path) as f: for line in f: record json.loads(line) if record.get(paid_at) and record.get(created_at): durations.append(record[paid_at] - record[created_at]) if durations: print(f平均支付耗时: {sum(durations)/len(durations):.1f}s) print(f最大支付耗时: {max(durations):.1f}s)analyze_reflow_log按返回码统计占比analyze_payment_log算支付耗时。日志用 JSON Lines 格式每行一条记录方便追加和解析。跑一段时间后根据统计结果调整max_reflow、base_delay和轮询间隔。还有一个技巧把回流和支付的成功率按时间段分组比如每 10 分钟一个窗口看哪个时间段成功率高。大麦的库存释放往往集中在整点或半点回流间隔可以围绕这些时间点做动态调整——临近整点时缩短间隔其他时间拉长。我自己踩过的坑是一开始只盯着 bp 接口的成功率忽略了支付环节的耗时结果抢到票但支付超时白忙一场。后来把支付成功率也纳入监控才发现问题出在二维码生成后没有及时提醒。现在我的习惯是任何抢票脚本上线前先跑一轮「回流 支付」的完整链路压测用测试场次验证确认支付能在 3 分钟内完成再上真实场次。希望帮到你。本文还有配套的精品资源点击获取