Amazon 商品评论是互联网上最丰富的公开信号之一。每条评论的星级、标题、正文和日期,共同构成了真实买家对某款商品看法的完整记录。这些数据可用于情感分析、商品研究和竞争对手对比,这也是为什么团队希望获得干净、结构化的数据馈送,而不是手动翻阅页面。

本指南将向您介绍如何使用 Python 抓取 Amazon 评论。您将构建一个可运行的小型爬虫,通过 Crawling API 获取商品评论页面,用 BeautifulSoup 解析每条评论,处理分页,并将结果导出为 JSON 和 CSV。整个教程仅涉及 Amazon 向任何访客公开的公开评论文本,结尾处的法律注意事项并非套话,请在对大规模数据进行爬取之前仔细阅读。

您将构建什么

一个 Python 脚本,接受 Amazon 商品评论页面的 URL,通过 Crawling API 获取已渲染的页面,并为每条评论提取一条结构化记录。我们以 Meta Quest Pro 为示例,从每个评论区块中提取以下字段:

  • 评论者姓名 评论上显示的公开展示名称。
  • 评分 星级评分,例如"4.0 out of 5 stars"。
  • 标题 评论者给评论起的简短标题。
  • 正文 完整的书面评论内容。
  • 日期 "Reviewed in the United States on ..." 这一行。

脚本会跨所有评论页面收集这些记录,并将其写入 amazon_reviews.jsonamazon_reviews.csv,可直接用于情感模型、电子表格或数据库。

为何直接请求会在 Amazon 上失败

如果将裸 HTTP 客户端指向 Amazon 评论页面的 URL,您很少能获得评论内容。Amazon 是网络上对自动流量防御最严密的网站之一。来自数据中心的 IP,或请求模式不像真实浏览器的访问,在到达评论区块之前就会遭遇 CAPTCHA、"Robot Check"拦截页面或直接封锁。即使请求侥幸通过,页面部分内容也通过 JavaScript 渲染,因此原始抓取可能返回空壳而非完整的标记。

因此,一个可用的 Amazon 评论爬虫需要在一次请求中实现两点:一个真正渲染页面的浏览器,以及平台识别为真实购物者的 IP。您可以自行组合无头浏览器和轮换住宅代理池,但将它们整合在一起并保持健康才是工作的主要难点。Crawling API 将两者合并为一次调用:您发送 URL,它在受信任的住宅 IP 后面渲染页面,为您轮换地址,并返回供 BeautifulSoup 解析的完整 HTML。

两种方式

Crawlbase 可以返回原始 HTML 供您自行解析,也可以通过 Scraper API 内置的 amazon-product-reviews 解析器返回预先解析的 JSON。本教程使用 BeautifulSoup 解析 HTML,让您清楚地看到哪些选择器对应哪些字段,并在最后说明自动解析路径可以省去这一步骤。

前置条件

在编写任何代码之前,您需要准备几样东西,都不需要太长时间。

基本的 Python 知识。 您应该能够编写并运行 Python 脚本,以及用 pip 安装软件包。如果您是语言新手,我们的Python 网页抓取入门指南和任何初学者课程都能让您达到本教程所假设的水平。

Python 3.8 或更高版本。python --version 确认版本。如果没有安装,请从 python.org 或通过 Anaconda 等发行版进行安装。

Crawlbase 账户和 token。 注册账户,打开控制台,从账户文档页面复制您的 token。Crawlbase 最多为您提供 20,000 次免费请求,无需信用卡,仅对成功的请求收费。请像保管密码一样对待该 token:它用于验证您的请求,因此请勿将其提交到版本控制系统。

设置项目

创建虚拟环境以隔离项目依赖项,然后安装爬虫所需的两个库。

bash
python --version

python -m venv amazon_env
source amazon_env/bin/activate

pip install crawlbase beautifulsoup4

在 Windows 上,请用 amazon_env\Scripts\activate 替代 source 命令激活环境。两个依赖项完成主要工作:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 通过 CSS 选择器解析返回的 HTML,从而从评论区块中提取每个字段。

