Amazon 是网络上最丰富的公开商业数据来源之一:商品标题、价格、评分、库存状态,以及一整天都在变化的畅销榜排名。抓取一次数据是有用的,但真正的价值要在你按计划持续采集时才会显现,这样你才能追踪价格如何变动、某个商品何时缺货,或者一个排名在一周内如何攀升。单次手动运行无法告诉你这些。

本指南将向你展示如何用 JavaScript 和 Node.js 自动化 Amazon 爬取。你会构建一个爬虫,通过 Crawling API 抓取公开的商品和搜索数据,然后用那些让它无需人工干预的部件把它包装起来:一个在 cron 上定时运行的任务、用于大批量运行并带 webhook 回调的异步 Crawler、用于保存结果的持久化存储,以及重试处理,这样单个失败的请求不会拖垮整个批次。我们把整个演练限定在公开的商品数据上,而结尾附近的合法性部分并非套话,所以在你把它对准真实流量之前请先读一读。如果你只需要做一次性抓取,我们关于如何爬取 Amazon 商品数据以及如何爬取 Amazon 畅销商品的指南涵盖了单次运行的情况;本文讲的是如何让它们自行运转。

你将构建什么

一个 Node.js 自动化程序,它按计划爬取公开的 Amazon 商品和搜索页面,并在每次运行时存储一条结构化记录。我们将以一个搜索结果页作为贯穿全文的示例,并为每个条目抓取这些字段:

  • ASIN Amazon 标准识别号,用作商品的唯一键。
  • 标题 卡片上显示的商品名称。
  • 价格 按显示方式给出的标价,例如 "$29.99"。
  • 评分 平均星级评分(如有)。
  • 评论数 卡片上显示的评论数量。
  • 商品 URL 指向单个商品页面的链接。

在这个爬虫周围,你将添加四个自动化部件:一个由 cron 驱动的调度器、一个为大批量任务设计的带 webhook 回调的异步运行、一个以运行时间戳为键的 JSON 存储,以及一个能处理瞬时失败而不中止整个批次的重试封装。

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

如果你用一个裸的 HTTP 客户端去请求 Amazon 搜索 URL,很少能拿到你想要的数据。Amazon 在浏览器中渲染页面的大部分内容,并且会积极地挑战自动化流量。一个数据中心 IP 在紧密循环中访问商品页面,往往还没采到有用的样本就先遇到 CAPTCHA、一个 "Robot Check" 插页,或者干脆被直接封禁。即使某个请求成功了,你拿回来的标记也可能是一个被精简过的外壳,缺少价格和评分。

所以一个能用的 Amazon 爬虫在一次请求中需要两样东西:一个真正渲染出来的页面,以及一个被平台读作真实访客的 IP。你可以自己用无头浏览器加一池轮换的住宅代理来拼凑出这套方案,但把它们拼到一起并保持其健康才是工作的大头,而且一旦你按计划运行、失败在无人值守的情况下越积越多,事情就更糟了。Crawling API 把这两者折叠进了一次调用:你把 URL 发给它,它在一个受信任的、轮换的 IP 背后抓取页面,然后把完成的 HTML 返回给你解析。

两种令牌

Crawlbase 给你一个普通令牌和一个 JavaScript (JS) 令牌。普通令牌抓取静态 HTML;JS 令牌先在真实浏览器中渲染页面,这会消耗更多额度。许多 Amazon 搜索和商品页面用普通令牌就能很好地解析,所以先从普通令牌开始,只有当你需要的某个字段返回为空时,才切换到 JS 令牌。

前置条件

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

基础的 JavaScript 和 Node.js。你应当能够熟练地编写并运行 Node 脚本,以及用 npm 安装包。如果你是 Node 新手,我们关于如何用 Node.js 构建一个网络爬虫的指南会讲解本教程所假定的基础知识。

Node.js 16 或更高版本。node --version 确认你的版本。如果还没有,请从 Node.js 网站或通过 nvm 之类的版本管理器安装。

一个 Crawlbase 账户和令牌。注册、打开你的仪表盘并复制你的令牌。免费套餐包含 1,000 次无需信用卡的请求,足够用来构建和调度它。请把令牌当作密码对待:它会对你的请求进行身份认证,所以别把它放进版本控制,要从环境变量中读取它。

搭建项目

创建一个项目文件夹,初始化它,并安装这个自动化所需的库。

