为什么不让 Selenium 点到底?大麦抢票脚本 Automatic_ticket_purchase.py 拆解:只让浏览器干一件事的 300 余行设计
【免费下载链接】Automatic_ticket_purchase大麦网抢票脚本项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase
模拟鼠标点击抢票,速度永远赢不过真实用户。大麦抢票脚本 Automatic_ticket_purchase 的核心文件 Automatic_ticket_purchase.py 用纯 HTTP 请求替代了整条购买链路,只给浏览器留了一件任务:一次性登录。
一、定位:它是什么、怎么被调用
这是个无框架、无配置文件的单入口 CLI 脚本,全部"路由"只是一个 argparse 参数。Automatic_ticket_purchase.py 的__main__直接实例化DaMaiTicket()并调用run(),调用方式见 README.md:
python Automatic_ticket_purchase.py # 账号/密码登录 python Automatic_ticket_purchase.py --mode qr # 扫码登录脚本的"元数据"是构造函数里的参数,摘录关键字段(Automatic_ticket_purchase.py):
self.item_id: int = 610820299671 # 商品id self.viewer: list = ['viewer1'] # 在大麦网已填写的观影人 self.buy_nums: int = 1 # 购买影票数量, 需与观影人数量一致 self.ticket_price: int = 180 # 购买指定票价逐字段看设计意图:
item_id= 脚本唯一的目标坐标。原文写着『item_id根据地区来确定,每一个城市对应不同的item_id』(README.md),意思就是大麦商品页地址栏里的那个id=参数,见下图:
viewer= 不是任意姓名,必须与在大麦网提前登记的购票人一致,脚本提交前会逐项比对服务端返回的列表(Automatic_ticket_purchase.py)。
buy_nums= 数量,run()开头强制与len(viewer)相等,不等立即返回(Automatic_ticket_purchase.py)。ticket_price= 一参两用:在 step1 里从skuList匹配skuId(Automatic_ticket_purchase.py),选座链路里再按它过滤价位(Automatic_ticket_purchase.py)。- 构造函数里的
login_id/login_password(Automatic_ticket_purchase.py)= 占位默认值,仅在 account 登录模式下被填入 selenium 表单(tools.py)。
仓库四个文件的分工:DaMaiTicket类是主流程,tools.py 是登录、cookie、取参、签名、选座的功能库,signcode.js 是接口签名算法,requirements.txt 只有 4 个依赖(requests、selenium、beautifulsoup4、pyexecjs)。
知道了怎么调用,只是入口;更重要的是它画了哪条能力线——这条线由 V2.0 的改造说明定下。
二、边界:管什么、不管什么
V2.0 的设计线写在 README.md:『之前的版本通过按钮操作,还要等待页面元素加载,效率低下。此版本仅需登录时用到页面……其余操作均通过 requests 进行请求』。意思是浏览器从主角降级为登录守门员,换来的是功能范围被明确划线。
管什么(4 件事):
- 登录:selenium 打开登录页,account / qr 两种模式(tools.py);
- 取参与签名:从线上 JS 刮 API 参数,本地生成 sign(tools.py);
- 状态轮询:反复请求商品接口,直到按钮状态变为"立即购买"(Automatic_ticket_purchase.py);
- 提交订单:POST createOrder 接口,成功后打印支付宝订单号(Automatic_ticket_purchase.py)。
不管什么(负向边界):
- 不支持连坐购买,选座仅支持单一价位,README.md 写明『暂时只支持抢购指定价格下的座位,且暂不支持连坐购买』;
pick_seat按迭代顺序取前 N 个座位即返回,没有任何相邻判定逻辑(tools.py);- createOrder 没有重试:单次 POST,按结果直接结束,不做并发提交;
- 只复刻 PC Web 接口,移动端链路不覆盖——README.md 已声明『大部分购买方式已迁移至手机端,需配合抓包处理。暂无更新计划』。
主循环的核心分支就是按钮文案btnText的 4 态状态机,脚本对每种状态行为不同:
| 按钮状态 (btnText) | 含义 | 脚本行为 | 位置 |
|---|---|---|---|
| 即将开抢 | 未开售 | continue,继续轮询 | L256-L257 |
| 立即购买 | 正常开售 | 进入orderConfirm后break | L261-L264 |
| 选座购买 | 需先选座 | 拉座位 JSON → 选座 → 提交 | L265-L302 |
| 缺货登记 | 已售罄 | 打印"手慢了"并退出 | L258-L260 |
范围确定之后,剩下的问题只有一个:触发之后这 7 步按什么顺序运转。
三、机制:数据入口与执行顺序
脚本的数据全部来自 4 类入口,每类的"拆包"方式不同:
| 输入类型 | 对应数据入口 | 可回答范围 | 易错提醒 |
|---|---|---|---|
| item_id + 请求参数 | detail.damai.cn/subpage | skuList、按钮状态 | 响应被null(...)/__jp0(...)包裹,须剥壳再json.loads(L58-L59) |
| exParams + buyParam | buy.damai.cn/orderConfirm | window.__INIT_DATA__订单数据 | 须剥掉前缀window.__INIT_DATA__ =与结尾分号(L103-L106) |
| h5_token + 时间戳 + api_param | mtop.damai.cn 座位系列接口 | standId、座位价格、可售座位 | sign 是token×tamp&12574478¶ms的本地 MD5,token 取自_m_h5_tkcookie(tools.py) |
| resourcesPath + stand_id.json | seatsvc 资源地址 | 全场座位清单 | 带签名拉取后,还要按 price_id 与可售状态二次过滤(L272-L297) |
完整流程 7 步:
- 参数自检:
len(viewer) != buy_nums直接返回(L228-L230); - 三级登录:
cookies.pkl存在则直接回放;否则按args.mode走 selenium 账号登录或扫码(L231-L237); - 主动验证登录:带 cookie 请求
passport.damai.cn/accountinfo/myinfo,页面标题等于"我的大麦-个人信息"才算登录成功(tools.py);验证通过且本地无缓存则 pickle 落盘(L244-L245); - 动态取参:
get_api_param()抓取线上 JSvue-pc/0.0.70,用两组正则抽出commodity_param与ex_params(tools.py); - 轮询:GET
detail.damai.cn/subpage,按ticket_price匹配skuId,读btnText决定分支(L253-L257); - 两条购买路径:普通票走
step2_click_buy_now,buyParam按固定约定拼成item_id_buy_nums_sku_id(L262-L263);选座票先取standId、拉全场座位 JSON,经format_valuable_seatid过滤、pick_seat选座(L267-L299); - 提交:
step3_submit_order比对观演人、置isUsed、选座时逐一写seatId,POSTbuy.damai.cn/multi/trans/createOrder,成功则打印alipayOrderId与支付链接(L190-L225)。
优先级关系很明确:cookie 回放是首选路径(省去 selenium 启动与反自动化对抗),selenium 登录是兜底;接口结构变化时脚本不盲目重试,而是把现场交给使用者——打印原始响应后退出。如果不把参数做成动态刮取,大麦一升级 JS 版本,脚本会静默失败且无从查起。
顺路跑通的流程之外,真正决定这类脚本能不能用的,是每一步出错时的行为。
四、护栏:抓取失败与信息过时怎么办
脚本没有异常重试框架,但每个失败点都有显式的退出条件和人话提示:
- cookie 回放后验证不过:『登录失败, 请检查登录账号信息。若使用保存的cookies,则删除cookies文件重新尝试』(L241-L243)——这一条的设计意图是把提示直接指向最可能的故障源(本地缓存的 cookies 过期),而不是笼统的"登录失败";
- selenium 流程走完后仍用接口复核,标题不符则打印『登录异常,请检查页面登录提示信息』(tools.py)——原文写着标题必须等于
damai_title才继续,意思是"页面看起来过了"与"真的过了"分开判定,防止假阳性放行; __INIT_DATA__解析失败:『获取购买必备参数异常,请重新解析response返回的参数』,随后把原始响应完整打印(L108-L111)——解析失败就把现场交给使用者,接口变更从此是 5 分钟的定位问题,而不是盲查;- 观演人不匹配:『请检查输入的观演人信息与大麦网观演人信息是否一致』(L196-L198)——与服务端实际返回的名单逐项比对,一个字的错别名不会浪费一次 createOrder 请求;
- 售罄:『手慢了,该票价已经售空』(L258-L260)——缺货登记是终态,立即退出,不继续轮询;
- 登录前主动去掉自动化指纹:
excludeSwitches: ['enable-automation']加--disable-blink-features=AutomationControlled(tools.py),注释直说『若关闭会导致出现滑块并无法进入』——这一条的设计意图是把登录页识别 selenium 的门槛挡在流程之外。
这些护栏背后是同一件事:出错时把现场完整交给使用者并快速退出。能搬走的,是下面这组通用模式。
五、启示:可以搬走的 5 条设计模式
- 接口优先(HTTP over UI):只在接口替代不了的环节(登录)保留 UI 自动化。可迁移做法:写自动化前先抓包目标页面的"点击"实际发出哪些请求,用 requests 复刻,selenium 只留给登录与滑块。
- cookie 持久化 + 轻量探针:cookie pickle 落盘(tools.py),每次运行先做一个最轻的带认证 GET 验证会话是否有效(tools.py)。可迁移做法:任何长任务自动化,进入主流程前先用最轻认证请求验证会话,别让过期会话跑完全程才发现。
- 动态取参代替硬编码:原文写着『可能大麦网js代码更新后需要修改此函数内的代码以重新获得参数信息』(tools.py),意思是把维护成本直接写进函数注释里,而不是藏起来。可迁移做法:"可能会变"的参数结构写成抓取代码,并在 docstring 标注参数来源与失效条件。
- 原样搬走站点签名函数:
signcode.js是标准 MD5 的纯 JS 实现(初始向量1732584193等常量见 signcode.js),拼接h5_token×tamp&12574478¶ms后由execjs调用原函数名calcaulate执行(tools.py)。可迁移做法:接口要求 sign 时先看前端 JS 里是否就是标准算法,是就搬函数、用 JS 引擎本地跑,不自己重写也不逆向猜。 - 轮询状态机穷举终态:
while True主循环显式枚举 4 种按钮状态,每种都有 continue / break / 退出(L252-L302)。可迁移做法:写轮询前先穷举所有可能状态并逐个写出口,"缺货登记"这类终态分支不能被默认分支吞掉。
阅读入口:
- 主文件:Automatic_ticket_purchase.py(登录 → 轮询 → 提交主流程)
- 调用方式与参数说明:README.md
- 登录、签名、选座辅助函数:tools.py
- 签名算法(纯 JS 实现的 MD5):signcode.js
- 依赖清单(共 4 个):requirements.txt
以上结论仅基于本仓库 V2.1.0 的代码与 README;README.md 已声明"已停止维护",线上 JS 版本(vue-pc/0.0.70)与 mtop 接口结构会随时间变化,脚本中的取参与解析逻辑在后续版本中可能失效,请以运行时的实际代码与接口行为为准。
【免费下载链接】Automatic_ticket_purchase大麦网抢票脚本项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考