了解 Amazon 评论页面

在编写选择器之前,请在浏览器中打开商品评论页面,右键点击单条评论,选择"检查"。Amazon 将每条评论包裹在标有 data-hook="review" 的容器中,并通过该容器内稳定的 data-hook 属性公开各个字段。这些钩子比 Amazon 的工具类名更耐用,因此尽可能使用它们来定位目标。

您所关心的字段在每个评论区块内映射到以下钩子:

  • 评论者姓名 span.a-profile-name 元素。
  • 评分 [data-hook="review-star-rating"](某些布局下为 review-star-rating-view-point)。
  • 标题 [data-hook="review-title"]
  • 正文 [data-hook="review-body"]
  • 日期 [data-hook="review-date"]

步骤 1:获取已渲染的评论页面

首先获取完整页面。导入 CrawlingAPI 类,用您的 token 初始化,指向商品评论页面的 URL,然后发出请求。在解析之前先检查状态码,确保错误能及时暴露而不是静默失败。

python
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

REVIEWS_URL = (
    "https://www.amazon.com/Meta-Quest-Pro-Oculus/product-reviews/"
    "B09Z7KGTVW/?reviewerType=all_reviews"
)

def crawl(page_url):
    options = {"ajax_wait": "true", "page_wait": 3000}
    response = api.get(page_url, options)
    if response["status_code"] == 200:
        return response["body"].decode("latin1")
    print(f"Request failed: {response['status_code']}")
    return None

if __name__ == "__main__":
    html = crawl(REVIEWS_URL)
    print(html[:500] if html else "No HTML returned")

两个等待选项在页面部分内容异步加载时很有用。ajax_wait 告知 API 等待异步内容加载完成,page_wait 在页面加载后等待固定的毫秒数,确保延迟渲染的评论区块在页面被捕获之前出现。将 body 解码为 latin1,是因为 Amazon 页面混入了严格 UTF-8 解码可能出错的字符。运行脚本后,您应该能看到真正的评论标记,而不是机器人检测页面。这表明请求在您编写任何选择器之前就已正常通过。

Crawlbase Amazon Scraper

那个机器人检测页面正是 Amazon 对裸请求的回应。Crawling API 在真实浏览器中渲染页面,在服务端轮换住宅 IP,并在一次调用中将完整 HTML 交给您,让您无需自行运行无头浏览器集群和代理池。先在免费套餐中指向评论 URL,然后再扩展。

步骤 2:用 BeautifulSoup 解析评论

拿到已渲染的 HTML 后,将其载入 BeautifulSoup,找到每个评论区块,并通过其 data-hook 选择器提取每个字段。将每个区块包裹在 try/except 中,确保一条格式错误的评论不会中断整个运行。

python
from bs4 import BeautifulSoup

def text_of(block, selector):
    el = block.select_one(selector)
    return el.get_text(strip=True) if el else None

def parse_reviews(html):
    soup = BeautifulSoup(html, "html.parser")
    blocks = soup.select('div[data-hook="review"]')
    reviews = []
    for block in blocks:
        try:
            reviews.append({
                "reviewer_name": text_of(block, "span.a-profile-name"),
                "rating": text_of(block, '[data-hook="review-star-rating"]'),
                "title": text_of(block, '[data-hook="review-title"]'),
                "text": text_of(block, '[data-hook="review-body"]'),
                "date": text_of(block, '[data-hook="review-date"]'),
            })
        except Exception as e:
            print(f"Skipped a review: {e}")
    return reviews

text_of 辅助方法在单条评论区块内查询单个元素,当元素缺失时返回 None,而不是对空对象调用 .get_text() 时抛出异常。这使得当某个字段不存在时,提取过程依然健壮。评分选择器可优雅降级:如果 review-star-rating 在特定布局上没有返回内容,请切换到 review-star-rating-view-point,Amazon 在某些页面上使用该钩子。评分以"4.0 out of 5 stars"这样的字符串形式返回;如果您需要为模型提供纯数字评分,稍后在" out of"处拆分即可。

