前言
🍊事情起因
最近狗哥盯上了一台 iPhone 18 Pro,512GB、黑色、青岛到店取。
定睛一看,拉倒,没库存。

官网偶尔显示今天可取货,晚一步再看,没了。总不能一天到晚守着页面刷新吧?于是我给 Codex 提了个需求:
帮我盯着苹果官网,只要青岛门店有货就提醒我。
听起来挺简单,看看狗哥如何指挥codex,不用写一行代码,完美实现。
说干就干,不服来战,先看下成果,源码在最下面!!!

🎯这次到底要做什么
先别急着写代码,狗哥把想要的效果给 Codex 说清楚:
- 页面上能选地区、门店、型号、容量和颜色。
- 点一下“保存并监听”,马上查一次库存。
- 后面自动盯着,不用我来回刷新官网。
- 真有货就响一声,再弹个桌面通知。
看着就四条,真做的时候每条都藏着坑。
接口从哪儿来?哪个字段才代表有货?一次查多少配置合适?查太快被拦了怎么办?浏览器关了还能不能继续?
下面不直接甩最终代码。咱们按当时的排查顺序,一步一步往下走。
正文
🍪第一步:先分析接口,看看库存藏在哪儿

苹果官网能显示“今天可取货”,说明浏览器肯定拿到了库存数据。第一步先打开开发者工具,在 Network 里盯住自己切换门店、容量和颜色时发出的请求。
我把抓到的请求和响应丢给 Codex,让它先别写功能,先找出真正决定库存的字段。
购物车页面会请求 bagx,响应里有一段数据很关键:
{
"partName": "iPhone 18 Pro 256GB 黑色",
"availabilityQuote": "今天 可取货",
"availableNowForLine": true
}
Codex 给出的判断很直接:
availableNowForLine: true,当前商品可以到店取货。availableNowForLine: false,当前商品不可取货。
到这里看着已经结束了,读一个布尔值就行。
结果页面一跑,32 个配置里只有购物车中的 3 个有结果,另外 29 个全是“未知”。
原因也找到了:bagx 只返回购物车里的商品。没加进购物车的配置,响应里压根没有。
它们到底有货还是没货?不知道。
这里不能图省事,把“没拿到数据”直接写成“无货”。页面是好看了,结果全是猜的。
所以代码里保留了四种状态:
| 状态 | 含义 |
|---|---|
| 可取货 | 官网明确返回可取货 |
| 暂无供应 | 官网明确返回不可取货 |
| 未知 | 没拿到可信库存字段 |
| 已过期 | 曾经查到结果,但太久没有刷新 |
👽人话解释
“没查着”和“没有货”差得可远了。医院没拿到你的化验单,不能直接宣布你身体倍儿棒。
我让 Codex 把这条规矩写死:官网明确返回布尔值,才允许更新库存。字段缺失、接口报错、门店对不上,继续显示未知。
第一步算是摸清了字段,可新问题又来了:总不能为了监控 32 个配置,先往购物车塞 32 台手机吧?
🔍第二步:购物车不够用,继续找商品页接口
继续抓。
这次不看购物车,回到商品详情页,选择型号和门店,再打开“供货情况”。Network 里出现了另一个到店取货接口:
/shop/retail/pickup-message
请求里带一家门店和多个货号:
store=R471
parts.0=MJT74CH/A
parts.1=其他货号
store 是门店编号,parts.0、parts.1 是商品货号。一个请求可以带多个货号,不需要先加购物车。
这下路走通了。
我让 Codex 整理商品目录,把机型、容量、颜色映射到真实货号,再做一个配置页面。选好门店和配置,程序自动拼出请求。
一开始我还想多选门店,青岛、济南一起盯。很快又把它砍了。
门店越多,单轮请求越多,触发限制的概率也越高。最后改成门店单选,型号、容量和颜色多选。同一门店的多个货号合成一次请求,够用,也省请求。

