ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

京东抢茅台Python脚本详解:从requests到selenium的自动化实践

京东抢茅台Python脚本详解:从requests到selenium的自动化实践 简介基于Python开发的京东茅台抢购自动化脚本面向有编程基础、想了解电商自动化抢购实现的开发者。项目覆盖登录鉴权、商品监控、定时抢购、异常重试等环节综合运用网络请求、数据解析、定时调度和浏览器模拟点击展示脚本自动化在真实业务中的落地方式。资源共1940个文件压缩包11.26MB核心为860个py源码与859个pyc编译文件另有83个头文件、20个exe组件及虚拟环境支持便于直接配置运行。目前已有12214人学习是研究京东接口交互与高并发抢购逻辑的参考案例。通过研读源码可掌握Cookie复用、多线程并发、日志管理、配置分离等技能项目保留了完整目录结构与依赖清单便于二次开发。需提醒此类自动抢购行为违反电商平台规则存在封号风险建议仅用于技术学习或沙箱验证。1. 京东抢茅台Python脚本到底在抢什么先拆需求和边界随手搜“京东抢茅台Python脚本”能找到的代码十有八九是两类一类是纯请求脚本登录后直接调库存接口轮询到点提交订单另一类是基于浏览器自动化用 Selenium 把登录、加购、结算、提交全流程串起来。两者的差别不只在速度还在维护成本。请求脚本快但平台改一次签名或字段就要跟着改浏览器脚本稳定但启动慢、容易被检测。下面从零构建一个能定时、能监控库存、能提交订单的最小闭环把登录态管理、库存探测、提交节奏和容错分开讲。适合已经会写基础 Python、想搞懂电商自动化脚本内部结构的人不只是为了“抢”这一件事——这套状态处理思路放到监控、告警、批量下单场景里同样成立。先声明边界不涉及任何协议破解与验证码绕过手段遇到验证码就停交给人工处理。2. 京东抢茅台Python脚本的两条路线requests 与 selenium 怎么选2.1 请求级脚本和浏览器脚本的差距不在速度在稳定性从技术角度纯requests脚本的过程是拿到登录后的 Cookie模拟库存查询接口等库存从 0 变 1立刻调加购接口、结算页接口、提交订单接口。全程没有浏览器毫秒级完成但两个问题绕不开一是接口字段里通常带签名、时间戳、设备指纹等反爬参数换一台机器跑就可能失效二是一旦某个环节的字段校验失败报错信息含糊排查成本高。Selenium 脚本的过程则是控制一个真实的 Chromium 浏览器走页面点击路径。它慢但页面渲染、JS 加密、Cookie 都由浏览器自己完成维护难度低得多。实际项目中我一般先用 Selenium 完成登录和关键流程探路观察 Network 面板里每一次请求的 URL、参数和返回结构再决定后续用请求脚本还是浏览器脚本。两种形态常常混用不是非此即彼。对比项requests 脚本selenium 脚本请求速度快毫秒级慢需等页面渲染依赖登录态Cookie/Token浏览器会话抗接口变化弱字段一变就失效较强跟随页面更新人机识别易暴露请求特征也会暴露但更像真人维护成本高需常抓包比对中低选择器稳定即可适用阶段库存探测、批量查询登录、提交订单表格里的“适用阶段”是理解这个脚本的一条主线。库存探测用 requests 做高频轮询省资源登录和提交订单用 selenium 做稳定。两条腿走路比单一技术路线靠谱。抓包我一般直接用 Chrome 开发者工具的 Network 面板过滤 Fetch/XHR 就能看到接口调用链不需要额外装 Charles。2.2 最小可运行的项目骨架把上面的思路落成文件结构不必复杂能跑通就行。以下是我常用的骨架jd_bot/ ├── config.py # 统一配置间隔、重试、开关 ├── session.py # 登录态管理Cookie 读取与校验 ├── stock.py # 库存探测模块 ├── order.py # 提交订单模块 ├── scheduler.py # 定时触发与重试编排 └── run.py # 入口依赖只有requests和selenium再加一个python-dotenv读环境变量避免把 Cookie 写死进代码。安装命令pip install requests selenium python-dotenv逻辑说明session.py只负责持有会话和 Cookie不掺业务逻辑stock.py只返回“有货/无货”布尔值order.py只管提交订单并返回结果码scheduler.py把这些串起来。每层各干各的事后续接口字段变了只改对应模块不会牵一发而动全身。参数说明requests是 HTTP 客户端selenium是浏览器自动化库python-dotenv负责从.env文件读取敏感信息。三者互相独立安装时保持 PyPI 上的最新稳定版即可不必特意锁定版本。2.3 依赖安装的环境陷阱pip install装完最容易出问题的是 Selenium 驱动的路径。下载对应版本的 ChromeDriver 后要确认webdriver.Chrome能直接创建实例。Windows 上报 “无法将 chromedriver 识别为 cmdlet、函数、脚本文件或可运行程序的名称” 之类错误大多是没把驱动所在目录加进 PATH或者驱动版本和浏览器大版本不匹配。先在命令行单独跑这段验证python -c from selenium import webdriver; wd webdriver.Chrome(); wd.quit(); print(ok)这行命令若能打印ok说明浏览器驱动链路是通的。真正的重点是观察 Selenium 弹出的浏览器窗口能否正常打开打不开就看驱动日志报错里会写明浏览器版本要求。3. 京东抢茅台Python脚本的库存探测从 Cookie 到动态退避3.1 登录态先解决一切脚本才有意义无论走哪条路线都要先解决“脚本如何证明我是我”。最常规、维护成本最低的做法是浏览器里人工登录一次然后把 Cookie 导出给脚本复用。将登录后复制的关键 Cookie 写入.env文件JD_COOKIEpt_keyxxx; pt_pinyyy;session.py里用如下方式加载并组装import os import requests from dotenv import load_dotenv load_dotenv() def build_session(): cookie_str os.getenv(JD_COOKIE, ) if not cookie_str: raise RuntimeError(缺少 JD_COOKIE 环境变量) s requests.Session() s.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Referer: https://www.jd.com/, }) for item in cookie_str.strip().split(;): if not item: continue k, _, v item.partition() s.cookies.set(k.strip(), v.strip()) return s逻辑说明使用requests.Session自动管理 Cookie后续所有请求共用同一个会话服务端才能识别为连续操作。load_dotenv()把.env文件里的变量加载进环境避免敏感信息进入 Git 仓库。Cookie 字符串按;分割后逐条写入会话。参数说明User-Agent必须用真实浏览器值不要用默认的python-requests否则极易被识别为脚本流量。Referer设置成站点首页是为了让请求上下文更接近正常访问路径。这里的pt_key和pt_pin是演示用键名真实键名与有效期以实际登录后浏览器中看到的为准脚本跑不通时第一件事就是检查这两个 Cookie 是否过期。3.2 库存轮询请求怎么写才不显得“机器人”库存探测的请求应该是轻量、低频、有退避的而不是用 while True 疯狂循环。先看一个可工作的框架import time import logging import requests logger logging.getLogger(stock) def check_stock(session, sku_id, stock_url, retries3, interval1.5): for attempt in range(1, retries 1): try: resp session.get( stock_url, params{skuId: sku_id}, timeout3 ) data resp.json() if data.get(stock) 1: return True return False except (requests.RequestException, ValueError) as exc: logger.warning(第 %s 次探测失败: %s, attempt, exc) if attempt retries: time.sleep(interval * attempt) return False逻辑说明每次调用只探测一次失败才重试重试间隔带乘法退避第一次等 1.5 秒第二次等 3 秒。把“探测”和“调度”拆开职责单一后续加并发的时候也容易扩展。注意异常捕获里同时处理了RequestException和ValueError因为接口返回值有可能是非 JSON 的页面源码resp.json()会抛ValueError。参数说明timeout3表示超过 3 秒没响应就按异常处理retries3是单次探测的重试上限interval1.5是基础间隔。这三个值没有绝对最优间隔太大容易错过库存窗口太小容易被限流。保守做法是高频探测每秒不超过 2 次日常探测保持 1~3 秒一次宁可慢半拍不要触发风控。提示stock_url的真实值必须通过浏览器开发者工具里的 Network 面板抓取以当天实际请求为准。凡是写死在代码里的 URL 都可能过时建议统一放到配置文件里方便随时替换。3.3 库存为 0 时的处理策略不是“继续死等”普通需求里库存为 0 时脚本会继续轮询但这会让日志每分钟刷上百行。更合理的方式是库存为 0 时按 3 秒间隔探测连续 10 次无货就指数退避到 30 秒等检测到有货时再降回 1 秒。这个“动态间隔”既保持了响应速度又避免无意义的请求洪峰。def monitor(session, sku_id, stock_url, on_stock): interval 1.0 empty_streak 0 while True: if check_stock(session, sku_id, stock_url): empty_streak 0 interval 1.0 logger.info(检测到库存触发后续动作) on_stock() break empty_streak 1 if empty_streak 10: interval min(30.0, interval * 2) logger.info(连续无货退避到 %.1fs, interval) time.sleep(interval)逻辑说明empty_streak记录连续无货次数超过 10 次后interval指数翻倍封顶 30 秒一旦有货立即把间隔拉回 1 秒。核心思路是“探测频率动态化”在长时间无货的场景下能把请求量降一个数量级也能直接降低账号被限流的概率。参数说明10是进入退避的触发阈值30.0是最大间隔。这两个数根据商品紧俏程度调整越抢手的商品阈值调小让退避来得更快越普通的商品间隔上限调大减少无效请求。日志里应当记录每次退避后的新间隔方便事后核对请求频次。4. 定时触发与订单提交把抢购节奏压进容错里4.1 本地时间同步误差要控在 200 毫秒以内定时任务的根基是时间。脚本所在机器的系统时间只要有偏差活动开始前就已经慢了一拍。开机后先做一次时间同步Windows 上执行w32tm /resyncLinux 上执行sudo systemctl restart systemd-timesyncd另外所有时间比对统一用目标时区而不是跟随系统时区from datetime import datetime, timezone, timedelta import time BEIJING_TZ timezone(timedelta(hours8)) def wait_until(target_hour10, target_minute0, target_second0): now datetime.now(BEIJING_TZ) target now.replace( hourtarget_hour, minutetarget_minute, secondtarget_second, microsecond0 ) delta (target - now).total_seconds() if delta 0: raise ValueError(目标时间已过请检查系统时间) print(f距离目标时刻还有 {delta:.2f} 秒) time.sleep(delta - 0.1)逻辑说明这里显式构造timezone(timedelta(hours8))保证无论脚本跑在哪个云主机或本机上触发时刻都不会被系统时区带偏。time.sleep(delta - 0.1)预留了 0.1 秒调度余量因为随后还有库存探测和提交请求提前唤醒比延后触发更安全。参数说明target_hour、target_minute、target_second是目标触发时刻。预留的0.1秒是本地调度开销不包含网络耗时。如果目标时刻精确到秒级就不能再叠加额外的随机等待否则误差会滚雪球。4.2 提交订单的流程要走“有状态”的编排订单提交不能只用一个 requests 请求干到底。合理的编排是先验证登录态再取结算参数最后提交订单。每一步都可能有明确的失败信号所以每一步都返回状态码而不是抛异常了事。def submit_order(session, settle_url, submit_url, order_params): step validate try: auth_code validate_session(session) if auth_code ! 200: return {ok: False, step: step, code: auth_code} step settle settle_resp session.post( settle_url, jsonorder_params[settle], timeout5 ) if settle_resp.status_code ! 200: return {ok: False, step: step, code: settle_resp.status_code} step submit submit_resp session.post( submit_url, jsonorder_params[submit], timeout5 ) if submit_resp.ok and submit_resp.json().get(orderId): return { ok: True, step: done, order_id: submit_resp.json()[orderId] } return {ok: False, step: step, code: submit_resp.status_code} except requests.RequestException as exc: return {ok: False, step: step, error: str(exc)}逻辑说明函数内用step变量记录当前执行到哪一步任何异常或非预期响应都能明确告诉调度器“卡在第几步、返回码是什么”。这里的编排思想是“把失败当作正常返回”而不是让异常一路冒泡只有这样才能配合后面的重试策略。参数说明settle_url是获取结算信息的接口submit_url是提交订单的接口。order_params里分settle和submit两组参数分别对应两个请求的请求体。timeout5表示单个接口 5 秒无响应即判定失败避免某个接口卡死拖垮整个脚本。4.3 提交失败后怎么重试不把接口打爆重试也要分级不能一失败就立刻原样重发那只会加重服务端压力、加速触发风控。常见的四级策略是登录态校验失败后隔 0.5 秒重试一次结算步骤失败隔 1 秒重试两次提交订单失败隔 2 秒重试三次。提交这种写操作重试三次仍失败就应当停止并告警因为后端可能已经创建订单继续重试有重复下单的风险。retry_policy { validate: (0.5, 1), settle: (1.0, 2), submit: (2.0, 3), } def run_with_retry(session, settle_url, submit_url, order_params): last None for step_name, (delay, times) in retry_policy.items(): for attempt in range(1, times 1): last submit_order(session, settle_url, submit_url, order_params) if last.get(ok): return last if last.get(step) step_name: time.sleep(delay * attempt) else: break return last逻辑说明retry_policy用字典定义每步的基础间隔与重试次数。内层循环按当前期望步骤匹配失败则退避等待如果返回的step已经推进到下一步说明前一步成功但下一步失败直接切换策略不再重复上一步。这种结构比统一的while True重试更可控也更容易在日志里解释清楚。参数说明0.5、1.0、2.0是基础间隔秒数分别对应三级操作的退避1、2、3是对应的最大重试次数。这些数值是经验值实际使用时依据接口响应耗时调整原则是“读操作可以快写操作必须慢”。如果重试后step停留在submit且返回码成功但没有orderId说明接口响应结构已变应立即停下检查。5. 把京东抢茅台Python脚本调顺的最后一公里日志、回放与参数自检5.1 日志信息要能让人在 10 秒内定位问题脚本跑起来后的可观察性直接决定维护成本。日志要按“时间、级别、步骤、结果、耗时”固定格式输出到文件并在控制台保留精简版import logging logging.basicConfig( levellogging.INFO, format%(asctime)s %(levelname)s %(name)s: %(message)s, handlers[ logging.FileHandler(jd_bot.log, encodingutf-8), logging.StreamHandler(), ], )FileHandler写入文件StreamHandler输出到控制台双通道保证现场日志和分析日志分离。每条日志都带时间戳和模块名排查时直接grep submit jd_bot.log就能定位到提交模块。levellogging.INFO表明生产环境只记录 INFO 以上信息调试时改成DEBUG会打印每次请求的 URL 和状态码信息量大但很有用。建议平时用DEBUG跑几轮观察行为关键时刻用INFO保持日志干净。5.2 用“定时回放”代替临阵磨枪脚本上线前最有效的验证方法是回放一次完整流程。把目标时刻改为下一小时或明天同一时间走一遍“等待触发 → 库存探测 → 提交订单”的完整链路观察每个接口的返回是否符合预期。这个过程不依赖真实库存只需确认各阶段之间的参数传递没有断裂。跑完回放后逐段核对日志等待阶段是否在预期时刻唤醒、探测阶段 URL 与参数是否有拼写错误、提交阶段是否拿到orderId。一次性跑通不说明问题连续三次回放稳定才值得在真实场景使用。回放期间如果某接口提示参数缺失优先检查 Cookie 是否过期这是最常见的静默失败。5.3 参数自检清单是交付前的最后一步以下是一份交付前必查的参数清单直接对照手头代码过一遍即可检查项预期值说明Cookie 键名与抓包一致键名错一个请求直接失败库存间隔1~3 秒太快触发风控太慢丢机会退避上限30 秒连续无货时降低服务端压力触发时间余量0.1~0.2 秒预留调度开销提交重试至多 3 次写操作不能无限重试日志级别INFO保持可读性与细节平衡这些参数没有普适的最佳值目标域名、账号等级、网络延迟都会改变最优区间。每次调整只改一个变量记录对应结果形成自己的参数基线比到处抄别人脚本里固定的sleep(0.05)可靠得多。脚本最终能稳定跑多少轮取决于日志里看不到的那部分细节Cookie 的过期时间、接口字段的校验规则以及你对每次调整的记录。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进