选择器会漂移

Amazon 经常修改其标记,且工具类名会在没有通知的情况下更改。这就是为什么上述选择器尽可能依赖 data-hook 属性。当每条评论的某个字段都返回 None 时,请在浏览器开发者工具中重新检查线上评论页面并更新选择器。定期维护是任何生产级爬虫的正常工作,不代表出了什么问题。Scraper API 的 amazon-product-reviews 解析器的存在,正是为了让您将这项维护工作外包出去。

步骤 3:处理评论分页

单页只是演示;真实任务需要处理商品所有页面的评论。Amazon 使用 pageNumber 查询参数对评论进行分页,因此您通过递增该参数来翻页,当某页没有返回评论区块时停止。这样可以避免硬编码页数,并能自然处理评论数量极少的商品。

要了解该模式,请比较 Amazon 使用的 URL:

  • 第 1 页 .../product-reviews/B09Z7KGTVW/?reviewerType=all_reviews
  • 第 2 页 .../product-reviews/B09Z7KGTVW/?reviewerType=all_reviews&pageNumber=2
  • 第 3 页 .../product-reviews/B09Z7KGTVW/?reviewerType=all_reviews&pageNumber=3
python
import time

def scrape_all_reviews(base_url, max_pages=10):
    all_reviews = []
    for page in range(1, max_pages + 1):
        page_url = f"{base_url}&pageNumber={page}"
        html = crawl(page_url)
        if not html:
            break
        reviews = parse_reviews(html)
        if not reviews:
            print(f"No reviews on page {page}; stopping.")
            break
        all_reviews.extend(reviews)
        print(f"Page {page}: {len(reviews)} reviews")
        time.sleep(2)
    return all_reviews

max_pages 上限将运行限定在范围内,避免评论数以千计的商品无限循环;当 Amazon 没有更多页面时,空结果中断会提前终止。页面间的 time.sleep(2) 控制请求速率,避免密集轰炸网站(这是最快触发限速的方式)。请根据您的请求量和下文的速率限制调整这两个参数。

步骤 4:组装并存储数据

现在将获取、解析和分页整合为一个可运行的脚本,然后将收集到的评论写入 JSON 和 CSV。JSON 保留管道所需的嵌套结构;CSV 可直接导入电子表格或 pandas DataFrame 进行情感分析。

python
import csv
import json
import time
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

REVIEWS_URL = (
    "https://www.amazon.com/Meta-Quest-Pro-Oculus/product-reviews/"
    "B09Z7KGTVW/?reviewerType=all_reviews"
)

def crawl(page_url):
    options = {"ajax_wait": "true", "page_wait": 3000}
    response = api.get(page_url, options)
    if response["status_code"] == 200:
        return response["body"].decode("latin1")
    print(f"Request failed: {response['status_code']}")
    return None

def text_of(block, selector):
    el = block.select_one(selector)
    return el.get_text(strip=True) if el else None

def parse_reviews(html):
    soup = BeautifulSoup(html, "html.parser")
    blocks = soup.select('div[data-hook="review"]')
    reviews = []
    for block in blocks:
        try:
            reviews.append({
                "reviewer_name": text_of(block, "span.a-profile-name"),
                "rating": text_of(block, '[data-hook="review-star-rating"]'),
                "title": text_of(block, '[data-hook="review-title"]'),
                "text": text_of(block, '[data-hook="review-body"]'),
                "date": text_of(block, '[data-hook="review-date"]'),
            })
        except Exception as e:
            print(f"Skipped a review: {e}")
    return reviews

def scrape_all_reviews(base_url, max_pages=10):
    all_reviews = []
    for page in range(1, max_pages + 1):
        page_url = f"{base_url}&pageNumber={page}"
        html = crawl(page_url)
        if not html:
            break
        reviews = parse_reviews(html)
        if not reviews:
            break
        all_reviews.extend(reviews)
        print(f"Page {page}: {len(reviews)} reviews")
        time.sleep(2)
    return all_reviews

