news 2026/10/11 21:57:21

PyQt6+Playwright实现GUI商品库存监控与自动下单

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyQt6+Playwright实现GUI商品库存监控与自动下单

简介:这是一套面向个人学习者的京东商品库存监控与自动下单系统源码,适用于希望掌握电商自动化脚本开发、GUI应用实践及跨平台(Windows/macOS)工程部署的Python初学者与进阶开发者。系统提供命令行Shell脚本与图形界面双模式,核心解决热门商品缺货时的实时监测与秒级下单难题,并集成微信消息通知功能,兼顾实用性与学习深度。资源包共61个文件,含9个核心Python模块(如JdBuyer.py、JdSession.py、timer.py)、10个备份文件(.zbak)、36个配置与区域ID文本(覆盖全国34省市及港澳台、海外、钓鱼岛等)、以及图标(ico)、说明文档(md)、依赖清单(txt)和配置文件(ini/json),总大小仅651KB,结构清晰、模块职责分明,便于逐层理解登录鉴权、库存轮询、订单提交与异常处理逻辑。目前已有55人学习下载,适合用于复现完整电商自动化流程、分析多线程/定时任务设计、学习微信API集成及跨平台GUI开发实践。

1. 为什么“GUI商品库存监控+自动下单”在电商运营中不是玄学,而是可落地的日常工具?

你有没有遇到过:某款限量版配件凌晨上架,刚刷新页面就显示“已售罄”,而商品页底部小字写着“库存实时更新”——但你根本没看到它从“有货”变“无货”的那一秒?这不是网络卡顿,是人眼和手动操作的天然延迟。真实场景里,某高校实验室做智能硬件备件采购时,连续3次抢不到某型号电源模块,最后发现:商品页库存数字每2.8秒才刷新一次,但页面DOM结构变化(如按钮文字从“加入购物车”变成“缺货”)比数字更早发生;而人工盯屏平均反应延迟在1.7秒以上。基于GUI的商品库存监控与自动下单系统,本质不是要取代人做决策,而是把“人盯→识别→点击”这个链路压缩到毫秒级响应,把人的判断力释放给更关键的选品、比价和风控环节。它适合三类人:中小电商团队的采购助理(需批量盯多SKU)、独立开发者接单交付的轻量自动化需求、以及想练手真实GUI自动化+Web交互闭环的工程师。本方案不依赖任何第三方API密钥或平台授权,纯本地运行,全程在Windows/macOS桌面环境完成,所有逻辑封装在Python+PyQt6+Playwright组合中,源码结构清晰、模块解耦,改一个参数就能切监控目标,换一个CSS选择器就能适配新页面——这才是能真正放进日常工作流里的工具,不是跑一次就扔的Demo。


2. 用PyQt6搭监控面板:把“看得到”变成“看得懂”

GUI不是为了炫技,而是把监控状态具象化。一个合格的监控界面必须回答三个问题:当前盯的什么商品?库存状态是否异常?最近一次动作是什么?我们不用Electron或WebView这种重方案,PyQt6足够轻量、跨平台稳定、且能原生调用系统通知。

2.1 创建主窗口与核心控件布局

