在 Amazon 上搜索任何热门关键词,你首先看到的结果往往不是最自然的那些。它们是赞助商品:品牌通过竞价获得搜索结果页顶部位置的按点击付费(PPC)广告,每个都带有小小的"赞助"或"广告"标签。这些广告位是公开的,而哪些商品在哪些关键词上投放广告,直接揭示了竞争对手的策略、广告支出优先级,以及一个品类正在争夺哪些词。

本指南将向你展示如何用 Python 抓取 Amazon PPC 广告数据。你将构建一个可运行的小型爬虫,通过 Crawling API 获取渲染后的 Amazon 搜索结果页,用 BeautifulSoup 从网格中只解析赞助广告位,并为每条广告提取干净的记录:标题、价格、页面位置和商品链接。整个流程仅涉及任何购物者都能看到的公开搜索页面数据,文末的合法性章节不是样板文字,请在正式使用前认真阅读。

你将构建什么

一个 Python 脚本,接受搜索关键词,通过 Crawling API 获取渲染后的 Amazon 结果页,将赞助卡片与自然排名结果分离,并为每条广告提取结构化记录。我们以 headphones 搜索为运行示例,从每张赞助卡片中提取以下字段:

  • 标题广告商品的标题文本。
  • 价格赞助卡片上显示的标价。
  • 位置广告在结果中的顺序位置,让你能区分页面顶部广告和较低位置的广告。
  • 链接广告商品详情页的 URL。
  • 关键词广告出现时的搜索词,贯穿整个流程以便按查询分组结果。

为什么直接请求 Amazon 会失败

如果你用裸 HTTP 客户端请求 Amazon 搜索 URL,你会得到状态 200 的响应,但响应体中几乎没有商品数据。两个因素对你不利。首先,Amazon 动态加载搜索网格:初始 HTML 大部分是框架,重要内容只有在页面在浏览器中运行并执行 JavaScript 和 Ajax 后才会填充。将原始请求保存到文件,你会发现赞助卡片根本就不在里面。其次,Amazon 会迅速标记自动化流量。来自数据中心 IP 和不像真实浏览器的请求模式,会在到达渲染后的商品之前就遭遇 CAPTCHA 或被封锁。

因此,一个可用的 Amazon 广告爬虫每次请求需要同时具备两样东西:一个真实渲染页面的浏览器,以及一个平台视为真实购物者的 IP。你可以自己组建无头浏览器加上轮换住宅代理池,但将这些整合在一起并保持健康才是主要工作量所在。Crawling API 将两者折叠进单次调用:你发送带有 JavaScript 令牌的 URL,它在受信任的住宅 IP 后面渲染页面,并返回完整 HTML 供你解析。有关客户端渲染网站为何难以直接请求的更多内容,请参阅如何爬取 JavaScript 网站

为什么需要 JS 令牌

Crawlbase 提供两种令牌类型。普通令牌(TCP)获取静态 HTML;JavaScript(JS)令牌首先在真实浏览器中渲染页面。Amazon 大量依赖 JavaScript 来渲染动态内容,因此这里需要 JS 令牌。普通令牌返回与普通请求相同的空框架,其中没有任何可解析的内容。

前提条件

开始编写代码之前,你需要准备好以下几样东西。每项都不需要太长时间。

Python 基础。你应该能够编写并运行 Python 脚本,以及用 pip 安装包。如果你刚接触这门语言,官方 Python 文档和任何入门课程将帮助你达到本教程所需的水平。

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

Crawlbase 账号和 JS 令牌。注册免费账号,打开 Dashboard,从账号文档页面复制你的 JavaScript(JS)令牌。免费套餐包含 1,000 次请求且无需绑定信用卡,足以完成本指南。像对待密码一样对待令牌:它对你的请求进行身份验证,不要将其提交到版本控制系统。

配置项目

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

bash
python --version

python -m venv amazon_env
source amazon_env/bin/activate

pip install crawlbase beautifulsoup4 pandas

在 Windows 上,请用 amazon_env\Scripts\activate 替换 source 这一行来激活虚拟环境。三个依赖各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML 以便通过 CSS 选择器提取各字段,pandas 在最后的关键词分析中帮助你整理抓取的广告。

