简介:面向大麦平台演出票抢购用户,这套自动化脚本基于Selenium打开浏览器完成登录并模拟真实购票流程,适合有一定Python基础、想摆脱手动刷新和排队等待的读者。压缩包共18个文件,大小约1.08MB,核心为8个py源码文件,配套json参数配置、txt说明、png示例图以及drawio流程图;另附移动端Appium自动化目录,能帮助延伸了解移动端抢票方案,按目录即可快速定位登录、选场、下单等模块。资源已有6195人学习下载,作者标注已有大佬验证可以成功。拿到后可先阅读说明和配置示例完成环境搭建,再依据自身账号与目标场次调整参数运行;同时能学习Selenium页面元素定位、浏览器事件模拟、配置文件读取等实战技巧,并借助可编辑流程图理解整体抢票链路,省去从零摸索的成本,二次开发门槛较低。
1. 抢票脚本不是玄学:一场与风控系统的攻防
网上流传的抢票脚本,总喜欢强调一句“已有大佬验证可以成功”,好像拿到代码就等于拿到了票。但以我接触这类自动化方案的经验看:凡是把抢票脚本当成“双击就能跑”的项目,最后基本都卡在验证码和接口风控上。这个标题真正指的事情,是把浏览器里一次点击背后的抢票动作,拆成登录、查场次、提交订单这几个接口调用,再由程序在开售那一瞬间发出请求。它能解决的,是“手动点太慢、每次都卡在选座和提交之间”的问题;适合的读者,是被抢票折磨过、懂一点抓包和编程、想把整个链路弄明白的人。往下读之前,先想清楚一件事:脚本的价值不在于代码本身,而在于你知不知道自己在跟什么样的风控系统打交道。
2. 抢票链路拆解:一次点击背后的五次请求与四个风控关口
先看点发生在“点击购票”之后的事。你在网页上点一次按钮,浏览器里实际发生的动作有五六次:页面先拉取当前场次列表,确认哪些场次还有票;然后读取你选中的票价档位与库存状态;接着校验当前登录账号的实名信息;再往后才是提交订单;最后创建订单并跳转支付。每一个步骤都对应一个HTTP接口,这几个接口之间的先后顺序,决定了脚本的骨架。如果对这个骨架没有概念,后面写代码就只能靠猜。
2.1 一次点击背后的五次请求:为什么脚本要动接口而不是模拟点击
很多人问过:“直接用模拟点击的RPA工具,把鼠标键盘录下来重放不行吗?”答案是,不行。第一,网页端DOM每次改版都会变,上周能识别的按钮位置,这周就换了一整套类名;第二,模拟点击有肉眼可感知的延迟,这里慢几百毫秒,在抢票场景里就是天壤之别;第三,平台行为检测模块最容易识别的就是机械鼠标轨迹和固定间隔点击。所以正常做法是动接口:用抓包把票务页这几次请求拿下来,再用脚本直接发HTTP请求。这样延迟可以压到几十毫秒级别,也不会因为DOM变化而重写整个脚本。
要拿到这几次请求,常规做法是在电脑上开一个带抓包配置的调试通道,手机端把网络指向它,然后手动完成一次购票流程。这里有一个容易被忽略的细节:先手动买一次,不是为了测试,而是为了拿到一份“能成功下单的请求样本”。之后所有参数对齐都以这份样本为准。
为什么提交订单和创建订单是两个接口而不是一个?这是票务系统的通用设计。提交订单负责校验库存、实名信息和票档规则,校验通过后服务端会生成一个订单token并锁定库存;创建订单再拿着这个token完成落单。脚本最容易搞错的地方,就是把这两个接口当成一次请求处理,结果token还没拿到就急着创建订单。理解请求之间的依赖关系,比多写几行代码重要得多。
2.2 影响抢票成败的四个风控关口
把抢票链路拆开之后,你会发现真正的难点并不在“会不会发请求”,而在于怎么越过这几个风控关口。
| 关口 | 检测维度 | 常见触发条件 |
|---|---|---|
| 账号状态 | 实名信息、账号活跃度、历史订单 | 新注册账号、换绑手机、频繁登录 |
| 设备指纹 | User-Agent、Canvas、WebGL、字体列表 | 无头浏览器、固定UA、缓存被清 |
| 请求频率 | 固定时间窗内请求数量 | 1秒内多次提交订单、持续高频轮询 |
| 下单速度 | 服务器时间对齐、库存扣减顺序 | 本地时钟偏慢、没有预填常用观演人 |
这四个口,每个都有一票否决权。账号状态是基础门槛,平台判断你像不像真实用户;设备指纹决定了你这次访问的“物理身份”;请求频率是实时熔断,短时间内动太快直接拒绝;下单速度才是大家讨论的“手速”。市面上那些声称“成功”的脚本,并不是四个口都能过,更多是恰好碰到风控策略比较松的场次。
这四个关口之间还有联动关系。账号在异常设备上登录,会拉低账号状态评分;账号分数低了,请求频率的阈值也跟着降低;频率一高,又触发滑块。这就是为什么有些人只改一个参数,反而越改越糟。抢票脚本实质上是一个排队系统:你在跟成千上万个请求同时抢一个库存扣减机会。脚本能做的,是保证请求规范、时序正确、速度够快,但没法让平台为它开后门。认清这一点,后面遇到失败就不会盲目怀疑代码。
3. 用 Python 搭一个最小抢票脚本:从抓包到会话保持
这一章开始动手。我讲一个最简但能跑通日常预约场景的方案:用Python的requests库保持登录态,轮询场次状态,在开售时间点发起下单请求。这套结构不依赖任何第三方抢票框架,自己维护起来也容易排查问题。
3.1 抓包定位下单链路:用抓包工具列出一张接口清单
先列接口清单,不然写代码没有依据。我一般会按“登录 → 查场次 → 查库存 → 提交订单 → 创建订单”的顺序整理一份表格,记下每个接口的请求方式、关键参数和返回结构。
| 阶段 | 接口(示意) | 方法 | 关键参数 |
|---|---|---|---|
| 登录 | /auth/login | POST | username, password |
| 场次列表 | /show/list | GET | city, page |
| 库存状态 | /show/stock | GET | showId, priceId |
| 提交订单 | /order/submit | POST | showId, priceId, userId |
| 创建订单 | /order/create | POST | orderToken |
抓包这一步有四个注意点。第一,手机或浏览器代理配置好后先访问一次首页,确认抓包工具能解开HTTPS流量,不然看到的全是加密乱码。第二,手动走完整个购票流程,不要中途退出,因为有些接口只在最后一步才会调用。第三,重点找那个返回订单token的响应,它往往在提交订单接口里。第四,如果页面用了WebSocket推送库存状态,还要额外记录消息格式。这份清单不需要把每个字段都搞清楚,但三个东西必须确认:提交订单接口的入参格式、创建订单接口依赖的token来源、下单接口的请求头。很多脚本翻车,就是因为“提交订单”和“创建订单”用了两套不同的请求头,导致服务端校验不通过。
3.2 登录与会话保持的完整代码
围绕这份清单,先写会话层。为什么单独做一层?因为票务平台登录成功后下发的cookie和token,后续所有请求都要带。如果每个接口各写各的,一旦token过期,排查成本会翻倍。
import requests import time from fake_useragent import UserAgent class TicketClient: """最小会话客户端:负责登录态和通用请求头""" def __init__(self, timeout=5): self.session = requests.Session() self.timeout = timeout ua = UserAgent() self.session.headers = { # 固定UA,不要每次请求随机变化,否则容易被风控盯上 "User-Agent": ua.chrome, "Accept": "application/json, text/plain, */*", "Content-Type": "application/json;charset=UTF-8", "Referer": "https://ticket.example.com/" } def login(self, username: str, password: str) -> bool: """模拟登录:真实平台多为扫码或短信验证,这里用账号密码做最小示意""" login_url = "https://api.example.com/auth/login" payload = { "username": username, "password": password, "source": "web" } resp = self.session.post(login_url, json=payload, timeout=self.timeout) data = resp.json() if data.get("code") == 0: # 登录成功后,把token放进公共请求头 self.session.headers["Authorization"] = "Bearer " + data["data"]["token"] return True return False def get_show_list(self, city_id: str) -> list: """获取场次列表,开售前用低频轮询判断可售状态""" url = "https://api.example.com/show/list" params = {"city": city_id, "page": 1, "pageSize": 50} resp = self.session.get(url, params=params, timeout=self.timeout) return resp.json().get("data", {}).get("list", [])这段代码有三个参数值得细说。超时时间timeout我习惯设成5秒,超过就直接抛异常,避免下单阶段卡死在一个请求上;User-Agent用fake_useragent生成一次然后固定,是因为如果每次请求UA都不一样,风控设备指纹里会留下“UA频繁漂移”的记录;Referer字段很多人会漏掉,但它是服务端校验来源是否合法的低成本手段,必须和浏览器抓包结果保持一致。cookie保持由requests.Session自动处理,登录接口返回的Set-Cookie会写进会话,后续请求不用手动维护。
3.3 定时触发下单请求:同步服务器时间比提前循环更重要
抢票成败有一个常被忽略的因素:本地电脑时钟和服务器时钟不一致。你本地显示10点整时,服务器可能已经过去了一秒半。所以脚本的第一件事不是发请求,而是校准时间。
def get_server_time(client: TicketClient) -> float: """从服务器取时间戳,计算本地与服务器的时间偏移量""" url = "https://api.example.com/time" resp = client.session.get(url, timeout=3) server_ts = float(resp.json()["data"]["serverTime"]) / 1000.0 local_ts = time.time() return server_ts - local_ts拿到这个offset之后,脚本里所有时间判断都以“本地时间 + offset”为准。开售点提前量怎么设?我看过很多人喜欢提前500毫秒循环发请求,结果就是被频率风控标记。我的习惯是:提前量设在300毫秒,只发一次请求,失败后按100毫秒、300毫秒、700毫秒的间隔做三次退避重试。这个参数的依据是,开售瞬间库存释放和扣减有一定缓冲,太早请求会被当成探测,太晚又排到队尾。重试时还要判断返回码:只有库存不足或人流过大才值得重试,如果是签名失效或账号异常,重试多少次都没有意义。
4. 抢票脚本真正的两座山:接口签名与滑块轨迹识别
requests能解决的只是请求层问题。当你把最小脚本跑通,准备对付热门场次时,会撞上两座山:接口签名和滑块验证。这两道坎过滤掉了大多数人。
4.1 接口签名:从固定口令到动态 token
不少票务平台的订单接口,都带一个名为sign的动态参数。它的生成逻辑通常是把请求参数按字典序排序,拼上时间戳和一个固定密钥,再做HmacSHA256哈希。表面上看并不难,但实际平台的JS代码会做多层混淆,参数名也可能是动态生成的。下面是这类签名的生成思路,便于理解。
import hashlib import hmac def generate_sign(params: dict, secret: str) -> str: # 参数按key排序,拼成 k=v&k=v 形式 raw = "&".join(f"{k}={params[k]}" for k in sorted(params)) raw += "&secret=" + secret # 大多数平台用HmacSHA256,也有用MD5或SHA1的 return hmac.new(secret.encode(), raw.encode(), hashlib.sha256).hexdigest()这个函数的关键在于“排序后的拼接顺序”和“密钥来源”,两者都需要从抓包样本里推。我见过有人把sign当固定值写死在脚本里,结果平台加了时间戳校验,一旦超过有效时间直接返回签名失效。处理这种接口,正确路径是去抓包JS里找生成函数,断点调试出参数拼装规则,然后在Python里复刻。这个过程比较枯燥,但也绕不开。一个可用的排查技巧是:在浏览器里修改某个请求参数,观察sign是否变化、变化发生在哪一段。如果参数A变了sign没变,说明A没有参与签名;如果变了,就把A追加到参数字典里再排序。反复几次,签名范围就摸清了。
4.2 滑块识别:为什么你的脚本在这里翻车
滑块是另一座山。很多抢票脚本辛辛苦苦搞定签名,却在滑块上消耗了大量时间。滑块验证的核心不是判断你是否拼图拼得准,而是分析你滑动过程中的轨迹特征:加速度曲线是不是符合人类操作、有没有停顿、点击位置是否过于精确。脚本用直线或匀速滑过去,几百毫秒内就会被识别为机器操作。
import random import time def human_slide_trajectory(distance: int) -> list: """生成一段带加速、减速和轻微抖动的人为滑动轨迹""" trajectory = [] current = 0 speed = 0 while current < distance: # 前半段加速,后半段减速,最后加一点反向修正 if current < distance * 0.7: speed += random.uniform(1, 3) else: speed -= random.uniform(1, 3) speed = max(speed, 0.5) current += speed trajectory.append({ "x": int(current), "y": random.uniform(-1, 1) }) time.sleep(random.uniform(0.005, 0.015)) return trajectory这段轨迹看似符合直觉,但我要提醒一句:光是轨迹像人远远不够。滑块服务端还会看浏览器环境里的JS指纹、鼠标事件监听上下文、甚至这次滑动对应的会话是否异常。能从脚本层面解决轨迹模拟,已经是相对初级的对抗。真正完整的方案需要无头浏览器叠加环境伪装,复杂度一下子高很多,而且每次平台更新风控算法,这一层就得跟着改。
4.3 一条务实的降级路径:半自动人工介入
正因为滑块和签名的组合拳很难完全自动化,我见过能稳定用的方案,几乎都是半自动的。脚本负责登录、查场次、构造请求、提交订单;遇到滑块验证码弹出时,程序立刻暂停,向用户推送提醒,由人工在屏幕上滑一下。这个方案的成功率比纯自动高得多,也省掉了无头浏览器的环境维护成本。代价是需要人盯着屏幕,但它才是多数人够得着的落地路径。
具体实现不复杂:在发下单请求之前,先发一个预检请求,看返回里有没有滑块标记。有就直接抛出一个可识别的异常,通知程序进入等待人工处理的状态;人工处理完成后,把会话里最新的cookie或token填回脚本。如果一套流程能连续跑通三场,再考虑要不要把滑块部分也自动化。
所以,如果你评估自己的时间和精力,纯自动脚本要达到“可长期使用”的稳定程度,投入产出比很低;半自动方案几小时就能搭起来,热门场次也能抢到。脚本真的过了三个晚场、没有被风控标记,再回头优化也不迟。
5. 抢票脚本踩坑实录:四个最容易让人放弃的问题
这一章写给准备投入时间的人。我不罗列空泛的风险清单,直接按“现象→原因→解决”写四个实际调试中重复出现的问题。你自己跑的时候遇到类似提示,可以对号入座。
5.1 现象:登录成功但下单接口一直返回“操作频繁”
你以为登录成功就安全了,其实登录本身就是一次高危行为。用脚本从陌生环境登录账号,尤其是账号绑定的手机号和常用设备不一致时,服务端会把这个会话标记为“低信任”。之后访问任何关键接口,都会被要求额外验证。
原因有两个。一是登录请求的文件指纹不一致,网页登录的指纹是浏览器真实生成的,脚本里却带了缺失UA和Canvas指纹的环境;二是登录后立刻去查库存,时间间隔远小于真人操作,触发了频率熔断。
解决方法是:登录后先模拟浏览行为,停2到3秒再查第一个接口;查库存的频率限制在每10秒一次,不要用1秒循环。关键接口务必带全页面真实抓到的请求头,从抓包工具里直接复制那一份,不要自己拼。
5.2 现象:网页端能看到库存,脚本请求却提示“场次不存在”
这个最容易让人懵。网页能看到票,说明库存没有清零;脚本提示场次不存在,说明接口参数有问题。多半是场次ID跟前端展示的不一致,或者脚本里的city参数和票档ID对不上。
原因通常是抓包抓的是列表页接口,而下单接口需要的是详情页的场次ID;两者在页面里同名但数值不同。解决:在抓包记录里找到“创建订单”前最后一次查询库存的请求,以那个请求里的参数为准;并且让脚本先调一次详情页接口,实时拿场次ID,不要硬编码到配置里。
5.3 现象:滑块验证码频繁弹出,且通过后仍报异常
滑块弹得频繁,说明设备指纹已经被标记了;通过后仍报异常,说明服务端记录的并不是滑块是否通过,而是这次会话中指纹的变化情况。
原因常见于:脚本运行在普通Python进程里,没有完整浏览器环境,JS能读取的Canvas、WebGL、字体信息全部缺失;这些缺失却和发起登录时的指纹对不上。解决:把滑块环节切换到半自动人工介入;如果坚持全自动,就要上无头浏览器并持久化浏览器Profile,让指纹保持稳定。指纹稳定比轨迹模拟重要得多,这是很多人没意识到的。
5.4 现象:下单成功但支付超时
下单成功不等于抢到票,订单通常只有几分钟支付时间。支付超时的原因一般是,脚本构造的下单请求没有带上有效的跳转凭证,或者支付入口依赖前端完成。
原因:创建订单接口返回的跳转链接只在当前会话有效;脚本并发重试时创建过多个订单,导致前一个订单占用了支付状态。解决:让脚本在下单成功后就停止后续请求,把支付跳转提示推送给用户手动完成。这也是我坚持做半自动方案的原因之一:支付环节交给真人,比让脚本去处理跳转和回调稳妥得多。另外,抢票成功后的通知要带订单号和支付链接,别只发一句“抢到了”,等用户打开页面再去找订单,支付时间早就过了。
这些问题的背后有一个共同原则:平台风控关注的是“行为的连贯性”。脚本想要高成功率,就得尽量把每一次请求的行为特征维持在同一个指纹和同一个账号画像里。任何突然的变化,都会成为被拦截的理由。
6. 用日志回放验证脚本:本地与线上成功率到底差在哪
最后讲验证,因为我见过太多人把“跑通一次”当成“可以用了”。验证脚本有没有价值,要看三个数据:请求发出到订单创建的耗时、触发风控的次数、连续三个场次的稳定表现。
我的做法是给脚本加结构化日志,把每次下单请求的时间点、接口响应码、耗时、是否出现滑块,按JSON格式落盘。每次抢票结束后,用一段小脚本做回放分析,看请求从发出到拿到订单成功码花了多少毫秒。如果一次成功的耗时在800毫秒以上,说明瓶颈在网络或接口本身,而不是脚本逻辑;如果经常在300毫秒内被拒绝,说明频率风控已经盯上了当前指纹。
更直观的验证是双开对照:同一场次,用一个人工浏览器窗口,一个脚本终端,同时抢。连续测三场,如果脚本的提交时间稳定比人工快300毫秒以上,才说明这套自动化确实有价值;如果两个方式都在同一个时间点失败,那就是风控策略的问题,不是脚本的问题。单次成功不值得投入,三次稳定才值得把时间花在研究签名和滑块上。这个习惯我保持了好几年,它帮我戒掉了“跑通即成功”的错觉。希望帮到你。
本文还有配套的精品资源,点击获取