简介:以演唱会抢票为实战场景的Python项目素材,面向有一定基础、希望深入接触网络请求与并发控制的开发者,覆盖爬虫、多线程/进程、定时任务与模拟登录等核心知识点,适合作为自动化脚本练习与编程进阶的入门项目。资源以rar压缩包提供,体积仅6KB,共2个文件:一个py脚本负责抢票主流程,一个txt文档配套解释代码结构与运行方式。已有1066人浏览学习,属于小而精的实战案例。脚本中演示了用请求库与解析库提取票务信息、用多线程或进程池提升重复操作效率、用定时调度在开票时自动运行,并结合会话对象与缓存模拟登录、绕过反爬校验;同时包含异常捕获与日志记录,让程序更稳定、便于调试。txt文档的注释有助于快速定位关键函数与改造点,阅读后可以自行扩展,把抢票思路迁移到秒杀、预约等类似场景,整体麻雀虽小、五脏俱全。
1. 为什么别人起售秒空,你的脚本却能抢到票
很多人第一次听说“python演唱会抢票”时,第一反应是“这不是外挂吗”。其实它更像一个帮你把手速练到极致的自动化助手——你要做的不是黑进平台,而是在开售瞬间完成查询、选票档、填数量、提交订单这一连串动作。手点需要一两秒,脚本只需要几十毫秒,差距就在这段时间里被拉开的。
这类脚本适合两类人:一类是给自己抢票的普通用户,只想在热门场次里多一分胜算;另一类是帮家人、朋友抢票的从业者,需要一套可复用、可调整的抢票工具。它解决的核心问题是:起售时刻的大量并发请求下,人为操作的点击延迟、页面刷新等待、表单填写误差,都会被自动化流程抹平。
这篇文章会把整个方案拆开讲透——从登录态怎么保存,到场次票档怎么拿到,再到下单请求怎么构造、订单确认怎么处理,最后是五个最容易翻车的坑。你可以照着一步步搭出自己的一套抢票流程,也能在遇到问题时快速定位是哪里出了问题。
2. 抢票脚本首先要立住的事:流程拆解与登录态准备
2.1 把一场抢票拆成四个可编程环节
抢票看起来是“点按钮”,但拆开之后其实是四个明确的动作:查询场次与票档、判断是否有票、提交订单、确认订单。每个动作对应一个或多个请求,脚本要做的就是按顺序把这些请求发出去,并在合适的时间点重试。
第一步是明确你要抢的场次、票档和数量。第二步是准备好登录态,让平台知道你是谁、你的收货人信息在哪。第三步是进入轮询循环,不断查询指定票档的库存状态。第四步是发现有票后立刻提交订单。听上去简单,但每一步都有细节:查询接口的返回结构是什么、字段名是什么、请求头要不要带特定参数、提交订单是否需要二次确认。
我的习惯是先手动打开浏览器开发者工具,把“查询场次”“提交订单”“确认订单”三个接口的请求方法、URL、参数和返回数据结构记下来。这一步是所有抢票脚本的地基——接口抓不准,后面全白搭。
2.2 先搞定登录态:Cookie/Session的保存与复用
登录态是抢票脚本的第一步,也是最容易被忽视的一步。很多脚本“抢不到票”不是逻辑问题,而是登录态失效,请求被弹回登录页。
常见做法是用 requests.Session 保持会话,同时把登录后的 Cookie 保存到本地文件,下次运行直接加载。具体分两步:
import pickle import requests session = requests.Session() # 第一次手动登录后,把 session 的 cookie 保存到本地 def save_session(session, path="session.pkl"): with open(path, "wb") as f: pickle.dump(session.cookies, f) # 后续运行时直接加载 cookie,跳过扫码登录 def load_session(path="session.pkl"): session = requests.Session() with open(path, "rb") as f: session.cookies.update(pickle.load(f)) return session这段代码的逻辑在于:requests.Session 对象会保存服务端返回的 Cookie,包括登录后下发的身份令牌。把 cookies 用 pickle 序列化存到本地,下次启动时恢复,就不需要每次都走登录流程。
有几个参数需要留意:Cookie 的有效期因平台而异,有的几天、有的只有几小时;如果脚本长时间轮询,可能中途失效,所以我会在每条请求后检查返回内容是否包含“请登录”或跳转到登录页的标志。另外,pickle 文件本身不加密,不要提交到公共仓库。
2.3 票档与场次信息怎么拿到手
拿到了登录态,下一件事是确认你要抢的场次和票档。最常见的坑是直接“猜”接口参数,结果查出来的不是目标场次。
正确路径是:先从演出详情页或列表页拿到场次 ID、票档 ID 和起售时间,再把这些 ID 作为查询参数传给余票查询接口。这一步通常在浏览器开发者工具的“网络”面板里能看到——点击某个票档时,页面会发起一个查询请求,里面就包含你要的 ID。
# 伪代码:拿到目标场次与票档的 ID 后,构造余票查询请求 def query_stock(session, show_id, session_id, price_id): url = "https://api.example.com/stock/query" # 以抓包结果为准 params = { "showId": show_id, "sessionId": session_id, "priceId": price_id, } resp = session.get(url, params=params, timeout=3) data = resp.json() return data这段代码展示的是查询接口的基本形态,真正落地时 URL、参数名都要按你抓到的包来替换。特别要关注返回值里表示“有票/无票”的字段:有些是remainNum,有些是status,还有些只有可售数量不为 0 才代表有票。脚本的轮询逻辑就依赖这个字段的判断。
参数设置上,timeout 建议设短一些,3 到 5 秒即可,避免某个请求卡死导致整个流程停住。轮询间隔建议在 0.5 到 1 秒之间——太频繁请求会被限流,太慢则可能错过起售瞬间的放票窗口。
3. 从查票到下单:最小可用抢票脚本
3.1 核心轮询与条件判断
余票查询接口确认后,就要把这个接口放进循环里,让它从起售时间前就开始工作。核心逻辑是:到达起售时间后,持续查询目标票档的库存,一旦发现有票,立刻跳出轮询,进入下单环节。
import time def wait_and_grab(session, show_id, session_id, price_id, start_timestamp, interval=0.5): while True: now = time.time() if now < start_timestamp: time.sleep(min(interval, start_timestamp - now)) continue data = query_stock(session, show_id, session_id, price_id) remain = data.get("remainNum", 0) if remain and int(remain) > 0: print("有票,开始下单!") return data time.sleep(interval)这里有几个值得细说的参数。start_timestamp 我建议设为起售时间前 1 到 2 秒,而不是精确踩点——因为你的本地时钟和平台服务器时钟本来就有偏差,提前一小段进入循环,才能在真正开售的第一时间发出查询。interval 我一般设置在 0.5 秒到 1 秒之间,起售前的等待阶段可以把间隔放宽到 1 秒,接近开售时收紧到 0.3 秒左右。
要注意的是,这里只做了简单的无票继续等逻辑,实际场景里还可能遇到接口异常、返回字段变更、限流提示等情况。我会在查询接口外套一层 try-except,异常时记录日志并继续轮询,而不是直接退出。
3.2 提交订单与订单确认
轮询到有票后,下一步是提交订单。提交订单的请求通常是 POST,需要带上场次 ID、票档 ID、数量、收货人信息等参数。这个请求的构造必须以抓包结果为准,不同平台差异很大。
def submit_order(session, order_payload): url = "https://api.example.com/order/create" # 以抓包结果为准 headers = { "Content-Type": "application/json", "Referer": "https://example.com/show/detail", "User-Agent": "Mozilla/5.0 ...", } resp = session.post(url, json=order_payload, headers=headers, timeout=5) result = resp.json() if result.get("code") == 0: print("订单提交成功,订单号:", result.get("orderId")) else: print("提交失败:", result.get("msg")) return result订单提交成功后,很多平台还会有一个“确认订单”的步骤。有些平台先下单后弹窗确认,有些平台则直接进入支付页。你需要在抓包时确认你的目标平台是哪种流程,并把确认步骤也自动化。常见做法是提交订单成功后,立刻调用确认接口,把订单状态推送到待支付,这样你只需要完成支付这一步。
订单提交失败时,需要看失败原因。常见的有“库存不足”“票档已下架”“限购数量超限”“收货人信息不完整”。前两种可以回到轮询逻辑里继续抢,后两种需要先修正账号信息。我的做法是把失败原因分类:可重试的和不可重试的,不可重试的直接停止脚本并弹窗提醒我人工介入。
3.3 运行时的几个关键参数
整个脚本从轮询到下单,有几个参数决定了成功率和稳定性:轮询间隔、请求超时、重试次数、并发检测窗口。我通常把这些参数放在脚本头部,方便不同场次直接调整。
CONFIG = { "show_id": "你的场次ID", "session_id": "你的场次分组ID", "price_id": "你的票档ID", "buyer_count": 1, "poll_interval": 0.5, "timeout": 3, "max_retry": 5, }这里的一个建议是:不要一上来就多线程并发请求。很多平台针对同一个账号在短时间内的高频查询会触发风控,IP 被封禁或账号被临时限制,得不偿失。单线程轮询配合 0.5 秒间隔,已经有足够竞争力——你抢的是“起售瞬间的先后”,而不是“后续的持续刷票”。这两个场景的技术选型完全不同,前者适合单账号轻量轮询,后者需要面对更复杂的限频和验证机制,失败率也高得多。
4. 用浏览器自动化兜底:为什么复现性不如接口直调
4.1 接口直调和浏览器自动化的差异
不少人在接口方案碰到验证码或复杂前端校验时,会转向 Selenium 或 Playwright 这类浏览器自动化工具。它们的思路是让浏览器真实加载页面,用脚本模拟鼠标点击、输入、页面等待,走完整套人工操作流程。
这确实是可行的,但代价是速度和稳定性。接口直调一个请求耗时往往在几十到几百毫秒,浏览器自动化一次点击要经过页面渲染、事件响应、网络请求,整体慢一个数量级。而且浏览器自动化对前端 DOM 结构变化非常敏感——平台改一次页面布局,你的选择器就全乱了,脚本立刻失效。
我的判断标准是:如果接口方案能做到的事,优先用接口方案。浏览器自动化只用来处理两类情况——一是登录时需要扫码或短信验证,接口方案很难自动化;二是提交订单时出现了图形验证码,只能等人工介入识别。
4.2 浏览器自动化的最小流程与等待策略
如果确实需要浏览器自动化,最常见的路径是用 Selenium 控制本地浏览器,打开演唱会详情页,定位“立即预订”按钮并点击。关键不是点击本身,而是点击之后的等待逻辑。
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Chrome() driver.get("https://example.com/show/detail") # 起售前页面保持打开,等待“立即预订”按钮可点击 WebDriverWait(driver, 30).until( EC.element_to_be_clickable((By.CSS_SELECTOR, ".btn-buy")) ).click() # 点击后等待票档元素出现,再点击目标票档 price_btn = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, ".price-item[data-price='680']")) ) price_btn.click()这段代码的等待策略要比固定 sleep(3) 可靠得多。WebDriverWait 会一直轮询页面,直到条件满足或超时,它的优势在于页面加载速度快时不会多等,慢时也不会因为固定等待不够而失败。
一个容易踩的坑是:点击“立即预订”后,页面可能会弹出一个选票档和数量的弹层,这个弹层可能是异步渲染的——DOM 元素存在但还不可见、不可点击。这种情况下用 element_to_be_clickable 而不是 presence_of_element_located 会更稳,前者会额外检查元素是否可见且可用。
浏览器自动化还有一个实际问题:窗口尺寸、浏览器版本、WebDriver 版本不一致,会导致元素定位失败。我一般会在脚本开头统一设置窗口尺寸和隐式等待,并把本地浏览器和驱动版本固定下来,不去追最新版。
4.3 浏览器指纹、IP 与账号风控的边界
无论是接口直调还是浏览器自动化,最终请求都带着你的 IP 地址和浏览器特征。平台的实时风控会关注几个维度:同一账号的请求频率、同一 IP 下的账号数量、账号在短期内是否出现异地登录、请求是否来自自动化脚本的识别标识。
这里我没有绕过风控的方案,也不建议尝试。正常的使用方式是一个账号配一个固定网络环境,用真实浏览器日常登录保持账号活跃,抢票时才运行脚本。新注册的账号、长期不登录的账号,脚本成功率明显低——这不是脚本问题,是账号本身的权重问题。
反过来也要提醒一句:不要用同一个 IP 做大量账号的抢票,也不要试图伪造请求头来规避风控。这类做法一旦被识别,轻则请求被拦,重则账号被平台限制,得不偿失。抢票脚本的定位是“把事情做得更快”,而不是“跟风控对抗”。
5. 抢票脚本常见问题与避坑记录:五条典型翻车现场
5.1 登录态失效导致所有请求被弹回登录页
现象:脚本跑了一个小时,日志里一直是“查询成功”,但实际拿到的全是登录页的 HTML,解析 JSON 时直接报错。
原因:Cookie 过期是最常见的情况。有些平台的用户令牌有效期只有几个小时,长时间轮询必然过期。另一个隐藏原因是网络切换导致 IP 变化,服务端把这次请求视为新设备,强制要求重新登录。
解决:保存 Cookie 后马上打一条日志记录保存时间;脚本里每次请求后检查返回内容是否包含“登录”或“验证”特征,一旦发现立即停止并提醒手动登录。不要写一套“自动刷新登录态”的逻辑,扫码登录需要人工参与,这个环节自动化成本很高。
5.2 本地时间与服务器时间不同步,起售瞬间错过放票
现象:脚本按自家电脑时间提前 5 秒进入轮询,结果平台实际开售时间比本地时间晚了几秒,前几次查询全部返回“未开售”,等真正放票时已经慢了一拍。
原因:本地时间和平台服务器时间有偏差,而且不同平台的偏差方向不一致。有的平台按整点开售,但服务器的整点和你电脑的整点差出几秒。
解决:起售前先校准时间。常见做法是用网络时间协议 NTP 协议同步本地时间,再把脚本的启动时间设置为“起售前 30 秒”开始轮询,宁可多查几次“未开售”,也不要卡着点启动。
5.3 票档 ID 是动态生成的,硬编码导致下单失败
现象:昨天脚本跑通了,今天开售时却提示“票档不存在”。
原因:部分平台的票档 ID 不是固定值,每次进入页面或者不同场次会动态变化。脚本里硬编码的 price_id 已经失效。
解决:在轮询前先调一次“获取场次信息”的接口,拿到最新的票档 ID,再拼进查询和下单请求里。这也意味着脚本不能完全脱离实际页面,至少启动时要动态拉一次数据。
5.4 并发轮询被限流,请求从成功变成 429
现象:为了增加成功率,把轮询间隔调到 0.1 秒,结果跑了 2 分钟后所有请求都返回 429 或类似限流状态码。
原因:短时间内的请求频率超过了平台的单账号阈值。这个阈值不同平台不一样,而且会动态调整——高峰期阈值比平时低,同一 IP 下多账号运行时阈值更低。
解决:把轮询间隔恢复到 0.5 秒以上,观察日志里的响应码或响应时间。如果 429 出现频率高,就继续调大间隔。抢票的胜负手在起售瞬间的前几百毫秒,过了那个窗口,再怎么刷也很难有票,没必要用高频请求换封禁风险。
5.5 下单成功但确认接口没被调用,订单滞留
现象:日志显示“订单提交成功”,但支付页面一直没出现,最后订单被系统自动取消。
原因:下单接口和确认接口是两步,有些平台下单后要再调确认接口才会锁定库存。只调了下单接口,订单停留在中间状态。
解决:提交订单成功后,立刻检查返回结果里的跳转参数或订单状态字段,按抓包时记录的确认流程调用确认接口。把“下单”和“确认”两个步骤放在同一个事务里,确认失败时保留现场日志,便于人工处理。
6. 进阶:定时启动、日志策略与一套验证清单
把脚本从“能跑”推进到“能稳定跑”,还需要补上三件事:定时启动、日志输出和验证机制。
定时启动常见做法是用系统自带的定时任务工具,在起售时间前 1 分钟拉起脚本。脚本内再做一个双重保险:启动时校验当前时间和目标开售时间,时间未到就进入等待轮询。这样即使定时任务触发存在几十秒的误差,也不会影响起售瞬间的发令。
日志方面,不要只 print 到控制台。我一般会把运行日志写入本地文件,记录每次请求的响应码、响应耗时、当前库存状态和错误信息。抢票失败时最怕的是“不知道卡在哪一步”,有了完整日志,回看记录就能定位是登录态过期、票档 ID 失效还是被限流。
验证这块我有一套固定的检查顺序:起售前一天用脚本跑一遍“查询余票”流程,确认参数和接口可用;起售前 2 小时手动登录一次,确认登录态有效;起售前 30 分钟跑一次演练——把查询目标改成一场未开售的演出,确认轮询逻辑和日志输出正常;最后才是真正的开售。这套流程看起来繁琐,但能过滤掉大半的“临场才发现的问题”。
我自己经历过最惨的一次,是起售当天才想起来要试跑,结果脚本一启动就报错——票档 ID 早已过期。从那以后,每次抢票前我都会把验证流程完整走一遍。抢票脚本就是这样,它的每一次成功都来自大量的事前准备,而不是开售瞬间的运气。希望这篇笔记能帮你少走弯路,在自己动手时有一个清晰、可靠的起点。
本文还有配套的精品资源,点击获取