bash
node --version

mkdir amazon-automation && cd amazon-automation
npm init -y

npm install crawlbase cheerio node-cron

三个依赖项承担了主要工作:crawlbase 是用于 Crawling API 和异步 Crawler 的官方 Node 客户端,cheerio 用一套 jQuery 风格的 API 解析返回的 HTML,让你能按 CSS 选择器抓取字段,而 node-cron 在同一个进程内部按计划运行爬虫。把你的令牌导出一次,这样文件夹里的每个脚本都能读到它:

bash
export CRAWLBASE_TOKEN='YOUR_CRAWLBASE_TOKEN'

第 1 步:抓取并解析搜索页

从爬虫本身开始,因为其他一切都是在自动化这个核心。导入 CrawlingAPI 类,用你的令牌初始化它,请求搜索 URL,并用 cheerio 解析每个结果卡片。在解析之前检查状态码,能让失败响亮地暴露出来而不是悄无声息。

javascript
const { CrawlingAPI } = require('crawlbase');
const cheerio = require('cheerio');

const api = new CrawlingAPI({ token: process.env.CRAWLBASE_TOKEN });

async function scrapeSearch(searchUrl) {
  const response = await api.get(searchUrl);
  if (response.statusCode !== 200) {
    throw new Error(`Request failed: ${response.statusCode}`);
  }
  return parseSearch(response.body);
}

function parseSearch(html) {
  const $ = cheerio.load(html);
  const items = [];

  $('div[data-asin]').each((_, el) => {
    const card = $(el);
    const asin = card.attr('data-asin');
    const title = card.find('h2 span').text().trim();
    if (!asin || !title) return;

    items.push({
      asin,
      title,
      price: card.find('.a-price .a-offscreen').first().text().trim() || null,
      rating: card.find('.a-icon-alt').first().text().trim() || null,
      reviews: card.find('.a-size-base.s-underline-text').first().text().trim() || null,
      productUrl: `https://www.amazon.com/dp/${asin}`,
    });
  });

  return items;
}

const searchUrl = 'https://www.amazon.com/s?k=wireless+headphones';
scrapeSearch(searchUrl).then((items) => {
  console.log(JSON.stringify(items.slice(0, 3), null, 2));
});

有几个细节让它保持稳健。Amazon 给每个结果卡片都打上一个 data-asin 属性,这是页面上最稳定的抓取点,所以我们以它为锚,并跳过任何不同时具备 ASIN 和标题的卡片(赞助位和布局占位符往往只有其一而没有其二)。价格藏在一个隐藏的 .a-offscreen span 中,它保存着干净、已格式化的值,比抓取被拆分的可见价格更可靠。每个字段在缺失时都回退到 null,所以一个缺失值绝不会让本次运行崩溃。把文件保存为 scraper.js 并用 node scraper.js 运行它;你应该会看到一个干净的商品记录数组。

Crawlbase Amazon Scraper

上面的爬虫一次能处理一个页面,但一个按计划走访数百个 Amazon 页面的自动化任务,正是同步等待每个请求开始拖累性能的地方。异步 Crawler 接收你的 URL,在一个轮换的、受信任的 IP 背后抓取每一个,并把完成的页面推送到你控制的 webhook,这样一个定时批次就能持续推进,而无需你管理一支无头浏览器队伍或一池代理。先在免费套餐上把它对准公开页面。

第 2 步:存储每一次运行

只有数据持久化下来,自动化才有用,所以把每次运行都以时间戳为键写入磁盘。这会给你一段历史,你以后可以对它做差异比较,看价格或排名如何变动。每次运行一个扁平的 JSON 文件是最简单的持久化存储,之后也容易加载到任何其他地方。

javascript
const fs = require('fs');
const path = require('path');

function saveRun(items) {
  const dir = path.join(__dirname, 'data');
  fs.mkdirSync(dir, { recursive: true });

  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
  const file = path.join(dir, `run-${stamp}.json`);

  const payload = { scrapedAt: new Date().toISOString(), count: items.length, items };
  fs.writeFileSync(file, JSON.stringify(payload, null, 2));
  console.log(`Saved ${items.length} items to ${file}`);
  return file;
}