了解 Amazon 赞助广告位

Amazon 搜索结果页是一个商品卡片网格。大多数是按相关性排名的自然结果,但其中穿插着赞助商品:PPC 广告。外观上它们与其他卡片相同,有标题、价格、图片和"赞助"标签。结构上,Amazon 用特殊标记包裹赞助卡片,让你能与自然结果区分开来,这正是让你只爬取广告成为可能的关键。

在编写选择器之前,在浏览器中打开搜索页面,右键点击赞助卡片并选择"检查"。赞助卡片位于带有 AdHolder 类的容器内,旁边还有常规的 data-asin 结果属性。标题位于卡片标题内的锚点中,价格通过 Amazon 的 a-price 标记暴露。这些就是你要定位的钩子。Amazon 的类名会随时间变化,但 AdHolder 标记和 a-price 结构一直较为稳定,因此请依赖它们。

步骤 1:获取渲染后的搜索页面

先获取完整的页面。导入 CrawlingAPI 类,用你的 JS 令牌初始化它,根据关键词构建搜索 URL,并使用 Amazon 动态网格所需的等待选项发起请求。在解析之前先检查状态码,可以让失败清晰可见而非悄无声息。

python
from crawlbase import CrawlingAPI

# Amazon is JavaScript-rendered, so use your JS token here
api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

def crawl(page_url):
    options = {"page_wait": 2000, "ajax_wait": "true"}
    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__":
    keyword = "headphones"
    search_url = f"https://www.amazon.com/s?k={keyword}"
    html = crawl(search_url)
    print(html[:500] if html else "No HTML returned")

两个等待选项对于这样的动态目标至关重要。ajax_wait 告诉 API 等待异步内容加载完成,page_wait 在页面加载后再等待固定毫秒数,确保延迟渲染的网格在页面被捕获前出现。两秒是一个合理的起点;如果卡片返回时缺失,可以适当调高。响应体以 latin1 解码,因为 Amazon 页面混入了严格 UTF-8 解码可能无法处理的字符,而 latin1 可以无错误地处理它们。运行脚本后,你应该看到真实的商品标记,而非普通请求返回的空框架。这在你编写任何选择器之前确认了渲染可以正常工作。

Crawlbase Amazon Scraper

Amazon 需要在受信任的 IP 后面获取渲染后的页面,一次调用即可完成,这就是上面普通请求返回空框架的原因。Crawling API 接受 JS 令牌,在真实浏览器中运行页面,在服务端轮换住宅 IP,并将完整 HTML 交给你,这样你无需自己运行无头浏览器集群和代理池。先用免费套餐在某个关键词上测试。

步骤 2:用 BeautifulSoup 解析赞助卡片

获得渲染后的 HTML 后,将其加载到 BeautifulSoup 并只选择赞助卡片。这是与普通搜索爬取的关键区别:不是抓取所有结果,而是定位 AdHolder 容器,这样你只收集广告而不是其他内容。然后通过选择器(直接来自实时页面标记)提取各字段。用 try/except 包裹每张卡片,这样一个格式错误的商品不会导致整个运行崩溃。

python
from bs4 import BeautifulSoup

def parse_ads(html, keyword):
    soup = BeautifulSoup(html, "html.parser")

    # Select only sponsored (PPC) cards, not organic results
    ads = soup.select(
        '.AdHolder div[data-asin], '
        'div[data-asin][data-component-type="s-search-result"].AdHolder'
    )

    results = []
    for position, ad in enumerate(ads, start=1):
        try:
            # Price inside the ad card
            price_el = ad.select_one("span.a-price span.a-offscreen")
            price = price_el.text.strip() if price_el else "Price not found"

            # Title inside the ad card
            title_el = ad.select_one(
                "div.a-section h2 a.a-link-normal span, "
                "div.a-section a.a-link-normal span.a-offscreen"
            )
            title = title_el.text.strip() if title_el else "Title not found"

            # Link to the advertised product page
            link_el = ad.select_one("h2 a.a-link-normal, a.a-link-normal")
            link = None
            if link_el and link_el.get("href"):
                link = "https://www.amazon.com" + link_el["href"]

            results.append({
                "keyword": keyword,
                "position": position,
                "title": title,
                "price": price,
                "link": link,
            })
        except Exception as e:
            print(f"Skipped a card: {e}")
    return results

