简介这是一套面向个人学习者的京东商品库存监控与自动下单系统源码提供命令行Shell脚本与图形界面两种运行模式分别适配Windows与macOS可7×24小时监测指定商品库存缺货恢复后自动触发购买流程并通过微信推送订单结果。资源包共61个文件约651KB以36个txt地区编码数据、9个py核心逻辑脚本、10个zbak备份文件为主另含ico图标、png截图、md说明、ini与json配置等目录结构清晰便于按模块阅读与二次调试。目前已有55人学习下载。通过研读JdSession、timer、log等模块读者可掌握会话保持、定时轮询、日志记录与异常处理等实现思路并对比Shell与GUI两种方案的取舍适合具备一定Python基础、希望理解自动化监控与下单流程的学习者参考使用时需遵守平台协议并合理设置监控频率。1. 从一次抢购翻车说起这套 GUI 库存监控到底能干什么去年帮朋友盯一款限量键盘我写了个 requests 轮询脚本结果京东前端一改库存接口返回的字段全变了脚本还在傻乎乎地拿旧 key 取值一晚上白跑。后来我换了个思路把「请求层」和「解析层」拆开用 GUI 把参数暴露出来改配置不用动代码——这就是今天要拆的这套基于 GUI 的京东商品库存监控与自动下单系统源码。它用图形界面把商品链接、轮询间隔、库存阈值、下单数量这些参数集中管理Windows 和 macOS 都能跑适合想学 GUI 自动化、想研究电商库存轮询逻辑的个人学习场景。源码不是成品外挂而是一套可读可改的工程骨架你能看到从界面事件到后台线程、从库存判断到下单请求的完整链路。下面我按「它怎么搭起来 → 参数怎么调 → 哪里容易翻车」的顺序把这份源码拆开讲。2. GUI 框架选型与线程模型为什么不能把轮询塞进主线程拿到源码先别急着跑第一件事是看它用什么 GUI 库、后台任务怎么调度。这决定了你后面改代码时会不会一改就卡死界面。2.1 Tkinter / PyQt 的取舍与源码里的实际选择这套源码的界面层用的是 Tkinter理由很实际标准库自带Windows 和 macOS 上不用额外装 Qt 运行时源码包体积小个人学习时拉下来就能跑。PyQt 做出来的界面确实更精致但 PyInstaller 打包后动辄七八十兆对一个监控工具来说没必要。源码里主窗口大概长这样import tkinter as tk from tkinter import ttk import threading class MonitorApp: def __init__(self, root): self.root root self.root.title(京东库存监控) self.root.geometry(520x360) # 商品链接输入 ttk.Label(root, text商品链接).grid(row0, column0, padx8, pady6) self.url_entry ttk.Entry(root, width48) self.url_entry.grid(row0, column1, padx8, pady6) # 轮询间隔秒 ttk.Label(root, text轮询间隔(秒)).grid(row1, column0, padx8, pady6) self.interval_entry ttk.Entry(root, width12) self.interval_entry.insert(0, 5) self.interval_entry.grid(row1, column1, stickyw, padx8, pady6) # 启动 / 停止 self.start_btn ttk.Button(root, text开始监控, commandself.start_monitor) self.start_btn.grid(row2, column0, padx8, pady10) self.stop_btn ttk.Button(root, text停止, commandself.stop_monitor, statedisabled) self.stop_btn.grid(row2, column1, stickyw, padx8, pady10) # 日志区 self.log_text tk.Text(root, height10, width62) self.log_text.grid(row3, column0, columnspan2, padx8, pady6) self._running False self._worker None这段代码的关键不在界面多好看而在self._running这个标志位和self._worker线程句柄。界面只负责收集参数和显示日志真正的轮询逻辑必须扔到独立线程里。参数说明interval_entry默认填 5 秒这是源码作者给的保守值后面第 4 章会讲为什么不能填 1 秒log_text用Text而不是Label因为轮询日志会持续追加Label刷新会闪。2.2 后台线程与界面刷新after 轮询和队列通信Tkinter 不是线程安全的后台线程直接碰界面控件轻则日志不刷新重则整个窗口卡成黑匣子。源码里用的是「队列 after 轮询」这套经典组合import queue class MonitorApp: def __init__(self, root): # ... 前面的界面代码 ... self.msg_queue queue.Queue() self.root.after(200, self._poll_queue) def _poll_queue(self): # 每 200ms 从队列取日志更新到界面 try: while True: msg self.msg_queue.get_nowait() self.log_text.insert(tk.END, msg \n) self.log_text.see(tk.END) except queue.Empty: pass self.root.after(200, self._poll_queue) def start_monitor(self): if self._running: return self._running True self.start_btn.config(statedisabled) self.stop_btn.config(statenormal) self._worker threading.Thread(targetself._monitor_loop, daemonTrue) self._worker.start() def _monitor_loop(self): # 后台线程只往队列里塞消息不碰界面 while self._running: self.msg_queue.put(正在检查库存...) # 实际的库存请求逻辑在下一章展开 time.sleep(float(self.interval_entry.get()))逻辑说明_poll_queue在主线程里每 200 毫秒跑一次把后台线程塞进队列的消息取出来写进Text。后台线程_monitor_loop只做两件事——发请求、往队列 put 消息绝不直接调self.log_text.insert。参数上200 毫秒是刷新频率太短会空转耗 CPU太长日志会有明显延迟daemonTrue保证关窗口时后台线程跟着退出不然进程会残留。常见做法是把这个刷新间隔做成常量放在文件顶部方便统一改。3. 库存轮询与下单请求从商品链接到提交订单的完整链路界面搭好只是壳真正决定这套源码能不能用的是中间那层请求逻辑。这一章把轮询、库存判断、下单触发三段拆开讲。3.1 商品 SKU 解析与库存接口轮询京东商品链接格式不统一有item.jd.com/100012043978.html这种也有带一堆追踪参数的。源码里第一步是从链接里抠出 SKU IDimport re import requests def extract_sku(url: str) - str: # 匹配 item.jd.com/数字.html 或 ?sku数字 m re.search(ritem\.jd\.com/(\d)\.html, url) if m: return m.group(1) m re.search(r[?]sku(\d), url) if m: return m.group(1) raise ValueError(无法从链接中解析 SKU请检查链接格式) def check_stock(sku: str, session: requests.Session) - dict: # 库存查询接口返回 JSON api fhttps://item-soa.jd.com/getWareBusiness?skuId{sku} headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Referer: fhttps://item.jd.com/{sku}.html } resp session.get(api, headersheaders, timeout8) resp.raise_for_status() data resp.json() return { sku: sku, stock: data.get(stockInfo, {}).get(stockDesc, 未知), price: data.get(price, {}).get(p, 未知) }逻辑说明extract_sku用正则兜住两种常见链接格式解析失败直接抛异常避免后面拿空 SKU 去请求。check_stock里timeout8是必须的不设超时的话网络一抖线程就挂死Referer头带上商品页地址部分接口会校验来源。参数上stockDesc字段是库存描述文本源码里判断「有货」就是看这个字段是否包含「现货」或「有货」字样——这个判断逻辑比较粗第 4 章会讲它的问题。3.2 库存判断与下单触发条件轮询到库存后什么时候触发下单源码里给了一套可配置的阈值判断def should_buy(stock_info: dict, threshold: int 1) - bool: desc stock_info.get(stock, ) # 简单文本匹配实际项目建议结合库存数字字段 if 现货 in desc or 有货 in desc: return True return False def place_order(session: requests.Session, sku: str, num: int 1) - bool: # 下单接口需要登录态 cookie order_api https://trade.jd.com/shopping/order/submitOrder.action payload { skuId: sku, num: num, submitOrder: true } resp session.post(order_api, datapayload, timeout10) if resp.status_code 200 and success in resp.text.lower(): return True return False逻辑说明should_buy是触发开关源码默认只要检测到有货就返回 Truethreshold参数目前没实际参与判断属于预留扩展位——你可以改成「库存数量大于 N 才下单」。place_order依赖session里已经带上的登录 cookie源码没有内置登录模块需要你手动从浏览器导出 cookie 塞进 session这是个人学习场景下的常见做法。参数上num控制下单数量默认 1timeout10比查询接口长因为下单请求链路更重。注意下单接口返回的 success 判断很粗糙真实场景要解析 JSON 里的code字段源码这里留了改进空间。4. 避坑与排查轮询频率、登录态、库存误判这三关这套源码跑起来不难难的是跑稳。下面四条是我实际调试时踩过的坑按「现象 → 原因 → 解决」记下来。4.1 轮询间隔设太短IP 被限流现象日志里开始出现 403 或返回空 JSON监控界面一直显示「未知」。原因轮询间隔填了 1 秒甚至更短短时间内大量请求触发风控。解决把间隔调到 5 秒以上源码默认值 5 是有道理的如果商品页面本身有缓存10 秒也够用。另外可以在check_stock里加随机抖动比如time.sleep(interval random.uniform(0, 1))让请求节奏不那么机械。4.2 登录态过期下单接口返回未登录现象库存能查到但place_order一直返回 False日志里能看到「请先登录」。原因cookie 有有效期源码没有自动刷新机制session 里的 cookie 过期后所有写操作都失效。解决下单前先请求一次用户信息接口验证登录态失效就弹窗提示重新导出 cookie或者把 cookie 存到本地文件启动时读取过期后手动更新。4.3 库存文本误判把「无货」当成「有货」现象明明商品显示无货程序却触发了下单下单失败还反复重试。原因should_buy只做关键词匹配而京东库存描述里可能出现「无现货」「暂时无货」这类包含「现货」「有货」字样的文本。解决改成先判断否定词再判断肯定词更稳妥的做法是解析接口返回的库存数字字段而不是依赖描述文本。4.4 macOS 上 Tkinter 界面字体发虚、按钮错位现象同一份源码在 Windows 上正常macOS 上界面元素挤在一起中文显示成方块。原因Tkinter 在 macOS 上的默认字体和 DPI 缩放跟 Windows 不一致。解决在创建控件时显式指定字体比如font(PingFang SC, 12)窗口尺寸用geometry固定不要依赖自动布局。如果还是错位把ttk换成tk原生控件兼容性更好但样式朴素。5. 进阶改造把硬编码参数抽成配置文件加一层重试与日志源码能跑通之后我一般会做两件事让它更耐用一是把散落在代码里的参数抽出来二是给请求加一层重试。下面这个配置文件方案是我常用的import json import os CONFIG_PATH config.json DEFAULT_CONFIG { sku_url: , interval: 5, max_retry: 3, retry_delay: 2, buy_num: 1, log_file: monitor.log } def load_config(): if not os.path.exists(CONFIG_PATH): with open(CONFIG_PATH, w, encodingutf-8) as f: json.dump(DEFAULT_CONFIG, f, ensure_asciiFalse, indent2) return DEFAULT_CONFIG with open(CONFIG_PATH, r, encodingutf-8) as f: return json.load(f)逻辑说明首次运行时自动生成config.json之后所有参数从文件读改配置不用动代码。max_retry和retry_delay配合使用请求失败后等retry_delay秒重试最多max_retry次。日志方面把msg_queue.put换成同时写文件和队列方便事后排查。参数默认值建议范围说明interval5515轮询间隔太短触发限流max_retry325单次请求失败重试次数retry_delay215重试前等待秒数buy_num112下单数量多数商品限购验证改造是否生效最简单的办法是故意把sku_url填错看日志里有没有按max_retry次数重试、有没有写入monitor.log。如果重试逻辑没触发检查check_stock里是不是把异常吞掉了——raise_for_status抛出的异常必须被外层捕获才能进重试分支。从那以后我每次改这类监控脚本都强制先把参数抽成配置文件、再跑一遍错误链接验证重试确认日志落盘了才接真实商品。这套源码的价值不在开箱即用而在它把 GUI 事件、后台线程、请求链路这三层拆得足够清楚你顺着改一遍比看十篇教程都管用。希望帮到你。本文还有配套的精品资源点击获取