插件精灵脚本开发知识库
这里按文章记录平台规则和已验证的行为。展开左侧“实测知识库”,即可选择文章;手机端先打开“浏览使用说明”。
本页目录
这里按文章记录平台规则和已验证的行为。展开左侧“实测知识库”,即可选择文章;手机端先打开“浏览使用说明”。
飞书多维表格
- KB-FEISHU-001:飞书授权码与写入接口:PersonalBaseToken、base-api.feishu.cn、独立多维表格、字段格式、批量写入和错误排查。
- KB-FEISHU-002:飞书表格配置与可选输出:模板声明、表头复制、链接识别、字段检查,以及必选/可选安装流程的测试结果。
Amazon
- KB-AMAZON-001:Amazon US 商品详情页最小字段:当前变体 ASIN、标题、价格和币种的保守提取策略、等待、缺失值、挑战页、脱敏样本以及待实际网站验证范围。
- KB-AMAZON-002:Amazon US 库存与卖家监控技术实录:大于 999 的卖家可售库存探测、购物车 1000 上限、
features.imb、All Offers 跟卖去重、购物车清理、类目排名/Review 与批量飞书避坑。 - KB-AMAZON-003:Amazon US Review 页面与翻页接口技术实录:Review 页 URL 与原生筛选、每页 10 条必须走 XHR 翻页、
reviewsCsrfToken与 403「Page Not Found」、成对标签解析与 HTML 实体转义坑、data-hook字段来源。
如何阅读
先看脚本开发说明了解插件契约,再根据目标平台选择文章。每篇文章分别标注整理日期、验证方式与尚未验证的范围;真实账号写入与模拟接口测试会明确区分。
“查看 Markdown 原文”提供知识库文章合集,便于交给 AI。每篇文章也有自己的 Markdown 地址。
后续文章
知识库会持续增加平台、接口与故障排查文章。每篇围绕一个具体问题,记录复现步骤、结论、适用范围与未验证事项。
KB-FEISHU-001:使用 PersonalBaseToken 写入飞书多维表格
| 项目 | 内容 |
|---|---|
| 平台 | 飞书多维表格 |
| 场景 | 插件精灵 offscreen 脚本使用用户选择的变量组,将结果直接写入指定多维表格 |
| 鉴权方式 | PersonalBaseToken(多维表格授权码),不创建飞书开放平台应用 |
| 验证状态 | 已根据完整实跑记录验证 |
| 整理日期 | 2026-09-10 |
| 适用边界 | 独立多维表格(URL 包含 /base/);不适用于知识库中的 /wiki/ 多维表格 |
1. 结论摘要
实现该方案时必须同时满足以下条件:
- 多维表格建在“云文档/云空间”,地址必须是
/base/形态; - 使用该表格内生成并已启用的 PersonalBaseToken;
- 请求发送到
https://base-api.feishu.cn,不能发送到open.feishu.cn; - 从 URL 分别提取
appToken和tableId,不能把view参数拼进tableId; - 批量数据必须调用
batch_create,并使用{ "records": [...] }请求体; - 字段名必须与表格列名逐字一致;
- 不能只依据 HTTP 状态判断失败,必须继续解析响应体中的飞书
code和msg。
2. 建表位置:使用独立多维表格
正确入口
在飞书“云文档”中创建多维表格,并将其保存到“我的空间”等云空间位置。正确地址包含 /base/:
https://example.feishu.cn/base/{app_token}?table={table_id}&view={view_id}
已确认的错误入口
如果在知识库中创建或嵌入多维表格,地址通常包含 /wiki/。该 URL 中的 Token 是知识库节点 ID,不是 app_token。
在本方案的实测环境中:
- PersonalBaseToken 无法通过
base-api.feishu.cn的 wiki 路径解析知识库节点; open.feishu.cn也不接受 PersonalBaseToken;- 因此
/wiki/表格不能直接套用本方案。
处理方式:改用独立 /base/ 多维表格;如果业务必须写入知识库表格,需要另行验证飞书开放平台应用方案。
3. 提取 appToken 和 tableId
对于以下地址:
https://example.feishu.cn/base/APP_TOKEN?table=TABLE_ID&view=VIEW_ID
appToken:/base/后、?前的完整字符串;tableId:table=后、下一个&前的完整字符串;viewId:本次写入接口不需要。
注意事项:
- 不要依赖
bascn前缀判断appToken。实测有效 Token 的前缀并不固定,应以 URL 位置和接口验证结果为准。 - 不要把
tblXXX&view=vewXXX整段保存为tableId,否则可能返回9499 Bad Request。 /wiki/后的知识库节点 ID 不能作为appToken使用。
4. 获取并启用 PersonalBaseToken
获取入口:多维表格右上角“多维表格插件” → “自定义插件” → “获取授权码”。
正确顺序:
- 勾选“启用授权码”;
- 点击确定并保存;
- 再复制授权码。
只打开“获取授权码”并直接复制,可能得到尚未启用的无效授权码,实测会返回:
1011 personal token is invalid
其他规则:
- PersonalBaseToken 通常以
pt-开头; - 授权码与具体表格关联,换表后需要为新表重新获取;
- 请求头格式为
Authorization: Bearer {PersonalBaseToken}; - 专用 API 域名为
base-api.feishu.cn; - 将该授权码发送到
open.feishu.cn,实测返回99991668 Invalid access token。
5. 插件精灵变量约定
建议脚本使用以下固定英文键名:
| 键名 | 含义 | 示例 |
|---|---|---|
baseToken | PersonalBaseToken | pt-... |
appToken | 独立多维表格 App Token | /base/ 后的字符串 |
tableId | 数据表 ID | table= 参数值 |
baseUrl | 可选,供结果消息返回表格链接 | https://example.feishu.cn/base/... |
脚本可以声明友好表单,但该 Schema 只帮助用户录入,不会限制变量组用途:
// @connect https://base-api.feishu.cn/*
// @sellerfeeds-execution-mode offscreen
// @sellerfeeds-variable {"title":"飞书多维表格(PersonalBaseToken)","description":"填写独立 /base/ 表格的授权信息","schema":{"type":"object","properties":{"baseToken":{"type":"string","title":"多维表格授权码"},"appToken":{"type":"string","title":"App Token"},"tableId":{"type":"string","title":"Table ID"},"baseUrl":{"type":"string","title":"表格链接"}},"required":["baseToken","appToken","tableId"]}}
运行时读取用户为当前任务选择的变量组:
const data = variables || {};
const { baseToken, appToken, tableId, baseUrl } = data;
if (!baseToken || !appToken || !tableId) {
throw new Error('所选变量组缺少 baseToken、appToken 或 tableId');
}
不要使用“多维表格授权码”等中文键名代替脚本约定的英文键名,否则脚本无法读取对应字段。不要把完整变量组写入日志、报告或异常信息。
6. 正确的批量写入接口
接口:
POST https://base-api.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_create
Authorization: Bearer {PersonalBaseToken}
Content-Type: application/json
请求体:
{
"records": [
{
"fields": {
"平台": "Amazon",
"问题": "What is it?",
"对话深度": 1
}
}
]
}
{ "records": [...] } 只适用于批量接口。如果把它发送到单条接口 POST .../records,单条接口期望的是 { "fields": {...} },实测会返回 99992402 field validation failed,详情可能显示 fields is required。
这类错误容易被误判为字段名或空值问题。排查时应先核对 URL 是否为 batch_create,再检查字段名和字段值。
7. 字段、批量与限频
fields中的键必须与飞书表格列名逐字一致,包括空格。例如回答Markdown与回答 Markdown是两个不同字段。- 文本字段接受空字符串
"",本次实测无需预先删除空字符串字段。 - 飞书批量创建接口单次最多 1000 条。本次稳定实测采用每批 500 条。
- 本次实测按单文档 2 QPS 控制请求,并在批次之间等待 700ms。
- 上述分批和间隔是当前实测可用策略;平台调整限制后,应以新的实测结果为准。
建议实现:
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function createBatch({ baseToken, appToken, tableId, records }) {
const url = `https://base-api.feishu.cn/open-apis/bitable/v1/apps/${encodeURIComponent(appToken)}/tables/${encodeURIComponent(tableId)}/records/batch_create`;
const response = await SellerFeeds.fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${baseToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ records }),
});
const raw = await response.text();
let payload = null;
try { payload = raw ? JSON.parse(raw) : null; } catch {}
if (!response.ok || (payload && payload.code !== 0)) {
const detail = payload
? `code=${payload.code ?? 'unknown'}, msg=${payload.msg || payload.message || 'unknown'}`
: raw.slice(0, 500);
throw new Error(`飞书多维表格写入失败:HTTP ${response.status}; ${detail}`);
}
return payload;
}
async function appendRecords(context, rows) {
const batchSize = 500;
for (let offset = 0; offset < rows.length; offset += batchSize) {
const records = rows.slice(offset, offset + batchSize).map((fields) => ({ fields }));
await createBatch({ ...context, records });
if (offset + batchSize < rows.length) await wait(700);
}
}
该示例执行“追加写入”。如果需要覆盖、更新、按主键去重或删除旧记录,必须由脚本明确实现,插件不会替脚本决定写入语义。
8. 清理测试数据
批量删除接口:
POST https://base-api.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_delete
请求体:
{
"records": ["recXXX", "recYYY"]
}
只删除本次测试明确创建并已记录 ID 的数据,不要用不受控条件清空用户表格。
9. 实测错误码与排查顺序
| HTTP/业务码 | 实测含义 | 优先排查 |
|---|---|---|
1011 | personal token is invalid | 授权码是否已启用、是否过期、是否属于当前表格、复制是否完整 |
9499 | Bad Request | appToken/tableId 是否有效,是否混入 &view=...,是否误用了 wiki 节点 ID |
99992402 | field validation failed | 是否把批量请求体发到了单条接口;随后检查字段名、字段类型和值 |
99991668 | Invalid access token | 是否误把 PersonalBaseToken 发到了 open.feishu.cn |
HTTP 404 | page not found | API 域名或路径是否存在,是否尝试在 base-api 调用 wiki 路径 |
推荐排查顺序:
- 确认表格 URL 是
/base/,不是/wiki/; - 确认 PersonalBaseToken 已勾选启用;
- 确认域名是
base-api.feishu.cn; - 确认
appToken和tableId已正确拆分; - 确认批量请求调用
batch_create; - 读取响应体中的
code、msg和详细信息; - 最后检查列名、字段类型和值。
10. 任务结果建议
成功写入后,脚本可返回成功消息和表格链接;机器人报告文件由插件生成并发送:
return {
message: `飞书多维表格更新成功,共追加 ${rows.length} 条记录`,
links: baseUrl ? [{ title: '打开飞书多维表格', url: baseUrl }] : [],
data: {
written: rows.length,
mode: 'append'
}
};
如果脚本同时返回 links 和 files,插件精灵通知会同时发送链接和文件。链接能否查看或编辑,由飞书文档自身的共享权限决定。
11. 提供给 AI 的强制约束
使用本条目生成脚本时,AI 必须遵守以下约束:
- 使用
offscreen执行模式; - 声明
@connect https://base-api.feishu.cn/*; - 从
variables.baseToken/appToken/tableId读取变量; - 不接受
/wiki/节点 ID 冒充appToken; - 批量追加调用
records/batch_create; - 请求体使用
{ records: [{ fields: ... }] }; - 解析 HTTP 状态和飞书业务
code/msg; - 不在源码、日志、结果或异常中输出授权码;
- 默认按 500 条分批、批间等待 700ms,除非新的实测记录明确更新该策略;
- 不得凭空推断表格字段名,字段映射必须来自用户需求或真实表结构。
12. 尚未覆盖的范围
以下内容不属于本次实测结论,使用前需要单独验证:
/wiki/知识库多维表格的开放平台应用写入方案;- PersonalBaseToken 的自动续期或生命周期;
- 附件、人员、关联记录等复杂字段类型;
- 按主键更新、幂等去重和覆盖写入策略;
- 飞书限频规则未来发生变化后的最佳批量参数。
KB-FEISHU-002:飞书表格配置与可选输出
| 项目 | 内容 |
|---|---|
| 适用版本 | 插件精灵 1.8.1 |
| 整理日期 | 2026-09-14 |
| 验证方式 | 自动化测试、本地浏览器流程测试、官网线上页面检查 |
| 验证边界 | 字段 API 使用模拟响应;本轮没有使用真实飞书账号验证写入 |
1. 如何识别飞书能力
插件读取脚本的 @sellerfeeds-table,不会通过脚本名称、描述或 @connect 域名猜测。该声明必须引用有效的 @sellerfeeds-variable 配置模板,并声明 @connect https://base-api.feishu.cn/*。
required: true 表示必须配置;省略时也按必须处理。required: false 表示可选,必须是 JSON 布尔值,字符串 "false" 会被拒绝。完整契约和参数示例见脚本开发说明。
2. 已验证的安装与运行流程
- 必选:主按钮“安装并配置表格”,次按钮“仅安装,稍后配置”;运行前要求完整的目标表格配置。
- 可选:主按钮“确认并安装”,安装后不会自动打开配置表单;次按钮“安装并配置飞书”会打开该脚本的配置模板。
- 未声明表格能力:不显示飞书表格引导;原有普通安装和配置模板流程保持可用。
当前版本使用 x-sellerfeeds-input-groups 参数组:整组留空跳过、部分填写阻止运行、完整填写自动回传。常用配置只快速填值,以本次参数为准;脚本在采集前再次复核。
3. 在运行窗口保存常用配置
在运行窗口的飞书参数组中填写表格链接和授权码,点击“保存为常用配置”即可直接保存到本机,保持当前运行窗口和其他任务参数不变。不再跳到另一张表单重复填写。
如果已选择与当前脚本对应的常用配置,保存会更新该配置中的飞书参数并保留其他字段;否则新建一份以脚本命名的配置,同名时自动加序号。保存后自动选中新配置,用户可在常用配置页面改名或编辑。保存本身不发起飞书请求,也不自动同步云端。
常用配置弹窗不再展示表头复制、模板介绍、字段列表和手动检查表结构步骤。脚本在执行写入时按声明处理目标表结构,用户无需提前复制表头。字段的新增、类型转换或删除由具体脚本的实现和授权范围决定,保存配置不会修改表格。
4. 链接识别边界
用户只填飞书多维表格链接和飞书授权码,不手填 App Token、Table ID,也不勾选写入开关。脚本在内部识别飞书 HTTPS 链接中 /base/ 后的 App Token,以及 ?table= 指定的 Table ID。识别后去掉无关的视图参数。知识库 /wiki/ 链接、缺少具体数据表的链接及非飞书地址会被拒绝,不会猜测标识。
5. 配置校验与状态
保存前在本地校验飞书链接格式及授权码是否完整,不把“已保存配置”当作已经验证写入权限。脚本在实际请求飞书时处理权限不足、字段不匹配等错误。脚本中心对已保存的配置显示“已保存配置”,不再提示用户进行手动字段检查。
6. 验证证据与未覆盖范围
1.5.2 发布前全量 396 项自动化测试通过,包含表格声明、链接解析、字段检查与状态隔离;浏览器实测可选脚本的直接安装和主动配置两个入口。官网实测复制表头成功,390px 页面无横向溢出。
本轮没有新建真实飞书模板、没有使用真实授权码验证写入,也没有验证所有脚本作者的可选输出实现。飞书平台写入接口的历史实跑结论单独记录于KB-FEISHU-001。
7. 字段类型设计与不匹配时的转换
面向普通用户发布写入飞书的脚本时,建议把表格字段默认全部声明为文本,并在飞书写入层把 number、boolean 等值显式转成字符串。脚本自动创建文本列,用户可以保留飞书默认字段类型,不需要逐列设置数字、单选或复选框;Tool 的 JSON 返回仍可保留原始 number/boolean 类型,只有飞书展示层文本化。只有确实需要在飞书内做数值排序、聚合、公式计算或类型校验时,才建议声明数字等专用字段,并在建表说明中明确标出。
还要兼容用户已经建好但字段类型与脚本声明不一致的表格。推荐的转换流程是:先按声明类型正常写入;收到 1254060 TextFieldConvFail 或 1254061 NumberFieldConvFail 后,读取目标数据表的真实字段列表,按字段名建立类型映射;文本列使用 String(value),数字列只接受可转换且有限的数字,空值保持为空;然后仅重试本条记录一次。读取到的字段结构可按 App Token 与 Table ID 缓存几分钟,供同一批后续记录复用。缺失字段自动新增;数字转换失败或遇到脚本不支持的飞书字段类型时,应返回具体字段名并停止该条写入,不能静默丢值,也不能无限重试。
批量任务尤其需要这层兼容。SellerFeeds 会把多条输入逐项执行,脚本通常为每个商品追加一行;某些字段可能在前几条为空,到后面的商品才第一次出现数字或布尔值,因此常见现象是“单条正常、批量写到中途才报类型转换错误”。首个失败项应触发上述字段结构读取,转换后重试当前行;同批后续项直接按已缓存的真实类型转换再写入,避免每一行都先失败一次或重复请求字段接口。每项仍应独立返回写入结果,某一项失败时保留准确错误和已成功行的状态,不能把部分成功误报为整批成功,也不要重复追加已经写入成功的记录。
8. 通用可选回传与批次节流
新开发的数据查询、采集和监控脚本,建议都提供飞书多维表格回传,但统一使用 required:false 和可整体留空的目标参数组。这是可选输出,不是使用脚本的前置条件;整组留空时不校验飞书 Token、不发出飞书请求。
新表继续优先使用全文本字段,数字和布尔值只在飞书落表层字符串化。旧表类型不同时,按第 7 节读取实际字段后转换,不能要求用户为每个脚本重复调整大量列类型。
批量追加的统一基线是:调用 records/batch_create,每批最多 500 行,相邻写入批次间等待 700ms,保持单文档约 2 QPS 以内。飞书明确限流时按 1s、2s、4s 退避,最多重试 3 次;不要立即循环重试,也不要无限重试。中途失败时返回已成功行数和明确的业务去重键,让用户可以安全续跑。
手动、批量、定时和 MCP 任务必须共用同一套参数组语义、字段映射、节流和重试逻辑,不要只修单条手动调用分支。这套写表能力与“任务完成后推送飞书机器人消息/文件”相互独立。
9. 自动补齐字段
写入前读取目标字段,按名称复用已有列,按声明类型自动创建缺失列,然后写入记录。用户无需逐个填表头;表格模板、复制表头仍可选用。多余列保留,不自动删列、重命名或改变类型。建列要求结构编辑权限,响应不确定时停止,重新操作前先读取结构以复用已创建字段。参数组具体定义见开发说明。1.7.0 流程以自动写表公约为准。
KB-AMAZON-001:Amazon US 产品详情与父子 ASIN 采集
| 项目 | 内容 |
|---|---|
| 平台/站点 | Amazon 美国站 www.amazon.com |
| 页面 | 商品详情页 /dp/{ASIN}、/gp/product/{ASIN} |
| 对应 Tool | amazon_collect_product_detail |
| 对应脚本 | amazon-product-detail-collector.user.js 1.0.0 |
| 字段 | 父/子 ASIN、当前变体、标题、品牌、卖点、描述、价格、评分、Review、排名、Coupon、库存/配送/退货提示、图片、A+、概览和详情 |
| 整理日期 | 2026-09-14 |
| 当前证据等级 | 真实 Amazon 页面选择器与 twister 数据 + 脱敏快照 + 合成 DOM/生成脚本测试;真实飞书写入尚未实测 |
1. 不能越过的证据边界
仓库样本位于 tests/fixtures/amazon-us-product-detail/:合成脱敏样本明确标为 static_fixture_only;live-variant-2026-09-12.json 来自真实浏览器页面,只保留字段相关 selector 快照和变体切换值,不含账号、Cookie 或完整 DOM。
真实验证仍不能证明 Amazon 所有账号、地区和 A/B 页面都采用相同 DOM。脚本只返回页面当前明确披露的字段;缺失值用 null、空数组或空对象表示,不用 0 或猜测值补齐。
2. 字段语义与提取顺序
当前选中变体 ASIN
优先读取变体区被选中节点的 data-defaultasin 或 data-csa-c-asin,再回退到 input#ASIN / #ASIN 的值,最后从 /dp/ 或 /gp/product/ URL 提取。只接受 10 位字母数字。
“当前选中”不能简单等同于初始 URL ASIN;切换变体后 DOM 属性优先级高于 URL。Tool 若收到期望 ASIN且与当前结果不一致,必须报错,不能返回另一个变体。
标题
优先读取 #productTitle,回退到 meta[property="og:title"]。合并空白并保留可见文本;没有标题时页面不满足最小商品证据,任务失败。
价格和币种
只在核心购买区的价格节点中按保守顺序查找,不使用全页 .a-price,以免误取推荐商品价格。真实页面表明 www.amazon.com 会随配送/币种偏好显示 CNY308.45,切换变体后可显示 $39.99;因此不能把美国站硬编码成 USD。解析器按页面明确的 USD/CNY/CAD/AUD/EUR/GBP/JPY/INR 代码返回币种,明确 $ / US$ 时返回 USD。
页面无报价、价格节点缺失或文本没有明确币种标记时,price 和 currency 返回 null,同时列入 missing_fields;不得返回 0。0 是真实数值,不是缺失值替代品。
父 ASIN、子 ASIN 与变体属性
解析器只从商品页 twister-js-init-dpx-data 中的 parentAsin、currentAsin、dimensions、variationDisplayLabels 和 dimensionValuesDisplayData 读取父子关系。返回的 variations 包含子 ASIN、Size/Color 等属性和当前选中状态;页面未披露完整关系时不猜测。
父页的 twister 数据能列出子 ASIN 和变体属性,但不能证明每个子 ASIN 自己的标题、卖点、图片和价格。如果用户要“每个 ASIN 的页面信息”,应先运行一次取得 variations[].asin,再由 SellerFeeds 批量展开,让每个子 ASIN 独立运行同一 Tool。这样每行都有自己的页面证据,也能控制请求数和续跑范围。
其他页面字段
- 品牌来自 byline、商品概览或详情表。
- 卖点来自
About this item列表;商品描述、A+ 文字和 A+ 图片分开返回。 - 评分、Review/Rating 数量、Best Sellers Rank、Coupon、库存文案、配送信息和高退货提示都是当次页面快照,不是历史数据。
- 主图优先取
data-old-hires或动态图片元数据中的高分辨率 URL;附图和 A+ 图片去重后返回。 product_overview和product_details保留页面键值表,因商品类目不同,键名是动态的。
飞书回传和机器人通知
飞书不是采集必要条件。baseUrl 和 baseToken 都留空时只返回采集结果,不请求飞书;只填一项时在采集前拒绝;两项完整时每个实际采集的 ASIN 追加一行,并在权限允许时自动创建缺失文本列。
成功写表且有有效 table_url 时,飞书机器人发送报告文件,摘要可附带多维表格链接。写入失败时返回 output_error,保留采集结果;任务中心和 MCP 的 JSON、Excel、Markdown 下载不受影响。
3. 页面状态与等待
page Tool 初次解析缺少 ASIN/标题时,使用 MutationObserver 最多等待 10 秒;页面卡片对变体相关 DOM 变化做 180ms 防抖刷新。出现标题 Robot Check、#captchacharacters 或验证表单时标记 challenge 并失败。
下列情况当前不保证:地区配送或登录弹窗遮挡、优惠券后的到手价、Subscribe & Save、Deal 多价格、二手/多卖家报价、逗号小数等本地化数字格式、Amazon A/B 新布局。遇到这些页面需补充证据后扩展解析器。
4. 已复现结果
- 选中变体样本:从变体属性得到
B0VARI2345,标题为Sample USB-C Hub,价格29.99,币种USD。 - 缺失价格样本:ASIN 和标题可用,价格与币种为
null,missing_fields包含两项。 - Robot Check 样本:抛出
AMAZON_CHALLENGE,不作为商品结果。 - 页面助手源码测试:复制处理器中没有飞书函数;Tool 的关闭分支位于读取飞书配置和写入之前;包含重复挂载清理与 observer 断开。
- 真实页面:
B07NHW8Z3N选择 Black Pack 后,URL 与input#ASIN更新为B08GJ8MW12;核心价格节点更新为$39.99。此前初始选择价格为CNY308.45,证明币种需读取页面而非按站点猜测。 - 2026-09-14 真实页面:
B0D9VTFCBK的 twister 数据披露父 ASINB0D9VVS7RG、Size/Color 维度和 243 个子体;另一真实商品页可见标题、About this item、主图、概览和详情区。这只证明当时页面结构,不是 Amazon 公开 API 承诺。
运行 node --test tests/amazon-product-detail.test.mjs 可复现解析断言。真实浏览器验证截图保存在本地 output/playwright/amazon-us-variant-b08gj8mw12-2026-09-12.png;测试中的 live selector 快照可随 Git 追溯。该实测仅证明上述字段和一次变体切换,不等同于扩展端到端安装验收。
5. 实际页面需要补充什么
对未覆盖或改版页面,请提供可公开使用的 URL、站点/地区、登录状态、当前变体、脱敏的商品区 DOM 或页面诊断,以及人工看到的预期字段。不要提供 Cookie、登录凭据、PersonalBaseToken 或订单隐私数据。
KB-AMAZON-002:Amazon US 库存与卖家监控技术实录
| 项目 | 内容 |
|---|---|
| 平台/站点 | Amazon 美国站 www.amazon.com |
| 对应 Tool | amazon_monitor_inventory_seller |
| 对应脚本 | amazon-inventory-monitor.user.js 1.3.0 |
| 整理日期 | 2026-09-14 |
| 验证方式 | 真实 Amazon 浏览器会话、脱敏/合成 DOM、模拟购物车与飞书 API、完整仓库自动化测试 |
| 当前证据等级 | 已验证部分真实商品与当前页面结构;不是 Amazon 官方库存 API,不保证所有账号、地区、商品和 A/B 页面永久适用 |
本文记录库存与卖家监控脚本从需求分析到实测发布遇到的关键问题。重点不是某个固定 ASIN 的结果,而是怎样区分页面展示、购物车购买上限、卖家可售数量和证据不足,避免把一个看起来合理的数字误报为精确库存。
1. 数据边界:监控到的是什么
脚本得到的是当前 Amazon 会话、配送地址、时间和目标 offer 下的卖家可售证据,不是仓库物理库存,也不是 Amazon Seller Central 的官方库存字段。价格、Buy Box、配送方式、限购、地区限制和登录状态都会影响结果。
库存结果必须带证据等级:
| certainty | 含义 | quantity | lower_bound |
|---|---|---|---|
exact | Amazon 响应明确给出目标卖家的可售数量 | 精确值 | 同一值 |
at_least | 只证明库存不少于某个数量,常见于限购或探测上限 | null | 已证明的下限 |
out_of_stock | 页面或购物车响应明确无货 | 0 | 0 |
unknown | 页面、卖家或响应证据不足 | null | 可能为 null 或已观察到的最小值 |
不能把 unknown 写成 0,也不能把购买限制当库存。脚本没有销量字段,也不通过库存差倒推销量。
2. 输入与目标卖家
输入接受 10 位 ASIN,或包含 /dp/{ASIN}、/gp/product/{ASIN} 的 Amazon US 链接。链接里的 smid 可以指定卖家,显式 seller_id 优先;Seller ID 留空时监控当前 Buy Box 卖家。
商品页先读取当前 Buy Box offer。指定卖家与 Buy Box 不同时,再请求 ?smid={SELLER_ID}&psc=1 的卖家页面。加入购物车所需的 ASIN、merchantID、offerListingID 和其他成功表单控件来自当前 offer 的购买表单,不能只拼 ASIN,否则可能探测到另一个卖家。
3. 为什么“加购 999”不够
常见做法是把购物车数量改成 999,再读取“Only N available”。它只能覆盖较小库存,无法解释库存大于 999、购物车行上限、卖家限购和站点防滥用响应。
当前实现的探测顺序是:
- 读取探测前购物车,记录目标 ASIN、Seller ID、item ID 与原数量。
- 购物车没有目标 offer 时,按商品页真实表单临时加入 1 件。
- 从购物车页面读取
anti-csrftoken-a2z和活动商品数据。 - 调用购物车数量更新 XHR,第一档提交
1,000,000。 - 若只命中购物车行的 1000 上限,刷新购物车并用
2,147,483,647再探测一次。 - 无论探测成功或失败,都进入清理:本次新增的商品删除;原本已有的商品恢复原数量。
这里的“协议调用”是复用浏览器当前会话,按 Amazon 页面自己使用的表单/XHR 结构发请求;它不是 Amazon 官方对外库存 API。接口和 DOM 都可能变化,必须保留超时、HTTP 状态、Robot Check 和结构异常处理。
4. 大于 999 的核心坑:商品行 1000 不等于库存 1000
真实测试发现,Amazon 对超大数量请求可能在同一个 JSON 响应中给出两类互相看似矛盾的信息:
features.active-cart:购物车商品行被硬限制为 1000,并可能显示类似 “only 1000 available”。features.imb:重要提示中出现 “than the N available from the seller”,其中 N 才是本次响应明确披露的卖家可售数量。
因此解析优先级必须是:
- 先解析
features.imb中明确的 seller available quantity。 - 再识别购买限制,返回
at_least,不能冒充精确库存。 - 再识别明确无货。
- 最后才看
features.active-cart和响应后的购物车数量。
当探测值大于 1000、商品行恰好返回 1000 时,只能得出“至少 1000”或继续升级探测,不能返回 exact=1000。这是本次开发最关键的正确性修复。
2026-09-12 在配送邮编 91789 的真实会话中,脚本曾从 features.imb 得到以下大库存精确证据:B082314NFL = 40076、B0GWFN7Y8T = 2208、B07T869RNY = 200079。同一商品稍早/稍后的读数可能变化,这符合当前卖家可售量而非固定常量的语义。
5. 购物车安全与清理
库存探测会临时改变购物车,所以清理不是附加功能,而是事务的一部分:
- 探测前已存在同一 ASIN 和 Seller ID:记录原数量,结束时恢复。
- 探测任务新加入:结束时删除该 item ID。
- 加购后没找到准确卖家:停止探测,仍尝试删除本次新增候选商品。
- 主流程抛错:
finally仍执行清理。 - 清理响应缺少确认:返回警告,要求人工检查,不宣称清理成功。
匹配时必须同时考虑 ASIN、Seller ID 和购物车 item ID。只按 ASIN 删除,可能误删用户原有的另一个卖家 offer。
6. 跟卖:不能直接使用 “New (N)”
商品页的 “Other sellers on Amazon / New (N)” 更接近报价入口数量,并不保证等于唯一卖家数。真实 B07T869RNY 页面入口显示 New (4),但 All Offers HTML 只解析到 3 个唯一 Seller ID。
脚本请求页面实际使用的 All Offers 地址:
/gp/product/ajax/aodAjaxMain/ref=dp_aod_NEW_mbc?asin={ASIN}&...&pc=dp
请求带 X-Requested-With: XMLHttpRequest,从每个 #aod-offer 的卖家链接或 merchantID 读取 Seller ID,再按 Seller ID 去重。跟卖基准为显式 Seller ID;未指定时为当前 Buy Box Seller ID。
返回字段包括当前唯一在售卖家总数、去除基准卖家后的跟卖数、是否有跟卖,以及跟卖名称和 Seller ID。All Offers 被限流、挑战页或结构改版时,跟卖状态返回 unknown,不能把“没解析到”写成“无跟卖”;库存探测本身仍可继续。
7. 商品页跟进信号
Review 读取商品页当前显示的 Review/Rating 总数,只返回 review_count 当前值。脚本不计算 review_change,也不维护独立 Review 历史。
类目排名读取 Best Seller badge 和商品详情中的 Best Sellers Rank。一个商品可能同时有大类和小类排名,因此返回 category_ranks 数组;写入飞书时格式化为 #名次 类目,多个排名用中文分号分隔。没读到时返回空数组,不编造排名。
1.3.0 起还保留 parent_asin、variation_attributes、rating、coupon_text、delivery_text 和 return_warning_text。这些都是当次商品页可见快照:没有页面证据时返回 null、空对象或空数组,不猜测父体、促销、配送或退货风险。这些字段用于定时跟进快照,脚本本身仍不保存历史、不计算变化值。
8. 历史飞书直写经验与 TextFieldConvFail
以下记录旧版脚本的直写实现。当前官网库存脚本 1.3.5 已删除相关参数和代码,按 v2 返回业务数据,回传统一由数据中心执行;旧缓存、全文本列、读后重试规则不适用于现行脚本。
SellerFeeds 的批量任务会把多个 ASIN 展开后逐项串行执行;库存脚本每个 ASIN 向飞书追加一行。批量失败不一定是并发问题。
一次真实反馈是单条正常,批量到某些 ASIN 时出现:
HTTP 200; code=1254060, msg=TextFieldConvFail
原因是某些条件字段只在部分商品有值。用户复制表头后,飞书默认创建文本列;当后续 ASIN 首次出现价格、精确库存等数字值时,向文本列发送 JSON number 才触发转换失败,所以看起来像“批量才失败”。反方向也可能出现 1254061 NumberFieldConvFail。
从 1.3.0 起,库存脚本的 32 个飞书字段全部声明为文本,写入层也默认把数字转成字符串。新用户选择飞书表格后,脚本在权限允许时自动补齐缺失文本列。Tool 的 JSON 输出仍保留 number/boolean 语义,只有飞书展示层文本化。
用户输入只显示 baseUrl 和 baseToken:两项留空则仅返回监控结果,不请求飞书;只填一项则在采集前拒绝;完整填写则自动追加。飞书机器人发送任务摘要和报告文件;成功写表且返回有效地址时,摘要可附多维表格链接。写表失败时保留采集结果和报告文件。
为兼容按旧版说明建成数字列的表格,运行时在收到 1254060/1254061 后会读取真实字段列表:文本列转字符串,数字列转有限数字,然后重试;字段类型缓存 5 分钟供同批后续 ASIN 复用。缺字段或遇到不支持的字段类型时返回具体列名。
9. 限流、挑战页与失败语义
- HTTP 429:返回明确限流错误,建议降低批量/定时频率。
- HTTP 401/403:提示在浏览器完成 Amazon 登录或验证。
- Robot Check/CAPTCHA:返回
AMAZON_CHALLENGE,不把挑战页当商品页。 - 请求超时:每个 Amazon 请求有超时保护。
- All Offers 单独失败:跟卖为 unknown,库存流程继续。
- 库存主流程失败:仍执行购物车清理,然后抛出原始错误并附清理警告。
定时任务不宜为了“更实时”而高频运行。多个 ASIN 的批量规模、间隔和账号会话应以不触发 Amazon 限流或验证为前提。
10. 代码、测试与复现入口
| 位置 | 作用 |
|---|---|
examples/shared/amazon-inventory-monitor.mjs | 输入、商品页、All Offers、购物车请求、库存证据分级和清理 |
scripts/generate-amazon-userscripts.mjs | Tool Schema、飞书 32 列声明、写入适配和正式脚本生成 |
examples/amazon-inventory-monitor.user.js | 生成后的可安装官方脚本 |
tests/amazon-inventory-monitor.test.mjs | DOM、All Offers、大小库存、清理、Seller ID、飞书单条/批量兼容测试 |
推荐验证顺序:
pnpm run generate:amazonnode --test tests/amazon-inventory-monitor.test.mjspnpm run verify:userscript -- examples/amazon-inventory-monitor.user.jspnpm testpnpm run build:userscripts- 用真实浏览器确认商品页、AOD 响应和购物车最终恢复状态
真实浏览器证据只能说明当时的页面和会话。选择器或请求结构变化时,应先保存脱敏证据、补测试,再调整解析器;不能为了“总能返回数字”而放宽到无证据猜值。
KB-AMAZON-003:Amazon US Review 页面与翻页接口技术实录
| 项目 | 内容 |
|---|---|
| 平台/站点 | Amazon 美国站 www.amazon.com |
| 页面 | Review 页 /portal/customer-reviews/{ASIN}/;商品页 /dp/{ASIN} |
| 接口 | /portal/customer-reviews/ajax/reviews/get/ref=…(翻页 XHR) |
| 对应 Tool | amazon_collect_review_content |
| 对应脚本 | amazon-review-collector-demo.user.js 0.8.1 |
| 字段 | Review ID、采集/子 ASIN、星级、标题、正文、评论者及主页、日期及原文、国家、变体、已验证购买、Vine、Helpful 数量、Review/图片/视频链接、页面 Review 数量、Global Ratings |
| 整理日期 | 2026-09-15 |
| 当前证据等级 | 登录态真实页面结构 + 真机抓包逐字重放 + 合成 HTML fixture 单测;飞书写入见 KB-FEISHU-002 |
1. 不能越过的证据边界
本文结论全部来自已登录会话的真实页面(作者本机 Chrome,经 CDP 附着)与 Amazon 自己发出的请求。文中只保留 DOM 结构、请求形状、HTTP 状态码、响应字节数和字段名,不保存 Cookie、CSRF token 值或账号信息。
页面结构不是 Amazon 的公开承诺。selector、token 位置和请求头都会随改版变化;任何一条结论过期都必须重新实测,不能用推断替代。
2. URL 与原生筛选参数
Review 页的标准形态:
https://www.amazon.com/portal/customer-reviews/{ASIN}/?_encoding=UTF8&reviewerType=all_reviews&sortBy=recent
| 参数 | 取值 | 说明 |
|---|---|---|
sortBy | recent / helpful | 最新 / 热门 |
reviewerType | all_reviews / avp_only_reviews | 全部 / 仅已验证购买 |
filterByStar | all_stars、five_star … one_star、positive、critical | 星级 |
mediaType | all_contents / media_reviews_only | 全部 / 仅图片和视频 |
formatType | current_format | 仅看当前子体;不需要限定子体时不要带 |
_encoding=UTF8 无害;ref=cm_cr_arp_… 只是来源追踪,不是必需参数。传入任一子 ASIN,Amazon 会把它落到该 Listing 的 Review 页,因此「输入 ASIN 采整个 Listing」是默认行为。
3. 分页:不是 URL 翻页,必须走 XHR
- Review 页每页固定 10 条。
- 纯 GET `&pageNumber=2` 不翻页:实测返回的评论集合与第 1 页完全相同(同一批 Review ID),只是页面外壳重新渲染。想靠改 URL 翻页的思路是错的。
- 下一页参数在
data-reviews-state-param属性里(JSON)。同一页面会出现多个,其中若干是pageNumber: 1且没有 token 的噪声项;必须取 `pageNumber` 最大且带 `nextPageToken` 的那一个。 - 真正翻页的请求:
POST https://www.amazon.com/portal/customer-reviews/ajax/reviews/get/ref=cm_cr_getr_d_paging_btm_{page}
| Body 字段 | 值 | 备注 |
|---|---|---|
sortBy / reviewerType | 同 URL 参数 | |
filterByStar / mediaType | 不筛选时传空串 | |
formatType | 不限定子体时传空串 | |
pageNumber | 目标页码 | |
nextPageToken | 上一页给的 token | 缺了会被拒 |
shouldAppend | true | |
deviceType / canShowIntHeader / reviewsShown | desktop / true / undefined | 与页面一致即可 |
reftag | cm_cr_getr_d_paging_btm_{page} | 与 URL 的 ref= 一致 |
pageSize | 10 | |
asin | 目标 ASIN | |
scope | reviewsAjax1 |
- 响应体是流式命令序列:多段 JSON 用
\n&&&\n分隔。每段若形如[action, selector, html],取索引 2 的 HTML 片段拼接后再解析;非命令行(遥测)应忽略而不是报错。
4. CSRF token:本文最重要的一节
请求头名是 anti-csrftoken-a2z,但值必须取自评论页自己的 token:
<span id="cr-state-object" data-state="{"reviewsCsrfToken":"…", …}">
- 该值是
reviewsCsrfToken字段内容(实测 64 字符,且已 percent-encode,形如hKvJbDQ…%2Fdv…)。 - 页面上还散落着多处同名的 `anti-csrftoken-a2z`:
<meta name="anti-csrftoken-a2z">、以及 HTML 转义 JSON 里的"ajaxHeaders":{"anti-csrftoken-a2z":…}。它们和 `reviewsCsrfToken` 不是同一个值。 - 用错 token 的结果是 HTTP 403 + 响应体 "Page Not Found"(约 2296 字节),看起来像页面不存在,极易误判成「Amazon 改版」或「URL 写错」。
因此脚本里那条曾出现的报错「页面存在下一页但没有 anti-csrftoken-a2z」,真实原因往往是取到了别处的同名 token,而不是 token 消失。
x-amzn-flow-closure-id(取自 <meta name="flow-closure-id">):Amazon 自己的翻页请求会带这个头,但 2026-09-15 实测去掉它仍然返回 200,属于非必需。带上更贴近真实请求,但不能把它当成 403 的原因。
定位这类问题的方法(可复用)
- monkey-patch
XMLHttpRequest.prototype.open/setRequestHeader/send与window.fetch,把 url / headers / body 记进数组; - 点击页面上的 “Show 10 more reviews”,读出 Amazon 自己发出的请求;
- 逐字重放该请求(应得 200)——证明请求形状没问题;
- 再把自己的请求与它做结构化 diff:逐个头/参数只回答「相同 / 不同 / 缺失」,不回显 token 明文。差异收敛到哪一项,根因就是哪一项。
本次就是靠第 4 步锁定「唯一差异是 token 值」,进而找到 cr-state-object。
5. HTML 解析坑
不要用「非贪婪正则 + backreference」匹配成对标签
/<([a-z][\w:-]*)\b[^>]*>([\s\S]*?)<\/\1\s*>/i // 危险
([\s\S]*?) 是最短匹配。评论正文里经常有同名的内层标签(最典型是 Amazon 用于高亮的 <span class="a-text-bold">),于是正则在第一个内层 `</span>` 就闭合,正文被截断。实测:I have used this for <span class="a-text-bold">three months</span> and it still works perfectly.<br>Highly recommend. 只解析出前半段。
正确做法是按标签深度计数,找到真正配对的最外层闭合标签,并处理 void 元素(br、img、input…)与不完整片段。
HTML 实体转义的 JSON
Amazon 会把配置 JSON 以实体转义塞进属性(" 代替 ")。直接匹配 "key":"value" 会失败;应先把实体反转义(至少 " / " / ")再匹配,或把 " 纳入分隔符字符类。
6. 字段来源(data-hook)
| 字段 | 来源 |
|---|---|
| Review ID | data-hook="review" 节点的 id(部分页面为 data-review-id);跨页与续跑的去重键 |
| 星级 | review-star-rating / cmps-review-star-rating 的文本或 aria-label |
| 标题 / 链接 | review-title(其文本常带 N out of 5 stars 前缀,需去掉) |
| 日期 / 国家 | review-date:Reviewed in {国家} on {月} {日}, {年} |
| 变体 / 子 ASIN | format-strip 的文本与 href(href 路径里含子 ASIN) |
| 已验证购买 / Vine | avp-badge / vine-review-badge |
| Helpful 数量 | helpful-vote-statement;one person found this helpful 要当 1,不能用数字解析 |
| 正文 | review-body |
| 图片 | review-image-tile 的 src / data-src |
| 视频 | <video>/<source> 的 src、data-video-url,以及 JSON 里的 videoUrl |
| 页面 Review 数量 | cr-filter-info-review-rating-count,文案可能带 more than(是下限) |
| Global Ratings | 页面 N global ratings 文案;与文字 Review 数量不是一回事,不能混用 |
7. 访问受限与失败诊断
429、登录页、/edgex/guard/、Robot Check/validateCaptcha一律如实报错并保留已采集结果 + 续跑游标,不绕过验证。- 续跑游标不含 Cookie 或 CSRF token;续跑时重新加载首页取新 token,并从保存的
nextPageToken继续。 - 报错信息必须带诊断(落地 URL、页面标题、已解析评论数)。只报「页面结构可能已变化」无法定位问题。
8. 已复现结果(2026-09-15,登录会话)
| 操作 | 结果 |
|---|---|
GET 首页 /portal/customer-reviews/B09LD2CD1L/ | 200;解析出 11 条卡片;取到 reviewsCsrfToken;取到 pageNumber=2 的 nextPageToken |
用页面同款 token + 空 formatType POST 翻页 | 200 / 76420 字节 / 10 段命令 / 含 review 卡片 |
同上但去掉 x-amzn-flow-closure-id | 仍 200 → 该头非必需 |
改用页面别处的 anti-csrftoken-a2z | 403 + "Page Not Found" → 根因确认 |
纯 GET &pageNumber=2 | 200,但内容与第 1 页完全相同 → 不翻页 |
该 ASIN 的页面显示 6611 条评论。实际可采范围同时受 review_limit_per_asin、单轮请求上限和「Amazon 前台只提供最近若干页」约束;0 表示翻到没有下一页为止,不等于能拿到全部历史评论。
9. 数据组织(与脚本约定配套)
- 本地:一次任务一份报告(数据集
amazon-review-reports,主键任务编号),全部 Review 明细收在「完整报告」字段。 - 飞书:明细表,每条 Review 一行(29 列全文本);写入前按「任务编号」查询已有 Review ID,只补写未成功项。
- 本地单条记录上限 1 MiB:超限要明确报错并保留报告,不截断原文。
10. 未验证范围
- 非
amazon.com站点、未登录会话、移动端 UA/布局、Amazon A/B 新评论页。 - 视频 Review 只记录页面明确披露的 URL,不做解密或绕过站点保护。
- 本文不含飞书授权、写入与限流细节,见 KB-FEISHU-002。
对改版或未覆盖页面,请提供可公开访问的 ASIN/URL、站点与登录状态、脱敏后的评论区 DOM 或页面诊断,以及人工看到的预期字段。不要提供 Cookie、登录凭据或订单隐私数据。