隔离广告的选择器是本爬虫的核心。.AdHolder div[data-asin] 匹配赞助卡片而不影响自然结果,因此 parse_ads 只处理 PPC 广告位。价格来自 Amazon a-price 块内隐藏的 span.a-offscreen,其中包含干净的数值,标题从卡片的标题锚点读取,并有备用选择器应对不同布局。enumerate(..., start=1) 记录每条广告在结果中的位置,这将扁平列表转化为广告位智能:位置 1 是页面顶部竞价,编号越低竞争越激烈。每个缺失字段会退化为哨兵字符串或 None,而不会导致循环崩溃。如果你想了解选择器语法本身,如何在 Python 中使用 BeautifulSoup 的指南深入介绍了这些内容。

选择器会漂移

Amazon 的类名和卡片结构会无预告地更改。上面的 AdHolder 标记、a-price 价格块和标题锚点是起始模板,而非不变的规范。当每条广告的标题或价格都返回"not found"哨兵值时,在浏览器开发工具中重新检查实时赞助卡片,并更新选择器。定期维护选择器对于任何生产爬虫都是正常的,并不意味着出了什么问题。

步骤 3:整合代码并分析关键词

现在将获取和解析整合进一个可运行的脚本,然后加入核心价值:一个小型分析,将原始广告列表转化为竞争情报。获取渲染后的搜索页面,隔离赞助卡片,将记录加载到 pandas DataFrame,这样你就能看到哪些商品在该关键词上投放广告以及它们的价格区间。

python
import json
import pandas as pd
from bs4 import BeautifulSoup
from crawlbase import CrawlingAPI

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

def crawl(page_url):
    options = {"page_wait": 2000, "ajax_wait": "true"}
    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 parse_ads(html, keyword):
    soup = BeautifulSoup(html, "html.parser")
    ads = soup.select(
        '.AdHolder div[data-asin], '
        'div[data-asin][data-component-type="s-search-result"].AdHolder'
    )
    results = []
    for position, ad in enumerate(ads, start=1):
        try:
            price_el = ad.select_one("span.a-price span.a-offscreen")
            price = price_el.text.strip() if price_el else "Price not found"
            title_el = ad.select_one(
                "div.a-section h2 a.a-link-normal span, "
                "div.a-section a.a-link-normal span.a-offscreen"
            )
            title = title_el.text.strip() if title_el else "Title not found"
            link_el = ad.select_one("h2 a.a-link-normal, a.a-link-normal")
            link = None
            if link_el and link_el.get("href"):
                link = "https://www.amazon.com" + link_el["href"]
            results.append({
                "keyword": keyword,
                "position": position,
                "title": title,
                "price": price,
                "link": link,
            })
        except Exception as e:
            print(f"Skipped a card: {e}")
    return results

def analyze(ads):
    if not ads:
        print("No sponsored ads found for this keyword.")
        return
    df = pd.DataFrame(ads)
    print(f"Sponsored placements found: {len(df)}")
    print("\nTop-of-page advertisers:")
    print(df[["position", "title", "price"]].head())
    return df

def main():
    keyword = "headphones"
    search_url = f"https://www.amazon.com/s?k={keyword}"
    html = crawl(search_url)
    if not html:
        return
    ads = parse_ads(html, keyword)
    print(json.dumps(ads, indent=2))
    df = analyze(ads)
    if df is not None:
        df.to_csv("amazon_ppc_ads.csv", index=False)

if __name__ == "__main__":
    main()

analyze 步骤使这成为一个广告情报工具,而不仅仅是原始数据转储。将记录加载到 DataFrame 让你能够按位置排序以查看谁赢得了顶部竞价,按价格分组以了解竞争价格区间,并将所有内容写入 amazon_ppc_ads.csv 以便随时间追踪。按计划对同一关键词运行,差异数据会告诉你何时有新竞争对手进入竞价,或现有竞争对手改变了竞价优先级。如果你想将这些价格纳入更广泛的模型,使用网络爬取进行价格情报的指南展示了此类数据的下游用途。

