news 2026/9/17 6:04:37

Python京东抢购脚本技术解析:虚拟环境、Cookie管理与异步高并发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python京东抢购脚本技术解析:虚拟环境、Cookie管理与异步高并发

简介:针对京东平台茅台抢购场景的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.batpyvenv.cfgpython.exepythonw.exe甚至t64-arm.exe这些文件时,很多人会怀疑是不是文件缺失。 其实这是 Python 虚拟环境被一起打包进来的典型结构。 虚拟环境的作用是给这个抢购脚本一个独立的第三方库空间,你在这个环境里装requestsaiohttp,不会影响系统 Python 和其他项目。 直接使用别人打包好的 venv 有个隐患:构建环境的 Python 版本和你本机不一致,可能导致sysconfig.cfg里的路径失效。 所以我一般不会直接用压缩包里的环境,而是花三分钟重建一个。

2.1 虚拟环境里那些文件到底干嘛的

打开Scripts目录,你会看到两个没有扩展名的文件activatedeactivate,以及对应的.bat版本。 前者是 bash 环境用的,后者是 Windows cmd 用的。 如果你在 PowerShell 里执行.bat后发现python命令还是指向系统解释器,说明激活脚本没有生效。 这时先看执行策略,很多机器默认禁止运行脚本,需要用Set-ExecutionPolicy -Scope Process Bypass临时放开。

文件作用踩坑点
pyvenv.cfg记录虚拟环境对应的系统 Python 路径和版本整体移动目录后路径失效
sysconfig.cfg保存 Python 构建时的编译/安装路径不需要手动编辑
activate.batWindows 下激活虚拟环境的入口必须使用 cmd 执行
deactivate.bat退出虚拟环境找不到环境变量时先deactivate
python.exe虚拟环境的 Python 解释器双击可运行,但与 pip 绑定不一致
pythonw.exe无控制台窗口的 Python 入口不能直接拿来跑交互脚本
t64-arm.exe/w64-arm.exepip 安装部分二进制包时调用的辅助程序杀毒软件可能误报风险

看到pythonw.exe就需要注意:抢购脚本里的printloggingpythonw中不会显示到控制台,所以调试时一定用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是项目作者留下的依赖清单,存在就用它统一装。 不存在就手动补requestsaiohttpschedule。 其中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.9

home指向基础 Python 的安装目录,include-system-site-packages设为false说明不会读取全局第三方库。 抢购脚本如果在虚拟环境里能 import 的包很少,不要往这个文件里乱加路径。 一个常见方案是重新创建虚拟环境,因为修改home和注册表项对普通用户来说太容易出错。 另外,虚拟环境不要放在中文路径和带空格的目录下,部分第三方库的 C 扩展在编码处理上会有问题,导致ImportError: DLL load failed

3. 抢购请求链路:用 requests 和 aiohttp 把登录、加购、结算串起来

抢购脚本本质上是一个带状态的爬虫:先登录获取会话,再查询商品库存,接着加购,最后提交订单。 这四步之间有严格的先后依赖,且每一步都需要从响应中提取参数传给下一步。 常见做法是用requestsSession保持 Cookie,用aiohttpClientSession做并发查询。 很多人纠结该用同步还是异步,我的判断是:查库存这种高度 I/O 密集的场景用异步;加购和提交这种链式、依赖强逻辑的步骤用同步更直观。

3.1 请求顺序与常用接口

阶段目的依赖数据
登录换取 Cookie账号密码或扫码
查库存判断是否可抢skuId, area
加购把商品放进购物车skuId, num
结算页获取订单 token购物车数据
提交订单创建订单支付密码、风险校验数据

这里面最容易出错的是area参数。 京东的库存和配送区域绑定,同一个 sku 在北京有货,在成都可能无货。 要拿自己账号的area,最直接的方式是打开商品页,用浏览器开发者工具过滤stock开头的接口,看请求参数里的area。 不同位置的用户不能直接复制别人的参数,否则轮询永远是 0。

这些接口参数不能靠猜。 我一般是打开浏览器开发者工具,切到移动端模拟器,访问商品页,然后过滤.jd.com请求,逐个看加购、库存接口的实际请求头和请求体。 特别注意User-AgentRefererCookie三个字段,脚本里任何缺失都会让响应变成error:0。 抓包时看到返回的 JSON 里有明显的时间戳或订单号,就是你要传给下一步的参数。

时间控制上,常见做法是用scheduletime.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, params=params, data=json.dumps(body), timeout=3) return resp.json() except requests.Timeout as exc: return {"error": f"timeout: {exc}"}