💥第三步:接口通了,HTTP 541 又来了
到这里,页面有了,商品也能查了。我自然想让结果再快一点。
60 秒太慢?那就 10 秒。
10 秒还不够快?保存配置以后立刻查。
很快,接口开始返回 HTTP 541,正文还是一张 Page Not Found 页面。
页面上又变成一排“未知”。
我先后让 Codex 检查请求头、接口参数、Cookie、无痕窗口,还讨论过换成 Python 自动化。折腾一圈,没有找到什么神奇开关。
后来把多轮日志放到一起看,规律出来了:限制窗口内,同一请求反复返回 541;等一段时间,它又会恢复 200。请求头换来换去,结果没变。
换 Python 也解决不了这个问题。请求频率和网络环境没变,外面套什么语言都一样。
最后采用的办法很朴素:少查,失败就等,别跟官网较劲。
最终策略是:
- 正常查询默认 120 秒一次。
- 一次请求合并当前门店的多个货号。
- 连续遇到 541 后进入冷却。
- 冷却时间按 5、10、20、30 分钟逐档增加。
- 冷却到点只发一次探测,成功再恢复正常轮询。
const COOLDOWN_STEPS = [300, 600, 1200, 1800];
程序还给每次请求加了 20 秒超时。官网一直不回,也不能让页面永远挂在那里。
👽人话解释
541 出现以后,继续猛点刷新就像电梯按钮没亮,拿手指一顿狂戳。电梯不会来得更快,按钮倒是快让你按秃了。
这套策略不能消灭 541,但能在撞上以后及时收手。库存仍然显示未知,不会被误报成无货。
风控问题稳住了,我以为终于能用了。结果点完“保存并监听”,页面半天没反应。

🧩第四步:把查询放回日常 Chrome
单独在 Node.js 里请求,和日常打开苹果官网的 Chrome 不在同一个会话里。手动复制 Cookie 麻烦、容易过期,也不适合长期运行。
于是 Codex 给项目补了一个本机 Chrome 扩展,它只负责三件事:
- 在
apple.com.cn页面里获取官网正常产生的库存响应。 - 按看板保存的配置发起低频库存查询。
- 把结果传给
127.0.0.1上的本机服务。
页面、扩展和官网的关系也很简单:
本机看板 → Chrome 扩展 → 苹果库存接口
↑ ↓
└──────── 库存结果 ────────┘
看板只监听 127.0.0.1,扩展权限也只给苹果官网和本机地址。真实 Cookie 不落进配置文件,更不会提交到仓库。
不过扩展接上以后,还有最后一个速度问题。
有一次追踪日志显示,Apple 接口只用了 586ms,整个流程却花了 18 秒。真正耗时的地方叫“等待扩展”,占了 17 秒多。
接口很快,结果为什么回来这么慢?
继续查,原因落在 Chrome 后台标签页。原来的扩展靠定时器读取新配置,标签页进了后台以后,定时器会被浏览器降频。
后来改成本机长轮询。保存配置时,服务端推送新的版本号,扩展收到以后马上查询;500ms 的页面检查只留作断线兜底。
为了不再靠感觉猜,Codex 还把一次查询拆成五段:保存请求、等待扩展、Apple 请求、解析传输、页面渲染与通知。以后慢在哪儿,看日志就知道。
🔔第五步:结果出来以后,别让我一直盯着
查询已经稳定,最后才轮到最开始的需求:有货叫我。
每次接口完成,看板都会更新对应配置。只有商品从“未知”或“暂无供应”变成“可取货”,程序才触发提醒。
提醒有两种:
- 浏览器播放一声短提示音。
- 桌面弹出通知,显示机型、容量、颜色和门店。
持续有货不会重复提醒。要不然两分钟叫一次,手机没买到,人先被逼疯了。
if (Notification.permission === 'granted') {
new Notification(`${event.modelName} 可取货`, {
body: `${event.capacity} · ${event.colorName} · Apple ${event.storeName}`
});
}

🛠️第六步:把工具交付成能直接用的样子
到这里,最初那四条目标终于串起来了:
- 选择地区、Apple Store、机型、容量和颜色。
- 在一个请求里查询当前门店的多个货号。
- 区分可取货、暂无供应、未知和已过期。
- 遇到 541 自动退避,并显示下次探测时间。
- 记录保存、等待扩展、官网请求、解析和渲染分别用了多久。
- 到货时播放声音并发送桌面通知。
- 在 Windows 上打包成单文件启动器。
项目代码已经放到 Gitee:
目前有两种运行方式。
- 想看代码、方便修改,用源码运行。
- 只想打开就用,直接运行 Windows EXE 包。
两种方式都要安装一次 Chrome 扩展。扩展负责在日常 Chrome 的苹果官网环境里查询库存,本机看板负责配置、展示和提醒。
🧰方式一:源码运行
适合开发者,后面想改门店目录、页面样式或查询逻辑,都从这里开始。
第一步,准备环境。
电脑需要安装:
- Node.js 22
- Google Chrome
- Git,可选;不想用 Git,直接下载仓库 ZIP 也行
把项目下载到本地:
注意分支:main
git clone https://gitee.com/javadog-net/apple-store-inventory-monitor.git
cd apple-store-inventory-monitor
第二步,安装依赖。
npm.cmd ci
第一次安装需要等一会儿。完成后可以先跑测试,确认当前环境没问题:
npm.cmd test
看到测试全部通过,再启动本机服务:
npm.cmd start
终端出现下面这个地址,说明服务已经起来了:
库存看板:http://127.0.0.1:4318