ISO 时间戳能自然排序,所以列出 data 目录就能按顺序得到运行历史。对于生产任务,你会把它换成数据库,但契约是相同的:每次运行一组记录,并标记上它被采集的时间。每个保存的文件都带一个 scrapedAt 字段,这样后续的比较就能确切知道某个价格属于哪个时刻。

第 3 步:为瞬时失败添加重试

一个定时任务是无人值守运行的,所以单个不稳定的请求绝不能中止整个批次。把抓取包裹进一个小的重试辅助函数里,让它在两次尝试之间回退,并只在尝试几次之后才放弃。大多数瞬时失败(一时的封禁、一次缓慢的渲染、一次网络抖动)会在下一次尝试时消失。

javascript
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function scrapeWithRetry(url, attempts = 3) {
  for (let i = 1; i <= attempts; i++) {
    try {
      return await scrapeSearch(url);
    } catch (err) {
      console.warn(`Attempt ${i} failed: ${err.message}`);
      if (i === attempts) throw err;
      await sleep(2000 * i);
    }
  }
}

这个回退会把等待时间乘以尝试次数,所以间隔会变长(2 秒,然后 4 秒,再 6 秒),而不是去猛敲一个已经吃力的目标。在最后一次尝试之后错误会重新抛出,这让调用方来决定是记录它并继续处理下一个关键词,还是停止本次运行。这种模式正是一个能自行恢复的调度器和一个需要你时刻照看的调度器之间的区别。

第 4 步:用 cron 调度它

现在让它无需人工干预。node-cron 包会在你的进程内部按一个标准的 cron 表达式运行一个函数,所以你可以把爬虫、存储和重试逻辑放在同一个地方。这里我们让任务在每天早上 6 点运行,并每次采集一组关键词。

javascript
const cron = require('node-cron');

const keywords = ['wireless headphones', 'mechanical keyboard', 'usb c hub'];

async function runJob() {
  console.log(`Run started at ${new Date().toISOString()}`);
  const all = [];

  for (const keyword of keywords) {
    const url =
      `https://www.amazon.com/s?k=${encodeURIComponent(keyword)}`;
    try {
      const items = await scrapeWithRetry(url);
      all.push(...items.map((it) => ({ ...it, keyword })));
    } catch (err) {
      console.error(`Skipping "${keyword}": ${err.message}`);
    }
    await sleep(3000);
  }

  saveRun(all);
}

cron.schedule('0 6 * * *', runJob);
console.log('Scheduler running. Daily job at 06:00.');

表达式 0 6 * * * 表示每天第 6 小时的第 0 分钟。每个关键词都走一遍重试封装,失败的关键词会被记录并跳过,而不会让整个运行崩溃,关键词之间 3 秒的暂停让节奏保持礼貌。让这个进程一直运行(在生产中用像 pm2 这样的进程管理器或一个 systemd 服务),它就会在每天早上采集一份新的快照,无需任何进一步的输入。如果你宁愿不让一个 Node 进程一直活着,就去掉那行 cron,改为从系统 crontab 或一个按相同表达式运行 node job.js 的云端定时任务来触发 runJob

第 5 步:用异步 Crawler 和 webhook 扩展规模

上面的同步循环对于少数几个关键词是没问题的,但一旦一次运行涵盖数百个商品 URL,依次等待每个请求就成了瓶颈。异步 Crawler 正是为此而生的:你把 URL 推送给它,它在后台于一个受信任的 IP 背后抓取每一个,并把完成的页面投递到你托管的 webhook。你的调度器不再在每次抓取上阻塞,而只是把工作排入队列,然后在结果到达时处理它们。

首先,把 URL 排入队列。异步客户端接收同样的令牌和一个指向你 webhook 端点的 callback URL。

javascript
const { CrawlingAPI } = require('crawlbase');

const api = new CrawlingAPI({ token: process.env.CRAWLBASE_TOKEN });

const productUrls = [
  'https://www.amazon.com/dp/B0CHX1W1XY',
  'https://www.amazon.com/dp/B09G9FPHY6',
];

async function enqueue() {
  for (const url of productUrls) {
    const response = await api.getAsync(url, {
      callback: 'https://your-server.com/crawlbase-webhook',
    });
    console.log(`Queued ${url} -> rid ${response.json.rid}`);
  }
}

enqueue();

每次调用都会立即返回一个请求 id(rid),所以无论你排入多少个 URL,循环都很快结束。Crawlbase 在后台完成抓取,并在每个页面完成时把完成的 HTML POST 到你的回调。你的 webhook 接收这些页面、解析它们并存储结果,和同步路径完全一样,只是与入队解耦了。

