
很多刚学Python的朋友问过我同一个问题票务平台的自动抢票脚本到底是怎么写的。这类需求确实是Python自动化里最有吸引力的练手项目之一它不复杂但能把你平时学的requests、Cookie、并发、异常处理全部串起来跑通了之后成就感相当强。这篇文章就围绕这个场景走一遍三步上手的完整流程把每一步背后的原理讲明白也把常见的坑提前帮你踩一遍。先说清楚适用范围这里讨论的是用Python脚本去处理某个票务平台演出票、活动票类网站的公开购票流程重点在技术原理和自动化实现。脚本本身是一条“自动化操作链路”不是去破解系统也不是攻击服务器。实际使用前一定要看目标平台的服务条款个人用来学习技术是合理的拿去刷票、囤票、绕过平台风控就不合适了这点心里要有数。1. 抢票脚本到底解决了什么问题1.1 手动抢票的痛点在哪里如果你手动抢过热门演出的票应该对下面这套操作不陌生提前几天研究开票时间快到点时不停刷新页面好不容易界面加载出来选择场次、票档、张数填好观演人再点提交订单最后跳出来“库存不足”或者“您来晚了”。整个过程全靠手速和网速而且那几十秒里人高度紧张非常容易点错。拆解一下手动抢票的瓶颈主要有三个。第一个是刷新频率。人眼关注一两个页面还行开票瞬间多个场次、多个票档一起变状态根本看不过来。脚本可以用循环去轮询接口几百毫秒一次也不累。第二个是操作链路长。从刷新到提交成功中间要刷新库存、读取场次、传入观演人、生成订单每步都是界面上的点击和表单操作人的点击速度是几百毫秒级别脚本提出一个HTTP请求通常几十毫秒就能完成差距非常明显。第三个是对状态的响应速度。手动刷新会发现页面有缓存你看到的库存状态可能滞后好几秒。直接请求后端接口拿到的是第一手数据更接近真实库存。明白了这三个痛点你就知道脚本不是在“作弊”它只是把一个重复的、要求反应速度的操作流程自动化了。理解了这一点后面设计脚本的逻辑就顺理成章了。1.2 自动化的核心目标快、准、稳抢票场景下的自动化追求的不光是“快”。“快”只是其中一方面还要“准”和“稳”。“准”指的是操作不能出错。手动操作时选错票档、填错观演人是常见失误脚本只要逻辑写对了每次请求的参数都一致不容易出现低级错误。但脚本有个反面问题如果目标场次不存在了或者参数写死就容易请求失败。所以脚本里要有校验逻辑每步请求前先确认前置条件。“稳”指的是整个链路要能跑完。开票瞬间服务的压力很大接口超时、连接重置是家常便饭脚本如果没有重试逻辑和异常处理第一层请求失败就直接退出那还不如手动。所以很多抢票脚本真正难写的地方不是“请求库存”这个动作而是整套异常处理和状态判断的工程能力。一个好的抢票脚本本质上是一个带状态感知的自动化请求客户端它知道现在是几点几分知道目标场次可不可以买知道订单提交成功了没有也知道失败后该怎么重试。把这个目标想清楚后面三步走的方向就不会偏。2. 三步上手的整体思路设计2.1 三步法怎么划分把整个流程压缩成三步是为了让新手不至于一上来就被细节淹没。很多人看到网上流传的抢票脚本源码第一反应是“怎么这么多代码”。其实那些代码再复杂也能归进三个环节第一步摸清抢票流程对应的HTTP请求。这是所有工作的基础。你得知道点击“立即购买”后浏览器到底向后端发了什么请求带上了哪些参数。第二步写一个能跑通核心链路的脚本。先不管验证码、加密参数这些复杂的东西把“查询库存 - 创建订单 - 提交订单”这条主链路跑通哪怕慢一点先看到一次成功。第三步补齐健壮性与并发能力。加上自动重试、请求频率控制、验证码处理、结果通知等。这步做完脚本才能从“能跑”变成“能用”。三步不是简单的流水线第二步做完往往要回过来改第一步的抓包结论因为有些参数在真实请求里才会暴露出来。我见过不少新手一上来就研究滑块验证码和JS逆向核心链路还没通最后卡在奇怪的地方。正确顺序永远是先打通主流程再折腾边缘环节。2.2 方案选型模拟请求还是浏览器自动化写抢票脚本通常有两套技术路线纯HTTP请求模拟和浏览器自动化。纯HTTP请求模拟是直接用requests库去调用后端接口。优点是非常快、资源占用低可以用Session保持登录态配合多线程或异步可以做到很高的请求频率。缺点是如果目标站点的接口有复杂的签名参数、加密逻辑或者频繁插入验证码代码复杂度会直线上升。浏览器自动化则用Selenium、Playwright这类的工具让一个真实的浏览器实例代替人去点击操作。优点是几乎不关心接口怎么加密、参数怎么生成只要模拟用户行为就行对付滑块验证码也天然有优势——人怎么拖它就怎么拖。缺点是慢单个浏览器实例每秒能完成的操作次数远低于HTTP请求而且并发要开多个浏览器对机器资源要求高。我的建议是以HTTP请求为主以浏览器自动化作为补充。比如登录环节如果出现滑块用浏览器自动化单独处理核心的下单请求还是走HTTP速度和稳定性更有保障。这也是大多数成熟脚本的架构思路能用接口解决的用接口接口解决不了的用浏览器补位。第一步的抓包目的就是判断这个平台到底适合哪种路线。有些票务系统的接口是纯JSON格式参数一目了然那写起来就很舒服如果发现请求头里有加密签名且每次请求签名都在变化那就要做好啃JS逆向的心理准备或者干脆考虑换浏览器自动化的路径。3. 核心细节与实操要点3.1 HTTP层面的关键字段不管目标平台长什么样抢票流程里一定有几个绕不开的HTTP字段务必要理解它们的含义。User-Agent是第一道门槛。很多平台会识别请求是不是来自浏览器如果你用Python默认的UA去请求很容易被拦下来。抓包时从DevTools的Network面板里复制真实的UA写进脚本里。顺手把Referer也补上很多接口会校验来源页面。Cookie是整个登录态的载体。你需要让脚本像一个已经登录的用户最简单的方式是把浏览器里的Cookie复制出来塞给requests.Session。但Cookie有时效性长期使用的脚本要处理过期问题。进阶做法是脚本自己走一遍登录接口用短信验证码换取Cookie这样每次都能拿到新鲜凭证。签名参数是最棘手的部分。很多平台为了防自动化会在请求参数里加入一个根据固定规则计算出来的签名内容通常是时间戳、固定盐值、当前请求参数拼接后取哈希。遇到这种情况要先尝试在浏览器里搜索参数名看能不能直接找到生成代码如果找不到可以考虑用浏览器自动化执行相关的JS代码来生成签名。这里记住一个原则签名算法的目的是增加自动化成本不是绝对安全所以不要怕多抓几次请求对比参数规律。请求方法里还有一个容易被忽略的点Content-Type。POST请求用表单格式还是JSON格式后端接收方式不同。如果你抓包时看到请求体是一串JSON字符串那脚本里就要用json参数如果看到的是keyvaluekey2value2那就是表单格式用data参数。搞错了会返回参数错误这是新手遇到频率最高的报错之一。3.2 登录态与会话保持登录态处理得好不好决定了脚本能走多远。最省事的方式是手动登录提取Cookie。打开浏览器开发者工具登录目标站点然后在Network面板找到任意一个请求从Request Headers里复制完整Cookie字符串。在脚本里直接设置到requests.Session的headers里二十行代码就能让脚本拥有登录身份。这种方式适合自己临时用缺点显而易见Cookie失效就要重新复制。复杂一点的方式是脚本自动登录。票务平台的登录往往会经过短信验证码、图片验证码等环节自动化登录非常容易触发风控。我见过一些脚本的设计是先手动登录获取Cookie然后保存到本地文件下次运行时优先读取文件里的Cookie如果发现请求返回未登录就提示用户重新登录。这种方式兼顾了灵活性和稳定性推荐新手采用。会话保持还与Session对象密切相关。requests的Session会自动保存服务端返回的Cookie后续请求自动带上模拟了同一个浏览器实例在不同请求间保持会话的效果。有些同学每次请求都新建一个Session那和每次重新打开一个未登录浏览器没区别下单必然失败。记住整个抢票流程全程使用同一个Session对象。3.3 验证码与滑块的处理思路验证码是抢票自动化绕不过去的话题平台之所以在关键时刻放验证码就是要拦截自动化脚本。先要明确验证码不是所有场景都会出现。很多平台在普通查询阶段不验证只有在下单提交前才弹出。所以脚本设计上可以先用无验证码模式跑通流程再针对触发点单独处理。滑块验证码是当前最普遍的。处理方案有三种。第一种是直接人工介入脚本运行到验证码出现时用某种方式通知你比如发送一个窗口提醒或消息在浏览器里手动滑一下之后脚本继续跑。第二种是模拟拖动用Selenium或Playwright定位滑块按人手的轨迹去拖动。这需要注意轨迹的随机性直线匀速拖动会被识别成机器要加入加速减速和轻微上下抖动。第三种是调用第三方打码平台准确性高但要花钱且有泄露风险个人学习不建议。文字类验证码可以用OCR思路识别成功率取决于字体扭曲程度。许多平台的验证码都是计算题或成语题单纯OCR不够还得做语义理解这个复杂度已经超过抢票本身了。所以我的建议始终是优先做成人工辅助验证把自动化投入在更有价值的请求逻辑上验证码环节交给人工处理这样成功率最高。3.4 并发策略与请求节奏抢票场景里并发是个需要仔细设计的点。很多人以为线程开得越多越好结果开票瞬间发出去几百个请求直接把账号封了。调速比提速更重要。先说简单的模型。假设你抢一个热门场次脚本启动后有两种工作模式开票前轮询模式和开票后提交模式。开票前脚本以固定间隔查询库存状态比如每500毫秒查一次这个频率低不容易触发风控。一旦发现目标场次变为可售立刻进入提交模式先创建订单再提交订单整个过程只发几次请求频率仍然不高。那并发体现在哪里体现在“提前准备好多个任务”上。比如你同时关注多个票档可以用一个线程池并行监控再比如你要抢多张票可以提前准备好观演人列表。真正的抢票行为本身不需要高并发它需要的是低延迟响应。所谓低延迟就是定时轮询的间隔要短且每次轮询的请求要轻量最好用最精简的接口参数。有些极端场景平台接口在开票前是关闭的轮询会一直返回无效数据这时候频繁请求只会增加封号风险。正确的做法是设置轮询窗口开票前5分钟开始轮询开票前1分钟提高频率开票瞬间全力请求。窗口之外的请求次数尽量降为零。请求节奏还有一个细节请求超时和重试之间的时间差。如果接口超时设置为30秒而轮询间隔是500毫秒当一次请求卡住时后面的请求都会堆积。所以每轮轮询之前要检查上一轮是否结束同时把超时时间调短比如连接3秒、读取3秒超时了立即重试而不是傻等。4. 实战实现从抓包到跑通第一版4.1 第一步抓包定位关键接口具体操作很固定打开一个无痕浏览器窗口登录目标票务平台打开开发者工具切到Network面板勾选Preserve log保留日志。然后从头到尾手动操作一遍购票流程哪怕最后不支付也要走到提交订单那一步。操作完后Network面板里会留下几十个请求。要筛选出和购票相关的接口诀窍是看请求URL里的关键词比如包含order、ticket、stock、deal之类的接口名。把每个XHR请求的请求头、请求体、响应体都过一遍在表格里记下接口的URL、方法、参数和用途。我以一个典型的场次查询为例说明抓包之后要提炼出什么接口用途请求方法关键参数响应关键字段获取演出详情GETid演出ID场次列表、可售状态获取票档库存GETitemId、skuId库存数量、价格创建订单POSTitemId、skuId、num订单号提交订票人信息POSTorderId、观众信息提交状态确认支付POSTorderId、支付方式支付链接不用把全部接口都整理出来先抓核心链路查库存、创建订单、提交订单。这三个接口能跑通整个脚本就已经成功了一半。如果某些接口的响应体里有加密内容先去浏览器里看渲染后的页面数据那往往就是解密后的结果。这一步产出最关键后面所有代码都围绕这份接口清单展开。中途发现某个接口参数很复杂先别急着硬刚JS逆向观察一下是否能在前一个接口的响应里直接取到。很多参数是服务端算好返回的不需要前端动态生成。4.2 第二步写一个能跑通的主流程脚本拿到接口清单后先用Python写个最简版本的脚本目标只有一个从查询库存一路走到提交订单哪怕流程很笨拙。可以这样组织代码。首先准备好请求基础设施包括Session对象、请求头、Cookie和基础URL配置import requests import time BASE_URL https://example-ticket.example.com UA Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ... cookies_str 你的Cookie字符串 session requests.Session() session.headers.update({ User-Agent: UA, Referer: BASE_URL /, Origin: BASE_URL, }) session.cookies.update(parse_cookie(cookies_str))接着实现库存查询。这个函数要轻量只请求最精简的接口并返回可售状态def check_stock(session, item_id, sku_id): url f{BASE_URL}/api/v1/item/{item_id}/sku/{sku_id}/stock resp session.get(url, timeout3) data resp.json() return data.get(status) 1 and int(data.get(stock, 0)) 0然后实现创建订单和提交订单。创建订单通常需要把场次、票档、数量传给后端后端返回一个订单号。提交订单要把观演人信息带上去def create_order(session, item_id, sku_id, num1): url f{BASE_URL}/api/v1/trade/order/create payload { itemId: item_id, skuId: sku_id, num: num, } resp session.post(url, jsonpayload, timeout5) return resp.json()[orderId] def submit_order(session, order_id, audience_list): url f{BASE_URL}/api/v1/trade/order/submit payload { orderId: order_id, audience: audience_list, } resp session.post(url, jsonpayload, timeout5) return resp.json()[success]主流程循环可以非常朴素先循环查库存发现可售后创建订单并提交def main_loop(): while True: if check_stock(session, ITEM_ID, SKU_ID): order_id create_order(session, ITEM_ID, SKU_ID) ok submit_order(session, order_id, AUDIENCE_LIST) if ok: print(提交成功订单号, order_id) break else: print(提交失败继续重试) time.sleep(0.5)这版脚本不考虑验证码不考虑异常不考虑签名参数但它足以让你确认一件事这个方案在原理上能不能走通。如果跑起来库存查询没问题但下单时返回错误那就说明参数不完整或者签名缺失。这时候回去看抓包记录把缺失字段补上。核心原则是“先跑起来再优化”。很多新手第一步就想写一个支持多线程、自动过验证码的终极版本结果代码写了一千行跑起来全是问题。用最短路径验证可行性是工程里最重要也最容易被忽略的方法。4.3 第三步加上异常处理、重试与通知机制第一版跑通后脚本还远没到能用程度。开票那一刻接口随时可能超时、返回繁忙或者莫名其妙地弹验证码。下面几个模块就是第三阶段要补的。异常处理是整个脚本的骨架。不要在脚本里用裸的requests.get()所有网络请求都要包裹try/except。捕获到的异常要区分类型超时连接错误可以重试服务端返回5xx可以短暂等待后重试4xx错误大概率是参数或风控问题重试没有意义应该记录日志并停下来。重试机制要有退避策略。比如首次失败等待300毫秒第二次失败等待500毫秒第三次失败等待1秒不要无限重试同一个失败请求。一般设计三层重试连接层超时重试、接口状态校验重试、订单提交失败重试。每一层的重试次数都要有限制避免开票高峰时脚本陷入死循环。结果通知是很多初学者容易漏掉的。脚本在后台跑如果成功了你不能一直盯着屏幕。可以在脚本里加一个通知模块订单提交成功后把订单号发到即时通讯工具里失败次数多了也要通知让你及时介入处理验证码。通知实现不复杂本质就是发一个HTTP请求到某个Webhook地址。这个阶段还应该加一个状态机。把脚本的运行状态抽象成几个阶段轮询中、已创建订单、提交中、已完成、失败。每个阶段有明确的进入条件和退出条件这样脚本的可读性和可维护性会提升很多。我见过一个连状态管理都没有的抢票脚本功能确实能跑但一旦出错日志完全看不出它现在到底在执行哪一步很难排查问题。5. 常见问题与排查技巧实录5.1 高频踩坑点整理这一节我把实际调试过程中遇到过的高频问题整理成一张速查表每个问题都给出定位方向和解决办法。现象可能原因排查/解决方向请求返回403User-Agent缺失或异常补全浏览器UA检查Referer请求返回468或418触发风控限制降低请求频率改用浏览器自动化提示未登录Cookie过期重新抓取Cookie检查Session是否一致参数错误Content-Type或参数格式不对对比抓包里的请求体格式库存明明有但下单失败请求缺少签名参数回去分析JS或者补全加密字段下单成功但无法支付事务ID或订单号未正确传递检查前后接口的参数关联开票后一直超时服务端压力大或连接池耗尽调短超时时间增加重试退避验证码频繁弹出请求频率过高被标记拉大轮询间隔减少并发这里最值得说的是第二条的468或418这类状态码这属于平台主动拦截。这种时候不要用“更暴力”的请求来对抗正确的做法是让脚本休息一段时间模拟正常用户的操作节奏。我之前调试某个票务平台的接口时开票前每分钟查询一次没问题有一次为了测试把频率调到每秒5次十分钟后账号所有请求都返回异常状态。休息半小时后才恢复。这个教训说明脚本设计里一定要有自我保护机制在高频请求连续失败N次之后自动暂停一段时间。还有一个新同学很容易踩的坑是Cookie管理混乱。有些人既是浏览器里登录着又用curl命令测试接口然后发现有时能请求成功有时不能。原因往往是命令行工具没有带上浏览器里的Cookie。推荐的做法是把Cookie统一保存在一个文本文件里脚本启动时读取需要更新时手动替换只维护这一个入口。5.2 如何判断脚本到底卡在哪一步脚本跑不通的时候很多人第一反应是看代码哪里写错了。实际上抢票脚本大部分调试时间不是在改代码而是在定位问题发生在哪个环节。最有效的手段是完善日志输出。在库存查询、创建订单、提交订单、异常捕获这四处都加上带时间戳的日志。日志要包含请求的URL、参数、返回状态码和响应摘要。写日志的时候不要只输出“请求失败”要把抛出的异常类名和错误信息一起打出来。有了完整的日志排查问题就是看时间线而不是靠猜。判断思路是这样的如果库存查询正常但创建订单报错问题大概率在参数拼装或签名逻辑如果创建订单成功但提交订单报错问题可能在订单状态未满足条件如果所有接口都返回超时问题在网络环境或目标服务端压力与代码关系不大。按这个方向逐层缩小范围通常十分钟内能找到根因。有些平台的前端有动态Token。所谓动态就是每次页面加载时服务端会下发一个有时效性的加密串前端请求时必须携带。动态Token过期后接口会返回类似“权限不足”的响应。这种问题单看接口日志不好发现需要对比两次请求之间的Token值是否有变化如果变了就要在设计上定期刷新Token。另一个隐蔽问题是本地时间和服务器时间不一致。部分签名算法会把时间戳作为参数如果你的机器时间快了或慢了超过几分钟请求就会被拒绝。遇到时间戳相关报错时最好先同步一下系统时间再排查其他原因。5.3 验证码介入之后的人工配合流程很多脚本选择人工辅助处理验证码这里有个值得分享的细节模拟请求和浏览器操作如何无缝衔接是个容易忽略的难点。建议这样设计。入口还是Python脚本负责轮询、创建订单和提交订单但在发现需要验证码时停止自动提交立即打开一个浏览器实例自动定位到验证码弹窗并发送通知消息给你。你只需要在浏览器里手动完成验证脚本检测到验证成功后再继续。实现的关键是“检测验证是否成功”。可以通过轮询一个接口的返回状态来实现比如验证完成前创建订单接口会持续返回“需要验证”的错误码一旦你在浏览器里通过了验证服务端会话状态更新接口再调用就会正常返回。脚本只需要每隔1秒重新提交一次创建订单请求直到它成功为止。这种方案的稳定性和用户体验都很好既有脚本的速度和可靠性又保留了人应对复杂验证码的能力。我建议你在写抢票脚本的时候把“人工介入”当成一个正式功能来设计不要等出问题再去补。6. 一步到位的优化技巧分享到这里三步走已经讲完了但有几个小技巧能让脚本更耐用顺手分享出来。第一个技巧是接口查询参数要尽量精简。抢票峰值时每一次请求都会给服务器造成压力也给自己带来风险。用最少的参数、最快的接口完成库存判断而不是用完整页面数据接口。比如只请求/sku/stock这个轻量接口就不要去请求/detail?includeall这类的重量级接口。响应数据越小请求速度越快对服务的扰动也越小。第二个技巧是巧用离线数据缓存。有些平台的演出详情、场次列表并不是频繁变化的。开票前可以把这些数据抓下来存到本地JSON文件轮询时直接读本地文件只有库存状态实时请求。这样既减少了请求量又保证了流程不中断。第三个技巧是准备好预案列表。抢票前把多个票档按优先级排好比如最低价优先、时间最早优先、或者指定座位范围优先。脚本发现最高优先级票档无票时自动尝试下一个。这个逻辑实现起来不复杂却能让脚本在真实场景里的成功率上升一个台阶。第四个技巧是预留扩展现的空间。比如支付环节很多平台要求跳转到支付页面如果脚本能在提交订单后自动打开浏览器并帮你跳转到支付二维码页面你只需要扫码体验会非常连贯。这同样需要手动浏览器和HTTP请求的配合思路跟验证码处理类似值得一试。我个人在实际调试中的体会是抢票脚本这类自动化项目最大的门槛从来不是Python语法而是“把流程拆成一系列可验证的请求”这种工程思维。很多人一开始盯着验证码和加密参数这些难关反而忽略了“先跑通主流程”这一基本原则。只要你按三步走来每一步都留好日志和验证节点哪怕第一次跑通用了半天第二次再遇到类似票务自动化项目半天就能摸完整个链路。最后再提醒一句这些方法只能用于学习交流目的是理解接口自动化的通用原理。真实购票请通过平台官方渠道按平台规则操作。技术本身没有对错怎么使用才是关键。