输出结果示例

python scraper.py 运行完整脚本,你将得到一份干净的赞助记录列表(每条广告一条)、CSV 文件和简短摘要。JSON 格式如下:

json
[
  {
    "keyword": "headphones",
    "position": 1,
    "title": "Wireless Bluetooth Headphones, Over-Ear, 40H Playtime",
    "price": "$39.99",
    "link": "https://www.amazon.com/dp/..."
  },
  {
    "keyword": "headphones",
    "position": 2,
    "title": "Noise Cancelling Headphones, Wired and Wireless",
    "price": "$59.95",
    "link": "https://www.amazon.com/dp/..."
  }
]

每条记录包含关键词、页面位置、广告标题和价格,以及商品页面链接。这足以回答核心竞争情报问题:对于你关心的任何关键词,哪些商品正在花钱占据顶部位置,它们在哪个价格区间竞争。

跨关键词和跨页面扩展

单个关键词在单页上只是演示。真正的价值来自按计划对一批关键词运行并追踪广告主集合如何变化。循环遍历你的目标词,为每个词抓取赞助卡片,并将结果合并,这样单个 CSV 就包含了完整的竞争态势。

python
import time

def scrape_keywords(keywords):
    all_ads = []
    for keyword in keywords:
        url = f"https://www.amazon.com/s?k={keyword}"
        html = crawl(url)
        if not html:
            continue
        ads = parse_ads(html, keyword)
        all_ads.extend(ads)
        print(f"{keyword}: {len(ads)} sponsored placements")
        time.sleep(2)
    return all_ads

keywords = ["headphones", "wireless earbuds", "bluetooth speaker"]
ads = scrape_keywords(keywords)

关键词之间的 time.sleep(2) 控制运行节奏,避免在紧密循环中频繁轰炸搜索,这是被限流的最快方式。对于单个关键词的多页结果,Amazon 搜索使用 &page= 参数分页,因此你可以递增它,并在某页不再返回赞助卡片时停止。页数上限保持保守:最有价值的广告位本来就在第一两页上。

保持不被封锁

即使有渲染处理,Amazon 仍然会监控爬虫形态的流量。一些习惯可以保持运行健康,这些习惯适用于任何难以爬取的商业目标。

  • 控制请求节奏。在关键词和页面之间设置延迟来分散请求,而不是全速爬取。循环中的 time.sleep 是最低限度,而非上限。
  • 善用轮换。住宅 IP 池将请求分散到许多真实用户地址上,使单个地址不会触发速率限制。Crawling API 为你处理这些;如果你构建自己的方案,这是需要重点做好的部分。
  • 关注状态码。当运行开始返回检测或错误时,这意味着当前速率或 IP 级别已不够用。将其视为降速的信号,而非可忽略的噪声。

有关在防御性商业网站上保持爬虫健康运行的更广泛策略,请参阅如何在不被封锁的情况下抓取网站

抓取 Amazon 广告数据合法吗?

是否允许抓取 Amazon 取决于 Amazon 的使用条件、你所在的司法管辖区以及数据的用途。Amazon 的条款限制自动化访问,因此无论你的工具多么谨慎,爬取行为都可能与这些条款相悖。此处的任何代码都不会改变这一点;它只是让技术层面的工作得以实现。阅读 Amazon 的使用条件及其 robots.txt,并将两者视为你收集数据的边界。

有几条原则值得坚守。本爬虫提取的赞助标题、价格、位置和商品链接是公开数据:运行相同搜索的任何人都会看到相同的广告,无需账号。坚守这个公开的广告界面。遵守 Amazon 声明的速率预期,将请求量控制在不给其服务器造成压力的范围内。避免个人数据,包括任何与可识别购物者、评论者或结果页面上公开列出的卖家之外的信息相关的内容,不要将受版权保护的商品图片或描述批量重新分发。将广告数据用于自己的竞争分析,与重新发布 Amazon 的内容是截然不同的事情。

