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、登录凭据或订单隐私数据。