这个函数不带重试,是把加购动作单独拆出来,方便在库存命中时立刻调用。params里的t是毫秒时间戳,很多客户端接口会用它做缓存控制;appid表示请求来自哪个客户端,改动会影响返回结构。 注意这里用的是data=json.dumps(body)而不是json=body,因为京东移动端接口对请求体编码更敏感,字符串形式能避免内容被二次转义。timeout=3是经验值,抢购瞬间如果 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, params=params, timeout=3) 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(headers=headers) 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 的请求超时,timeout=3会在 3 秒后让整个 gather 继续,不会影响其他 sku 的结果。

4. 登录态与风险控制:Cookie 有效期、配置文件和异常处理

很多抢购代码跑不起来,不是算法错,而是登录态早就失效。 浏览器里打开京东还能看到商品,是因为浏览器还有内存中的会话;脚本从配置文件里读到的 Cookie 可能是一个月前的。 判断 Cookie 是否有效的方式很简单:拿它请求一次购物车接口,返回空列表说明登录态可用;返回login跳转说明失效。 我的经验是抢购前 10 分钟专门用一个任务检测 Cookie,不通过就输出明确错误,而不是等开场后才在日志里看到一个 302。

4.1 登录态从哪来

手动获取的好处是简单,坏处是 Cookie 有效期不可控。 流程很固定:浏览器打开京东登录页,扫码登录,开发者工具里找到pt_keypt_pin,复制下来写入配置文件。 这两个值加在一起是一条有效 Cookie。 有些人直接把整个请求头里的所有 Cookie 都复制进去,能用,但里面那些_gidshshshfpa之类的跟踪字段会拉长请求头,增加被风控识别的概率。

如果页面要求验证码,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 只做登录这一步,真正抢购还是回到requestsaiohttp,因为浏览器自动化本身太重,抢购时一个页面渲染就可能耗掉一秒,早就错过下单窗口了。 抓到的 Cookie 可以拼成pt_key=xxx; pt_pin=yyy直接写进config.json。 这里要留意,Selenium 版本和 Chrome 版本不匹配时,webdriver.Chrome()会直接抛异常,先确认 chromedriver 和浏览器主版本一致。

4.2 把账号和 Cookie 拆到独立配置文件

不要让代码里的人名、Cookie 和商品 ID 混在一起,尤其是二次分发时很容易泄露。 我会新建一个config.json

{ "accounts": [ { "name": "primary", "cookie": "pt_key=xxx; pt_pin=yyy;", "sku": "100012043978", "area": "1_72_4137" } ] }

读取逻辑:

import json import sys def load_accounts(path: str = "config.json") -> list[dict]: try: with open(path, "r", encoding="utf-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

这段代码有两个细节:一是用encoding="utf-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, json=payload, timeout=3) data = resp.json() if resp.status_code != 200: raise requests.RequestException(f"HTTP {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( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[ logging.FileHandler("jd_seckill.log", encoding="utf-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 串掉。 正确做法是每个账号一个 session:

async 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(headers=headers) as session: ok = await poll_and_buy(session, account["sku"], account["area"])

poll_and_buy是把第 3 章的库存检查和第 4 章的异常重试组合起来的主流程函数。 每个账号的ClientSession独立创建,协程内部产生的 Cookie 变化不会污染其他账号。 多账号并发测试时,建议先用两个账号在非真实场次跑十分钟,观察日志里有没有出现跨账号的请求头错乱;确认稳定后再投入真正活动。 我以前在非活动场次用假商品 ID 跑通完整流程后,才敢切真实账号和真实场次,这个顺序比调参更重要。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 6:03:37

可编程直流电源在光模块测试中的关键作用与AT66333A应用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 6:03:14

OpenSandbox task-executor任务执行器解析:批任务如何驱动沙箱

OpenSandbox task-executor任务执行器解析:批任务如何驱动沙箱 【免费下载链接】OpenSandbox Secure, Fast, and Extensible Sandbox runtime for AI agents. 项目地址: https://gitcode.com/GitHub_Trending/ope/OpenSandbox OpenSandbox 的 task-executor …

作者头像 李华
网站建设 2026/9/17 6:03:09

AI算力供电芯片测试座定制:大电流、接触电阻与散热全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 6:03:07

Python数据可视化:Matplotlib核心技巧与应用

1. 为什么需要掌握Matplotlib图表绘制在数据分析和科学计算领域,可视化是理解数据和传达见解的关键手段。Matplotlib作为Python生态系统中最基础的绘图库,其重要性体现在三个维度:首先,它是Python数据科学生态的基础设施。超过83%…

作者头像 李华
网站建设 2026/9/17 6:00:47

RK3568 USB鼠标驱动开发实战:从HID协议到设备树全链路解析

很多从单片机转过来玩RK3568的朋友,拿到板子问的第一句话往往是:USB鼠标的驱动怎么写?这是个很有代表性的问题。要理解这个问题,得先知道一个事实:Linux内核自带USB HID驱动,默认情况下鼠标插上去就能用。真…

作者头像 李华
网站建设 2026/9/17 6:00:37

驱动钛丝在汽车腰托气阀中的应用与失效排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华