javascript
const express = require('express');
const cheerio = require('cheerio');

const app = express();
app.use(express.text({ type: '*/*', limit: '5mb' }));

app.post('/crawlbase-webhook', (req, res) => {
  const $ = cheerio.load(req.body);
  const title = $('#productTitle').text().trim();
  const price = $('.a-price .a-offscreen').first().text().trim();

  if (title) saveRun([{ title, price: price || null }]);
  res.sendStatus(200);
});

app.listen(3000, () => console.log('Webhook listening on 3000'));

回调 URL 必须能被公开访问到,所以在开发期间用像 ngrok 这样的隧道把你的本地服务器暴露出去,并把那个 HTTPS 地址用作 callback 值。务必尽快返回一个 200,好让 Crawlbase 知道页面已被接收;把解析和存储放在响应之前或一个后台任务里完成。要更深入地了解这种模式,请看如何用 Crawlbase Crawler 提取数据,那篇文章详细讲解了异步队列和回调。

输出长什么样

每次运行都会在 data/ 下写出一个单独的 JSON 文件,并标记上它被采集的时间。无论记录来自同步循环还是异步 webhook,其形态都保持一致,所以下游的任何东西都读到同一种格式。

json
{
  "scrapedAt": "2026-06-11T06:00:04.812Z",
  "count": 2,
  "items": [
    {
      "asin": "B0CHX1W1XY",
      "title": "Wireless Over-Ear Headphones, 40H Playtime",
      "price": "$59.99",
      "rating": "4.5 out of 5 stars",
      "reviews": "2,184",
      "productUrl": "https://www.amazon.com/dp/B0CHX1W1XY",
      "keyword": "wireless headphones"
    },
    {
      "asin": "B09G9FPHY6",
      "title": "Compact Mechanical Keyboard, Hot-Swappable",
      "price": "$45.99",
      "rating": "4.4 out of 5 stars",
      "reviews": "1,073",
      "productUrl": "https://www.amazon.com/dp/B09G9FPHY6",
      "keyword": "mechanical keyboard"
    }
  ]
}

有了这样一段运行历史,自动化就有了回报:加载任意两个运行文件,按 ASIN 匹配条目,你就得到了一条可以绘图或据以告警的价格随时间变化序列。同一个存储也能喂给一个畅销榜追踪器或一个库存变动通知器,而完全不用改动爬虫。

保持不被封禁

即便 Crawling API 处理了渲染和轮换,一个无人值守运行的定时任务也需要一些习惯,让它在数周而不仅仅是一次成功运行的范围内保持健康。

  • 给调度安排节奏,而不只是给循环。每隔几分钟全天候运行一点也不像人类,还会很快烧掉额度。对于价格和排名追踪来说,每天或每小时的节奏就足够了,而且能让你的足迹保持小巧。
  • 让轮换发挥它的作用。Crawling API 把请求分散到许多住宅 IP 上,所以没有任何单个 IP 会触发速率限制。如果你哪天改为自建一套技术栈,这就是要做对的部分,我们关于如何爬取网站而不被封禁的指南涵盖了完整的攻略。
  • 盯着你日志里的状态码。一个开始返回非 200 响应的定时运行是在告诉你有什么变了。因为任务会记录每次失败、重试封装会回退,你得到的是一条线索记录,而不是数据中一段悄无声息的空白。

爬取 Amazon 合法吗?

爬取 Amazon 是否被允许,取决于 Amazon 的使用条件、你所在的司法管辖区,以及你用这些数据做什么。Amazon 的条款限制自动化访问,所以无论你的工具多么谨慎,爬取都可能与那些条款相抵触。这里的任何代码都改变不了这一点;它只是让技术部分能跑起来。请阅读 Amazon 的使用条件及其 robots.txt,并把两者都当作你采集内容的边界。

有几条值得坚守的底线。只采集公开的商品数据:标题、价格、评分、评论数、ASIN,以及任何人无需账户就能看到的商品链接。尊重 Amazon 所声明的速率预期,并把你的定时采集量保持得足够低,使你不会给它的服务器造成压力,这正是本指南中 cron 节奏和速度控制为什么重要的原因。避免个人数据,包括除页面上公开评论文本之外任何与可识别评论者相关联的内容。如果你打算把数据用于商业用途,就去取得许可或一份授权协议,而不是假定沉默即同意。