本指南有意将范围限定为搜索页面上的公开赞助广告位,因为这是使工作合理可辩护的边界。本指南不涉及任何需要登录的内容、卖家中心或广告系列管理数据、其他广告主的私人账号指标、支付流程,或任何绕过身份验证的尝试。你在这里收集的页面上"赞助"数据是竞争对手的公开广告足迹,而非其内部广告系列数据。如需获得授权或批量访问,Amazon 提供了官方广告和商品 API,当你需要大量数据、有保证的结构或商业权利时,这才是正确的工具。如果你的项目需要的数据超出公开广告位的范围,官方 API 或数据协议才是正确路径,而非更聪明的爬虫。

回顾

核心要点

  • 赞助广告位是公开的竞争情报。哪些商品在某关键词上投放广告、处于什么位置和价格区间,对任何购物者都是可见的,揭示了真实的广告策略。
  • Amazon 搜索是 JavaScript 渲染的。普通请求返回空框架,因此在任何赞助卡片存在可供解析之前,必须先用 JS 令牌渲染页面。
  • AdHolder 选择器隔离广告。定位 .AdHolder div[data-asin] 只收集 PPC 卡片,a-price 和标题锚点选择器从每张卡片中提取标题、价格和链接。
  • 位置将列表转化为广告位数据。enumerate 记录每条广告的顺序,然后加载到 pandas,让你能够随时间追踪各关键词上的顶部竞价和价格区间。
  • 坚守公开数据范围。遵守 Amazon 的使用条件和 robots.txt,对于获授权或批量数据请使用官方 Amazon API,切勿触碰账号、广告系列内部信息或个人数据。

常见问题

什么是 Amazon PPC 广告?

Amazon PPC(按点击付费)广告让卖家和品牌能在 Amazon 搜索结果和商品页面内推广商品。这些赞助商品带有"赞助"或"广告"标签,只有当购物者点击时广告主才付费。由于广告是竞价驱动的,在特定关键词上投放广告的商品集合,是对哪些品牌正在花钱竞争该词的实时反映。

为什么普通请求无法从 Amazon 返回广告?

因为 Amazon 用 JavaScript 和 Ajax 动态加载其搜索网格。初始 HTML 大部分是框架,因此原始 HTTP 请求返回状态 200,但赞助和自然排名卡片都是空白的。要获取真实数据,必须先渲染页面,这正是 Crawling API 的 JS 令牌在 BeautifulSoup 解析之前为你处理的工作。

如何只抓取赞助广告而不抓取自然排名结果?

Amazon 将赞助卡片包裹在带有 AdHolder 类的容器中。选择 .AdHolder div[data-asin] 只匹配这些卡片,因此你的解析器只收集 PPC 广告位而完全跳过自然结果。从每张广告卡片中,你再读取其标题锚点的标题、a-price 块的价格,以及商品链接的 href。

我的标题或价格返回"not found",是什么发生了变化?

几乎可以肯定是 Amazon 的标记发生了变化。其类名和卡片结构会无预告地更改,上面的 a-price 或标题锚点选择器可能已停止匹配。在浏览器开发工具中重新检查实时赞助卡片,确认 AdHolder 包裹器以及内部的价格和标题选择器,并更新它们。定期维护选择器对于任何生产爬虫都是正常的。

我可以用抓取到的 Amazon 广告数据做什么?

随时间追踪竞争对手的广告:哪些商品进入或离开特定关键词的赞助集合、它们处于什么位置,以及它们在哪个价格区间竞争。按计划运行相同关键词,差异数据会揭示新进入者和竞价优先级的变化。位置和价格字段直接输入广告和价格情报仪表板。

我需要普通令牌还是 JS 令牌来访问 Amazon?

需要 JS 令牌。普通令牌获取静态 HTML,而在 Amazon 上这与普通请求返回的相同空框架。JS 令牌在将 HTML 返回之前先在真实浏览器中渲染页面,这样当你的解析器运行时赞助卡片就存在了。配合 page_waitajax_wait 选项使用,确保动态网格有足够时间完成加载。

开始构建

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

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

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