ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Python监控演唱会回流票:接口轮询与SKU状态检测实战

Python监控演唱会回流票:接口轮询与SKU状态检测实战 简介一份用于学习演唱会门票回流监控与自动抢购逻辑的Python项目分析资料适合对票务自动化、爬虫与反爬策略感兴趣的初中级开发者。压缩包内共67个文件以py源文件、pyc编译文件、json配置文件及zbak备份为主整体约96KB目录包含监控模块、模拟登录、浏览器Cookie获取、二维码登录及多平台适配脚本如Monitor_DM、Monitor_PXQ、Monitor_MY等结构清晰。已有193人学习下载。内容重点讲解回流检测、库存实时监测、二次定制等核心机制并展示如何结合模拟登录与配置文件实现跨平台监控同时强调需遵守票务平台规则与法律法规避免账户风险。对想快速上手Python自动化购票、理解抢票工具设计思路的读者具有实用参考价值。1. 演唱会回流票监控从秒罄到捡漏的机制拆解一场热门演唱会开票后真正的战斗发生在开售后 20 到 40 分钟。这个窗口里未支付订单被系统陆续释放回流票源零星出现在票务平台库存中。手工刷新页面去搏这种退票回流手速和运气各占一半。PyTicketMonitoring 这类 Python 监控脚本做的事情就是把这个窗口盯住轮询票务接口检测 SKU 从不可售变为可售触发后续抢购请求。它适合有 Python 爬虫基础、想研究接口轮询与状态机判定的开发人员作为个人学习项目去拆解而不是为了突破平台限制去做量产工具。下面以解压后的项目文件为线索把登录、监控、触发与排错整条链路拆开讲。2. 项目结构与登录态获取Cookie 是整个监控链路的地基2.1 解压后的目录与运行环境压缩包解开后是典型的 venv 隔离环境加源码混合结构。venv/pyvenv.cfg标注了 Python 3.9依赖清单在requirements.txt主要用到 requests、websocket、python-dotenv、qrcode 这几类库。其中 websocket 用于部分票务接口的长连接消息推送qrcode 配合登录流程生成扫码图dotenv 负责读取.env里的敏感配置。源码集中在src目录下核心文件职责如下文件职责main.py/start.py启动入口加载配置并拉起监控任务src/monitor/Monitor.py监控调度主模块统一管理各平台监控实例src/monitor/Monitor_DM.py大麦回流监控实现src/monitor/Monitor_MY.py猫眼回流监控实现src/monitor/Monitor_PXQ.py票星球回流监控实现src/monitor/Monitor_FWD.py针对另一渠道场次的扩展监控src/simulateLogin/Login_DM.py大麦模拟登录生成二维码完成扫码src/openBrowerGetCookie.py拉起浏览器自动化获取 Cookie 并落盘wrapper-test.py/test.py接口包装与回归测试Dockerfile.zbak说明原作者做过容器化尝试但最终放弃原因后面排错章节细说。第一次跑项目直接激活 venv 装依赖即可不推荐在宿主机全局环境跑依赖版本冲突会浪费很多时间。cd PYTicketMonitoring-master source venv/bin/activate pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple激活虚拟环境后pip list里能看到 playwright、requests、websocket-client 等核心依赖。如果activate提示权限不足先检查当前用户对 venv/bin 目录是否有写权限而不是急着用 sudo 重新安装避免把虚拟环境的软链搞乱。2.2 登录态的三种获取方式与选型票务平台的购票接口基本都要求登录态而登录态在爬虫场景里最常见的载体就是 Cookie。项目里提供了三种获取路径按自动化程度从高到低排列simulateLogin/Login_DM.py模拟大麦的账号密码登录流程运行后生成二维码图片QRCode_DM.png手机扫码后自动保存会话。openBrowerGetCookie.py通过 playwright 拉起一个真实浏览器窗口用户手动登录一次脚本从浏览器上下文里抽取 Cookie 写入cookie目录。手动方式从浏览器开发者工具里复制完整 Cookie粘贴进cookie/*.txt。我一般推荐第二种原因很直接票务平台对账密登录的验证码和滑块拦截率很高模拟登录链路维护成本大而手动登录一次拿到的是人工环境下产生的完整请求上下文风控特征更接近正常用户。账号密码登录方案适合做全自动化但遇到滑块要么接打码平台要么就频繁断链。openBrowerGetCookie.py核心逻辑是启动浏览器后进入指定演出详情页等待用户完成扫码或账密登录然后从 cookie 存储里序列化出user_token、ci这类关键字段。拿到 Cookie 之后先做一个最小化校验确认登录态是活的再进监控循环。def validate_cookie(cookie_str: str, headers: dict) - bool: resp requests.get( https://api.example.com/user/info, headers{**headers, Cookie: cookie_str}, timeout10, ) if resp.status_code ! 200: return False data resp.json() return data.get(ret, -1) 0 and data.get(data, {}).get(nick) is not None这段代码的核心价值在于把「Cookie 是否有效」从主观判断变成可编程判定。ret字段是业务状态码不同平台的用户信息接口返回值结构不同但思路一致先看 HTTP 状态码排除网关层拦截再看业务码排除登录态失效。nick字段只是顺带确认返回体里确实有用户数据避免拿到的是一段缓存页面而不是 JSON。2.3 Cookie 落盘与失效判定登录完成后Cookie 会按平台写入独立文件比如cookie/dm_cookie.txt、cookie/my_cookie.txt。监控模块启动时统一加载放在一个全局 Session 对象里复用。这里有个容易踩的坑Requests Session 默认会自动管理部分 Cookie 的更新但如果响应头里有Set-Cookie且和本地文件不同步几轮请求之后本地文件和实际会话就脱节了。我的做法是每次监控结束后强制用session.cookies覆盖落盘下次启动直接加载最新值。同时定期用 2.2 节里的校验函数做健康检查一旦返回 401 或业务码提示未登录立即停止抢购逻辑写日志并退出而不是拿着失效的登录态继续空转。3. 回流检测核心Monitor 模块的轮询循环与 SKU 状态判定3.1 四个监控模块的差异化设计Monitor_DM.py、Monitor_MY.py、Monitor_PXQ.py、Monitor_FWD.py都继承自Monitor.py里的基类但各自覆写了请求构造、响应解析和 SKU 提取逻辑。原因是四个票务平台的接口路径、字段命名、库存表达方式都不一样。以大麦为例演出详情接口返回的数据结构里perform包含场次信息skuList里每个元素代表一档价位的票最关键的字段是skuStatus和remainNum。猫眼的字段名则完全不同可售状态体现在soldOut布尔值和stock数字上。票星球又是一种风格库存状态分散在perform和extInfo两个字段里。{ perform: { performId: 1001, beginTime: 2025-06-01 19:30 }, skuList: [ {skuId: 50001, price: 580, skuStatus: 1, remainNum: 0}, {skuId: 50002, price: 880, skuStatus: 0, remainNum: 356} ] }上面这段 JSON 是简化后的票务响应模型。skuStatus为 0 表示档位可售为 1 表示售罄或下架。回流检测盯的正是skuStatus从 1 变为 0或remainNum从 0 变为正数的瞬间。这种状态变化在数据库里对应一条 UPDATE 语句在接口层表现为一次响应体字段差异。3.2 轮询主循环状态缓存与触发信号理解了数据面监控循环的实现就清晰了。核心思路是维护一个last_status字典每次请求后对比新旧状态发现从不可售变为可售就触发回调。class ReflowMonitor: def __init__(self, session, sku_id: int, interval: float 1.5): self.session session self.sku_id sku_id self.interval interval self.last_status {} self.running False def check(self) - dict: resp self.session.get(self.detail_url, timeout8) data resp.json().get(data, {}).get(perform, {}) current {} for sku in data.get(skuList, []): current[sku[skuId]] { status: sku[skuStatus], remain: sku[remainNum], } changed [] for sku_id, info in current.items(): prev self.last_status.get(sku_id) if prev and prev[status] 1 and info[status] 0: changed.append(sku_id) self.last_status current return {changed: changed, current: current} def run(self): while self.running: result self.check() if result[changed]: self.on_reflow_triggered(result[changed][0]) time.sleep(self.interval)核心判定放在check方法里先解析最新响应中的 SKU 状态字典再和上一轮的last_status逐项比对。只有「上一轮不可售、当前轮可售」才判定为回流触发其他状态迁移全部忽略。on_reflow_triggered是留给下单模块的回调入口监控层不关心具体怎么抢票只管状态变更信号是否可靠。interval参数决定轮询密度。设 0.5 秒理论上响应更快但实际会被网关限流设 3 秒以上又容易错过回流窗口。我一般在 1.2 到 2 秒之间取值同时加随机抖动比如interval random.uniform(0, 0.3)避免请求节奏呈现固定频率特征。对于回流票这种极少量释放的场景1.5 秒的密度已经能覆盖绝大多数情况。3.3 请求头伪装与反爬边界轮询循环里最容易被忽略的是请求头的一致性。浏览器访问时Cookie、User-Agent、Referer、Accept-Language 是同时出现的而脚本默认只会带前两个Referer 缺失是风控系统最容易识别的异常特征之一。项目中用requests.Session统一管理请求头登录时从浏览器复制完整的头信息再按会话复用。session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36, Referer: https://detail.yoursite.com/perform/1001, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9, Origin: https://www.yoursite.com, })注意Origin头只在 POST 请求时需要GET 请求伪造 Origin 反而显得不自然。Accept要跟浏览器实际协商值保持一致有的接口返回 JSON有的返回 HTML 片段乱了就可能触发降级响应。风控系统会在几分钟内完成对请求频率、头完整性和 IP 维度的综合画像脚本层面能做的只有把请求做得像真实访客而不是突破风控。4. 抢购触发与 config 参数从可售信号到提交订单的实战配置4.1 监控到可售之后发生了什么回流信号触发后抢购链路才真正开始。完整动作序列是选中目标 SKU、调用下单接口生成订单草稿、确认票价与库存、提交订单。这一串操作必须在秒级完成否则回流票会被其他用户先锁定。Monitor.py里的触发回调指向purchase.py或直接调用平台的下单接口。以常见票务流程为例下单接口接收itemId、performId、skuId、buyNum四个参数返回订单号或错误码。请求方式一般是 POSTBody 里带 JSON头部 Cookie 必须完整。def submit_order(session, profile: dict): payload { itemId: profile[item_id], performId: profile[perform_id], skuId: profile[sku_id], buyNum: profile.get(buy_num, 1), } resp session.post(profile[order_api], jsonpayload, timeout5) result resp.json() if result.get(ret) 0: return result[data][orderId] if result.get(ret) in (401, 40011): raise CookieExpiredError(result.get(msg)) raise OrderFailedError(result.get(msg))这里把错误分两类处理401或40011表示登录态失效继续重试没有意义直接抛出CookieExpiredError让上层终止任务其他错误码针对具体业务条件比如「库存不足」「票价变动」「限购达到上限」每种都要打日志。我见过不少项目把这三类错误混在一起重试结果 Cookie 失效后还在疯狂请求下单接口账号直接被限制购票。4.2 config 参数表与实际取值建议config目录下的文件是整个任务的可调参数集合。不同平台的配置结构略有差异但核心字段都有对应关系参数含义建议值interval监控轮询间隔秒1.5 ~ 2.0request_timeout单次请求超时秒8max_retry下单失败重试次数3retry_delay重试间隔秒0.6buy_num购买数量1sku_id目标票价档位 ID从详情接口获取perform_id目标场次 ID从详情接口获取cookie_fileCookie 文件路径cookie/dm_cookie.txtheadless是否启用无头浏览器falseheadless参数值得单独提一下。首次运行时务必设为false让浏览器窗口打开人工确认登录、选座和支付环节都正常走通。确认无误后再开无头模式。跳过这一步直接无头运行如果下单流程里有验证码脚本会卡在一个无法恢复的状态。config里还有一个容易被忽略的字段zone_id它对应的是大麦这类平台的分区 ID。同一个场次下不同区域定位不一样抢回流票时如果只指定sku_id不指定区域可能出现订单锁定成功但座位超出预期价位区间的情况。务必将区域、价位、场次三者对齐后再启动。4.3 二次定制新增监控源的改动点Monitor_FWD.py的存在说明这个项目的扩展方式就是「复制一份监控模块改请求适配层」。新增渠道时标准的改动点有三个请求构造、Cookie 字段映射、SKU 解析。请求构造包括接口域名、查询参数和请求头Cookie 字段映射是因为不同平台登录态字段名不同整合进统一 Session 前要先做别名映射SKU 解析是工作量最大的部分涉及嵌套 JSON 中不同层级的状态字段提取。以Monitor_PXQ.py为参照新平台接入通常只需要修改parse_sku_list和build_request两个方法基类的轮询与触发机制可以整体复用。class Monitor_FWD(MonitorBase): def build_request(self, config): return { url: config[fwd_detail_api], params: {itemId: config[item_id], performId: config[perform_id]}, } def parse_sku_list(self, resp_data: dict): skus [] for item in resp_data[data][skuList]: status 0 if item.get(soldOut) is False else 1 skus.append({ sku_id: item[id], price: item[price], status: status, remain: item.get(quantity, 0), }) return skus这个扩展示例里有意的设计是每个平台子类只负责「输入请求」和「输出标准 SKU 结构」轮询密度、触发判定、下单回调这些公共逻辑全在基类。这样新增一个平台改动量从整个模块收敛到两个方法。wrapper-test.py就是用来验证这个收敛的每个子类跑一遍同样的测试用例集确认parse_sku_list输出结构一致。5. 运行验证与边界排错Cookie 失效、风控响应与扩展监控源5.1 启动前的配置自检第一次启动前先做三项检查确认 ticket 接口在本机网络可达Cookie 文件里包含完整登录字段config中interval和sku_id没有明显笔误。可以通过一行命令验证详情接口的返回结构是否符合解析器预期。curl -s -H Cookie: $(cat cookie/my_cookie.txt) \ https://__YOUR_DOMAIN__/perform/detail?itemId1001performId2001 \ | jq .data.perform.skuList[] | {skuId, skuStatus, remainNum} | head -20__YOUR_DOMAIN__替换成实际票务域名jq只提取 SKU 列表的三个关键字段。如果看到skuStatus和remainNum输出正常说明 Cookie 有效、接口路径正确、解析器能拿到数据可以启动python start.py。如果 jq 输出为空先查 Cookie 是否失效再确认接口参数名是否匹配。5.2 常见异常与处理建议轮询长时间无日志输出。这种情况大概率是接口被降级响应返回了一个固定缓存页而不是实时 JSON。处理方式是检查响应体里是否包含html标记如果是降低轮询频率并重试而不是增加并发。检测到可售但下单失败先看返回码ret为 0 但data里没有订单号通常是参数校验失败抓取完整请求体对比浏览器开发者工具里的真实请求重点核对perform_id和sku_id的类型是 int 还是 string。Cookie 过期日志里出现 401 或业务码 40011脚本会主动退出此时重新执行openBrowerGetCookie.py刷新会话不要试图通过修改过期时间字段来绕过鉴权。Dockerfile.zbak被注释掉的原因也在这里容器环境里请求来源 IP 和 TLS 指纹特征与宿主机不一致轮询空闲期容易触发额外验证Cookie 过期概率显著升高。本地 venv 直跑比容器方案稳定得多这也是原作者保留Dockerfile.zbak而不是Dockerfile的原因。验证整个链路是否通顺用tail -f monitor.log观察一轮完整周期详情请求成功、状态缓存更新、无异常堆栈然后等一次真实回流确认触发回调里有订单提交日志。本文还有配套的精品资源点击获取
返回列表