第三步,安装 Chrome 扩展。
扩展只需要安装一次:
- Chrome 地址栏打开
chrome://extensions/。 - 打开右上角“开发者模式”。
- 点击“加载已解压的扩展程序”。
- 选择项目里的
chrome-extension文件夹。 - 扩展列表出现“Apple 库存本机桥接”,说明加载成功。
以后扩展代码更新了,就回到这个页面点一次“重新加载”,再刷新已经打开的 Apple 官网标签页。



第四步,打开看板和苹果官网。
浏览器打开两个页面:
http://127.0.0.1:4318/
https://www.apple.com.cn/iphone/
Apple 官网标签页不要关,它是扩展执行库存查询的地方。看板顶部显示“日常 Chrome 已连接”,说明本机服务和扩展已经接上。

第五步,保存监听条件。
在看板中依次选择地区、门店、型号、容量、颜色和查询间隔,点击“保存并监听”。第一次保存时,Chrome 会询问是否允许桌面通知,选择允许。
保存后会立即查询一次。运行记录里能看到门店、SKU、HTTP 状态和耗时;后续按设置的间隔自动检查。
要停止源码服务,回到 PowerShell 按 Ctrl+C。源码运行日志保存在:
.local\apple-api.log
📦方式二:Windows EXE 运行包
注意分支:feat/windows-single-file-launcher

如果只是自己盯库存,不准备改代码,EXE 更省事。目标电脑不需要安装 Node.js。

第一步,解压运行包。
运行包里至少保留下面两项:
Apple库存监控.exe
chrome-extension\
EXE 已经内嵌库存看板、商品目录和门店目录。chrome-extension 不能删,第一次使用还要从这个文件夹加载扩展。
第二步,加载 Chrome 扩展。
还是打开 chrome://extensions/,开启开发者模式,点击“加载已解压的扩展程序”,这次选择运行包里的 chrome-extension 文件夹。
如果之前通过源码方式加载过同一个扩展,可以继续使用,不用重复添加。
第三步,双击启动。
双击:
Apple库存监控.exe

启动器会自动完成三件事:
- 在本机启动
127.0.0.1:4318库存服务。 - 用日常 Chrome 打开库存看板。
- 再打开 Apple 中国官网,供扩展查询库存。
当前 EXE 没有代码签名。Windows SmartScreen 可能提示“未知发布者”,确认文件来自自己的构建或可信发布地址后,再选择“更多信息 → 仍要运行”。

再次双击 EXE,不会再启动一个重复服务,只会打开已经运行的看板。
第四步,选择配置并开始监听。
操作和源码版完全一样:选地区、选一家门店,再选型号、容量和颜色,最后点击“保存并监听”。顶部显示 Chrome 已连接,运行记录出现 Apple 请求,才算真正开始工作。

关闭浏览器页面不会自动结束服务。停止 EXE 要在黑色控制台窗口按 Ctrl+C,或者直接关闭该控制台窗口。
EXE 运行日志保存在:
%LOCALAPPDATA%\Apple库存监控\apple-api.log
如果想自己从源码构建 EXE,需要 Node.js 22.20.0 或更高的 Node 22:
npm.cmd ci
npm.cmd run build:win
构建完成后,文件位于:
dist\Apple库存监控.exe
👽人话解释
源码版适合继续折腾,EXE 版适合直接开机干活。两边库存逻辑一样,区别只是启动方式。
项目测试覆盖了库存解析、单门店约束、541 冷却、配置推送、通知状态和页面交互。自动测试不会访问苹果官网,避免跑一次测试就给官网增加一轮请求。
源码
【公众号】JavaDog程序狗,回复【Apple库存监控】即可免费获得