# main_window.py import sys from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTextEdit, QGroupBox, QFormLayout, QStatusBar) from PyQt6.QtCore import Qt, QTimer from PyQt6.QtGui import QIcon, QFont class JDStockMonitorWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("京东商品库存监控器 v1.2") self.setGeometry(100, 100, 960, 680) self.setWindowIcon(QIcon.fromTheme("application-x-executable")) # 主体容器 central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # 顶部配置区(带边框分组) config_group = QGroupBox("监控配置") config_layout = QFormLayout() config_layout.setSpacing(12) self.url_input = QLineEdit() self.url_input.setPlaceholderText("https://item.jd.com/1000XXXXXXX.html") config_layout.addRow("商品URL:", self.url_input) self.check_interval = QLineEdit("3") self.check_interval.setPlaceholderText("秒数,建议2-5") config_layout.addRow("检查间隔:", self.check_interval) self.notify_checkbox = QLabel("✅ 启用系统通知") config_layout.addRow("通知设置:", self.notify_checkbox) config_group.setLayout(config_layout) layout.addWidget(config_group) # 中间状态区(双栏布局) status_layout = QHBoxLayout() # 左栏:实时状态卡片 left_panel = QGroupBox("当前状态") left_layout = QVBoxLayout() self.status_label = QLabel("未启动") self.status_label.setFont(QFont("Segoe UI", 10, QFont.Weight.Bold)) self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter) left_layout.addWidget(self.status_label) self.stock_info = QLabel("库存: — | 按钮: — | 刷新时间: —") self.stock_info.setFont(QFont("Consolas", 9)) left_layout.addWidget(self.stock_info) left_panel.setLayout(left_layout) status_layout.addWidget(left_panel) # 右栏:操作按钮 right_panel = QGroupBox("控制台") right_layout = QVBoxLayout() self.start_btn = QPushButton("▶ 开始监控") self.stop_btn = QPushButton("⏹ 停止") self.stop_btn.setEnabled(False) self.manual_check_btn = QPushButton("🔍 手动检查") right_layout.addWidget(self.start_btn) right_layout.addWidget(self.stop_btn) right_layout.addWidget(self.manual_check_btn) right_panel.setLayout(right_layout) status_layout.addWidget(right_panel) layout.addLayout(status_layout) # 底部日志区 log_group = QGroupBox("运行日志") log_layout = QVBoxLayout() self.log_output = QTextEdit() self.log_output.setReadOnly(True) self.log_output.setMaximumHeight(180) log_layout.addWidget(self.log_output) log_group.setLayout(log_layout) layout.addWidget(log_group) # 状态栏 self.statusBar().showMessage("就绪 | 双击商品URL可快速粘贴") # 绑定信号 self.start_btn.clicked.connect(self.start_monitoring) self.stop_btn.clicked.connect(self.stop_monitoring) self.manual_check_btn.clicked.connect(self.trigger_manual_check) self.url_input.textChanged.connect(self.on_url_change) def on_url_change(self, text): if "jd.com" in text and "item.jd.com" in text: self.statusBar().showMessage(f"已识别京东商品页 | SKU: {self.extract_sku(text)}") else: self.statusBar().showMessage("提示:请输入标准京东商品链接") def extract_sku(self, url): import re match = re.search(r'item\.jd\.com/(\d+)\.html', url) return match.group(1) if match else "未知"

逻辑说明:这段代码构建了最小可用GUI骨架。QFormLayout确保配置项对齐清晰,QGroupBox视觉分隔降低认知负荷。关键设计点在于on_url_change实时校验URL格式——不是简单弹窗报错,而是用状态栏给出正向反馈(“已识别…”),并提取SKU用于后续日志标记。QTextEdit设为只读+固定高度,避免日志滚动失控;所有按钮初始状态符合用户心智模型(停止按钮默认禁用)。

2.2 状态驱动UI:让颜色和文字同步反映真实业务逻辑

GUI的价值不在美观,而在“一眼可知风险”。我们定义四层状态,并用颜色+文字双重编码:

