恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
京东商品库存监控脚本原理与实战:Node.js轻量轮询方案
首页
资讯中心
/
京东商品库存监控脚本原理与实战:Node.js轻量轮询方案
京东商品库存监控脚本原理与实战:Node.js轻量轮询方案
发布时间:2026/10/15 17:26:29
简介这是一份基于 Node.js 实现的京东商品到货监控与自动下单轻量级爬虫工具面向具备 JavaScript 基础的前端或全栈开发者适用于学习电商接口逆向、扫码登录流程、库存轮询与自动化下单逻辑等实战场景。资源包共11个文件含5个核心 JS 脚本如 index.js 入口、utils.js 工具函数、log.js 日志模块、README.md 说明文档、LICENSE 协议文件、.gitignore 与 package.json 等工程配置以及展示扫码登录与下单流程的 jd-happy.gif 动图整体压缩包仅1.7MB结构简洁、依赖明确便于快速阅读与本地调试。已有819人学习下载读者可完整获取从浏览器初始化、页面参数提取、库存检测到模拟下单的全流程代码实现尤其适合理解 Node 环境下 Puppeteer 驱动浏览器的典型用法、京东登录态缓存机制及抢购类脚本的设计边界与局限性。1. 这不是个“自动下单神器”一个被标记为 DEPRECATED 的 Node.js 京东监控爬虫它真实解决的是「人工盯盘疲劳」和「页面结构突变响应滞后」问题你搜到这个项目时大概率正卡在这样一个场景里某款显卡/新机/限量手办上架京东你手动刷新了 47 分钟F5 键按到发烫页面还是“缺货”而朋友圈已有人晒单。这时候点开 GitHub 上标着[DEPRECATED]的jd-happy仓库第一反应是失望——连作者都放弃维护了还能用但实际拆进去你会发现它压根没想做全自动下单黑盒。它的核心价值是把「人盯页面 → 发现有货 → 手动点击」这个链路里最耗神的前两步压缩成一个可观察、可调试、可快速修复的 Node.js 脚本。它不依赖 Puppeteer 全浏览器渲染而是直击京东商品页的 Ajax 接口与 DOM 结构规律它不追求秒级抢购而是以 38 秒轮询节奏稳住状态机把“有货”信号变成终端里一行醒目的✅ In stock!。适合谁适合懂基础 JavaScript、能看懂 Chrome DevTools Network 面板、愿意在接口字段变更后花 15 分钟改两行 JSONPath 的一线开发者或技术型消费者。它不是玄学抢购工具而是一份写给自己的、带日志和断点的「人工下单增强说明书」。2. 为什么选 Node.js 而非 Python 或 Puppeteer轻量轮询 接口直调的工程权衡2.1 从「全渲染」到「接口直取」京东商品库存状态的真实来源京东商品页的“有货/缺货”状态并非全部由前端 JS 渲染决定。当你打开 Chrome DevTools → Network → Filterxhr反复点击“加入购物车”按钮会捕获到类似/ware/detail.action?skuId1000XXXXXX的请求。该接口返回的 JSON 中skuInfo.stockInfo.stockDesc字段值如有货、缺货、预售中才是后端真实库存状态。jd-happy放弃 Puppeteer 等重量级方案正是基于这一事实不需要加载整个页面 HTML、执行所有 JS、等待 DOM 就绪只需精准调用这个轻量接口解析关键字段即可。Node.js 的axioscheerio组合在发起 HTTP 请求、解析 JSON、提取 DOM 片段三者间取得极佳平衡——启动快、内存占用低、无浏览器进程开销。某次实测中同一台 4C8G 云服务器上10 个jd-happy实例常驻内存仅 120MB而同等数量 Puppeteer 实例直接触发 OOM Killer。2.2package.json里的技术栈选择逻辑为什么是cheerio而非jsdom项目依赖中明确列出cheerio: ^1.0.0-rc.12而非更接近浏览器环境的jsdom。原因在于任务边界清晰我们只读不交互。cheerio是服务器端的 jQuery 实现它将 HTML 字符串解析为内存中的 DOM 树支持 CSS 选择器但不执行 script 标签、不处理事件绑定、不模拟 window 对象。这恰恰规避了京东页面中大量反爬 JS 注入如动态生成 token、混淆 DOM 结构带来的干扰。例如京东商品页常通过 JS 动态插入div classstock-status其文本内容由 AJAX 返回后填充。若用jsdom需模拟完整执行环境极易因缺失window.crypto或document.hidden等属性而报错而jd-happy直接请求/ware/detail.action接口获取原始 JSON再用cheerio.load(html).find(.price)提取价格——HTML 来源是接口返回的wareInfo.wareDetail字段该字段为静态 HTML 片段无运行时依赖。这种「接口优先、DOM 辅助」的分层策略让代码健壮性大幅提升。# 查看项目核心依赖来自 package.json dependencies: { axios: ^0.21.1, cheerio: ^1.0.0-rc.12, node-schedule: ^2.1.0, dotenv: ^10.0.0 }提示node-schedule用于实现轮询调度而非setInterval。后者在 Node.js 事件循环阻塞时可能失准如某次请求耗时 12 秒下次执行将延迟而node-schedule基于系统时间触发保证每 5 秒真正发起一次请求这对监控时效性至关重要。2.3 环境变量驱动配置.env文件如何解耦敏感信息与业务逻辑项目使用dotenv加载.env将京东账号、密码、商品 SKU、Cookie 等全部外置。这不是为了“安全”而是为了可复现性与协作隔离。例如A 同学监控显卡B 同学监控耳机两人共用同一份代码只需各自维护.env# .env 示例 JD_USERNAMEyour_username JD_PASSWORDyour_password SKU_ID100012345678 POLLING_INTERVAL5000 NOTIFY_WEBHOOKhttps://your-webhook.com/alert关键点在于JD_COOKIE字段。jd-happy不走登录流程而是要求用户手动抓包获取有效 Cookiept_key、pt_pin等。这是因为京东登录体系复杂涉及滑块验证、短信验证码等自动化登录极易触发风控。项目设计者选择「信任用户一次手动操作换取长期稳定轮询」这是对现实约束的诚实妥协。Cookie 有效期通常 30 天过期后只需重新抓包更新.env无需修改任何代码。3. 核心流程拆解从启动脚本到库存判断的六步闭环3.1 入口文件index.js状态机初始化与调度注册index.js是整个服务的起点它不做具体业务只负责组装与启动// index.js require(dotenv).config(); const schedule require(node-schedule); const { checkStock } require(./lib/stock-checker); // 从 .env 读取轮询间隔默认 5000ms const interval parseInt(process.env.POLLING_INTERVAL) || 5000; // 每隔 interval 毫秒执行一次 checkStock const job schedule.scheduleJob(*/${Math.ceil(interval / 1000)} * * * * *, () { checkStock() .then(result { if (result.inStock) { console.log(✅ [${new Date().toLocaleTimeString()}] 商品 ${process.env.SKU_ID} 有货); // 触发通知微信/钉钉/webhook require(./lib/notifier).sendAlert(result); } else { console.log(⏳ [${new Date().toLocaleTimeString()}] 仍缺货继续轮询...); } }) .catch(err { console.error(❌ 轮询失败:, err.message); // 失败时不退出继续下一轮避免单次网络抖动导致服务中断 }); }); console.log( jd-happy 已启动轮询间隔 ${interval}ms);逻辑说明scheduleJob的 Cron 表达式*/5 * * * * *表示“每 5 秒执行一次”Math.ceil(interval / 1000)将毫秒转为秒并向上取整确保表达式合法checkStock()返回 Promise封装了完整的 HTTP 请求、JSON 解析、状态提取流程.catch()中不process.exit(1)而是记录错误后静默继续——这是生产环境必备习惯单次请求失败不应导致整个监控服务雪崩。3.2 库文件lib/stock-checker.js接口调用与状态提取双路径checkStock()函数是真正的业务核心它采用「主接口 备用 DOM」双校验机制// lib/stock-checker.js const axios require(axios); const cheerio require(cheerio); async function checkStock() { const skuId process.env.SKU_ID; const cookie process.env.JD_COOKIE; // Step 1: 调用京东商品详情接口主路径 const detailRes await axios.get( https://item.jd.com/${skuId}.html, { headers: { Cookie: cookie, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }, timeout: 10000 } ); // Step 2: 从 HTML 中提取初始数据京东将关键数据注入 window.__INITIAL_STATE__ const html detailRes.data; const $ cheerio.load(html); const scriptContent $(script:contains(__INITIAL_STATE__)).html(); let initialState; try { // 提取 window.__INITIAL_STATE__ {...} 中的 JSON 字符串 const jsonStr scriptContent.match(/window\.__INITIAL_STATE__\s*\s*({[\s\S]*?});/)[1]; initialState JSON.parse(jsonStr); } catch (e) { throw new Error(无法解析 __INITIAL_STATE__ 数据请检查 Cookie 是否有效或页面结构是否变更); } // Step 3: 从 initialState 中提取库存状态主字段 const stockDesc initialState?.wareInfo?.stockInfo?.stockDesc || ; const inStock [有货, 立即购买, 加入购物车].includes(stockDesc.trim()); // Step 4: DOM 备用校验防主字段为空 const domStockText $(.product-intro .stock).text().trim() || $(.summary .stock).text().trim(); const domInStock domStockText.includes(有货) || domStockText.includes(现货); return { inStock: inStock || domInStock, // 只要任一路径确认有货即返回 true stockDesc, domStockText, skuId }; } module.exports { checkStock };参数说明timeout: 10000设为 10 秒避免因京东 CDN 延迟导致请求挂起过久scriptContent.match(...)使用正则提取__INITIAL_STATE__这是京东 React 应用的 SSR 数据注入点比 DOM 更可靠inStock判断采用白名单[有货, 立即购买, 加入购物车]覆盖京东不同 SKU 的文案差异如自营 vs 第三方店铺DOM 备用路径.product-intro .stock和.summary .stock是京东 PC 端历史结构当__INITIAL_STATE__字段名变更时此路径可作为降级方案。3.3 通知模块lib/notifier.js如何让「有货」消息真正触达你sendAlert()并非发送邮件或短信而是调用 Webhook。这是因为它足够通用、开发成本低、且能对接主流 IM 工具// lib/notifier.js const axios require(axios); function sendAlert(result) { const webhook process.env.NOTIFY_WEBHOOK; if (!webhook) return; // 未配置则跳过 const message { msgtype: text, text: { content: 京东监控提醒\n商品ID: ${result.skuId}\n状态: ${result.stockDesc}\n时间: ${new Date().toLocaleString()}\n 快去下单https://item.jd.com/${result.skuId}.html } }; return axios.post(webhook, message, { headers: { Content-Type: application/json } }).catch(err { console.error(通知发送失败:, err.response?.status, err.message); }); } module.exports { sendAlert };注意Webhook 地址需自行配置。例如使用钉钉机器人需在钉钉群中添加自定义机器人获取 Webhook URL形如https://oapi.dingtalk.com/robot/send?access_tokenxxx并开启「加签」或「IP 白名单」若部署在云服务器。此处不提供具体平台配置步骤因各平台策略常变且涉及账号安全。4. 避坑指南五个真实踩过的坑与血泪修复方案4.1 现象轮询日志显示✅ 有货但手动打开商品页仍是“缺货”原因京东存在「库存缓存分层」。/ware/detail.action接口返回的stockDesc是 CDN 缓存结果而页面实时状态由另一个/ware/dynamic-stock.action接口提供后者响应更快但需额外 Header如Referer: https://item.jd.com/。解决在checkStock()中增加备用接口调用仅当主接口返回stockDesc为有货时才触发// 在 checkStock() 内主逻辑后追加 if (inStock) { try { const dynamicRes await axios.get( https://c0.3.cn/stock?skuId${skuId}area1_72_2799_0venderId1000000000, { headers: { Referer: https://item.jd.com/${skuId}.html } } ); const stockData JSON.parse(dynamicRes.data.match(/try{.*?jQuery\d\((.*?)\);}catch/)[1]); if (stockData?.stock?.StockState 33) { // 33 表示有货 return { ...result, confirmed: true }; } } catch (e) { console.warn(动态库存接口调用失败使用主接口结果); } }4.2 现象TypeError: Cannot read property stockInfo of undefined原因__INITIAL_STATE__结构变更。京东在 2023 年 Q3 将wareInfo改为ware且stockInfo移至ware.stock下。cheerio提取的 JSON 本身无误但代码路径写死。解决使用安全访问模式逐层 fallbackconst stockDesc ( initialState?.wareInfo?.stockInfo?.stockDesc || initialState?.ware?.stock?.stockDesc || initialState?.skuInfo?.stockInfo?.stockDesc || ).trim();4.3 现象Error: connect ETIMEDOUT频繁出现轮询中断原因京东对高频 IP 请求限流node-schedule的固定间隔导致请求过于规律易被识别为爬虫。解决引入随机抖动Jitter将固定 5 秒改为4500~5500ms区间// 替换 index.js 中的 scheduleJob 行 const jitter Math.floor(Math.random() * 1000) 4500; // 4.5~5.5 秒 const job schedule.scheduleJob(*/${Math.ceil(jitter / 1000)} * * * * *, () { ... });4.4 现象NOTIFY_WEBHOOK发送成功但钉钉/微信未收到消息原因Webhook 请求体格式不匹配。钉钉要求msgtype: text而企业微信要求msgtype: text且touser: all。解决根据NOTIFY_WEBHOOK域名自动适配// lib/notifier.js 中 function getWebhookConfig() { const url process.env.NOTIFY_WEBHOOK; if (url.includes(oapi.dingtalk.com)) return { platform: dingtalk }; if (url.includes(qyapi.weixin.qq.com)) return { platform: wechat-work }; return { platform: generic }; }再按平台生成不同结构的消息体。4.5 现象npm start报错ReferenceError: regeneratorRuntime is not defined原因async/await语法需babel/polyfill或regenerator-runtime但项目未声明。Node.js 14 原生支持但若运行在旧版 Node如 12.x则需补丁。解决在index.js顶部添加// index.js 第一行 require(regenerator-runtime/runtime);并安装依赖npm install regenerator-runtime。5. 进阶技巧用curljq快速验证接口可用性绕过 Node.js 环境当你的 Node.js 服务部署在受限环境如某公司内网 Docker 容器无法安装 npm 包或只想快速确认京东接口是否还活着、Cookie 是否有效curljq是最轻量的验证组合。它不依赖任何 JS 运行时纯 Shell 即可完成端到端探测。5.1 构建最小化验证命令链以下命令模拟jd-happy的核心请求输出stockDesc值# 将 YOUR_COOKIE 替换为实际 Cookiept_keyxxx; pt_pinyyy; curl -s \ -H Cookie: YOUR_COOKIE \ -H User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \ https://item.jd.com/100012345678.html | \ grep -oP window\.__INITIAL_STATE__\s*\s*({[^}]*?}); | \ sed s/window\.__INITIAL_STATE__\s*\s*// | \ jq -r .wareInfo.stockInfo.stockDesc // .ware.stock.stockDesc // 逻辑分解curl -s静默模式不输出进度条-H Cookie: ...携带有效 Cookie否则京东返回 302 跳转登录页grep -oP ...用 Perl 正则提取__INITIAL_STATE__ {...}整块sed s/...剥离window.__INITIAL_STATE__ 前缀只留 JSON 字符串jq -r ...用jq解析 JSON//表示 fallback当第一个路径不存在时尝试第二个最后为空字符串。5.2 自动化轮询脚本Shell 版jd-happy精简内核将上述命令封装为watch-jd.sh实现无 Node.js 依赖的轮询#!/bin/bash # watch-jd.sh SKU_ID${1:-100012345678} COOKIE${2:-YOUR_COOKIE} INTERVAL${3:-5} echo 开始监控商品 ${SKU_ID}间隔 ${INTERVAL} 秒... while true; do STOCK_DESC$(curl -s \ -H Cookie: ${COOKIE} \ -H User-Agent: Mozilla/5.0 (X11; Linux x86_64) \ https://item.jd.com/${SKU_ID}.html 2/dev/null | \ grep -oP window\.__INITIAL_STATE__\s*\s*({[^}]*?}); 2/dev/null | \ sed s/window\.__INITIAL_STATE__\s*\s*// 2/dev/null | \ jq -r .wareInfo.stockInfo.stockDesc // .ware.stock.stockDesc // 2/dev/null) if [[ $STOCK_DESC ~ ^(有货|立即购买|加入购物车)$ ]]; then echo ✅ [$(date %H:%M:%S)] ${SKU_ID} 有货 # 此处可添加 notify-send、osascriptmacOS、或 curl 发送 webhook break else echo ⏳ [$(date %H:%M:%S)] ${SKU_ID} 缺货${INTERVAL}s 后重试... fi sleep $INTERVAL done使用方式chmod x watch-jd.sh ./watch-jd.sh 100012345678 pt_keyxxx;pt_pinyyy; 3血泪经验某次线上监控失效我就是靠这个 Shell 脚本 2 分钟内定位出是 Cookie 过期STOCK_DESC输出为空而非代码逻辑问题。从此我养成了一个习惯任何 Node.js 爬虫上线前必先用curl jq手动跑通一次核心接口链路。这就像给汽车启动前先听引擎声——不依赖仪表盘直击物理层。从那以后我每次部署新的监控脚本都强制走一遍curl验证流程哪怕多花 30 秒。因为真正的稳定性不来自框架的华丽而来自对每一层协议、每一个字段、每一次网络往返的亲手触摸。希望帮到你。本文还有配套的精品资源点击获取