一个 Amazon 产品列表是围绕它的图片构建的:一张主打的大图、一组不同角度的图集、生活场景照,以及那些把功能和尺寸讲清楚的信息图。那些图片是公开的,而能够批量拉取它们对于目录构建、产品研究、竞品分析,以及把一个数据集喂进图像分类或视觉搜索工作都很有用。

本指南将向你展示如何以可靠的方式用 Python 从一个 Amazon 产品页面下载图片。你会构建一个小巧、可运行的抓取器,它通过 Crawling API 获取一个渲染后的产品页面,提取图片 URL(主图加上图集和缩略图),并把每一张保存到本地文件。整个演练严格限定在公开产品图片上,而临近结尾的合规性章节并非套话,所以在你把它指向任何真实流量之前,请先读一读。

你将构建什么

一个 Python 脚本,它接收一个 Amazon 产品 URL,通过 Crawling API 获取渲染后的页面,收集每一个产品图片 URL,并把每一张下载到一个本地文件夹。我们将用单个产品页面作为贯穿全文的示例,并提取这些产物:

  • 主图 列表顶部显示的主打大图。
  • 图集图片 缩略图条之后的不同角度图和生活场景照。
  • 缩略图 与每张图集图相对应的小预览图。
  • 图片 URL 指向每个图片文件的直接、全分辨率链接。
  • 本地文件 每张图片都保存在一个按产品分类的文件夹下的磁盘上。

为什么对 Amazon 发起普通请求会失败

如果你用一个裸 HTTP 客户端请求一个 Amazon 产品 URL,你很少能得到你来取的那套图集。有两件事在跟你作对。第一,Amazon 在客户端加载图片图集的大部分内容:高分辨率变体以及缩略图到主图的映射,是在初始 HTML 落地之后由 JavaScript 填充的,所以一次原始请求往往只看到一张低分辨率占位图或一套不完整的集合。第二,Amazon 会很快标记出自动化流量。数据中心 IP 以及看起来不像真实浏览器的请求模式,在它们抵达渲染后的列表之前就会被一个 CAPTCHA 挑战或封锁。

因此,一个能用的 Amazon 图片抓取器需要在同一个请求里满足两件事:一个真正渲染页面的浏览器,以及一个被平台读作真实购物者的 IP。你可以自己用无头浏览器加一池轮换住宅代理来拼凑这套方案,但把它们拼到一起并保持健康才是大部分工作。Crawling API 把两者折叠进一次调用:你把带 JavaScript token 的 URL 发给它,它在一个受信任的住宅 IP 之后渲染页面,并返回完成的 HTML 供你解析。

为什么用 JS token

Crawlbase 提供两种 token 类型。普通 token 获取静态 HTML;JavaScript(JS)token 会先在一个真实浏览器里渲染页面。Amazon 在客户端水合高分辨率图集,所以 JS token 给你完整的图片集合。用普通 token 可能返回一个更单薄的页面,其中一些图集变体从未加载。

前置条件

在写任何代码之前,你需要准备好几样东西。它们都花不了多久。

基础 Python。你应当能自如地编写和运行一个 Python 脚本,并用 pip 安装软件包。如果你刚接触这门语言,官方 Python 文档和任何入门课程都能把你带到本教程默认的水平。配套指南如何用 Python 下载图片更深入地讲解了文件写入的机制。

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

一个 Crawlbase 账户和 JS token。注册、打开你的仪表盘,从账户文档页复制你的 JavaScript(JS)token。你的前 1,000 次请求是免费的,并且你只为成功的请求付费。把 token 当作密码对待:它用于认证你的请求,所以不要把它放进版本控制。

搭建项目

创建一个虚拟环境,让项目依赖保持隔离,然后安装抓取器需要的库。

bash
python --version

python -m venv amazon_env
source amazon_env/bin/activate

pip install crawlbase beautifulsoup4 requests

在 Windows 上,请用 amazon_env\Scripts\activate 替代那行 source 命令来激活环境。三个依赖各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML,于是你能按选择器取出图片 URL,而 requests 把每个图片文件下载到磁盘。