def save(reviews):
    with open("amazon_reviews.json", "w", encoding="utf-8") as f:
        json.dump(reviews, f, indent=2, ensure_ascii=False)
    if reviews:
        with open("amazon_reviews.csv", "w", newline="", encoding="utf-8") as f:
            writer = csv.DictWriter(f, fieldnames=reviews[0].keys())
            writer.writeheader()
            writer.writerows(reviews)
    print(f"Saved {len(reviews)} reviews to JSON and CSV")

def main():
    reviews = scrape_all_reviews(REVIEWS_URL)
    save(reviews)

if __name__ == "__main__":
    main()

python scraper.py 运行脚本。脚本会遍历评论页面,打印每页的数量,并在同一目录下写入 amazon_reviews.jsonamazon_reviews.csv。之后,这些记录可以输入情感模型、评分趋势图表,或与竞争对手商品进行对比分析。在此之前,建议先对数据进行结构化和清洗处理,确保评分和日期字段类型一致。

输出示例

每条记录是一个包含五个字段的扁平对象。JSON 文件如下所示:

json
[
  {
    "reviewer_name": "Grrgoyl",
    "rating": "4.0 out of 5 stars",
    "title": "No regret",
    "text": "My 256 gb Quest 2 is in danger of running out of space, so the Pro was an easy call.",
    "date": "Reviewed in the United States on August 2, 2023"
  },
  {
    "reviewer_name": "Damian",
    "rating": "3.0 out of 5 stars",
    "title": "Excellent comfort, poor display",
    "text": "I purchased this to upgrade from my first gen Rift and the comfort is great, the display less so.",
    "date": "Reviewed in the United States on November 1, 2022"
  }
]

CSV 包含相同的五列:reviewer_nameratingtitletextdate。如果您想完全跳过解析步骤,Crawling API 通过其 amazon-product-reviews 解析器以 JSON 格式返回这些预先解析的字段,还包含评论 ID 和已验证购买标志等额外字段。

保持不被封锁

即使渲染和轮换都已为您处理,Amazon 仍会监控爬虫形态的流量。以下几个习惯能保持运行健康,适用于任何难以爬取的商业目标。

  • 控制请求速率。 页面之间加入延迟,而不是全速爬取。分页循环中的 time.sleep 是下限,不是上限。
  • 依赖轮换。 住宅 IP 池将请求分散到众多真实用户地址,避免单一地址触发速率限制。Crawling API 为您处理这些;如果自建技术栈,这是最值得投入的部分。
  • 关注状态码。 当运行开始返回挑战或错误时,说明当前速率或 IP 层级已不够用。将其视为退让的信号,而非可以忽略的噪音。

更全面的操作指南,请参阅如何在不被封锁的情况下爬取网站。如果您希望了解多家零售商的完整评论情况,通用的如何抓取客户评论指南涵盖了跨站点的模式,而抓取 Amazon 商品数据则很适合与本文配合使用,以同时获取商品详情。

抓取 Amazon 评论合法吗?

抓取 Amazon 评论是否被允许取决于 Amazon 的服务条款、您所在的司法管辖区以及您对数据的使用方式。Amazon 的使用条款限制了自动化访问,因此无论您的工具多么谨慎,爬取行为都可能违反这些条款。本文中的代码不会改变这一事实,只是让技术层面的工作得以实现。请阅读 Amazon 的使用条款和 robots.txt,并将两者作为数据收集的边界。Amazon 还会运行 CAPTCHA 挑战以确认是真人在浏览,这是同一防御体系的组成部分。

以下几条原则值得遵守。仅收集公开的评论文本:评分、标题、正文和日期,这些是任何访客无需账户即可在评论页面上阅读的内容。评论上显示的评论者姓名是公开的,但这是您应该保留的最大限度。不要建立个别评论者的档案,不要追踪个人资料链接来汇总某人跨商品的评论历史,也不要尝试将显示名称与真实身份关联起来。尊重隐私,将每条评论视为关于商品的数据点,而非关于某个人的信息。