对于大批量或商业用途,Amazon 提供了官方途径,包括 Product Advertising API 和面向卖家的 Selling Partner API,当你需要大批量、有保证的结构,或商业权利时,那些才是正确的工具。本指南有意把范围限定在公开的商品和搜索页面上,因为那正是让这项工作站得住脚的那条线。它不涉及任何登录之后的内容,买家或卖家账户数据,订单历史,私信,或任何绕过身份认证的尝试。如果你的项目需要的不止公开数据,那么 Amazon 的官方 API 或一份数据协议才是正确的路径,而不是一个更聪明的爬虫或一个更快的调度。

回顾

核心要点

  • 是自动化让数据变得有价值。单次爬取是一张快照;一个定时的爬取则给你随时间变化的价格、排名和库存。
  • Crawling API 处理渲染和轮换。你以 Amazon 的 data-asin 卡片为锚,用 cheerio 抓取价格、评分和评论,而 API 应对封禁和 IP。
  • 重试和节奏让一个无人值守的任务保持健康。一个回退封装能从瞬时失败中恢复,一个被跳过的关键词绝不会让整个运行崩溃。
  • cron 让它无需人工干预。node-cron 或一个系统调度器按一个固定表达式运行任务,每次运行都被存为一个带时间戳的 JSON 文件,你可以对它做差异比较。
  • 异步 Crawler 让它可扩展。对于大批量,用一个 webhook 回调把 URL 排入队列,这样抓取就在后台发生,而不是阻塞你的循环。

常见问题

我该如何调度一个 Amazon 爬虫使其自动运行?

在 Node 内部最简单的途径是 node-cron,它会在你的进程内按一个标准的 cron 表达式运行一个函数。把你的爬取并存储逻辑写成一个函数,然后调度它,例如用 0 6 * * * 表示每天早上 6 点。如果你宁愿不让一个 Node 进程一直活着,就把同样的逻辑放进一个脚本,并用相同的表达式从系统 crontab 或一个云端定时任务来触发它。

我什么时候该用异步 Crawler 而不是同步请求?

一旦一次运行涵盖许多 URL、依次等待每个请求成了瓶颈,就该使用异步 Crawler。你用一个 webhook 回调把 URL 排入队列,Crawlbase 在后台于一个受信任的 IP 背后抓取每个页面,并把完成的 HTML POST 到你的端点。同步请求更简单,对于每次运行寥寥几个页面也没问题。

在一个无人值守的爬取任务中我该如何处理失败?

把每次抓取包裹进一个重试辅助函数里,让它在两次尝试之间回退,并在尝试几次之后放弃,然后在关键词层面捕获并记录失败,这样一个糟糕的请求只会跳过那个条目,而不是中止整个批次。记录每个非 200 状态,好让你的运行留下一条线索记录。Crawling API 的轮换已经预防了大多数封禁,所以重试主要应对的是瞬时抖动。

爬取 Amazon 我需要 JS 令牌吗?

往往不需要。许多 Amazon 搜索和商品页面用普通令牌就能很好地解析,而普通令牌消耗更少的额度。先从普通令牌开始,只有当你需要的某个字段因为是客户端渲染而返回为空时,才切换到 JavaScript (JS) 令牌。在普通令牌行得通的地方使用更便宜的它,能让一个定时任务在免费套餐上保持负担得起。

我该把爬取到的结果存在哪里?

对于一个小任务,每次运行一个带时间戳的 JSON 文件是持久的,而且之后容易做差异比较以追踪价格或排名变化。随着量增长,转向数据库,这样你就能高效地跨运行查询。无论哪种方式,都让记录形态保持稳定,并给每次运行标记一个 scrapedAt 时间,这样后续的比较就能知道某个值属于哪个时刻。

自动化 Amazon 爬取合法吗?

自动化并不改变规则;它只是重复运行同样的爬取,这就提升了节奏控制的重要性。停留在公开的商品数据上,尊重 Amazon 的使用条件和 robots.txt,把你的定时采集量保持适中,并避免个人数据和任何登录之后的内容。对于大批量或商业再利用,Amazon 的官方 Product Advertising 或 Selling Partner API 才是被认可的途径。

开始构建

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

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

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