理解 Amazon 图片图集

Amazon 在一个产品页面上展示几种类型的图片,在写选择器之前了解它们会有帮助。主图是列表顶部的主打产品照。备选图展示不同的角度或功能。生活场景图展示产品在真实使用中的样子,而信息图把功能、尺寸或规格讲清楚。它们全都位于同一个图集里,并共享同一个图片主机。

在写选择器之前,在你的浏览器里打开一个产品页面,右键点击主图,然后选择检查。你会看到大图位于一个容器里,带着一个稳定的 id,值为 landingImage(也可以通过 #imgTagWrapperId img 触及),而缩略图条渲染成一个带 id altImages 的小图列表。Amazon 通过把尺寸编码进文件名来为每张图片提供一个定尺寸的变体,所以一个缩略图 URL 和它的全分辨率对应版本仅在那个尺寸标记上不同。正是那个细节让你能把一个小预览链接变成一次高分辨率下载。

第 1 步:抓取渲染后的产品页面

先从获取已完成的页面开始。导入 CrawlingAPI 类,用你的 JS token 初始化它,并请求产品 URL。在解析之前检查状态码,能让失败大声暴露而不是悄无声息。

python
from crawlbase import CrawlingAPI

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

def crawl(page_url):
    options = {"ajax_wait": "true", "page_wait": 4000}
    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__":
    product_url = "https://www.amazon.com/dp/B0CHX3QBCH"
    html = crawl(product_url)
    print(html[:500] if html else "No HTML returned")

这两个等待选项对像这样的客户端渲染图集很重要。ajax_wait 告诉 API 等待异步内容完成加载,而 page_wait 在加载后再保持固定毫秒数,好让渲染较晚的图片变体在页面被捕获之前出现。四秒是个合理的起点;如果图集返回得单薄,就把它调高。响应体被解码为 latin1,因为 Amazon 页面混入了一些严格 UTF-8 解码会噎住的字符。运行脚本,你应当看到真实的产品标记,这在你写下一个选择器之前就确认了渲染正常工作。

Crawlbase Amazon Scraper

Amazon 需要在一个受信任的 IP 之后、在一次调用里得到一个渲染后的页面,而它的全分辨率图集甚至要在那之后才会出现在 HTML 里。Crawling API 接收一个 JS token,在一个真实浏览器里运行页面,在服务端轮换住宅 IP,并把已完成的 HTML 交给你,于是你免去了自己运行一支无头舰队和一个代理池的工作。先在免费额度上把它指向一个产品 URL。

第 2 步:提取图片 URL

手握渲染后的 HTML,把它载入 BeautifulSoup 并取出图片链接。从 #landingImage 读取主图,然后从 #altImages 下的缩略图条收集图集。Amazon 把缩略图存为一个小尺寸,所以你通过剥掉 Amazon 编码进文件名的尺寸标记,把每个缩略图 URL 改写成它的全分辨率形式。

python
import re
from bs4 import BeautifulSoup

def full_res(url):
    # Amazon encodes a size token like _SX466_ or _AC_US40_ into the
    # filename; dropping it returns the full-resolution image.
    return re.sub(r"\._[^.]+_\.", ".", url)

def extract_image_urls(html):
    soup = BeautifulSoup(html, "html.parser")
    urls = []

    main = soup.select_one("#landingImage, #imgTagWrapperId img")
    if main and main.get("src"):
        urls.append(full_res(main["src"]))

    for thumb in soup.select("#altImages img"):
        src = thumb.get("src")
        if src and "images/I/" in src:
            urls.append(full_res(src))

    # De-duplicate while preserving order.
    seen = set()
    unique = []
    for u in urls:
        if u not in seen:
            seen.add(u)
            unique.append(u)
    return unique

full_res 辅助函数是关键的一步。Amazon 通过在图片 id 和文件扩展名之间插入一个像 ._SX466_._AC_US40_ 这样的标记,以许多尺寸提供同一张图片;这个正则移除那个标记,于是你保留的是原始的全分辨率文件,而不是一个缩略图。主图来自 #landingImage,以 #imgTagWrapperId img 作为后备,而图集来自 #altImages 下的缩略图。在源里对 images/I/ 做过滤跳过了那些不是产品照的 Amazon sprite 和图标资源,而最后的去重处理丢掉了主图也作为第一张缩略图出现的那种情况。

选择器会漂移

Amazon 的图集标记在不同页面变体之间以及随时间都会改变。#landingImage#altImages 这两个 id 属于较为耐用的钩子,但某个给定列表可能以不同方式渲染它的图集。当列表返回为空时,在你浏览器的开发者工具里重新检查实时产品页面并更新选择器。对任何生产环境的抓取器来说,定期维护选择器都很正常,并不是出了什么问题的迹象。

第 3 步:把每张图片下载到一个本地文件

现在把这些 URL 变成文件。对每个图片 URL,请求字节并把它们写到一个按产品分类的文件夹,从 URL 里的图片 id 推导出文件名。让下载经由 Crawling API 路由,能让它走在与页面抓取相同的受信任 IP 路径上,这避免了一个来自你自己 IP 的独立请求被封锁。

python
import os
from urllib.parse import urlparse

def download_images(urls, folder="amazon_images"):
    os.makedirs(folder, exist_ok=True)
    saved = []
    for i, url in enumerate(urls):
        name = os.path.basename(urlparse(url).path) or f"image_{i}.jpg"
        path = os.path.join(folder, name)
        response = api.get(url)
        if response["status_code"] == 200:
            with open(path, "wb") as f:
                f.write(response["body"])
            saved.append(path)
            print(f"Saved {path}")
        else:
            print(f"Skipped {url}: {response['status_code']}")
    return saved

这个函数用 exist_ok=True 一次性创建输出文件夹,于是重跑不会报错,然后走过这个 URL 列表。文件名来自 URL 的最后一个路径段,对 Amazon 来说就是那个唯一的图片 id,于是两张不同的图集照绝不会在磁盘上撞名。每张图片以二进制模式写入,而一个非 200 的响应会被报告并跳过,而不是让整次运行崩溃。因为请求经由 api.get 返回,图片抓取复用了与页面抓取相同的受信任 IP 路径。

第 4 步:把它拼起来

现在把抓取、提取和下载串成一个可运行的脚本。

python
import os
import re
import json
from urllib.parse import urlparse
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

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

def crawl(page_url):
    options = {"ajax_wait": "true", "page_wait": 4000}
    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 full_res(url):
    return re.sub(r"\._[^.]+_\.", ".", url)

def extract_image_urls(html):
    soup = BeautifulSoup(html, "html.parser")
    urls = []
    main = soup.select_one("#landingImage, #imgTagWrapperId img")
    if main and main.get("src"):
        urls.append(full_res(main["src"]))
    for thumb in soup.select("#altImages img"):
        src = thumb.get("src")
        if src and "images/I/" in src:
            urls.append(full_res(src))
    seen, unique = set(), []
    for u in urls:
        if u not in seen:
            seen.add(u)
            unique.append(u)
    return unique

def download_images(urls, folder="amazon_images"):
    os.makedirs(folder, exist_ok=True)
    saved = []
    for i, url in enumerate(urls):
        name = os.path.basename(urlparse(url).path) or f"image_{i}.jpg"
        path = os.path.join(folder, name)
        response = api.get(url)
        if response["status_code"] == 200:
            with open(path, "wb") as f:
                f.write(response["body"])
            saved.append(path)
            print(f"Saved {path}")
    return saved

def main():
    product_url = "https://www.amazon.com/dp/B0CHX3QBCH"
    html = crawl(product_url)
    if not html:
        return
    urls = extract_image_urls(html)
    print(json.dumps(urls, indent=2))
    download_images(urls)

if __name__ == "__main__":
    main()

把它保存为 amazon_images.py,填入你的 token,并运行 python amazon_images.py。脚本打印出全分辨率图片 URL 的列表,然后把每一张下载到脚本旁边的一个 amazon_images 文件夹里。如果你也在抓取结构化的产品字段(标题、价格、ASIN 等等),配套指南如何抓取 Amazon 产品数据把这同一次抓取扩展成一条完整的记录。

输出长什么样

打印出的列表是解析器找到的图片 URL 集合,按图集顺序排列,而文件夹会被填满,每个 URL 一个文件。

json
[
  "https://m.media-amazon.com/images/I/71xKDtMfaZL.jpg",
  "https://m.media-amazon.com/images/I/61hr19MzN3L.jpg",
  "https://m.media-amazon.com/images/I/71f7tsamQ4L.jpg",
  "https://m.media-amazon.com/images/I/81Qj0nFLanL.jpg"
]

在磁盘上你会得到对应的文件,每个都以它的图片 id 命名:

bash
amazon_images/
  71xKDtMfaZL.jpg
  61hr19MzN3L.jpg
  71f7tamQ4L.jpg
  81Qj0nFLanL.jpg

扩展到许多产品

一个产品只是演示;一项真实的工作会跑遍一份产品列表。因为每一步都是一个普通函数,你通过循环遍历一份产品 URL 列表来扩展规模,并给每一个它自己的输出文件夹,于是文件永不混在一起。用一段短暂的延迟为循环控速,这样你就不会在一个紧凑的周期里猛攻 Amazon,那是被限流的最快方式。

python
import time

def scrape_many(product_urls):
    for url in product_urls:
        asin = url.rstrip("/").split("/")[-1]
        html = crawl(url)
        if not html:
            continue
        urls = extract_image_urls(html)
        download_images(urls, folder=f"amazon_images/{asin}")
        print(f"{asin}: {len(urls)} images")
        time.sleep(2)

每个产品都得到一个以它的 ASIN 命名的文件夹,即一个 /dp/ASIN URL 的最后一个路径段,于是一批产品在磁盘上保持整齐地分开。产品之间的 time.sleep(2) 为运行控速。关于从任意 Amazon URL 推导并校验那个 ASIN,指南如何查找并抓取 Amazon ASIN讲得更深。

保持不被封锁

即便渲染已经被处理好,Amazon 仍会监视带抓取器特征的流量。有几个习惯能让一次运行保持健康,它们适用于任何难啃的商业目标。

  • 为你的请求控速。在产品之间加一段延迟把请求摊开,并避免在一个紧凑的循环上抓取同一个列表。批处理循环里的 time.sleep 是下限,而非上限。
  • 依靠轮换。一池住宅 IP 把请求分散到许多真实用户的地址上,于是没有任何单个地址会触发速率限制。Crawling API 替你处理这一点;如果你自建技术栈,这就是需要做对的部分。
  • 读取状态码。一次开始返回挑战或错误的运行,是在告诉你当前的速率或 IP 层级已经不够了。把它当作退避的信号,而不是可以无视的噪音。

关于更广的套路,参见指南如何抓取网站而不被封锁。同样的先抓取再下载的模式在其他图片密集的站点上也能工作;配套演练从 DeviantArt 抓取图片把它应用到一个非常不同的图集布局上。

下载 Amazon 图片合法吗?

下载 Amazon 图片是否被允许,取决于 Amazon 的服务条款、你所在的司法管辖区,以及你拿这些图片做什么。Amazon 的使用条件限制自动化访问,并对其内容的再利用设有明确的限制,所以无论你的工具多么谨慎,抓取都可能与那些条款相抵触。这里的任何代码都不会改变这一点;它只是让技术部分能跑通。请阅读 Amazon 的使用条件及其 robots.txt,并把两者都当作你采集范围的边界。

最重要的一点是下游用途。Amazon 上的产品图片几乎总是受版权保护的,归品牌、卖家或摄影师所有,既不归你,也不归 Amazon 用以对外授权。为分析而收集公开图片是一回事:把它们喂进图像分类、构建一个内部目录参考,或跨列表比较变体。重新分发或重新发布那些图片、把它们冒充为你自己的,或在你自己的店面里重新使用它们,则是另一回事,并且可能侵犯版权。不要重新分发受版权保护的产品图片,也不要去除或更改水印。如有疑问,请向权利持有者取得许可。

本指南刻意被限定在公开的产品图片上,因为那正是让这项工作站得住脚的边界。它不涉及任何登录墙之后的内容、账户或订单数据、个人信息,或任何绕过认证的尝试。要获得授权或大批量访问,Amazon 提供 Product Advertising API 和卖家计划,当你需要大批量、有保证的结构,或对媒体的商业权利时,那才是正确的工具。如果你的项目需要的不止于供你自己分析的公开图片,那么一个官方 API 或一份与权利持有者的直接协议才是正确的路径,而不是一个更聪明的抓取器。

回顾

核心要点

  • Amazon 在客户端水合它的图集。一次普通抓取往往返回一套单薄的图片集,所以你在解析之前用一个 JS token 渲染页面。
  • 一次调用同时给你渲染和一个受信任的 IP。ajax_waitpage_wait 的 Crawling API 等待图集加载,然后返回已完成的 HTML。
  • 主图和图集有稳定的钩子。读取 #landingImage 取大图、读取 #altImages img 取缩略图,然后剥掉 Amazon 的尺寸标记以拿到全分辨率文件。
  • 经由同一个 API 路径下载。让图片抓取经由 api.get 返回,能让它们留在受信任的 IP 上,并以每个文件唯一的图片 id 命名。
  • 仅把公开图片用于分析。尊重 Amazon 的服务条款和 robots.txt,不要重新分发受版权保护的产品图片,并对授权或大批量访问优先用一个官方 API。

常见问题

为什么一次普通请求返回一套低分辨率或不完整的图集?

因为 Amazon 在初始 HTML 加载之后用 JavaScript 填充高分辨率图片变体和缩略图映射。一次原始 HTTP 请求在那些脚本运行之前就看到了页面,所以它往往得到一张占位图或一套不完整的集合。先渲染页面,也就是 Crawling API 的 JS token 所做的,能在你解析之前给你完整的图集。

我如何拿到全分辨率图片而不是缩略图?

Amazon 通过在图片 id 和扩展名之间用一个像 ._SX466_._AC_US40_ 这样的标记,把请求的尺寸编码进文件名。用一个小正则移除那个标记,会返回原始的全分辨率文件。本指南里的 full_res 辅助函数正是对主图和每张缩略图都做了这件事。

哪些选择器持有 Amazon 产品图片?

主图位于 #landingImage(也可以通过 #imgTagWrapperId img 触及),而备选图、生活场景图和信息图来自 #altImages 下的缩略图条。在源里对 images/I/ 做过滤跳过了 sprite 和图标。这些 id 耐用但并非永久,所以如果列表返回为空,就重新检查实时页面。

我能抓取 Amazon 图片而不被封锁吗?

把你的请求速率保持得低,在产品之间加一段延迟,并通过轮换住宅 IP 路由,这样就没有任何单个地址会触发速率限制。Crawling API 替你管理轮换和一个受信任的 IP 池;如果你自建技术栈,那就是需要投入的部分。盯着状态码,在你开始看到挑战时退避。

从 Amazon 下载图片合法吗?

为你自己的分析而收集公开产品图片通常风险较低,但图片本身通常由品牌、卖家或摄影师持有版权。重新分发、重新发布或商业性地重新使用它们可能侵犯那份版权,而自动化访问无论如何都可能与 Amazon 的条款相抵触。请阅读 Amazon 的使用条件和 robots.txt,不要重新分发受版权保护的图片,并对授权访问优先用官方的 Product Advertising API。

我能一次为一整份产品列表下载图片吗?

能。因为每一步都是一个普通函数,你循环遍历一份产品 URL 列表,给每一个它自己的、以它的 ASIN 命名的文件夹,于是文件不会混在一起,并在产品之间加一段短暂的延迟来为运行控速。本指南里的 scrape_many 辅助函数是那个批处理作业的一个可用起点。

开始构建

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

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

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