
简介针对京东平台茅台抢购场景的Python自动化脚本资源包面向有一定Python基础、希望了解电商自动化抢购实现原理的开发者。脚本覆盖登录会话、商品页请求、购物车操作、定时执行与异常处理等环节涉及requests/aiohttp、Selenium/PyAutoGUI、BeautifulSoup、threading/asyncio等常用库适合作为网络请求、页面解析、定时任务和并发控制的综合练习材料。资源共1940个文件主体为860个Python源码文件和859个pyc编译文件另含头文件、可执行程序、配置与文档等压缩包大小约11.26MB文件类型丰富既能看到脚本实现也能对比字节码与依赖组件便于本地调试和运行环境还原。已有12214人学习/下载实用性和关注度较高。通过该包可拿到完整项目目录、依赖组件与运行脚本重点体会登录态维护、抢购时机判断、多任务并发和异常兜底等自动化关键设计同时注意平台规则与账号风险。1. 京东抢茅台的脚本逻辑从登录到提交订单要过几道坎很多人拿到jd_seckill系列脚本第一反应是“到点帮我点一下”但真正让它有意义的是提前把登录态、商品 ID、区域编码和请求顺序都准备好。 这个 Python 脚本解决的不是“手速”而是把“查询库存、加入购物车、提交订单”这一串动作在同一个时间窗口内有节奏地执行。 它的核心价值在 Cookie 管理、库存轮询和异步请求这三件事上。 适合已经有 Python 基础、想研究网络请求和协程的开发者如果你连本地环境都没搭过建议先看完 Python 安装教程和虚拟环境那部分再回来改脚本。2. 虚拟环境与依赖从 activate.bat 到 sysconfig.cfg 的 Python 运行环境解压脚本包看到activate.bat、pyvenv.cfg、python.exe、pythonw.exe甚至t64-arm.exe这些文件时很多人会怀疑是不是文件缺失。 其实这是 Python 虚拟环境被一起打包进来的典型结构。 虚拟环境的作用是给这个抢购脚本一个独立的第三方库空间你在这个环境里装requests、aiohttp不会影响系统 Python 和其他项目。 直接使用别人打包好的 venv 有个隐患构建环境的 Python 版本和你本机不一致可能导致sysconfig.cfg里的路径失效。 所以我一般不会直接用压缩包里的环境而是花三分钟重建一个。2.1 虚拟环境里那些文件到底干嘛的打开Scripts目录你会看到两个没有扩展名的文件activate和deactivate以及对应的.bat版本。 前者是 bash 环境用的后者是 Windows cmd 用的。 如果你在 PowerShell 里执行.bat后发现python命令还是指向系统解释器说明激活脚本没有生效。 这时先看执行策略很多机器默认禁止运行脚本需要用Set-ExecutionPolicy -Scope Process Bypass临时放开。文件作用踩坑点pyvenv.cfg记录虚拟环境对应的系统 Python 路径和版本整体移动目录后路径失效sysconfig.cfg保存 Python 构建时的编译/安装路径不需要手动编辑activate.batWindows 下激活虚拟环境的入口必须使用 cmd 执行deactivate.bat退出虚拟环境找不到环境变量时先deactivatepython.exe虚拟环境的 Python 解释器双击可运行但与 pip 绑定不一致pythonw.exe无控制台窗口的 Python 入口不能直接拿来跑交互脚本t64-arm.exe/w64-arm.exepip 安装部分二进制包时调用的辅助程序杀毒软件可能误报风险看到pythonw.exe就需要注意抢购脚本里的print和logging在pythonw中不会显示到控制台所以调试时一定用python.exe而不是pythonw.exe。 至于t64-arm.exe这是在 Windows arm64 环境中安装pydantic-core这类带 C 扩展的包时由 pip 调用的辅助进程正常情况下不会单独运行。 如果杀毒软件把它隔离了pip 安装时会报Executable does not exist重装虚拟环境就能恢复。2.2 三步重建一个能跑的环境不要迷信压缩包里的 venv直接在你的机器上重建命令顺序如下cd /path/to/jd_seckill python -m venv .venv # Windows PowerShell .venv\Scripts\activate pip install requests aiohttp schedule # Linux / macOS source .venv/bin/activate pip install -r requirements.txt 2/dev/null || pip install requests aiohttp schedule第一句python -m venv .venv会调用你系统级 Python 创建.venv目录。 这里的python必须是能直接运行的那个如果报无法将“python”项识别为 cmdlet...先检查 PATH 里有没有python.exe的路径。 执行完activate后命令行提示符前面会出现(.venv)这时候再执行pip -V显示的应该是.venv下的pip。 如果还显示系统路径说明激活失败。requirements.txt是项目作者留下的依赖清单存在就用它统一装。 不存在就手动补requests、aiohttp、schedule。 其中schedule并不是抢购必需它是用来做秒级定时任务的脚本如果用while True自旋轮询就不需要它。 装完后验证一下python -c import requests, aiohttp; print(env ok, requests.__version__)能输出版本号说明模块导入路径正确。 这一步能过滤掉大部分人启动即ModuleNotFoundError的问题。 创建虚拟环境前顺手看一眼 Python 位数python --version python -c import struct; print(struct.calcsize(P) * 8)输出 64 是正常值32 位 Python 在处理大并发请求时内存寻址容易出问题建议换成 64 位。2.3 pyvenv.cfg虚拟环境的身份证pyvenv.cfg内容很短但决定了这个 venv 归属于哪个 Python。 结构大概像下面这样home C:\Users\someone\AppData\Local\Programs\Python\Python311 include-system-site-packages false version 3.11.9home指向基础 Python 的安装目录include-system-site-packages设为false说明不会读取全局第三方库。 抢购脚本如果在虚拟环境里能 import 的包很少不要往这个文件里乱加路径。 一个常见方案是重新创建虚拟环境因为修改home和注册表项对普通用户来说太容易出错。 另外虚拟环境不要放在中文路径和带空格的目录下部分第三方库的 C 扩展在编码处理上会有问题导致ImportError: DLL load failed。3. 抢购请求链路用 requests 和 aiohttp 把登录、加购、结算串起来抢购脚本本质上是一个带状态的爬虫先登录获取会话再查询商品库存接着加购最后提交订单。 这四步之间有严格的先后依赖且每一步都需要从响应中提取参数传给下一步。 常见做法是用requests的Session保持 Cookie用aiohttp的ClientSession做并发查询。 很多人纠结该用同步还是异步我的判断是查库存这种高度 I/O 密集的场景用异步加购和提交这种链式、依赖强逻辑的步骤用同步更直观。3.1 请求顺序与常用接口阶段目的依赖数据登录换取 Cookie账号密码或扫码查库存判断是否可抢skuId, area加购把商品放进购物车skuId, num结算页获取订单 token购物车数据提交订单创建订单支付密码、风险校验数据这里面最容易出错的是area参数。 京东的库存和配送区域绑定同一个 sku 在北京有货在成都可能无货。 要拿自己账号的area最直接的方式是打开商品页用浏览器开发者工具过滤stock开头的接口看请求参数里的area。 不同位置的用户不能直接复制别人的参数否则轮询永远是 0。这些接口参数不能靠猜。 我一般是打开浏览器开发者工具切到移动端模拟器访问商品页然后过滤.jd.com请求逐个看加购、库存接口的实际请求头和请求体。 特别注意User-Agent、Referer、Cookie三个字段脚本里任何缺失都会让响应变成error:0。 抓包时看到返回的 JSON 里有明显的时间戳或订单号就是你要传给下一步的参数。时间控制上常见做法是用schedule或time.sleep做秒级定时。 抢购场次更常见的是开场前 5 秒开始轮询一旦库存状态变化立刻行动。schedule.every().second.do(job)这种写法不是不行但要注意任务执行本身占时间轮询间隔要留余量否则实际请求频率会比预期低。3.2 用 requests 模拟加购请求import requests import time import json def add_to_cart(session: requests.Session, sku_id: str, area: str, cookie: str) - dict: url https://api.m.jd.com/client.action params {t: int(time.time() * 1000)} body { functionId: addToCart, appid: jd_shop_member, body: { skuId: sku_id, num: 1, area: area, }, } session.headers.update({ Content-Type: application/json, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Cookie: cookie, Referer: https://item.jd.com/, }) try: resp session.post(url, paramsparams, datajson.dumps(body), timeout3) return resp.json() except requests.Timeout as exc: return {error: ftimeout: {exc}}这个函数不带重试是把加购动作单独拆出来方便在库存命中时立刻调用。params里的t是毫秒时间戳很多客户端接口会用它做缓存控制appid表示请求来自哪个客户端改动会影响返回结构。 注意这里用的是datajson.dumps(body)而不是jsonbody因为京东移动端接口对请求体编码更敏感字符串形式能避免内容被二次转义。timeout3是经验值抢购瞬间如果 3 秒没返回等重试都比继续等这个响应强。3.3 用 asyncio 并发监听库存轮询多个 sku 时同步requests会按顺序等待造成第一个慢请求拖累后面全部请求。 这里换成aiohttp后事件循环在等待网络响应时可以切到另一个协程整体耗时会大大缩短import asyncio import aiohttp async def check_stock(session: aiohttp.ClientSession, sku_id: str, area: str) - tuple[str, str]: url https://api.m.jd.com/stock params { skuId: sku_id, area: area, t: int(time.time() * 1000), } async with session.get(url, paramsparams, timeout3) as resp: try: data await resp.json() except Exception: return sku_id, parse_error return sku_id, str(data.get(stock, {}).get(isStock, 0)) async def poll_skus(sku_ids: list[str], area: str) - dict[str, str]: headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)} async with aiohttp.ClientSession(headersheaders) as session: tasks [check_stock(session, sku, area) for sku in sku_ids] results await asyncio.gather(*tasks) return dict(results)这里的时间戳直接用time.time() * 1000表示真实 Unix 毫秒时间而不是事件循环的单调时间抢购场景里时间戳不仅要唯一更要接近服务器时间。 等到库存命中后把返回的sku_id交给同步的add_to_cart也就是先异步探测命中后同步下单。 这种混用方式比全部塞进同一个协程更好排查问题因为同步部分可以直接加日志和重试。 另外asyncio.gather默认会等待所有任务返回如果其中一个 sku 的请求超时timeout3会在 3 秒后让整个 gather 继续不会影响其他 sku 的结果。4. 登录态与风险控制Cookie 有效期、配置文件和异常处理很多抢购代码跑不起来不是算法错而是登录态早就失效。 浏览器里打开京东还能看到商品是因为浏览器还有内存中的会话脚本从配置文件里读到的 Cookie 可能是一个月前的。 判断 Cookie 是否有效的方式很简单拿它请求一次购物车接口返回空列表说明登录态可用返回login跳转说明失效。 我的经验是抢购前 10 分钟专门用一个任务检测 Cookie不通过就输出明确错误而不是等开场后才在日志里看到一个 302。4.1 登录态从哪来手动获取的好处是简单坏处是 Cookie 有效期不可控。 流程很固定浏览器打开京东登录页扫码登录开发者工具里找到pt_key和pt_pin复制下来写入配置文件。 这两个值加在一起是一条有效 Cookie。 有些人直接把整个请求头里的所有 Cookie 都复制进去能用但里面那些_gid、shshshfpa之类的跟踪字段会拉长请求头增加被风控识别的概率。如果页面要求验证码requests这条路就断了。 此时退一步用 Selenium 打开浏览器完成扫码把 Cookie 抓出来再给requests继续跑。 常见做法是写一个只负责拿 Cookie 的脚本from selenium import webdriver driver webdriver.Chrome() driver.get(https://passport.jd.com/new/login.aspx) input(扫码完成后按回车) cookies {c[name]: c[value] for c in driver.get_cookies()} print(cookies) driver.quit()用 Selenium 只做登录这一步真正抢购还是回到requests和aiohttp因为浏览器自动化本身太重抢购时一个页面渲染就可能耗掉一秒早就错过下单窗口了。 抓到的 Cookie 可以拼成pt_keyxxx; pt_pinyyy直接写进config.json。 这里要留意Selenium 版本和 Chrome 版本不匹配时webdriver.Chrome()会直接抛异常先确认 chromedriver 和浏览器主版本一致。4.2 把账号和 Cookie 拆到独立配置文件不要让代码里的人名、Cookie 和商品 ID 混在一起尤其是二次分发时很容易泄露。 我会新建一个config.json{ accounts: [ { name: primary, cookie: pt_keyxxx; pt_pinyyy;, sku: 100012043978, area: 1_72_4137 } ] }读取逻辑import json import sys def load_accounts(path: str config.json) - list[dict]: try: with open(path, r, encodingutf-8) as fh: data json.load(fh) except FileNotFoundError: print(config.json 不存在) sys.exit(1) accounts data.get(accounts, []) if not accounts: print(config.json 里没有账号信息) sys.exit(1) return accounts这段代码有两个细节一是用encodingutf-8明确打开文件避免 Windows 默认编码把中文注释读乱二是配置文件缺失时直接sys.exit(1)让上层逻辑不会在空列表上继续跑。 还有一点Cookie 字符串内部有分号在 JSON 里不用转义但如果你用的配置文件是.ini分号可能被当成注释符这就是我选 JSON 的原因。4.3 敏感信息的落地保护抢购脚本被二次转发的概率很高手里有别人账号 Cookie 这种事情不能发生在你身上。 至少要在.gitignore里处理掉这些文件.venv/ venv/ config.json *.log __pycache__/.gitignore只能防止提交远程仓库不能阻止本地合作者读取文件。 如果需要发给其他人调试把config.json里的 Cookie 替换成空串并把账号名改成your_name。 日志层面也不要打印完整 Cookie处理异常时用cookie[:10] ...既保留可读性又不会把敏感值刷到终端里。4.4 异常处理与退避重试抢购开始瞬间服务器的压力集中在同一个秒级窗口请求失败是常态。 不加异常处理的脚本一次ConnectionError就会整个崩掉。 我习惯把请求封装成带重试的函数import time import logging import requests def send_with_retry(session: requests.Session, url: str, payload: dict, retries: int 5) - dict: for attempt in range(retries): try: resp session.post(url, jsonpayload, timeout3) data resp.json() if resp.status_code ! 200: raise requests.RequestException(fHTTP {resp.status_code}) if data.get(error): logging.error(接口返回业务错误: %s, data[error]) return data return data except (requests.Timeout, requests.ConnectionError) as exc: wait 0.1 * (2 ** attempt) logging.warning(第 %s 次失败%s%.3f 秒后重试, attempt 1, exc, wait) time.sleep(wait) except ValueError: logging.error(响应不是 JSON可能被风控拦截) break return {}这里的wait 0.1 * (2 ** attempt)是退避的核心每次重试等待时间翻倍避免固定频率重试被接口统计成高频访问。HTTP 200和业务成功是两回事很多接口即使订单失败也会返回 200所以一定检查data.get(error)字段。 另外ValueError分支处理的是resp.json()解析失败这种一般不是网络问题而是返回了验证码页面继续重试没有意义直接跳出循环比空转更合理。5. 排错与压测从日志定位到多账号并发脚本上线前先用日志把整个流程串起来再谈怎么抢。 这套逻辑同样适用于抢票脚本或者其他定时任务脚本。5.1 日志输出格式import logging logging.basicConfig( levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s, handlers[ logging.FileHandler(jd_seckill.log, encodingutf-8), logging.StreamHandler() ] )FileHandler让日志同时写到文件和控制台。 排查时优先看jd_seckill.log里最后一次库存查询的时间如果时间戳远早于开场时间说明脚本提前退出如果一直刷库存但从未进入加购问题出在库存判断条件上。 日志还能帮你确认本地时间和服务器时间到底差多少这个差值直接影响提交订单的时机会不会早于服务器开放时间。5.2 三个最容易忽略的验证点现象检查项常见原因脚本启动后立刻闪退检查入口是否有if __name__ __main__被 IDE 当成模块运行返回 302 或空 bodyCookie 是否过期用浏览器重新抓取pt_key提交订单失败本地时间是否与京东服务器差超过 2 秒开启系统自动时间同步或用 NTP 校准5.3 多账号并发时的 Cookie 隔离我见过有人用threading给每个账号开一个线程再让每个线程里各自用requests这种方式理论可行但代码结构很容易把Session共享出去。 用asyncio时更要小心一个ClientSession默认带 Cookie 容器两个账号共用会把 Cookie 串掉。 正确做法是每个账号一个 sessionasync def run_account(account: dict) - None: headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Cookie: account[cookie], } async with aiohttp.ClientSession(headersheaders) as session: ok await poll_and_buy(session, account[sku], account[area])poll_and_buy是把第 3 章的库存检查和第 4 章的异常重试组合起来的主流程函数。 每个账号的ClientSession独立创建协程内部产生的 Cookie 变化不会污染其他账号。 多账号并发测试时建议先用两个账号在非真实场次跑十分钟观察日志里有没有出现跨账号的请求头错乱确认稳定后再投入真正活动。 我以前在非活动场次用假商品 ID 跑通完整流程后才敢切真实账号和真实场次这个顺序比调参更重要。本文还有配套的精品资源点击获取