☰
为什么不让 Selenium 点到底?大麦抢票脚本 Automatic_ticket_purchase.py 拆解:只让浏览器干一件事的 300 余行设计
2026/9/30 1:49:57 网站建设 项目流程

为什么不让 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 件事):

  1. 登录:selenium 打开登录页,account / qr 两种模式(tools.py);
  2. 取参与签名:从线上 JS 刮 API 参数,本地生成 sign(tools.py);
  3. 状态轮询:反复请求商品接口,直到按钮状态变为"立即购买"(Automatic_ticket_purchase.py);
  4. 提交订单: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后breakL261-L264
选座购买需先选座拉座位 JSON → 选座 → 提交L265-L302
缺货登记已售罄打印"手慢了"并退出L258-L260

范围确定之后,剩下的问题只有一个:触发之后这 7 步按什么顺序运转。

三、机制:数据入口与执行顺序

脚本的数据全部来自 4 类入口,每类的"拆包"方式不同:

输入类型对应数据入口可回答范围易错提醒
item_id + 请求参数detail.damai.cn/subpageskuList、按钮状态响应被null(...)/__jp0(...)包裹,须剥壳再json.loads(L58-L59)
exParams + buyParambuy.damai.cn/orderConfirmwindow.__INIT_DATA__订单数据须剥掉前缀window.__INIT_DATA__ =与结尾分号(L103-L106)
h5_token + 时间戳 + api_parammtop.damai.cn 座位系列接口standId、座位价格、可售座位sign 是token&timestamp&12574478&params的本地 MD5,token 取自_m_h5_tkcookie(tools.py)
resourcesPath + stand_id.jsonseatsvc 资源地址全场座位清单带签名拉取后,还要按 price_id 与可售状态二次过滤(L272-L297)

完整流程 7 步:

  1. 参数自检:len(viewer) != buy_nums直接返回(L228-L230);
  2. 三级登录:cookies.pkl存在则直接回放;否则按args.mode走 selenium 账号登录或扫码(L231-L237);
  3. 主动验证登录:带 cookie 请求passport.damai.cn/accountinfo/myinfo,页面标题等于"我的大麦-个人信息"才算登录成功(tools.py);验证通过且本地无缓存则 pickle 落盘(L244-L245);
  4. 动态取参:get_api_param()抓取线上 JSvue-pc/0.0.70,用两组正则抽出commodity_param与ex_params(tools.py);
  5. 轮询:GETdetail.damai.cn/subpage,按ticket_price匹配skuId,读btnText决定分支(L253-L257);
  6. 两条购买路径:普通票走step2_click_buy_now,buyParam按固定约定拼成item_id_buy_nums_sku_id(L262-L263);选座票先取standId、拉全场座位 JSON,经format_valuable_seatid过滤、pick_seat选座(L267-L299);
  7. 提交:step3_submit_order比对观演人、置isUsed、选座时逐一写seatId,POSTbuy.damai.cn/multi/trans/createOrder,成功则打印alipayOrderId与支付链接(L190-L225)。

优先级关系很明确:cookie 回放是首选路径(省去 selenium 启动与反自动化对抗),selenium 登录是兜底;接口结构变化时脚本不盲目重试,而是把现场交给使用者——打印原始响应后退出。如果不把参数做成动态刮取,大麦一升级 JS 版本,脚本会静默失败且无从查起。

顺路跑通的流程之外,真正决定这类脚本能不能用的,是每一步出错时的行为。

四、护栏:抓取失败与信息过时怎么办

脚本没有异常重试框架,但每个失败点都有显式的退出条件和人话提示:

  1. cookie 回放后验证不过:『登录失败, 请检查登录账号信息。若使用保存的cookies,则删除cookies文件重新尝试』(L241-L243)——这一条的设计意图是把提示直接指向最可能的故障源(本地缓存的 cookies 过期),而不是笼统的"登录失败";
  2. selenium 流程走完后仍用接口复核,标题不符则打印『登录异常,请检查页面登录提示信息』(tools.py)——原文写着标题必须等于damai_title才继续,意思是"页面看起来过了"与"真的过了"分开判定,防止假阳性放行;
  3. __INIT_DATA__解析失败:『获取购买必备参数异常,请重新解析response返回的参数』,随后把原始响应完整打印(L108-L111)——解析失败就把现场交给使用者,接口变更从此是 5 分钟的定位问题,而不是盲查;
  4. 观演人不匹配:『请检查输入的观演人信息与大麦网观演人信息是否一致』(L196-L198)——与服务端实际返回的名单逐项比对,一个字的错别名不会浪费一次 createOrder 请求;
  5. 售罄:『手慢了,该票价已经售空』(L258-L260)——缺货登记是终态,立即退出,不继续轮询;
  6. 登录前主动去掉自动化指纹:excludeSwitches: ['enable-automation']加--disable-blink-features=AutomationControlled(tools.py),注释直说『若关闭会导致出现滑块并无法进入』——这一条的设计意图是把登录页识别 selenium 的门槛挡在流程之外。

这些护栏背后是同一件事:出错时把现场完整交给使用者并快速退出。能搬走的,是下面这组通用模式。

五、启示:可以搬走的 5 条设计模式

  1. 接口优先(HTTP over UI):只在接口替代不了的环节(登录)保留 UI 自动化。可迁移做法:写自动化前先抓包目标页面的"点击"实际发出哪些请求,用 requests 复刻,selenium 只留给登录与滑块。
  2. cookie 持久化 + 轻量探针:cookie pickle 落盘(tools.py),每次运行先做一个最轻的带认证 GET 验证会话是否有效(tools.py)。可迁移做法:任何长任务自动化,进入主流程前先用最轻认证请求验证会话,别让过期会话跑完全程才发现。
  3. 动态取参代替硬编码:原文写着『可能大麦网js代码更新后需要修改此函数内的代码以重新获得参数信息』(tools.py),意思是把维护成本直接写进函数注释里,而不是藏起来。可迁移做法:"可能会变"的参数结构写成抓取代码,并在 docstring 标注参数来源与失效条件。
  4. 原样搬走站点签名函数:signcode.js是标准 MD5 的纯 JS 实现(初始向量1732584193等常量见 signcode.js),拼接h5_token&timestamp&12574478&params后由execjs调用原函数名calcaulate执行(tools.py)。可迁移做法:接口要求 sign 时先看前端 JS 里是否就是标准算法,是就搬函数、用 JS 引擎本地跑,不自己重写也不逆向猜。
  5. 轮询状态机穷举终态: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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询