简介:这是一份面向有Python基础、希望在大麦网抢票场景中提升操作效率的开发者与票务用户的自动化脚本资源。脚本基于Selenium WebDriver实现浏览器模拟操作,可覆盖页面刷新、登录、购票按钮点击、表单填写及提交订单等关键环节,帮助使用者减少手动操作延迟带来的购票劣势,同时也可作为学习浏览器自动化的参考案例。压缩包共2个文件,包含一个py格式的抢票主脚本和一个txt格式的用户信息配置文件,包体仅5KB,结构轻量、便于直接查看和修改。资源目前已有22056人浏览学习,说明其在自动化抢票与Selenium应用领域具备一定关注度。需要留意的是,脚本源自知乎技术分享,原始内容存在改动痕迹,适用时建议结合大麦网当前页面结构和实测教程进行调整,并关注网站防爬策略变化。通过阅读源码,读者可以了解Selenium控制浏览器的常见写法、抢票工具的基本实现思路,以及如何在本地配置用户信息并尝试运行与调试。
1. 抢票脚本资源:先搞明白它到底帮你省了什么事
手动抢票最憋屈的不是手速慢,而是你看着“无票”提示时,根本不知道它是在你提交请求前还是提交后卖光的。这份资源是一套基于 Python 的票务平台抢票脚本,核心思路是用代码在开售瞬间发起请求,把手动刷新、选择票档、填写观演人这些步骤压缩到秒级。它适合有 Python 基础、想研究自动化下单流程的开发者,也适合被抢票折磨但愿意折腾脚本的普通用户。脚本不保证成功,但能把你的操作延迟从“秒”降到“毫秒”,这就是它在开售场景里最大的价值。解压后你需要自己配置账号、场次和票档参数,本质上是一个半自动工具。
2. 脚本原理与选型:为什么用 Python 而不是手刷或浏览器插件
这一章先把原理讲透,再谈选型。很多拿到脚本就跑的人,遇到问题后只能盲猜,就是因为没搞懂抢票脚本到底在做什么。
2.1 抢票的本质:请求重放与状态管理
抢票不是什么玄学,本质是 HTTP 请求与状态管理的竞速游戏。你手动操作时,浏览器替你完成了登录、进入详情页、选择场次、选择票档、填写观演人、提交订单这一串动作。脚本要做的,就是把这一串动作对应的接口请求按顺序重放出来,并且在整个过程中保持一个稳定登录的会话状态。
为什么手动拼不过脚本?因为浏览器每次点击都要加载整个页面和静态资源,一次交互往往要花一两秒,而脚本可以直接请求后端接口,跳过页面渲染,一个请求几十毫秒就能返回。尤其在库存从“有”变“无”的那几百毫秒里,手动用户可能还卡在票档下拉框里,脚本已经到了订单确认页。
接口请求的核心是会话状态。票务平台的前端页面只是一个壳,真实的库存数据和下单入口都在接口里,而这些接口普遍要求请求者携带 Cookie、Referer、csrf token 等状态信息。脚本中使用 requests.Session 对象保存登录后写入的 Cookie,后续请求会自动携带。这样的设计让用户不必关心浏览器内部状态,只需维持好 Session 即可。
我拆过几套抢票脚本,发现做得好的和做得烂的之间最大的差距就在于状态管理。有的脚本登录后直接用全局变量存一个 token,但 token 在开售前就过期了;有的脚本把登录函数放在线程内部,每个线程重新登录,反而互相挤掉线。正确做法是登录只做一次,Session 在多个线程间共享,并且定期用一个轻量接口检测登录是否仍然有效。
2.2 脚本的核心模块拆解:登录、信息采集、抢票主循环、订单提交
一个完整的抢票脚本通常拆成四个模块:登录模块、信息采集模块、抢票主循环模块、订单提交模块。登录模块负责模拟登录并保存会话状态;信息采集模块负责拉取目标场次的实时库存和票价;抢票主循环模块在开售时间点反复请求下单接口;订单提交模块负责把观演人、收货方式等信息封装成 POST 参数。
这四个模块的调用顺序直接影响成功率。我自己的习惯是:先跑登录模块,再跑信息采集模块拿到场次列表和票档 ID,接着进入开售等待循环,最后在循环内反复调用订单提交模块。这样做的原因是信息采集模块可以动态刷新场次 ID,避免平台改版后写死的 ID 全部失效。
每个模块都需要在日志里留下痕迹。信息采集模块至少要打印出当前库存量,抢票主循环要记录每次请求的状态码和返回关键字段,订单提交模块要输出完整的返回原文。因为抢票失败的原因千奇百怪,没有日志你只能靠猜。很多初学者只关注提交订单的代码,忽略了日志,结果出错后连是哪一步失败都不知道。
这里有个容易犯的错:把信息采集和订单提交放在同一个函数里,导致每次抢票循环都先去拉一遍场次列表。实际上场次列表在开售前已经固定,只需要请求一次。库存查询和下单应该单独走快捷接口,不能把页面数据接口混进来。混用的话,每次循环多出几百毫秒,错过的时间窗会被放大很多倍。
2.3 技术选型对比:requests 请求直连 vs Selenium 浏览器模拟
选 requests 还是 Selenium,本质上是在速度和稳定性之间做权衡。requests 直接构造 HTTP 请求,速度飞快,但需要你手动分析接口参数、加密逻辑和登录过程;Selenium 模拟真实浏览器操作,不用关心接口细节,但每次点击都会加载页面,延迟以秒为单位,开售瞬间很容易错失窗口。
我个人的选择流程是:先用浏览器开发者工具抓包,看目标平台的登录接口和下单接口复杂度。如果接口参数是明文,而且没有复杂的 JS 加密,就毫不犹豫用 requests。如果发现密码是 RSA 加密、请求头里有动态 signature、或者页面需要滑块验证,那 requests 方案就要花大量时间逆向,不如直接用 Selenium。
这套脚本资源里两种方案都有对应实现。普通场次用 requests 方案就能跑进几十毫秒,遇到加密参数复杂的大平台再切到 Selenium 方案。Selenium 方案也不是一味地慢,你可以通过隐藏浏览器界面、禁用图片加载等手段把单次操作压到几百毫秒,但无论如何都比不上 requests 直连。我记得有一次用 Selenium 抢一个热门演唱会票,页面刚加载完就显示无货,换成 requests 直连接口后反而成功捡到一张回流票。
如果你不确定平台属于哪一类,可以用一个很笨但有效的办法:在非开售时间手动用脚本模拟一次下单流程,看接口返回的错误信息是“参数错误”“签名错误”还是“无权限”。参数错误说明接口明文,签名错误说明有加密逻辑,无权限说明需要先处理登录态。这样分类比瞎猜靠谱得多。
3. 环境搭建与依赖安装:从零跑通脚本的第一步
环境搭建是抢票脚本的分水岭,很多人卡在这一步不是因为写不对代码,而是因为依赖环境一团乱麻。
3.1 Python 环境准备与虚拟环境
脚本基于 Python 3,建议使用 3.8 以上版本。我在多台机器上跑过,3.10 和 3.11 都兼容得很好,原因是用到的 requests、beautifulsoup4、pandas 这些库在新版本 Python 下都持续更新。如果你电脑里已经装了多个 Python 版本,一定要用虚拟环境隔离依赖,避免系统环境中某个包版本过旧,把脚本拖进莫名其妙的报错里。
创建虚拟环境的命令非常简单,但很多人会跳过这一步,直接全局安装依赖。等到别的项目升级了 requests,这个抢票脚本就开始出怪问题:有时是 Cookie 处理不变,有时是连接池报错。虚拟环境的后悔药效果极好,几分钟就能重建一个干净环境。
python3 -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install --upgrade pip逻辑说明:python3 -m venv venv 会在当前目录下创建一个名为 venv 的文件夹,里面包含独立的 Python 解释器和 bin 目录。source 命令激活环境后,终端提示符前面会出现 (venv) 标记,后续 pip 命令都会安装到这个孤立环境里,不会污染系统 Python。升级 pip 是为了避免旧版 pip 在解析依赖时出现版本兼容问题。
参数说明:venv 是虚拟环境目录名,你可以改成 .venv 或者 env,但注意不要和源代码目录混淆。Windows 系统的激活命令不同,在 cmd 里执行 venv\Scripts\activate.bat,在 PowerShell 里执行 venv\Scripts\Activate.ps1。激活后可以通过 which python(Windows 为 where python)确认当前使用的解释器路径在虚拟环境内。
3.2 安装依赖:requests、beautifulsoup4、pandas 等
解压资源后,先看有没有 requirements.txt。一般我都会先执行 pip install -r requirements.txt,如果没有这个文件就手动安装核心依赖。这份资源对应的完整依赖列表我整理成了四个核心库:requests 负责 HTTP 请求,beautifulsoup4 负责解析 HTML 响应,pandas 负责整理日志中的库存数据,lxml 是 beautifulsoup 的解析引擎。
pip install requests==2.28.1 pip install beautifulsoup4==4.11.2 pip install lxml==4.9.2 pip install pandas==1.5.3逻辑说明:这里通过 == 锁定了版本号,主要目的是避免未来库接口变更导致脚本失效。requests 2.28.1 支持到 Python 3.7+,并且修复了多个连接池问题;beautifulsoup4 4.11.2 配合 lxml 解析速度比默认的 html.parser 快不少,尤其在处理大段 HTML 时差距明显。pandas 用于从接口返回的 JSON 里整理场次和库存,调试阶段可以把数据打印成表格。
参数说明:如果你在国外或网络受限,可以给 pip 加 -i 参数切换镜像源,但要注意,不同镜像源同步速度略有差异,个别版本号可能滞后,导致锁定版本安装失败。遇到这种情况,去掉版本号安装最新版即可,但后果是后续代码可能需要微调。另一个容易踩的坑是 lxml 在 Windows 上需要编译环境,建议从 pypi 下载对应 Python 版本的预编译 wheel 文件,而不是用 pip 现场编译。
3.3 配置文件与全局变量:把账号、场次、时间参数分离
我见过最混乱的抢票脚本是把账号密码、场次编号、票档编号、观演人 ID 全部写死在请求函数里,每次换场次都要在代码里找半天。资源里给出的做法是单独维护一个 config.py 文件,把可变参数集中到一个字典中。这一方面方便不同场次复用,另一方面也降低了误改代码逻辑的风险。
# config.py CONFIG = { "username": "你的手机号", "password": "你的密码", "show_id": "1273482", # 演出项目ID,从详情页URL获取 "session_id": "20240520", # 场次ID,对应具体演出日期 "price_id": "880", # 票档ID,可在价目表接口查到 "buyer_ids": "9182,9183", # 观演人ID,逗号分隔 "start_time": "2024-05-20 10:00:00", "rush_threads": 3, "sleep_interval": 0.2, }逻辑说明:username 和 password 是登录凭据;show_id 是演出项目的唯一编号,通常可以从详情页 URL 的路径参数中获得;session_id 精确到某一天或某一场,注意它和 show_id 是父子关系,不能混用;price_id 对应具体票价档位,比如“看台508元”或“内场1680元”;buyer_ids 是下单时要提交的观演人身份 ID,多个 ID 用逗号隔开;start_time 是开售时间,脚本会根据这个时间计算休眠多久;rush_threads 和 sleep_interval 分别控制并发线程数和线程间间隔。
参数说明:show_id 和 session_id 的获取方式有个很实用的技巧:在浏览器打开目标场次的详情页,地址栏里通常有一串数字,第一个是 show_id,第二个是 session_id,如果网址里只有一个,就从页面源码里搜索 showId 和 sessionId 关键字。price_id 需要先调用一次查询价目的接口,返回的 JSON 数组里会列出所有票价档位和对应 ID。buyer_ids 可以在常用观演人页面里查看账号对应的数字标识。注意这几个参数的匹配关系,配错了会报“场次不存在”或“该票档不可售”。配置完成后,我习惯用一段自检代码打印出所有参数类型,确保没有字符串和整数混用的问题。
4. 核心流程实现:从登录到提交订单的关键代码与参数说明
这一章是脚本的主干,每一段代码都可以直接抄到自己的项目里改一改,但更要理解为什么这样写。
4.1 会话登录与 Cookie 维持
抢票脚本的登录模块看似简单,但我在复现时发现很多版本都栽在登录状态失效上。正常浏览器登录后,平台会种下几个 Cookie,后续请求靠它们识别身份。requests 库的 Session 对象能自动保存 Cookie,但要注意登录请求本身需要携带正确的请求头和表单字段。
import requests def login(username, password): s = requests.Session() s.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0 Safari/537.36", "Referer": "https://xxx.com/login", "Origin": "https://xxx.com", }) login_data = { "username": username, "password": password, } login_url = "https://xxx.com/api/login" res = s.post(login_url, data=login_data, timeout=3) if res.status_code != 200: raise RuntimeError("登录失败, 状态码: %s" % res.status_code) return s逻辑说明:这里通过 requests.Session 初始化一个会保存 Cookie 的会话对象,并用 headers.update 设置了 User-Agent、Referer 和 Origin 三个请求头。很多平台会校验请求头来源,如果你直接用一个默认的 python-requests 标识,会在登录阶段就被风控拦下。login_data 里的字段名需要根据实际接口调整,有的平台用 account,有的用 phone,有的用 login_name,需要抓包确认。
参数说明:timeout=3 表示 3 秒无响应就抛出异常。如果登录接口返回 200 但响应体里其实包含“密码错误”或“账号锁定”信息,脚本不会自动判断,所以这里建议加上对响应内容的检查。资源里内置了一个简单的检测函数:它会把返回的 JSON 解析后查找 error 字段,如果存在就打印错误信息并退出。登录成功后,返回的 Session 对象就是整个抢票流程的通行证,后续的库存查询和订单提交都必须使用同一个 Session,不能重新创建。
4.2 抢票主循环:多线程与时间窗口
开售瞬间的抢票竞争本质上是并发请求的竞争。单线程顺序执行时,每次请求的耗时包括网络往返和接口响应时间,通常要几百毫秒,在这几百毫秒里库存可能已经被抢光。多线程可以把这些请求并行发出,在相同时间内发送更多次请求,但线程数不是越多越好。
import threading import time import logging def rush(session, info): while True: try: result = submit_order(session, info) if result.get("success"): logging.info("抢票成功, 订单号: %s", result.get("orderId")) break except Exception as e: logging.error("请求异常: %s", str(e)) time.sleep(CONFIG["sleep_interval"]) def start_rush(session, info): for i in range(CONFIG["rush_threads"]): t = threading.Thread(target=rush, args=(session, info)) t.daemon = True t.start()逻辑说明:rush 函数是每个线程的工作函数,它不断调用 submit_order 去尝试下单。如果返回结果里的 success 字段为 True,就记录订单号并退出循环;如果抛异常,则记录错误并等待 sleep_interval 后再试。start_rush 会创建指定数量的线程并全部启动,daemon=True 让线程随主进程退出而停止,避免开售结束后还残留后台线程。
参数说明:CONFIG["rush_threads"] 建议设置在 3 到 5 之间。3 是大多数平台的容忍下限,超过 5 容易触发每秒请求数限制。CONFIG["sleep_interval"] 是每次请求之间的间隔,0.2 表示 200 毫秒。如果你的网络延迟很低,可以适当降低到 0.1,但不要取消间隔,否则会被平台封禁。这里还需要注意,所有线程共用一个 Session 对象,requests 库的 Session 是线程安全的,但如果你自己加入了非线程安全的操作(例如更新全局计数器),需要加线程锁。
主线程还需要在开售前等待到精确时间点才能启动这些线程。开售前 1 秒启动线程,线程内部第一次请求正好落在开售瞬间。等待逻辑可以用一个简单的 while 循环实现,每次读当前时间并和目标时间比较。
from datetime import datetime target_ts = datetime.strptime(CONFIG["start_time"], "%Y-%m-%d %H:%M:%S").timestamp() while time.time() < target_ts - 1: time.sleep(0.1) if time.time() - target_ts < -0.1: time.sleep(target_ts - time.time() - 0.1) start_rush(session, info)这段代码先计算目标时间戳,在开售前 1 秒退出快速等待循环,然后精确休眠到目标时间点前 0.1 秒,最后启动线程。这里提前 0.1 秒是为了抵消线程启动和系统调度的误差,保证第一次请求发出时正好是开售时刻。注意,这里依赖的是本机时间,如果本机时间与服务器时间偏差较大,需要先做时间校准(见第 5 章)。
4.3 订单提交与库存校验
订单提交是整个脚本里参数最多、最容易出错的部分。先查询库存,再提交订单,两个请求之间只有几十毫秒的时间窗。库存查询可以用 GET 请求轻量完成,订单提交必须用 POST,并携带观演人列表、数量等业务参数。
def submit_order(session, info): stock_url = "https://xxx.com/api/stock/check" stock_params = { "showId": info["show_id"], "sessionId": info["session_id"], "priceId": info["price_id"], } stock_resp = session.get(stock_url, params=stock_params, timeout=1) stock_data = stock_resp.json() remain = stock_data.get("data", {}).get("remainCount", 0) if remain <= 0: return {"success": False} order_url = "https://xxx.com/api/order/create" order_data = { "showId": info["show_id"], "sessionId": info["session_id"], "priceId": info["price_id"], "buyerIds": CONFIG["buyer_ids"], "count": CONFIG.get("count", 1), } order_resp = session.post(order_url, data=order_data, timeout=2) return order_resp.json()逻辑说明:stock_url 返回的 JSON 结构里,我们通过 data.remainCount 获取当前库存量,字段名需要根据实际接口调整。如果库存小于等于 0,直接返回失败,不再构造下单请求。order_url 是下单接口,提交的数据中 buyerIds 是观演人 ID 字符串,count 是购票数量。这里要注意的是,下单接口可能要求数据以 JSON 格式发送,此时需要改用 Session.post(order_url, json=order_data)。
参数说明:timeout=1 和 timeout=2 分别控制查询和下单请求的超时时间。查询库存必须快,1 秒足够;下单允许稍慢,给 2 秒。下单接口返回的 JSON 中通常包含 orderId 和 success 字段,脚本主循环通过 success 字段判断是否成功。但有些接口即使成功也返回 200 和 success=True,却只生成待支付订单,并不保证支付成功。也就是说,脚本帮你完成了“锁库存”和“生成订单”两步,后面的支付仍然需要手动完成。
资源里其实还提供了一个“回流票蹲守”的变体,在订单提交失败后自动切换到全价票档,而不是一直盯着原票档。这个变体的逻辑是:如果目标票档返回“已售罄”,就自动把 price_id 切换为备选票档的 ID,再尝试一次。我实测过,这个策略在回流票出现时能提高一点成功率,但也会增加下单风险,所以要在配置文件里显式打开这个功能,避免误购。
5. 避坑与常见问题:实测中翻车的五件事
以下五条是根据多轮复现和实跑踩出来的坑,每一条都附上现象、原因和解决方式,值得在开抢前过一遍。
5.1 登录状态过期导致抢票失败
现象:脚本在开售前测试接口一切正常,开售瞬间登录接口返回 302 或者提示“请重新登录”。
原因:抢票线程在开售时才调用 login(),从发起登录到 Cookie 写入再到访问下单接口,中间有几百毫秒的空窗。另外,部分平台会在开售当天收紧会话有效期,原本 24 小时的 Cookie 在几分钟内就可能失效。
解决:把登录动作提前到开售前至少 5 秒完成,让所有线程复用同一个 Session 对象。每 30 秒调用一次轻量接口(比如查询当前用户信息)来续期 Cookie。如果仍然失效,就在 Session 初始化后增加一个检查函数,检测到登录失效时自动重新登录,然后再继续抢票循环。重新登录的接口调用频率要低,否则会触发二次风控。
5.2 场次 ID 写死导致接口变动后全部失效
现象:脚本在测试时能查到库存,开售当天却查不到任何数据,日志显示接口返回“参数错误”。
原因:平台改版后接口的字段名或 ID 类型发生了变化,写在配置里的 show_id 和 price_id 不再匹配。常见做法是配置写死了一个数字,而没有先拉取一次最新的场次列表,结果平台调整了场次顺序或 ID 生成规则,脚本自然就失效了。
解决:在脚本启动时先调用“获取场次列表”接口,通过演出名称匹配目标场次,再把返回的 ID 传给后续函数。也就是让 show_id 和 session_id 从配置项改为动态解析。我习惯写一个 get_real_id 函数,遍历接口返回的列表,找到名称与目标匹配的记录,输出它的场次 ID。这样即使平台调整了 ID,只要演出名称没变,脚本都能自愈。
def get_real_id(session, target_name): url = "https://xxx.com/api/show/list" shows = session.get(url).json()["data"]["list"] for item in shows: if target_name in item["name"]: return item["id"] raise ValueError("未找到目标场次")逻辑说明:这个函数会拉取场次列表,然后在内存中匹配名称。target_name 可以是演出名的一部分,比如“某某巡回演唱会”,匹配成功后返回的 id 就是真正的 session_id 或 show_id,取决于接口定义。这样做的好处是脚本不依赖写死的数字,即使平台改版后 ID 从 8 位变成 10 位,也能正常找到目标。
参数说明:这里假设接口返回的 data.list 是一个数组,每个元素有 name 和 id 字段。实际接口可能有分页,需要在循环里处理分页参数。如果列表太大,可以先加上日期过滤条件减少匹配范围。
5.3 并发过高触发风控被封
现象:开售后脚本以 10 个线程高频请求,两分钟后所有请求返回 403,随后登录也被拒绝。
原因:平台的频率限制策略根据 IP 和会话令牌统计请求次数,短时间内大量请求会被识别为机器行为。线程数超过 5 后,成功率反而下降,因为请求全部被封禁,连正常手动登录也被牵连。
解决:把并发控制在 3 到 5,sleep 间隔保持在 0.1 到 0.3 秒之间。另外,不要用同一账号同时跑多份脚本,多个设备并发同一个账号更容易触发风控。被封后需要等待数小时或者更换网络环境才能恢复,所以提前压测线程数值得花点时间。我通常在测试环境用 10 个线程跑 30 秒,观察有没有 403 响应,如果有就降低线程数,直到稳定。
5.4 系统时间不同步导致提前或延后
现象:脚本在开售前等到 start_time 才启动,但平台服务器已经显示“已售罄”,或者脚本提前开始请求却被接口拒绝。
原因:本机时间和服务器时间存在几十秒偏差,尤其在 Windows 机器上,系统时间同步间隔较长。脚本里 time.time() 读取的是本机时间,不是服务器时间,导致触发时机偏早或偏晚。
解决:在脚本启动时获取平台服务器时间戳,通常可以从任意接口响应的 Date 头中读取。计算本机与服务器的偏移量后,再用偏移量校准 start_time。校准方法很简单:先测一次服务器时间,减去本机时间得到偏移量,之后的等待循环判断条件改成 time.time() + offset >= target_ts。资源里有一个 time_sync.py 工具函数,封装了联网获取服务器时间的逻辑。这个细节能让整个脚本的节奏准确到秒级。
import requests from datetime import datetime def get_server_time(session): resp = session.get("https://xxx.com/api/ping") date_str = resp.headers.get("Date") return datetime.strptime(date_str, "%a, %d %b %Y %H:%M:%S GMT").timestamp()逻辑说明:这个函数从任意接口的响应头里读出服务器时间并转换为时间戳。HTTP 协议规范要求 Date 头必须是这个格式,所以解析不会出错。拿到服务器时间后,你在主等待循环里用 time.time() + offset >= target_ts 来判断是否到达开售点,其中 offset = server_time - local_time。
参数说明:注意一定要用 datetime.strptime 严格匹配格式,大小写敏感。如果接口返回的 Date 头为空或者格式异常,可以换一个静态资源接口再试。时间校准只需要做一次,不需要在每个线程里重复请求。
5.5 依赖版本冲突导致脚本崩溃
现象:脚本安装后能运行,但开售瞬间报错 AttributeError: module 'requests' has no attribute 'get' 或 lxml 解析中文乱码。
原因:系统里已经存在旧版本的 requests 或 lxml,pip 安装了新版,但运行时被别的路径下的旧版本抢先导入。另一类情况是 beautifulsoup4 和 lxml 的版本匹配不上,导致解析器内部崩溃。
解决:强制在虚拟环境中重新安装所有依赖,并检查当前实际加载的版本。用下面的命令确认 requests 和 lxml 的版本号是否与 requirements.txt 一致。
python -c "import requests; print(requests.__version__)" python -c "import lxml; print(lxml.__version__)"如果发现版本不一致,使用 pip install --force-reinstall 重新安装指定版本。另外,不要在系统 Python 里直接跑脚本,系统环境通常有太多不明依赖。我是被坑过一次后才养成习惯:每次换机器第一件事就是重建虚拟环境并跑一遍完整流程,确认依赖稳定后才继续调脚本。
6. 进阶用法与验证:让脚本更可靠的两个习惯
6.1 日志记录与返回原文校验
抢票脚本最容易出现“假成功”的情况:提交订单返回 200,但 JSON 里提示“该票档已无货”。如果只看状态码,就会误判,甚至以为自己抢到了,结果进入待支付列表才发现根本没下单成功。所以我会在关键请求处把返回原文记录下来,并写入带时间戳的日志文件。
import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s", filename="rush.log" ) logger = logging.getLogger("rush") def submit_order(session, info): # 其他逻辑 logger.info("stock_resp: %s", stock_resp.text[:200]) logger.info("order_resp: %s", order_resp.text[:200]) return order_resp.json()这样每次请求都能留痕,开售结束后可以翻日志复盘。我一般会把日志文件名带上日期,比如 rush_20240520.log,方便按场次归档。日志里除了返回原文,还要记录每次请求的时间戳、线程号和服务器返回的响应时间,这些信息能帮你判断是网络原因还是接口原因导致失败。
6.2 定时任务与预约提醒的配合
脚本解决的是“抢”的动作,但“什么时间抢”同样重要。我会在开售前五分钟跑一个提醒脚本,把目标场次的票价和库存变化打印出来,及时调整抢票策略。这个提醒脚本可以复用主脚本的信息采集模块,只是不执行提交订单。定时任务用操作系统自带的 crontab 或 Windows 计划任务即可,不需要额外依赖。
# 每天 9:55 检查库存并写入日志 55 9 * * * cd /path/to/script && python remind.py >> remind.log 2>&1提醒脚本输出的数据能让你在开售前了解到目标场次的实时库存趋势。如果发现某个票档库存已经变灰或者被抢光,可以临时调整配置里的 price_id 改抢另一个票档。毕竟脚本抢到的票也需要你手动确认支付,提前知道自己目标场次的状态能减少无效抢票。
这套脚本我前后拆过三四套,最深的感受是:抢票脚本的关键不是代码写得多么花哨,而是把会话状态、时间校准、并发阈值和日志记录这四件事做扎实。以前我有一场演出,脚本第一轮抢到票但没支付成功,第二轮调大了线程数,结果账号被风控,连手动都进不去。从那以后,我每次开抢前都会先在测试环境里跑一遍全流程,检查日志里的请求间隔和返回码,确认没有异常才放到真实场次上。希望这些经验能帮到你。
本文还有配套的精品资源,点击获取