本指南刻意将范围限制在公开评论页面,因为这是保持合规的边界。它不涵盖登录后才可见的内容、账户或订单数据,也不涉及任何绕过身份验证的尝试,并且不重新发布受版权保护的评论媒体。若需授权或批量访问,Amazon 为商品和评论数据提供官方 API 和合作伙伴计划,当您需要大量数据、有保障的结构或商业权利时,那才是正确的工具。当您的项目需要的不只是公开评论文本时,官方 API 或数据协议才是正确路径,而非更聪明的爬虫。

回顾

核心要点

  • 直接请求会被封锁。 Amazon 会用机器人检测或 CAPTCHA 回应裸 HTTP 流量,因此您需要在受信任 IP 后面的已渲染页面,Crawling API 一次调用即可实现。
  • 以 data-hook 属性为目标。 每条评论位于 div[data-hook="review"] 区块中,姓名、评分、标题、正文和日期通过比工具类名更耐用的稳定 data-hook 选择器公开。
  • 通过 pageNumber 分页。 递增 &pageNumber= 直到某页没有返回评论区块,加入延迟控制请求速率,并设置页数上限。
  • 导出为 JSON 和 CSV。 JSON 保留管道所需的结构;CSV 可导入电子表格或 pandas 进行情感和趋势分析。
  • 仅限公开评论文本。 尊重 Amazon 的条款和 robots.txt,仅保留公开的评分和文本,切勿为个别评论者建立档案,对于授权或批量数据优先使用官方 API。

常见问题

为何直接请求 Amazon 评论会失败?

Amazon 对自动流量的防御非常严密。来自数据中心 IP 或不像真实浏览器的请求,在到达评论区块之前就会遭遇 CAPTCHA、机器人检测拦截页面或封锁,更何况页面部分内容还通过 JavaScript 渲染。Crawling API 在受信任的住宅 IP 后面渲染页面,确保 BeautifulSoup 解析时评论内容已就位。

我能从 Amazon 评论中提取哪些字段?

本爬虫提取评论者的公开展示姓名、星级评分、评论标题、正文和日期。每个字段在评论区块内都映射到一个 data-hook 属性:review-star-ratingreview-titlereview-bodyreview-date,加上 span.a-profile-name 用于姓名。Scraper API 的 amazon-product-reviews 解析器会返回相同的字段以及评论 ID 和已验证购买标志等额外信息。

如何抓取所有页面的评论?

Amazon 在商品评论 URL 上使用 pageNumber 查询参数进行分页。在循环中递增该参数,用相同代码解析每一页,当某页没有返回评论区块时停止。设置页数上限,并在请求之间加入短暂延迟以控制速率,避免触发限速。

我的选择器返回 None,是什么变了?

几乎可以肯定是 Amazon 的标记发生了变化。其工具类名会在没有通知的情况下更改,这正是上述选择器优先使用 data-hook 属性的原因。如果星级评分返回空值,请尝试 review-star-rating-view-point,Amazon 在某些布局中使用该钩子。在浏览器开发者工具中重新检查线上评论页面并更新选择器;定期维护是任何生产级爬虫的正常工作。

我可以将抓取的评论用于情感分析吗?

可以,这是收集评论最常见的原因之一。导出为 CSV,在 pandas 中加载,然后将正文通过情感模型或评分趋势分析处理。先将评分转换为数值,并将日期解析为真实日期类型,确保字段符合模型输入要求。

存储评论者姓名安全吗?

请将其控制在最低限度。公开评论上的展示姓名是公开的,但这是您应该保留的最大限度,且切勿用它来建立个别评论者的档案或将姓名与真实身份关联起来。将每条评论视为关于商品的数据点,尊重隐私,并在存储任何个人字段之前查阅 Amazon 的条款和当地的数据保护法规。

开始构建

大规模爬取任何站点,无需与基础设施对抗。

Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

自助开通 · 无需销售通话 · 提供企业级爬取量