i茅台预约脚本实战:从接口自动化到多账号调度
发布时间:2026/9/25 5:32:44来源:尧图网络
简介这份资源是面向对茅台预约自动化感兴趣的技术爱好者与Python初学者的一套脚本合集围绕i茅台平台的预约流程提供登录、加密处理、定时提交等环节的代码参考帮助读者理解自动化脚本的基本结构与实现思路。压缩包共10个文件以6个py脚本为核心辅以2个txt依赖与说明、1个md文档和1个example配置示例整体仅8KB轻量易读适合本地快速浏览与二次学习。目前已有142人学习下载具备一定参考热度。通过阅读main、process、encrypt、login等模块读者可梳理出请求构造、参数加密、日志记录与流程调度的完整脉络并借助README与配置示例快速搭建运行环境理解脚本工程化的目录组织方式。需要提醒的是此类自动化操作可能触及平台规则与法律边界建议仅作技术研究理性看待其实际效果与潜在风险。1. 从「i茅台预约脚本.zip」说起一个被低估的自动化练手场景很多人第一次看到「i茅台预约脚本.zip」这个标题第一反应是「抢购外挂」。但真正做过一段时间的人会告诉你它更像一个移动端接口自动化的入门练手项目定时触发、参数签名、请求头拼装、结果判定、失败重试一整套流程都能在这一个场景里跑通。i茅台每天固定时段开放预约接口结构相对稳定返回体是规整的 JSON非常适合拿来练「定时任务 签名参数 会话保持」这三件事。它适合谁适合已经会写 Python 请求、但没做过完整自动化闭环的人也适合想理解「逆向算法 mt-v 这类签名参数到底怎么落地」的移动端方向从业者。不适合谁不适合想靠它稳定中签的人——预约成功与否取决于配额和随机性脚本只能保证「不漏约、不手抖、不忘记」不能保证结果。把预期放正这个方向才值得投入。2. 拆解 i茅台预约链路从登录态到提交预约的四个关键环节在动手写任何一行代码之前得先把整条链路拆清楚。i茅台 App 的预约流程本质上是一串 HTTP 请求按顺序串起来的状态机任何一环断了后面的提交都会失败。我一般把它拆成四段设备与登录态准备 → 商品与门店信息拉取 → 签名参数生成 → 预约提交与结果判定。这四段里前两段是「体力活」第三段是「技术活」第四段是「耐心活」。2.1 登录态与设备标识为什么 token 和 deviceId 必须成对保存i茅台的服务端会同时校验两样东西一个是用户身份的 token通常放在请求头里另一个是设备标识deviceId 或类似的字段。很多人只存 token结果换台机器跑就报「登录失效」或者「请求异常」。原因是服务端把 token 和设备做了绑定token 单独拿出来用会被判定为异常会话。常见做法是登录成功后把 token、deviceId、以及登录时用到的 User-Agent 一起序列化存到本地文件下次启动直接读避免频繁重新登录触发风控。下面是一个最小化的会话保存结构import json import os SESSION_FILE session.json def save_session(token, device_id, user_agent): 把登录态三件套落盘缺一不可 data { token: token, device_id: device_id, user_agent: user_agent, saved_at: int(__import__(time).time()) } with open(SESSION_FILE, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) def load_session(): 读取本地会话文件不存在返回 None if not os.path.exists(SESSION_FILE): return None with open(SESSION_FILE, r, encodingutf-8) as f: return json.load(f)逻辑说明save_session把三样东西一起写盘是为了保证下次请求时请求头能完整还原。参数上token是身份凭证device_id是设备指纹user_agent必须和登录时一致——这三者任意一个变了服务端都可能拒绝。saved_at只是记录时间方便你判断会话是否过期不参与请求。提示会话文件不要提交到任何公开仓库里面等同于账号凭证。2.2 商品与门店信息shopId 和 itemId 从哪来预约提交时需要两个核心 ID商品 IDitemId和门店 IDshopId。这两个值不是写死的而是通过查询接口动态拿到的。常见做法是先请求「可预约商品列表」再根据商品请求「该商品支持的门店列表」最后选定一个 shopId。这一步的坑在于不同城市的门店列表差异很大而且部分门店会临时下架。如果你把 shopId 硬编码在脚本里某天门店调整脚本就会一直提交失败却不知道为什么。我一般会做成「每次运行前重新拉一次门店列表取第一个可用门店」并在日志里打印选中的门店名方便排查。def pick_shop(session, item_id): 拉取商品可用门店返回第一个可预约的 shopId headers build_headers(session) resp requests.get( f{BASE_URL}/shop/list, params{itemId: item_id}, headersheaders, timeout10 ) data resp.json() shops data.get(data, {}).get(shops, []) for shop in shops: if shop.get(status) 1: # 1 表示可预约 print(f选中门店: {shop[name]} id{shop[shopId]}) return shop[shopId] raise RuntimeError(没有可用门店检查商品是否下架)逻辑说明build_headers负责拼装带 token 的请求头后面会讲。参数item_id来自上一步的商品列表。返回时只取status 1的门店避免选到已关闭的。如果全部不可用就抛异常而不是静默失败——静默失败是自动化脚本最坑的地方你根本不知道它有没有在干活。2.3 签名参数 mt-v它到底在防什么热搜里出现的「i茅台算法 mt-v」「i茅台逆向」指向的就是请求签名。服务端要求每个请求带一个动态计算的签名字段这里统称 mt-v它通常由时间戳 请求参数 固定盐值经过某种哈希或加密得到。它的作用是防止请求被篡改和重放时间戳保证请求有时效性参数参与计算保证内容没被改过。需要说清楚的是签名算法的具体实现属于逆向范畴不同版本可能变化我这里不展开具体算法细节只讲工程上怎么组织。核心思路是——把签名计算封装成一个独立函数输入是参数字典和时间戳输出是签名字符串其余代码不关心它怎么算。import hashlib import time def build_sign(params: dict, salt: str) - str: 通用签名骨架按 key 排序拼接 时间戳 盐再做哈希 实际算法以你抓到的请求为准这里只演示组织方式 ts str(int(time.time() * 1000)) params[ts] ts # 按 key 字典序拼接保证每次顺序一致 raw .join(f{k}{params[k]} for k in sorted(params)) raw_with_salt f{raw}salt{salt} sign hashlib.md5(raw_with_salt.encode(utf-8)).hexdigest() return sign, ts逻辑说明params是本次请求的所有业务参数salt是固定盐值从客户端里提取属于敏感信息自己保存好。函数先把参数按 key 排序拼接再拼上盐做哈希。返回签名和时间戳时间戳要一起放进请求参数里否则服务端无法校验时效。参数上要注意排序规则、拼接分隔符、是否包含空值这三处任何一处和真实算法不一致签名都会校验失败。2.4 提交预约与结果判定别把「请求成功」当成「预约成功」这是新手最容易翻车的地方。HTTP 返回 200 不代表预约成功返回体里通常还有一层业务状态码。常见结构是{code: 200, data: {...}, message: ...}其中code才是业务结果。我见过有人脚本跑了一周日志全是「成功」结果一个都没约上——因为他只判断了 HTTP 状态码。正确做法是解析返回体的业务码并把每次结果写进日志文件包含时间、门店、返回消息。这样出问题时能回溯。下面是一个判定片段def submit_reservation(session, item_id, shop_id): params {itemId: item_id, shopId: shop_id} sign, ts build_sign(params, SALT) params[sign] sign params[ts] ts resp requests.post( f{BASE_URL}/reserve/submit, dataparams, headersbuild_headers(session), timeout10 ) body resp.json() biz_code body.get(code) msg body.get(message, ) if biz_code 200: print(f[成功] {msg}) return True else: print(f[失败] code{biz_code} msg{msg}) return False逻辑说明build_sign生成签名后把sign和ts一起塞回参数。请求用 POST 提交表单。判定时只看body[code]不看 HTTP 状态码。参数上item_id和shop_id必须来自前面动态拉取的结果不能硬编码。失败时打印业务码和消息方便对照服务端返回排查。3. 把脚本跑起来定时、重试与日志三件套的落地写法链路拆清楚之后接下来是让它「自己跑」。一个能长期稳定运行的预约脚本靠的不是算法多精妙而是定时准、重试稳、日志全。这一章讲具体怎么落地包括调度方式的选择、重试策略的参数、以及日志该记什么。3.1 定时触发为什么我不用 while True sleep很多人写定时任务的第一反应是while True: do_job(); time.sleep(86400)。这个写法在预约场景里有两个致命问题一是 sleep 期间如果进程被系统挂起或机器休眠时间会漂移二是如果某次任务卡住整个循环就废了。我一般用系统级调度Linux 的 cron 或 Windows 的任务计划程序来触发脚本脚本本身只负责「跑一次」跑完就退出。以 Linux 为例假设脚本路径是/home/user/mt_reserve/main.py预约时间是每天 9:00cron 配置写成# 每天 8:59 启动脚本内部再等到 9:00 精确提交 59 8 * * * /usr/bin/python3 /home/user/mt_reserve/main.py /home/user/mt_reserve/cron.log 21逻辑说明cron 表达式59 8 * * *表示每天 8:59 触发。为什么提前一分钟因为脚本启动、加载会话、拉取门店都需要时间提前启动能让它在 9:00 前完成准备工作到点直接提交。 cron.log 21把标准输出和错误都追加到日志避免 cron 的邮件告警。参数上/usr/bin/python3要用绝对路径cron 的环境变量和登录 shell 不一样写相对路径经常找不到解释器。注意cron 的最小粒度是分钟如果你需要秒级精度得在脚本内部用time.sleep补足到目标秒数。3.2 重试策略重试几次、间隔多久才不触发风控预约提交失败的原因分两类一类是网络抖动、超时这类值得重试另一类是业务拒绝比如「今日已约」「库存不足」这类重试没意义反而增加风控风险。所以重试必须区分错误类型不能无脑循环。我一般的策略是网络类异常重试 3 次间隔 1 秒、2 秒、4 秒指数退避业务类失败直接记录并退出不重试。下面是一个带类型判断的重试封装import time import requests def retry_request(func, max_retry3, base_delay1): 只对网络异常重试业务失败直接抛出 for attempt in range(max_retry): try: return func() except (requests.Timeout, requests.ConnectionError) as e: wait base_delay * (2 ** attempt) print(f网络异常 {e}{wait}s 后重试 ({attempt1}/{max_retry})) time.sleep(wait) raise RuntimeError(重试次数用尽仍失败)逻辑说明func是一个无参函数内部封装了单次请求。max_retry控制最大重试次数base_delay是基础间隔。2 ** attempt实现指数退避第 1 次等 1 秒第 2 次等 2 秒第 3 次等 4 秒。参数上max_retry不建议超过 3间隔不建议小于 1 秒——太密集的请求容易被服务端标记。捕获的异常只包含超时和连接错误业务异常不在这里处理。3.3 日志设计出问题时你能查到什么日志不是「打印点东西就行」。预约脚本的日志至少要包含运行时间、选中的门店、每次请求的结果码、失败原因。我习惯用 Python 的 logging 模块按天切分文件避免单个日志文件无限增长。import logging from logging.handlers import TimedRotatingFileHandler def setup_logger(namemt): logger logging.getLogger(name) logger.setLevel(logging.INFO) handler TimedRotatingFileHandler( reserve.log, whenmidnight, backupCount7, encodingutf-8 ) fmt logging.Formatter(%(asctime)s [%(levelname)s] %(message)s) handler.setFormatter(fmt) logger.addHandler(handler) return logger逻辑说明TimedRotatingFileHandler在每天午夜切分日志backupCount7保留最近 7 天。whenmidnight是切分周期。格式里带上时间、级别、消息方便按时间检索。参数上encodingutf-8必须写否则中文门店名会乱码。日志级别用 INFO 就够DEBUG 会打印太多请求细节反而干扰排查。4. 避坑与排查i茅台预约脚本最常见的五个翻车点这一章是我自己踩过、也见过别人踩的坑按「现象 → 原因 → 解决」写。每一条都是真实会遇到的不是凑数。坑一脚本显示成功实际没约上。现象日志里全是「成功」但账号里没有预约记录。 原因只判断了 HTTP 200没判断返回体的业务码。 解决解析body[code]只有业务码等于成功值才算成功其余一律记为失败并打印 message。坑二跑几天后突然全部请求失败。现象前几天正常某天开始所有请求返回登录失效。 原因token 过期或者服务端更新了签名算法导致 mt-v 校验不通过。 解决先重新登录刷新 token如果刷新后仍失败说明签名算法变了需要重新抓包对比参数。这也是为什么签名函数要独立封装——改一处就行。坑三同一账号频繁请求被限制。现象短时间内多次提交返回「操作频繁」或直接无响应。 原因重试策略没区分业务失败把「今日已约」也当成可重试错误导致密集请求。 解决业务类失败不重试网络类失败才重试且间隔用指数退避。单账号每天提交次数控制在个位数。坑四换机器后 deviceId 对不上。现象本地跑正常部署到服务器就报设备异常。 原因deviceId 是登录时生成的和 token 绑定换环境没同步。 解决把 deviceId 和 token 一起保存和迁移不要在新环境重新生成。如果必须重新登录就重新走一遍完整登录流程。坑五cron 跑了但没日志。现象cron 配置看起来没问题但日志文件一直是空的。 原因cron 环境变量缺失Python 找不到依赖或者工作目录不对导致相对路径失效。 解决所有路径写绝对路径在 cron 里显式设置PATH或者用一个 shell 脚本包一层先cd到脚本目录再执行。提示排查时优先看日志的最后一次运行记录而不是从头翻。大部分问题在最后一次运行里就有线索。5. 进阶把单账号脚本改造成可维护的多账号调度器单账号跑通之后很多人会想扩展到多账号。这里有个反直觉的结论多账号的难点不在并发而在隔离。每个账号的 token、deviceId、请求节奏都必须独立否则一个账号触发风控会连累其他账号。我一般不会用多线程硬怼而是用「顺序执行 账号间随机延迟」的方式牺牲一点速度换稳定性。具体做法是把账号配置写成一个列表每个元素包含账号标识和会话文件路径主循环遍历列表每个账号独立加载会话、独立提交、独立记录日志账号之间 sleep 一个随机秒数比如 3 到 8 秒模拟人工操作节奏。import random import time ACCOUNTS [ {name: acc1, session: session_acc1.json}, {name: acc2, session: session_acc2.json}, ] def run_all(): for acc in ACCOUNTS: print(f 处理账号 {acc[name]} ) session load_session_from(acc[session]) if not session: print(f{acc[name]} 无有效会话跳过) continue try: item_id pick_item(session) shop_id pick_shop(session, item_id) submit_reservation(session, item_id, shop_id) except Exception as e: print(f{acc[name]} 执行异常: {e}) # 账号间随机延迟降低风控概率 delay random.uniform(3, 8) print(f等待 {delay:.1f}s 后处理下一个账号) time.sleep(delay)逻辑说明ACCOUNTS是账号配置列表每个账号对应一个独立的会话文件。run_all顺序遍历单个账号异常不影响其他账号用 try 包住。random.uniform(3, 8)生成 3 到 8 秒的随机延迟避免固定间隔被识别。参数上延迟范围可以根据账号数量调整账号越多单个账号的间隔可以适当拉长。验证改造是否成功有个简单方法先只放一个账号跑一天确认日志正常再放两个账号跑一天观察是否有账号因为「操作频繁」失败。如果两个账号都正常再逐步增加。不要一次性上十个账号出了问题你根本不知道是哪个环节的锅。最后说个我自己的习惯每次改完脚本我会先手动跑一次盯着日志从头看到尾确认每一步的打印都符合预期再交给 cron。这个习惯帮我省了无数次「半夜发现脚本根本没跑」的后悔药。自动化脚本的可靠性从来不是写出来的是盯出来的。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网