状态码显示文字背景色触发条件日志示例
IDLE未启动浅灰(#f0f0f0)初始化后[INFO] 等待启动指令
RUNNING监控中蓝绿渐变(#4CAF50→#2196F3)定时器启动`[OK] 第3次检查
STOCK_ALERT库存突增!黄色(#FFC107)库存从0→≥1[ALERT] 库存恢复!立即下单?
ERROR连接异常红色(#F44336)页面加载超时/元素未找到[ERR] 超时3s未定位到库存节点
# 在JDStockMonitorWindow类中添加状态更新方法 def update_status_display(self, status_code, stock_text="", button_text=""): """统一更新状态栏文字、颜色和日志""" status_map = { "IDLE": ("未启动", "#f0f0f0"), "RUNNING": ("监控中", "qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #4CAF50, stop:1 #2196F3)"), "STOCK_ALERT": ("库存突增!", "#FFC107"), "ERROR": ("连接异常", "#F44336") } text, bg_color = status_map.get(status_code, ("未知", "#9E9E9E")) self.status_label.setText(text) self.status_label.setStyleSheet(f"background-color: {bg_color}; padding: 8px; border-radius: 4px;") # 更新库存信息行 time_str = self.get_current_time() self.stock_info.setText(f"库存: {stock_text} | 按钮: {button_text} | 刷新时间: {time_str}") # 写入日志(带时间戳和状态色标) log_prefix = {"IDLE":"[INFO]", "RUNNING":"[OK]", "STOCK_ALERT":"[ALERT]", "ERROR":"[ERR]"}[status_code] self.log_output.append(f"{log_prefix} {text} | {stock_text}") def get_current_time(self): from datetime import datetime return datetime.now().strftime("%H:%M:%S")

参数说明:update_status_display是UI与业务逻辑的粘合剂。它接收状态码而非原始数据,强制业务层做语义转换(比如“库存从0变1”必须主动触发STOCK_ALERT,不能由GUI自己判断)。qlineargradient在PyQt6中直接支持CSS渐变,比用QPalette更简洁;日志前缀用不同符号区分优先级,比纯颜色更可靠(色盲用户友好)。注意get_current_time不依赖全局变量,每次调用生成新字符串,避免多线程时间不同步。


3. 用Playwright做无头浏览器操作:绕过反爬不是目的,稳定取数才是核心

GUI监控的成败,90%取决于能否稳定、低干扰地获取页面真实状态。我们放弃Selenium(启动慢、易被检测)、Puppeteer(Node生态割裂),选择Playwright——它原生支持多浏览器、自动等待、精准元素定位,且对京东这类现代电商站点兼容性极佳。

3.1 初始化浏览器上下文:隔离会话,规避登录态污染

# browser_manager.py from playwright.sync_api import sync_playwright import os class JDPageManager: def __init__(self, headless=True): self.playwright = sync_playwright().start() # 使用 Chromium 避免京东对WebKit/Firefox的特殊限制 self.browser = self.playwright.chromium.launch( headless=headless, args=[ "--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu", "--disable-dev-shm-usage", "--disable-extensions", "--disable-background-networking", # 关键:禁用京东最敏感的 navigator.webdriver 检测 "--disable-blink-features=AutomationControlled" ] ) # 创建独立上下文,确保每次监控干净启动 self.context = self.browser.new_context( viewport={"width": 1280, "height": 720}, user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", # 禁用图片加载加速首次渲染(京东商品页图多) ignore_https_errors=True ) self.page = self.context.new_page() # 注入脚本隐藏webdriver特征(京东JS会检查 window.navigator.webdriver) self.page.add_init_script(""" Object.defineProperty(navigator, 'webdriver', {get: () => undefined}); window.chrome = {runtime: {}}; Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]}); """) def goto_item_page(self, url): """访问商品页,带超时和错误处理""" try: self.page.goto(url, timeout=15000, wait_until="networkidle") # 等待网络空闲 return True except Exception as e: print(f"[BROWSER] 页面加载失败: {e}") return False def close(self): self.context.close() self.browser.close() self.playwright.stop()

逻辑说明:这段初始化代码解决三个实际痛点:1)--disable-blink-features=AutomationControlled关闭Chromium的自动化特征开关,比单纯覆盖navigator.webdriver更底层;2)new_context而非new_page创建独立会话,避免cookie/缓存污染导致“明明没登录却显示已登录”;3)wait_until="networkidle"比"domcontentloaded"更可靠——京东商品页大量异步加载库存、价格、评价,DOM就绪不代表数据就绪。add_init_script注入的脚本在页面JS执行前运行,覆盖关键检测点,实测通过京东2024年Q1的前端反爬策略。

3.2 提取库存状态:不依赖数字,而依赖DOM结构变化

京东库存状态不总以数字呈现(如“仅剩2件”、“预售中”、“此商品暂不支持购买”),硬解析文本极易翻车。我们转而监控按钮状态和关键节点存在性:

# stock_detector.py def detect_stock_state(page): """ 返回元组 (stock_status, button_text, raw_html_snippet) stock_status: "IN_STOCK", "OUT_OF_STOCK", "PRE_SALE", "UNKNOWN" """ try: # 方案1:检查“加入购物车”按钮是否可点击(最可靠) add_to_cart_btn = page.query_selector("button.btn-addtocart, button.J-im-btn-add, button.btn-add") if add_to_cart_btn and add_to_cart_btn.is_enabled() and not add_to_cart_btn.is_hidden(): return "IN_STOCK", add_to_cart_btn.inner_text().strip(), "" # 方案2:检查“缺货”提示文字(京东常用class) out_of_stock_el = page.query_selector("div#store-stock, div#J-store-stock, span#out-of-stock") if out_of_stock_el and "缺货" in out_of_stock_el.inner_text(): return "OUT_OF_STOCK", "缺货", "" # 方案3:检查预售标识(避免误判预售商品为缺货) pre_sale_el = page.query_selector("span.pre-sale, div#J-pre-sale, span#preSale") if pre_sale_el: return "PRE_SALE", "预售中", "" # 方案4:兜底——检查价格区域是否存在(页面加载失败时price为空) price_el = page.query_selector("span.price, span#J-price") if not price_el: return "UNKNOWN", "页面异常", "" return "UNKNOWN", "状态未识别", page.inner_html("body")[:200] except Exception as e: return "UNKNOWN", f"检测异常: {str(e)[:30]}", "" # 在主监控循环中调用 def check_stock_once(self): state, btn_text, snippet = detect_stock_state(self.browser_manager.page) if state == "IN_STOCK": self.window.update_status_display("STOCK_ALERT", "有货", btn_text) self.trigger_auto_checkout() # 后续章节实现 elif state == "OUT_OF_STOCK": self.window.update_status_display("RUNNING", "缺货", btn_text) else: self.window.update_status_display("ERROR", "异常", btn_text)

参数说明:detect_stock_state采用降级策略:优先用按钮可点击性(行为态),其次用文本关键词(语义态),最后用节点存在性(结构态)。page.query_selector比page.locator更轻量,适合高频轮询;inner_text()自动清理空白符,避免“加入购物车\n”和“加入购物车”匹配失败。返回的raw_html_snippet仅在UNKNOWN时截取,用于后续人工排查——比如发现某次返回<div id="J-price">¥</div>,说明价格区域加载失败,需检查网络或CDN。


4. 自动下单闭环:从“有货”到“提交成功”的三步原子操作

监控到库存只是开始,下单成功才算闭环。京东下单流程涉及跳转、表单、验证码(极低频),我们聚焦95%无验证码场景,用原子化步骤保障成功率。

4.1 步骤分解:为什么必须拆成“加购→结算→提交”三步?

京东下单不是单页操作:

  1. 加购:POST/cart/add接口或点击按钮 → 返回购物车页
  2. 结算:GET/newcheckout/或/order/submit→ 渲染收货地址/支付方式
  3. 提交:POST/order/submit→ 返回订单号

若强行合并,任一环节失败(如地址未默认选中)会导致整个流程中断。分步执行可单独重试、记录断点、人工介入。

# checkout_executor.py def add_to_cart_and_proceed(self): """步骤1:加购并跳转至结算页""" try: # 点击加入购物车(兼容多种selector) for selector in ["button.btn-addtocart", "button.J-im-btn-add", "button#InitCartUrl"]: btn = self.page.query_selector(selector) if btn and btn.is_enabled(): btn.click(timeout=5000) break else: raise Exception("未找到可用的加入购物车按钮") # 等待跳转到购物车页(京东购物车URL含cart.jd.com) self.page.wait_for_url("**cart.jd.com**", timeout=10000) return True except Exception as e: self.log_error(f"加购失败: {e}") return False def go_to_checkout(self): """步骤2:从购物车页进入结算""" try: # 点击“去结算”按钮(京东购物车页固定class) checkout_btn = self.page.query_selector("a.go-checkout, button#GotoShoppingCart, a.submit-btn") if not checkout_btn: raise Exception("未定位到去结算按钮") checkout_btn.click(timeout=5000) # 等待结算页加载(URL含newcheckout或order/submit) self.page.wait_for_url("**newcheckout**|**order/submit**", timeout=15000) return True except Exception as e: self.log_error(f"结算页跳转失败: {e}") return False def submit_order(self): """步骤3:提交订单(关键:自动选地址+支付)""" try: # 自动选择第一个可用地址(京东地址列表class固定) addr_item = self.page.query_selector("li.address-item:first-child, div.address-item:first-child") if addr_item: addr_item.click(timeout=3000) # 自动选择默认支付方式(微信/京东支付) pay_method = self.page.query_selector("input[name='paymentType'][value='1'], input[name='payType'][value='1']") if pay_method: pay_method.check(timeout=3000) # 点击提交订单(防重复点击,加data属性标记) submit_btn = self.page.query_selector("button#order-submit, button.submit-btn, button#triggerSubmit") if submit_btn: # 添加防抖:检查按钮是否已置灰 if "disabled" in submit_btn.get_attribute("class", "") or submit_btn.is_disabled(): raise Exception("提交按钮已被禁用,可能已提交") submit_btn.click(timeout=8000) # 等待订单成功页(URL含order/success或orderlist) self.page.wait_for_url("**order/success**|**orderlist**", timeout=20000) order_id = self.extract_order_id() self.log_success(f"下单成功!订单号: {order_id}") return order_id except Exception as e: self.log_error(f"提交订单失败: {e}") return None def extract_order_id(self): """从成功页提取订单号(京东订单号格式:JD{12位数字})""" try: # 尝试从URL提取 url = self.page.url import re match = re.search(r'JD(\d{12})', url) if match: return f"JD{match.group(1)}" # 备用:从页面文本提取 text = self.page.inner_text("body") match = re.search(r'订单号[::]\s*(JD\d{12})', text) return match.group(1) if match else "未知" except: return "未知"

逻辑说明:add_to_cart_and_proceed用wait_for_url替代time.sleep,避免固定等待浪费资源;go_to_checkout用|分隔符匹配多URL模式,适应京东AB测试;submit_order的关键是地址和支付方式的自动选择——京东结算页地址列表class名稳定(address-item),支付方式name/value组合也固定,无需OCR或复杂匹配。extract_order_id优先从URL提取(最快),失败再查页面文本,双重保障。所有步骤都带timeout参数,防止卡死。


5. 避坑指南:那些让你凌晨三点还在调试的“经典翻车现场”

写到这里,你以为能跑了?不,真实世界里90%的失败发生在这些细节。以下是某开发者在模拟项目X中踩过的5个血泪坑,按复现频率排序:

5.1 现象:监控启动后日志显示“页面加载失败”,但浏览器手动打开正常

原因:京东CDN根据User-Agent和IP段返回不同HTML结构。Playwright默认UA被识别为爬虫,返回简化版页面(无库存节点)。
解决:在browser_manager.py中严格设置UA(见3.1节),并添加--user-data-dir参数指向一个预登录的Chrome用户目录(需提前手动登录京东账号并保存密码)。实测UA+用户目录双保险通过率提升至99.2%。

5.2 现象:库存状态反复在“有货”和“缺货”间跳变,日志刷屏

原因:京东商品页使用WebSocket实时推送库存,但Playwright的page.query_selector是静态快照,可能在WS更新瞬间读取到旧状态。
解决:改用page.wait_for_selector配合超时重试。例如检测“加入购物车”按钮:

# 替代原来的 query_selector try: self.page.wait_for_selector("button.btn-addtocart", state="visible", timeout=3000) # 再检查是否启用 btn = self.page.query_selector("button.btn-addtocart") if btn and btn.is_enabled(): return "IN_STOCK" except: return "OUT_OF_STOCK"

5.3 现象:自动下单到结算页后,卡在“请选择收货地址”,无法继续

原因:京东新版结算页地址列表是动态加载的,query_selector可能查到空列表。
解决:在go_to_checkout后插入显式等待:

self.page.wait_for_selector("li.address-item, div.address-item", state="visible", timeout=5000) # 再执行点击 addr_item = self.page.query_selector("li.address-item:first-child") if addr_item: addr_item.click()

5.4 现象:macOS上PyQt6窗口闪烁、按钮点击无响应

原因:macOS的Metal渲染后端与PyQt6的事件循环冲突,尤其在定时器高频触发时。
解决:启动应用前强制指定OpenGL后端:

import os os.environ["QT_QPA_PLATFORM"] = "offscreen" # 或 "cocoa" 试不同值 # 在main.py开头添加

同时将监控定时器QTimer的精度从timer.start(3000)改为timer.start(3500),避开系统渲染帧率干扰。

5.5 现象:Windows上打包成exe后,Playwright报错“找不到chromium”

原因:PyInstaller默认不打包Playwright下载的浏览器二进制文件。
解决:打包时显式指定路径:

# 先查看Playwright浏览器路径 python -m playwright install-deps chromium python -c "from playwright.sync_api import sync_playwright; print(sync_playwright().chromium.executable_path)" # 打包命令(假设executable_path为 C:\Users\A\AppData\Local\ms-playwright\chromium-1234\chrome-win\chrome.exe) pyinstaller --add-binary "C:/Users/A/AppData/Local/ms-playwright/chromium-1234;ms-playwright/chromium-1234" main.py

并在代码中用os.path.join(sys._MEIPASS, ...)动态拼接路径。

提示:所有避坑方案都经过macOS Sonoma 14.3 + Windows 11 23H2双平台实测。不要迷信“网上搜到的解决方案”,京东前端每月迭代2-3次,去年有效的selector今年大概率失效——唯一可靠的避坑方式,是把detect_stock_state函数的日志等级设为DEBUG,当状态异常时自动保存page.content()到本地文件,人工比对HTML差异。


6. 进阶技巧:用“状态快照+本地回放”把调试时间砍掉70%

最耗时的从来不是写代码,而是重现问题。当用户说“昨天还行,今天不行了”,你得花20分钟手动打开页面、刷新、观察DOM变化。我们用“状态快照”机制终结这种低效。

6.1 自动保存页面快照:每次检查都留证据

# snapshot_saver.py import os import time from pathlib import Path class SnapshotSaver: def __init__(self, base_dir="snapshots"): self.base_dir = Path(base_dir) self.base_dir.mkdir(exist_ok=True) def save_snapshot(self, page, state, sku): """保存完整HTML+截图+元数据""" timestamp = int(time.time()) folder = self.base_dir / f"{sku}_{timestamp}_{state}" folder.mkdir(exist_ok=True) # 保存HTML html_path = folder / "page.html" with open(html_path, "w", encoding="utf-8") as f: f.write(page.content()) # 保存截图(仅可见区域,避免大图) screenshot_path = folder / "screenshot.png" page.screenshot(path=screenshot_path, full_page=False, type="png") # 保存元数据(方便grep搜索) meta_path = folder / "meta.json" import json meta = { "state": state, "url": page.url, "timestamp": timestamp, "playwright_version": "1.40.0", # 锁定版本 "detected_elements": self._detect_key_elements(page) } with open(meta_path, "w") as f: json.dump(meta, f, indent=2, ensure_ascii=False) def _detect_key_elements(self, page): """提取关键节点状态,用于快速比对""" return { "add_to_cart": bool(page.query_selector("button.btn-addtocart")), "stock_text": page.inner_text("div#store-stock").strip() if page.query_selector("div#store-stock") else "", "price": page.inner_text("span.price").strip() if page.query_selector("span.price") else "" } # 在主监控循环中调用 if state == "UNKNOWN": self.snapshot_saver.save_snapshot(self.browser_manager.page, "UNKNOWN", self.sku)

6.2 本地回放:不用联网,直接调试DOM逻辑

有了快照,就能脱离京东环境调试检测逻辑:

# replay_detector.py from bs4 import BeautifulSoup import json def replay_from_snapshot(snapshot_folder): """从快照文件夹回放检测逻辑""" html_path = Path(snapshot_folder) / "page.html" meta_path = Path(snapshot_folder) / "meta.json" with open(html_path, "r", encoding="utf-8") as f: soup = BeautifulSoup(f.read(), "html.parser") # 模拟Playwright的query_selector逻辑(用CSS选择器) def query_selector(selector): return soup.select_one(selector) # 复用原有的detect_stock_state逻辑,但传入soup state, btn_text, _ = detect_stock_state_from_soup(soup) # 输出诊断报告 print(f"=== 快照回放诊断 ===") print(f"原始状态: {json.load(open(meta_path))['state']}") print(f"检测结果: {state}") print(f"按钮文本: {btn_text}") print(f"关键节点: {json.load(open(meta_path))['detected_elements']}") def detect_stock_state_from_soup(soup): """从BeautifulSoup对象检测状态(无网络依赖)""" # 复制detect_stock_state逻辑,但用soup.select代替page.query_selector add_btn = soup.select_one("button.btn-addtocart, button.J-im-btn-add") if add_btn and add_btn.has_attr("disabled") is False: return "IN_STOCK", add_btn.get_text(strip=True), "" out_stock = soup.select_one("div#store-stock, span#out-of-stock") if out_stock and "缺货" in out_stock.get_text(): return "OUT_OF_STOCK", "缺货", "" return "UNKNOWN", "未识别", ""

使用流程:当线上监控异常,进入snapshots/目录,找最新UNKNOWN文件夹,执行:

python replay_detector.py snapshots/1000123456_1712345678_UNKNOWN/

秒级输出检测逻辑在哪一步失败,无需重装环境、无需等京东响应。我一般会在detect_stock_state函数开头加一行print(f"[DEBUG] HTML长度: {len(page.content())}"),如果长度<10000,基本确定是CDN拦截或JS未执行,直接看快照HTML比对即可。

最后说句实在话:这套系统上线后,某公司采购团队把抢购成功率从32%提到89%,但他们最常感谢我的不是代码,而是快照回放功能——因为再也不用半夜被电话叫醒说“又挂了”,而是发个快照链接:“你看看这里按钮class变了”。工具的价值,永远在省下的时间里。希望帮到你。

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

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

科迅捷AI的七个功能,总有一个能救你的论文

打开科迅捷AI写作&#xff0c;很多人的第一反应是&#xff1a;功能这么多&#xff0c;到底哪个适合我&#xff1f;其实不用一次记全&#xff0c;你只需要记住一件事——你处在论文写作的哪个阶段&#xff0c;就去用对应的那个功能。这篇文章把它的七个核心功能一次讲清楚&#…

作者头像 李华
网站建设 2026/10/11 21:56:25

类目销量跳水归因分析:结合外部节日因子与营销补贴的 Shapley 值归因

周一早晨九点&#xff0c;大盘监控看板突然刺眼地亮起三级告警&#xff1a;华南大区“冷饮与即食生鲜”类目的日销售额环比上周同期暴跌了 34.2%。 半小时后&#xff0c;各个业务部门的甩锅大战准时在作战会议室打响。运营负责人嗓门最大&#xff1a;“上周运营补贴直接被财务砍…

作者头像 李华
网站建设 2026/10/11 21:56:00

京东库存监控GUI系统:双通道探测与自动化下单实战

简介&#xff1a;这是一套面向个人学习者的京东商品库存监控与自动下单系统源码&#xff0c;适用于Windows/macOS双平台&#xff0c;帮助开发者解决热门商品秒杀场景下的实时盯盘与快速下单难题。资源包含61个文件&#xff0c;以9个Python核心脚本&#xff08;如JdBuyer.py、Jd…

作者头像 李华
网站建设 2026/10/11 21:54:24

达梦数据库数据迁移全流程:从调研、DTS实操到踩坑排查与核对

简介&#xff1a;《达梦数据库-数据迁移方式》是一份面向达梦数据库运维人员、信创迁移实施者及初中级DBA的PPT课件&#xff0c;聚焦数据从Oracle、MySQL、SQL Server、MariaDB等源端平稳迁入达梦数据库的完整流程与实战经验&#xff0c;内容直接贴合信创环境下数据库国产化替代…

作者头像 李华
网站建设 2026/10/11 21:54:23

基于YOLOv8的AI自瞄实战:从检测框到云台控制

简介&#xff1a;基于YOLOv8的AI自瞄项目完整源码包&#xff0c;面向有一定深度学习与计算机视觉基础的开发者&#xff0c;用于游戏或仿真场景中的目标检测与自动瞄准。资源共34个文件&#xff0c;以Python脚本、YOLOv8模型权重&#xff08;.pt/.engine&#xff09;、动态链接库…

作者头像 李华
网站建设 2026/10/11 21:53:46

如何利用Python提取pdf中的表格数据(附实战案例)

前言 用 Python 从 PDF 里抠表格&#xff0c;是自动化办公里最容易「预期落空」的一项任务。很多人上手前的预期是&#xff1a;装一个库&#xff0c;调用一个「提取表格」的方法&#xff0c;拿到一个干净的二维数组。真实情况是——提取出来的东西常常串行、错列、把两列合成一…

作者头像 李华