简介:基于 Node.js 的京东商品监控与自动下单脚本,面向有京东抢购、到货提醒与自动结算需求的开发者,适合具备基础 JavaScript 与 Node.js 常识、希望了解扫码登录、库存轮询和下单接口调用流程的中级爬虫学习者。项目已标注部分接口因京东更新而失效,但仍保留了较完整的实现思路与踩坑记录,可用于学习模拟登录、状态缓存、定时监测及下单触发等关键逻辑。资源包共11个文件,以 5 个 JS 脚本为主,分别承担入口、参数解析、日志与工具函数等职责,另含 README 说明、package.json 依赖配置、yarn.lock 锁定文件、演示动图及许可证。整体压缩后仅 1.7MB,结构轻量,便于快速阅读和按需改造。目前已有818人学习下载。通过该资源,读者既能获得一套可运行的京东商品监控脚本框架,也可借鉴其扫码登录、本地缓存、库存判断与自动下单的设计方式,为后续维护或开发类似电商自动化工具提供直接参考。 做电商自动化的人,多少都会经历这么一遭:某个想买的东西一直缺货,半夜爬起来刷新页面,结果还是“无货”。时间长了,就容易萌生“写个脚本帮我盯着”的念头。我当时折腾的就是这么个东西——一个叫 jd-happy 的 Node 爬虫项目,用来监控京东商品到货状态,顺手实现了下单流程。虽然现在已经被我标成 DEPRECATED 弃用了,但整个项目的设计思路和踩坑记录,放到今天看依然很有参考价值,尤其是对那些想入门 Node 爬虫、或者琢磨电商自动化流程的朋友。
这个项目能做的其实就两件事:第一,定时抓取商品详情页,解析出“有货/无货”的状态;第二,一旦检测到有货,自动触发下单请求,抢在人工反应之前把单子提交上去。标题里那个 DEPRECATED 才是精华——意味着这个项目里藏着一堆“因为平台规则变动而失效”的坑,这些坑才是普通人拿钱买不到的实战经验。适合谁看呢?适合用过 Node 做过爬虫、想了解完整自动化下单链路、想避开反爬和接口变动坑的人。
1. 项目整体设计与思路拆解
1.1 核心需求解析
拆解需求是第一步。把一个模糊的“想看商品到货”拆成清晰的技术问题,比直接上手写代码要重要得多。我当时的原始需求是“早上醒来能买到某款显卡”,但这个需求落到技术层面,至少要拆成四块:
一是数据采集,需要拿到商品 sku 的价格、库存状态、上下架状态;二是状态监控,要周期性重复请求,并感知状态从“无货”变成“有货”;三是通知触发,一旦监控到状态变化,就要立刻进入下单逻辑;四是下单执行,把商品加入购物车、确认订单、提交订单,这个链路要尽量模拟人工操作,又比人工快。
拆解完需求之后,还要做减法。比如“先到货通知我”和“到货自动下单”其实是两条路线。前者只需要爬虫加消息推送,后者才需要爬虫加完整下单链路。我当时脑子一热选了后者,因为“自动下单”听起来才是真正解决问题的方式——通知了也没用,货还是抢不到。
这个项目的核心架构其实很简单,没有一开始就拆什么微服务,就是 Node 单进程跑定时任务。爬虫部分负责请求商品接口,拿到 JSON 数据之后做字段抽取,然后对比库存状态字段,状态变化时进入下单模块。下单模块内部独立成几个函数,分别对应加购物车、结算页确认、提交订单这几步。每次请求之间设置随机延迟,全部用 Promise 串行控制,避免并发太猛被平台限流。
1.2 方案选型的背后考量:为什么是 Node
选 Node 做这个项目,最开始纯粹是因为熟悉 JavaScript,生态里爬虫相关的库足够多,request、axios 这些发 HTTP 请求都是现成的。后来深入做下去才发现,Node 在电商自动化这个场景里有几个天然优势,并不是随便选的。
第一个优势是异步 I/O 模型。爬虫本质上是密集型的 I/O 操作,大量的时间都花在等待网络响应上。Node 的事件循环机制可以用单线程处理海量并发请求,资源占用相比多线程模型要小得多。在监控场景下,我需要每几秒请求一次接口,Node 的异步定时器可以轻松做到高频轮询不阻塞。
第二个优势是 npm 生态确实方便。加购物车、提交订单这些操作本质上是 HTTP 请求加上 Cookie 管理,jsonwebtoken 做登录态处理、cheerio 做 HTML 解析、node-schedule 做定时调度,这些库都是一行命令装好,开发效率很高。我当时还用过 puppeteer 做无头浏览器方案,但后来发现接口直调的效率远高于浏览器渲染,最终主方案还是回归到纯 HTTP 请求层。
第三个优势是调试方便。下单流程出问题的时候,Node 可以直接在浏览器 DevTools 里调试,不用像其他语言那样折腾环境。JavaScript 对象和 JSON 数据的天然亲和性也让接口数据的处理少了一层转换成本。
当然,Node 也有自己的短板。单线程在 CPU 密集型的场景下会吃紧,比如大规模解析复杂 HTML 或者做加密算法逆向的时候。不过在京东监控这个场景里,主要的计算量是 JSON 解析和字符串匹配,Node 的性能完全够用。
2. 核心细节解析与实操要点
2.1 爬虫技术选型与核心逻辑
京东商品页有两种数据来源,一种是服务端渲染出来的 HTML 页面,另一种是前端异步加载的 JSON 接口。刚开始做的时候我走了弯路,直接去抓 HTML,然后想尽办法从混在一起的标签里抽取数据,又慢又容易碎。后来抓包分析才发现,商品详情页的数据其实有很大一部分来自一个单独的接口,返回的是干净的 JSON 结构,里面直接包含了“库存状态”“价格”“促销信息”这些字段。
爬虫的代码骨架大概是这样的:用 axios 建一个实例,设置好 baseURL 和 headers,特别是 User-Agent、Referer 这些必须伪装成真实浏览器的值。请求商品接口拿到 JSON 之后,重点看几个字段:
skuId:商品唯一标识stockStatus:库存状态,这个字段直接决定要不要触发下单price:价格,用于比对是否符合预期name:商品名称,用于日志记录
爬虫核心部分我拆成了独立的函数,方便单独测试。每隔 5 秒调一次查询接口,但是每次请求的间隔加一个 1 到 3 秒的随机延迟,避免节奏太规律被封。结果用一个状态机来管理,上一次是无货、这一次还是有货,那就不动;上一次无货、这一次有货,那就进入下单流程。这个状态切换是整个监控逻辑最核心的地方。
2.2 监控策略与下单链路实现
监控策略上,除了“轮询检测-状态切换”这个基础逻辑,还有一个很关键的设计:重试机制。有时候接口返回的是瞬时异常数据,比如超时、或者平台限流返回了一个假状态,这时候如果直接触发下单,很有可能会下错单。所以我在状态字段变成“有货”之后,不会马上跑下单链路,而是再连续确认两次,如果三次检测都是“有货”,才会真的执行下单。这个“三重确认”机制帮我挡掉了很多误触发。
下单链路是最复杂的一部分。这一步已经完全超出爬虫的范畴,本质上是在模拟一个真实用户的购物行为。完整的链路是这样:
- 先通过商品接口拿到 skuId,调用“加入购物车”接口,把这个商品添加到购物车。
- 然后获取购物车结算页的详情,这里需要带上用户登录后的 Cookie,否则拿不到有价格的结算信息。
- 调用“获取订单结算信息”接口,拿到订单价格、库存、收货地址、优惠券这些关键数据。
- 最后调用“提交订单”接口,携带着地址、支付方式、优惠券信息,把订单真正提交上去。
这里每一步都需要处理好 Cookie 和签名。Cookie 直接在程序里写死,一个会话对应一套 Cookie,有效期大概是几天。签名这块是当时踩坑比较深的点,京东接口有个a2参数,是一个加密签名,基于请求参数和某个时间戳生成的。当时我花了很多时间尝试逆向这个算法,最后用了一个取巧的办法:先用浏览器手动登录一次,抓下某个请求的完整参数,直接把签名参数缓存起来复用。这个方案能用,但不够优雅,也因为这个原因,一旦签名过期或者算法变化,程序就直接失效了——这也是后来项目被标记为 DEPRECATED 的原因之一。
2.3 定时任务与防封策略
定时任务我用了node-schedule这个库,写起来很简洁。但有一个细节值得说一下:任务执行频率不能太激进。我一开始是把轮询间隔设成 2 秒,跑了不到一个小时,IP 就被平台临时禁止访问了。后来我做了两层调整:第一层是把轮询间隔拉到 8 到 15 秒之间,加上随机波动;第二层是做了一个简单的“请求状态反馈”机制,如果连续多次请求返回 403 或者验证码页面,就自动降低请求频率,而不是继续硬碰硬。
防封策略里还有一个容易被忽略的点:请求头顺序和 Cookie 的一致性。有些平台的校验逻辑会检查请求头里字段的顺序是否和浏览器保持一致,虽然看起来玄学,但实测下来,把请求头字段的顺序固定成和真实浏览器一致,确实能减少很多风控误判。另外,同一个会话的 Cookie 不要频繁变更,频繁变化容易触发“异常登录”风控。
3. 实操过程与核心环节实现
3.1 环境搭建:Node 与依赖准备
Node 环境的搭建,对于经常在多个项目间切换的人来说,强烈建议用 nvm 而不是直接装系统级 Node。我之前遇到过项目 A 依赖 Node 12、项目 B 需要 Node 16,直接装系统级版本就是灾难现场,每次切换都要改环境变量。nvm 可以通过一行命令切换版本,全局配置也简单,装完 nvm 之后顺手把默认别名指到一个长期维护版本上,比如nvm alias default 16.20.2。
依赖方面,这个项目用到的核心库不多:
npm install axios cheerio node-scheduleaxios 负责 HTTP 请求,cheerio 用来解析偶尔返回的 HTML 页面(虽然主链路走 JSON 接口,但有时候商品页面跳转验证码的时候需要解析 HTML 提取字段),node-schedule 负责定时调度。
项目初始化的时候还有个经验:提前建好log目录,并且日志要同时输出到控制台和文件。爬虫跑起来之后不可能一直盯着终端看,日志却是事后排查问题最重要的资料。我习惯把日志按天切分,文件名带上日期,这样出问题的时候定位到具体时间点,直接翻当天的日志就行。
3.2 核心代码实现与说明
商品状态监控的核心实现长这样:
const axios = require('axios'); const schedule = require('node-schedule'); const COOKIE = '你的登录Cookie'; const SKU_ID = '目标商品SKU'; const http = axios.create({ headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Cookie': COOKIE, 'Referer': 'https://item.jd.com/', }, timeout: 10000, }); let lastStatus = 'OUT_OF_STOCK'; let confirmCount = 0; async function checkStock() { try { const url = `https://api.m.jd.com/client.action?functionId=pc_detail&skuId=${SKU_ID}`; const { data } = await http.get(url); const currentStatus = data.stock && data.stock.stockStatus === 0 ? 'IN_STOCK' : 'OUT_OF_STOCK'; if (currentStatus === 'IN_STOCK' && lastStatus === 'OUT_OF_STOCK') { confirmCount++; if (confirmCount >= 3) { console.log('检测到有货,开始下单流程'); await placeOrder(); confirmCount = 0; } } else { confirmCount = 0; } lastStatus = currentStatus; } catch (error) { console.error('监控请求失败:', error.message); } }这个实现有几个关键点。第一,axios.create建了一个实例而不是每次请求都临时设置 header,这样可以保证请求头的一致性。第二,confirmCount实现的三次确认机制,防止单次请求返回异常数据导致误判。第三,placeOrder函数在下单完成之后会继续回到监控状态,形成一个循环。
下单流程的代码实现会稍微长一些,核心逻辑是依次调用三个接口:
async function placeOrder() { // 1. 加入购物车 await http.post('https://cart.jd.com/addToCart.html', null, { params: { pid: SKU_ID, pcount: 1, ptype: 1 }, }); // 2. 获取结算信息 const trade = await http.get('https://trade.jd.com/shopping/order/getOrderInfo.action'); const orderData = parseTradeInfo(trade.data); // 3. 提交订单 await http.post('https://trade.jd.com/shopping/order/submitOrder.action', { ...orderData, payPassword: undefined, // 不支持密码支付,必须提前设置为免密或小额免密 }); }这里有个非常重要的细节:如果账号设置的是密码支付,提交订单的时候必须传加密后的支付密码,否则会下单失败。我当时被这个问题卡了很久,最后发现直接用“小额免密”或者“指纹支付”模式,就不需要传支付密码参数了。另外,订单提交之前一定要确认收货地址有货,有些 SKU 有区域库存限制,A 地址没货不代表 B 地址没货,代码里最好预留一个参数来切换地址 ID。
3.3 下单链路的几个关键参数与计算逻辑
电商自动下单里,参数计算是最容易出现问题的环节。拿京东来说,下单的时候有几个参数是动态计算的,不是写死就能用的。
第一个是pcount,购买数量。这个看似简单,但如果商品有起购限制,比如“限购一件”,你传 2 就会直接被拒。我当时做了一个简单的校验:用正则从商品页面抓取限购信息,如果抓不到就默认 1。
第二个是配送地址 ID。这个参数在结算信息接口返回的数据里可以拿到,不要自己猜,也不要写死,因为地址 ID 会变。我踩过这个坑:写死了一个地址 ID,结果地址改了之后下单一直报错,排查了半天才发现是地址对不上。
第三个是优惠券抵扣逻辑。如果账号里有优惠券,下单时优惠券的 ID 也要算进去,不传的话就默认优先使用全局最优券。这个逻辑不同平台不一样,京东是服务端自动算价的,普通账号不用特意去选券。
第三个参数的处理逻辑写清楚之后,下单部分的代码就稳定了很多。不过这里也要提醒一句:不同账号的权限、不同会员等级,下单链路的返回数据差异很大,最保险的做法是先在浏览器里手动走一遍完整的下单流程,用开发者工具把每一步的请求参数记录下来,再照着这个参数结构去写代码,比对着文档猜要省时间得多。
3.4 运行效果与结果分析
程序跑起来之后的效果,说实话还是挺兴奋的。第一次看到日志里出现“检测到有货,开始下单流程”这句话的时候,心跳都漏了一拍。整个流程跑通之后,从检测到有货到订单提交成功,大概花了 3 秒左右。对于一个手动操作需要十几秒的流程来说,这个速度已经算是“抢购级别”了。
但运行一段时间之后也会发现一些尴尬的情况。比如有时候监控到有货,也成功提交了订单,但最后支付超时导致订单被取消。这个问题的根源在于,支付环节我没有做自动化——只做到了“提交订单”这一步,后续的跳转支付还是需要人工完成。如果人不在电脑前,光有订单没支付,最终订单也会消失。这也是我后来觉得这种“半自动下单”模式上限有限,从而弃用这个项目的一大原因。
4. 常见问题与排查技巧实录
4.1 典型的运行报错与解决方案
做这个项目的过程中,我整理了不少运行报错和对应的解决思路,挑几个典型的列出来,都是真实案例。
第一个是npm warn deprecated node-domexception@1.0.0: use your platform's native dome。这个出现在安装依赖的时候,提示某个传递依赖被弃用了。听起来吓人,其实问题不大,说明某个依赖的底层库已经不需要 polyfill 了,直接把相关依赖升级到新版本就能解决。实测下来,升级 axios 到最新版本之后这个警告就消失了。这类 warning 和项目的 DEPRECATED 标识本质上是一回事,技术选型更新迭代,老方案自然会被标记为废弃。
第二个是node:internal/modules/cjs/loader:1568 throw err; ^ error: cannot find module。这个问题九成是因为路径写错了或者模块没有安装成功。我当时遇到过一次,排查了半天才发现是.env文件里的路径变量多了个空格,导致模块加载失败。Node 的模块查找机制是从当前目录向上逐级找node_modules的,如果是项目内的深层目录,偏偏 node_modules 在根目录,也会出现这个问题。
第三个是failed to execute 'insertbefore' on 'node'。这个是我后来用 puppeteer 方案时遇到的,本质上是 DOM 操作时机不对,页面还没渲染完就尝试插入节点。解决方案很简单,等domcontentloaded事件触发之后再做操作。
我把这几个典型问题整理成了一张速查表:
| 异常现象 | 根本原因 | 解决方案 |
|---|---|---|
| npm 报 deprecated 警告 | 依赖库版本过旧 | 升级到最新版本,忽略警告即可 |
| Cannot find module | 路径错误或模块未安装 | 检查相对路径和项目根目录 |
| insertBefore 报错 | DOM 未加载完成就操作 | 等待加载事件后再操作 |
| 请求返回 403 | 请求频率过高触发风控 | 降低频率,加随机延迟 |
| 下单返回“没有合适的配送方式” | 地址 ID 错误或区域无货 | 切换正确的地址 ID |
4.2 项目弃用原因与经验沉淀
标题里那个 DEPRECATED 是怎么来的,值得好好说一下。这个项目最终被弃用,不是因为爬虫逻辑写错了,而是因为电商平台的接口变动太频繁。我今天写好的接口参数,可能过一个礼拜就加签名了,再过一个月返回的 JSON 结构也变了。维护这种依赖特定平台接口的项目,需要持续投入大量精力去跟进上游变化,ROI 实在太低。
此外还有一个绕不开的现实问题:平台的反爬策略越来越严格。这个项目的核心依赖是登录后的 Cookie,Cookie 有效期一过,整个链路就断了。频繁登录又容易触发账号风控,轻则要求验证码,重则限制登录。用脚本抢购这件事本身就是钻平台的空子,平台一旦认真起来,脚本的生存空间就很小了。
从中学到的东西反而比跑通项目更有价值:第一,写爬虫之前先抓包,搞清楚数据源是 HTML 还是异步 JSON 接口,这能省掉一半时间。第二,永远不要信任一次请求返回的数据,加一层状态确认机制能规避大量偶发问题。第三,自动化流程里每个环节都要预留人工兜底,比如下单之后如果支付失败,至少要有通知提示,不然就白忙活了。
4.3 给后来者的建议
如果你现在想做类似的电商监控与自动下单工具,我的建议是先做“监控+通知”,再做“自动下单”。监控加通知的风险和复杂度低得多,而且已经能解决 80% 的需求——大部分人只是想第一时间知道到货了,然后自己动手下单。自动下单看着很酷,实际上要处理的问题很杂,而且一旦平台接口变动,维护成本会瞬间爆炸。
还有一个建议是:不要只盯着“接口直调”这一条路。现在的爬虫方案已经非常多样化,Python 系的 requests 依旧经典,大模型辅助逆向爬虫也是一个新方向——让 AI 帮你分析加密参数、生成解密代码,整体上能省不少逆向的时间。Node 生态里也有 puppeteer 这种浏览器自动化方案,遇到接口加密太复杂的场景,用无头浏览器模拟人工操作反而更省事。
技术选型上,监控频率不要太激进,我后来复盘的时候发现,把轮询间隔拉到 20 秒,和 5 秒的差距并没有想象中那么大,但 IP 被封的概率低了一个量级。与其拼命抢那零点几秒的时效性,不如保证服务稳定在线。
如果你真要写下单部分,建议先用小号测试,而且不要真的支付,跑到“提交订单”那一步就停。这样既能验证链路通不通,又不会因为操作太频繁导致大号被风控。
我自己经过这个项目之后,最大的体会是:爬虫这事儿,技术难度不是核心壁垒,真正的壁垒在于对平台规则的敬畏和对异常情况的兜底能力。脚本跑起来很容易,但能否在平台改版之后快速跟上、在账号被封之后迅速止损,这才是区分新手和老手的地方。弃用一个项目不代表失败,从废弃项目里提炼出可复用的套路和边界,反而是更有价值的事。
本文还有配套的精品资源,点击获取