在任何拥有多个卖家的 Amazon 商品页面上,只有一个报价能赢得"加入购物车"和"立即购买"按钮旁边的核心位置。这个位置就是 Buy Box,它承载了商品绝大部分的销量。研究表明,大约 90% 的 Amazon 购买行为是通过 Buy Box 完成的,而非页面更下方的"其他卖家"列表。对于在共同商品列表上竞争的卖家而言,赢得或失去 Buy Box 是订单源源不断与几乎无声无息之间的差距。
本指南将向你展示如何用 Python 抓取 Amazon Buy Box 数据,以便随时间追踪获胜报价。你将构建一个可运行的小型爬虫,通过 Crawling API 获取渲染后的商品页面,用 BeautifulSoup 解析,并提取定义当前 Buy Box 的字段:获胜价格、卖家、订单履行方,以及库存状态。整个流程仅涉及公开商品页面数据,文末的合法性章节在你正式投入使用前值得认真阅读。
你将构建什么
一个 Python 脚本,接受 Amazon 商品 URL,通过 Crawling API 获取渲染后的页面,并提取描述当前 Buy Box 获胜者的结构化记录。我们以一款摩托罗拉手机商品列表为运行示例,提取以下字段:
- 商品标题Buy Box 中展示的商品名称。
- 价格获胜报价的当前价格,随卖家竞争而波动。
- 卖家名称当前持有 Buy Box 的主体,可能是 Amazon 本身或第三方卖家。
- 发货方名称履行订单的主体,告知你是 FBA(亚马逊配送)还是卖家自发货。
- 库存状态报价旁显示的在库状态。
- 立即购买/加入购物车购买按钮是否存在,确认这是一个有效的 Buy Box 而非不可用商品。
什么是 Amazon Buy Box,为什么卖家要追踪它
Buy Box 是商品页面右侧的报价框,包含价格、卖家和配送详情、库存状态,以及"加入购物车"和"立即购买"按钮。当多个商家在同一商品列表中销售时,Amazon 会将其中一个报价展示在那里。大多数购物者会直接点击"加入购物车",而不会滚动到"Amazon 上的其他卖家"或"与类似商品比较",因此被推荐的报价会获得这笔销售。Amazon 现在正式将其称为"精选报价",但大多数卖家仍说 Buy Box。
哪个报价获胜由一个算法决定,该算法综合考量价格、配送速度和成本、卖家绩效指标、配送方式和库存,且持续评估。竞争对手将你的价格压低几分钱,或切换到更快的配送方式,都可能在一小时内从你手中夺走 Buy Box。由于位置实时变化,卖家会以编程方式而非手动监控:
- 实时监控。Buy Box 持续变化。按计划对商品列表进行采样的爬虫,能告诉你谁当前持有它,以及它翻转的频率,这是无法跨目录手动追踪的。
- 价格情报。价格是决策中最重要的因素之一,因此了解当前获胜价格可以让你调整自己的价格来竞争。更广泛的规律请参阅我们的价格情报网络爬取指南。
- 竞争对手分析。追踪哪些卖家获胜、以什么价格获胜以及采用什么配送方式,可以让你了解在特定商品列表上竞争所需的条件。
- 规模化策略。跨数百个商品列表时,自动化收集是监控每件商品并快速响应的唯一可行方式。
为什么直接请求 Amazon 会失败
如果你用裸 HTTP 客户端请求 Amazon 商品 URL,你会得到状态 200 的响应,但页面中几乎没有 Buy Box 数据。两个因素对你不利。首先,Amazon 通过 JavaScript 和 AJAX 动态加载大部分报价块,包括卖家、配送和库存详情,这些内容在初始 HTML 到达后才会渲染。原始请求捕获的是这些部分渲染之前的框架。其次,Amazon 会迅速标记自动化流量:来自数据中心 IP 和非浏览器请求模式的请求,会在到达渲染后的报价之前就遭遇 CAPTCHA、速率限制或直接封锁。
因此,一个可用的 Amazon 爬虫每次请求需要同时具备两样东西:一个真实渲染页面的浏览器,以及一个平台视为普通购物者的 IP。你可以自己组建无头浏览器加上轮换住宅代理池,但将这些整合在一起并保持健康才是主要工作量所在。Crawling API 将两者折叠进单次调用:你发送带有 JavaScript 令牌的 URL,它在受信任的住宅 IP 后面渲染页面,并返回完整 HTML 供你解析。
Crawlbase 提供两种令牌类型。普通令牌获取静态 HTML;JavaScript(JS)令牌首先在真实浏览器中渲染页面。Amazon 在客户端加载其报价块,因此这里需要 JS 令牌。普通令牌返回的是与普通请求相同的不完整框架,Buy Box 字段不在其中,无法解析。
前提条件
开始编写代码之前,你需要准备好以下几样东西。每项都不需要太长时间。
Python 基础。你应该能够编写并运行 Python 脚本,以及用 pip 安装包。如果你刚接触这门语言,我们的Python 爬取入门和官方文档将帮助你达到本教程所需的水平。
Python 3.8 或更高版本。用 python --version 确认你的版本。如果没有,请从 python.org 安装,或通过 Anaconda 等发行版安装。
Crawlbase 账号和 JS 令牌。注册账号,打开 Dashboard,复制你的 JavaScript(JS)令牌。Crawlbase 提供 1,000 次免费 Crawling API 请求,无需绑定信用卡。像对待密码一样对待令牌:它对你的请求进行身份验证,不要将其提交到版本控制系统。
配置项目
创建虚拟环境以隔离项目依赖,然后安装爬虫所需的三个库。
python --version python -m venv buybox_env source buybox_env/bin/activate pip install crawlbase beautifulsoup4 pandas
在 Windows 上,请用 buybox_env\Scripts\activate 替换 source 这一行来激活虚拟环境。三个依赖各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML 以便通过 CSS 选择器提取各字段,pandas 最终将记录写入 CSV,方便你随时间追加样本。
步骤 1:获取渲染后的商品页面
先获取完整的页面。导入 CrawlingAPI 类,用你的 JS 令牌初始化它,并请求商品 URL。两个等待选项在这里很重要,因为报价块渲染较晚。在解析之前先检查状态码,可以让失败清晰可见而非悄无声息。
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def crawl(product_url): options = {"page_wait": 2000, "ajax_wait": "true"} response = api.get(product_url, options) if response["status_code"] == 200: return response["body"].decode("latin1") print(f"Request failed: {response['status_code']}") return None if __name__ == "__main__": product_url = "https://www.amazon.com/Motorola-Stylus-Battery-Unlocked-Emerald/dp/B0BFYRV4CD" html = crawl(product_url) print(html[:500] if html else "No HTML returned")
两个等待选项是让像这样的动态目标正常工作的关键。ajax_wait 告诉 API 等待异步内容加载完成,page_wait 在页面加载后再等待固定毫秒数,确保延迟渲染的报价块在页面被捕获前出现。响应体以 latin1 解码,因为 Amazon 页面混入了严格 UTF-8 解码可能无法处理的字符。运行后你应该看到真实的商品标记,而非普通请求返回的不完整框架。这在你编写任何选择器之前确认了渲染可以正常工作。
报价块之所以能填充,是因为页面在受信任的 IP 后面完成了渲染。Crawling API 接受 JS 令牌,在真实浏览器中运行页面,在服务端轮换住宅 IP,并将完整 HTML 交给你,这样你无需自己运行无头浏览器集群和代理池。先用免费套餐在一个商品 URL 上测试。
步骤 2:检查页面并映射选择器
编写解析器之前,在浏览器中打开一个商品页面,右键点击报价块并选择"检查"。每个 Buy Box 字段都存在于一个可以通过 CSS 选择器定位的稳定元素中。将光标悬停在开发工具面板中的元素上,查看它们覆盖页面的哪个部分,然后复制选择器。以下是 Buy Box 字段的可靠选择器,来自实际 Amazon 商品页面:
-
商品标题
#productTitle -
价格
.a-price .a-offscreen(价格字符串的无障碍副本) -
库存状态
#availability span -
发货方名称
#fulfillerInfoFeature_feature_div span.offer-display-feature-text-message -
卖家名称
#merchantInfoFeature_feature_div span.offer-display-feature-text-message -
立即购买按钮
span#submit.buy-now以及加入购物车按钮span#submit.add-to-cart
发货方和卖家选择器是告诉你报价如何获胜的两个关键字段。当两者都显示"Amazon.com"时,订单由 Amazon 销售并发货。当卖家是第三方但发货方是 Amazon 时,这是 FBA 报价(亚马逊配送)。当第三方卖家也负责发货时,这是卖家自发货。随时间追踪这两个字段,不仅能显示商品的价格竞争,还能揭示获胜方背后的配送策略。
步骤 3:用 BeautifulSoup 解析 Buy Box 数据
获得渲染后的 HTML 后,将其加载到 BeautifulSoup 并通过选择器提取各字段。两个小辅助函数保持提取代码整洁:一个在元素缺失时返回默认值,另一个报告按钮元素是否存在。注意含点的 ID(submit.buy-now 和 submit.add-to-cart)在 CSS 选择器中需要转义为 submit\.buy-now,因为裸点会被读作类选择器。
from bs4 import BeautifulSoup def scrape_buy_box(html_content): soup = BeautifulSoup(html_content, "html.parser") buy_box = {} def text_or_default(selector, default="Not found"): el = soup.select_one(selector) return el.text.strip() if el else default def is_present(selector): return "Present" if soup.select_one(selector) else "Not Present" buy_box["Buy Now Button"] = is_present("span#submit\\.buy-now") buy_box["Add to Cart Button"] = is_present("span#submit\\.add-to-cart") buy_box["Availability"] = text_or_default("#availability span") buy_box["Product Title"] = text_or_default("#productTitle") buy_box["Price"] = text_or_default(".a-price .a-offscreen") buy_box["Shipper Name"] = text_or_default( "#fulfillerInfoFeature_feature_div span.offer-display-feature-text-message" ) buy_box["Seller Name"] = text_or_default( "#merchantInfoFeature_feature_div span.offer-display-feature-text-message" ) return buy_box
每次查询都通过 text_or_default,因此缺失的元素返回 "Not found",而不是在对空值调用 .text 时抛出异常。这种弹性在这里很重要:当商品列表没有当前 Buy Box 或缺货时,这些字段中有几个会缺失,你希望得到一条说明情况的干净记录,而不是程序崩溃。is_present 辅助函数将购买按钮的存在转换为一个简单标志,这是你判断报价当前是否可购买的最快信号。
步骤 4:组装完整脚本
现在将获取、解析和存储整合进一个可运行的脚本。它抓取商品页面,提取 Buy Box 记录,以 JSON 格式打印,并将其追加到 CSV 中,方便你随时间建立样本历史。
import json import os from datetime import datetime, timezone from crawlbase import CrawlingAPI from bs4 import BeautifulSoup import pandas as pd api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def crawl(product_url): options = {"page_wait": 2000, "ajax_wait": "true"} response = api.get(product_url, options) if response["status_code"] == 200: return response["body"].decode("latin1") print(f"Request failed: {response['status_code']}") return None def scrape_buy_box(html_content): soup = BeautifulSoup(html_content, "html.parser") buy_box = {} def text_or_default(selector, default="Not found"): el = soup.select_one(selector) return el.text.strip() if el else default def is_present(selector): return "Present" if soup.select_one(selector) else "Not Present" buy_box["Buy Now Button"] = is_present("span#submit\\.buy-now") buy_box["Add to Cart Button"] = is_present("span#submit\\.add-to-cart") buy_box["Availability"] = text_or_default("#availability span") buy_box["Product Title"] = text_or_default("#productTitle") buy_box["Price"] = text_or_default(".a-price .a-offscreen") buy_box["Shipper Name"] = text_or_default( "#fulfillerInfoFeature_feature_div span.offer-display-feature-text-message" ) buy_box["Seller Name"] = text_or_default( "#merchantInfoFeature_feature_div span.offer-display-feature-text-message" ) return buy_box def save_to_csv(record, path="buy_box_history.csv"): record["Captured At"] = datetime.now(timezone.utc).isoformat() df = pd.DataFrame([record]) write_header = not os.path.exists(path) df.to_csv(path, mode="a", header=write_header, index=False) def main(): product_url = "https://www.amazon.com/Motorola-Stylus-Battery-Unlocked-Emerald/dp/B0BFYRV4CD" html = crawl(product_url) if not html: return buy_box = scrape_buy_box(html) print(json.dumps(buy_box, indent=2)) save_to_csv(buy_box) if __name__ == "__main__": main()
脚本抓取页面,解析 Buy Box,并将每次运行的带时间戳行追加到 buy_box_history.csv。Captured At 字段是将单次快照转化为追踪的关键:按计划运行(通过 cron、任务调度器,或异步 Crawler 回调),每行记录某个已知时刻 Buy Box 的持有者、价格和配送方式。比较数天或数周内的这些行,可以看出获胜者翻转的频率以及持有该位置所需的价格。
输出结果示例
用 python scraper.py 运行完整脚本,你将得到当前 Buy Box 的结构化记录,以 JSON 形式打印并追加到 CSV 中。
{ "Buy Now Button": "Present", "Add to Cart Button": "Present", "Availability": "In Stock", "Product Title": "Motorola Moto G Stylus 5G | 2021 | 2-Day Battery | Unlocked | Made for US 4/128GB | 48MP Camera | Cosmic Emerald", "Price": "$149.99", "Shipper Name": "Amazon.com", "Seller Name": "Amazon.com", "Captured At": "2024-01-15T09:30:00+00:00" }
在这个示例中,卖家和发货方都显示"Amazon.com",因此 Amazon 本身持有 Buy Box 并履行订单。对于第三方卖家获胜的商品,你会在 Seller Name 中看到商家名称,在 Shipper Name 中看到"Amazon.com"(FBA 报价)或相同的商家(卖家自发货)。比较每次捕获中的这两个字段,就能了解每次 Buy Box 获胜背后的配送故事。
扩展至商品目录
单个 URL 只是演示;真实的追踪器需要监控许多商品。由于每次 Buy Box 捕获都是一次独立的页面请求,你可以通过循环遍历商品 URL 列表并控制请求节奏来实现扩展,避免频繁轰炸 Amazon。
import time def track_listings(product_urls): for url in product_urls: html = crawl(url) if not html: continue buy_box = scrape_buy_box(html) buy_box["Product URL"] = url save_to_csv(buy_box) print(f"{buy_box['Seller Name']} @ {buy_box['Price']} -> {url}") time.sleep(2)
请求之间的 time.sleep(2) 控制运行节奏,避免向同一目标发起连续请求,这是被限流的最快方式。对于更大规模的目录,异步 Crawler 让你推送多个 URL 并通过回调接收结果,而非逐个阻塞等待。如果你只需要少数几个字段且希望完全跳过编写选择器,Crawling API 可以自动将 Amazon 商品页面解析为结构化 JSON。
保持不被封锁
即使有渲染处理,Amazon 仍然会监控爬虫形态的流量。一些习惯可以保持运行健康,这些习惯适用于任何难以爬取的商业目标。
-
控制请求节奏。在商品列表之间设置延迟来分散请求,而不是全速爬取。循环中的
time.sleep是最低限度,而非上限。 - 善用轮换。住宅 IP 池将请求分散到许多真实用户地址上,使单个地址不会触发速率限制。Crawling API 为你处理这些;如果你构建自己的方案,这是需要重点做好的部分。
- 关注状态码。当运行开始返回检测或非 200 状态码时,这意味着当前速率或 IP 级别已不够用。将其视为降速的信号,而非可忽略的噪声。
更广泛的策略请参阅如何在不被封锁的情况下抓取网站。如果你想抓取完整商品列表而不仅仅是 Buy Box,相关指南抓取 Amazon 商品数据涵盖了页面的其他部分;我们的 Amazon 畅销榜教程展示了如何收集商品列表以供此类追踪器使用。
抓取 Amazon 合法吗?
是否允许抓取 Amazon 取决于 Amazon 的服务条款、你所在的司法管辖区以及数据的用途。Amazon 的使用条件限制自动化访问和数据收集,因此无论你的工具多么谨慎,爬取行为都可能与这些条款相悖。此处的任何代码都不会改变这一点;它只是让技术层面的工作得以实现。阅读 Amazon 的使用条件及其 robots.txt,并将两者视为你收集数据的边界。
有几条原则值得坚守。只收集公开数据:任何人无需账号即可在商品页面上看到的商品标题、报价价格、卖家和配送标签,以及库存状态。将请求量控制在不给 Amazon 服务器造成压力的范围内,并按上述方式控制运行节奏。避免个人数据,包括任何与可识别购物者相关的信息,以及超出公开评论文本之外的评论者信息。不要将受版权保护的媒体(如商品图片或描述)作为你自己的内容重新分发。本指南有意限定在公开报价块范围内,因此不涉及任何需要登录的内容:账号数据、订单历史、卖家中心后台,或支付和结账流程都不在范围之内,爬虫不应绕过身份验证来访问这些内容。
如果你是 Amazon 卖家,获取自己 Buy Box 和定价数据的官方渠道是 Amazon 官方的 Selling Partner API,它提供结构化、有授权的访问,无需爬取。对于大规模竞争监控或任何涉及商业再分发的用途,当你需要有保证的结构、数量或权利时,官方 API 或数据协议才是正确工具。将爬取用于它擅长的场景:对你可以在浏览器中自己打开的页面进行轻量、公开、只读的采样。
核心要点
- Buy Box 是赢得销售的报价。约 90% 的 Amazon 购买通过它完成,因此卖家需要追踪谁持有它、以什么价格、采用何种配送方式。
- Amazon 在客户端渲染报价块。普通请求返回不完整的框架,因此在卖家、发货方和库存字段可供解析之前,必须先用 JS 令牌渲染页面。
- 卖家加发货方揭示配送故事。两者都显示"Amazon.com"意味着亚马逊配送;第三方卖家但发货方是 Amazon 是 FBA;同一商家在两个字段中出现是卖家自发货。
-
时间戳将快照转化为追踪。按计划每次运行追加一行带
Captured At的记录,让你能够对比 Buy Box 获胜者和价格随时间的变化。 - 坚守公开数据范围。遵守 Amazon 的条款和 robots.txt,对于获授权或卖家数据请使用官方 Selling Partner API,切勿触碰账号、订单或任何需要登录的内容。
常见问题
什么是 Amazon Buy Box,为什么它如此重要?
Buy Box 是商品页面上的精选报价,包含价格、卖家和配送详情,以及"加入购物车"和"立即购买"按钮。当多个商家销售同一商品时,Amazon 会在那里推荐其中一个,而大多数购物者会直接从该报价购买,而不会比较其他报价。约 90% 的 Amazon 销售通过 Buy Box 完成,因此赢得它是共享商品列表上的卖家影响订单量的最大杠杆。
为什么普通请求无法返回 Buy Box 数据?
因为 Amazon 通过 JavaScript 和 AJAX 动态加载大部分报价块,包括卖家、发货方和库存字段,这些内容在初始 HTML 到达后才会渲染。原始 HTTP 请求在这些部分渲染之前就捕获了框架,因此你想要的字段是空白的。首先渲染页面(Crawling API 的 JS 令牌就是做这件事的)才能让 Buy Box 数据出现在你解析的 HTML 中。
如何区分 FBA 报价和卖家自发货报价?
比较 Seller Name 和 Shipper Name 字段。如果两者都显示"Amazon.com",则 Amazon 销售并发货。如果卖家是第三方但发货方是"Amazon.com",则该报价是 FBA(亚马逊配送)。如果同一第三方名称出现在两个字段中,则卖家自己履行订单。这两个字段的选择器分别是 #merchantInfoFeature_feature_div 和 #fulfillerInfoFeature_feature_div。
如何随时间追踪 Buy Box 而不只是获取单次快照?
将每次捕获作为带时间戳的行追加到 CSV(如完整脚本中的 Captured At 字段所示),并按计划运行爬虫。Cron、任务调度器或异步 Crawler 回调都可以。一旦你有了历史记录,比较连续行即可看出某个商品列表的获胜卖家、价格或配送方式何时发生了变化。
我需要普通令牌还是 JS 令牌来访问 Amazon?
需要 JS 令牌。普通令牌获取静态 HTML,而在 Amazon 上这与普通请求返回的内容一样,都是不完整的页面,省略了动态加载的报价块。JS 令牌首先在真实浏览器中渲染页面,因此当 BeautifulSoup 解析它们时 Buy Box 字段是存在的。Amazon 大量使用 JavaScript,因此 JS 令牌是其商品页面的正确默认选择。
我的选择器返回"Not found",是什么发生了变化?
通常是两种情况之一。要么 Amazon 更新了其标记,此时你需要在浏览器开发工具中重新检查实时商品页面并更新选择器;要么报价块没有完成渲染,此时需要提高 page_wait 值,让 API 在捕获 HTML 之前等待更长时间。定期维护选择器对于任何生产爬虫都是正常的,并不意味着出了什么问题。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
