Tokopedia是印度尼西亚最大的电商平台之一,拥有数千万活跃买家和数十万在电子产品、服装、杂货和家居用品等品类上架商品的商家。其公开搜索和商品页面是丰富的需求信号:任何查询所呈现的标题、价格、卖家、评分和销量,正是团队用于价格追踪、竞争对手研究和品类趋势分析的数据。

本指南将向您展示如何使用JavaScript和Node.js结合cheerio抓取Tokopedia数据。您将构建一个小型可运行的爬虫,通过Crawling API获取Tokopedia搜索列表和商品页面,解析每个商品的标题、价格、店铺、评分、销量和链接,处理分页,并将结果导出为JSON和CSV格式。整个演练范围限定于公开商品列表数据,文末的合法性部分在您将其应用于任何实际规模之前值得认真阅读。

您将构建什么

一个Node.js脚本,接受一个公开的Tokopedia搜索URL,通过Crawling API获取渲染后的HTML,并为结果网格中的每个商品提取结构化记录。我们以耳机搜索作为示例,每个条目提取以下字段:

  • 标题 商品卡片上显示的产品名称。
  • 价格 显示的价格,例如"Rp178.000"。
  • 店铺 上架该商品的店铺或卖家名称。
  • 评分 卡片显示时的星级评分文本。
  • 销量 存在时的已售件数,例如"60+ terjual"。
  • 链接 到该商品详情页的URL。

为什么普通请求在Tokopedia上会失败

如果您用普通HTTP客户端请求Tokopedia搜索URL,几乎永远拿不到商品网格。有两个原因对您不利。首先,Tokopedia在浏览器中通过JavaScript渲染其商品卡片,因此初始HTML是一个接近空白的外壳,直到页面脚本运行并获取商品数据后才会填充。其次,平台会标记自动化流量:不像真实浏览器的数据中心IP和请求模式,在到达渲染列表之前就会被限速或封锁。

因此,一个有效的Tokopedia爬虫在一次请求中需要两样东西:一个真正渲染页面的浏览器,以及一个平台视为真实访客的IP。您可以自己用无头浏览器加上轮换住宅代理池来实现,但将这些组合在一起并保持健康运行才是大部分工作量所在。Crawling API将两者折叠进一次调用:您发送URL,它在受信任的IP背后渲染页面,并返回完整HTML供您用cheerio解析。

选择JavaScript令牌

Crawlbase发放两种令牌:用于静态网站的普通令牌,以及用于浏览器渲染内容的JavaScript令牌。Tokopedia通过JavaScript加载商品,因此这里请使用JavaScript令牌。免费层提供1,000次请求且无需绑卡,您可以在付费前测试完整流程。

前提条件

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

JavaScript和Node.js基础。 您应该能够编写和运行Node脚本,并用npm安装包。如果您是Node新手,Node.js网络爬虫构建指南涵盖了本教程所假设的基础知识。

Node.js 16或更高版本。node --version确认您的版本。如果没有,请从Node.js官网安装或通过版本管理器(如nvm)安装。

Crawlbase账号和令牌。 注册后打开仪表盘,复制您的JavaScript令牌。免费层提供1,000次请求且无需绑卡。请像对待密码一样对待令牌:它用于验证您的请求,所以不要将其放入版本控制。

搭建项目

创建一个项目文件夹,初始化它,然后安装爬虫所需的两个库。

bash
node --version

mkdir tokopedia-scraper && cd tokopedia-scraper
npm init -y

npm install crawlbase cheerio

两个依赖各司其职:crawlbase是Crawling API的官方Node客户端,cheerio使用类jQuery的API解析返回的HTML,让您可以通过CSS选择器提取各个字段。在此文件夹中创建名为tokopedia-scraper.js的文件,并添加以下步骤中的代码。

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

首先获取完整页面。导入CrawlingAPI类,用您的令牌初始化,然后请求搜索URL。Tokopedia延迟加载结果,因此传入ajax_waitpage_wait延迟,给页面足够的渲染时间。在解析之前检查状态码,让失败可见而非静默。

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

const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' });

const options = { ajax_wait: 'true', page_wait: '5000' };

const searchURL = 'https://www.tokopedia.com/search?q=headset';

api
  .get(searchURL, options)
  .then((response) => {
    if (response.statusCode === 200) {
      console.log(response.body.slice(0, 500));
    }
  })
  .catch((error) => console.error('API request error:', error));

node tokopedia-scraper.js运行脚本,您应该在响应体顶部看到真实的Tokopedia商品标记,而非精简过的外壳。这在您编写任何选择器之前就能确认渲染正常工作。ajax_wait标志指示API等待页内请求完成,page_wait则额外等待5秒,让延迟加载的商品卡片有时间出现。

Crawlbase Crawling API

第一次请求刚刚返回了完整渲染的Tokopedia搜索页面,无需在您这边部署无头浏览器或代理。Crawling API在真实浏览器中运行页面,等待JavaScript加载的商品卡片稳定,并在服务端轮换住宅IP,让您一次调用就能获得完整HTML。先在免费层对任何公开搜索查询试试。

第2步:用cheerio解析每个商品

手握渲染后的HTML,将其加载进cheerio并遍历商品卡片。Tokopedia将每个结果排布在搜索结果区域内的重复容器中,因此您选取每张卡片,然后从中读取标题、价格、店铺、评分、销量和链接。防御性地读取每个字段,确保一个缺失的值不会导致整个运行崩溃。

javascript
const cheerio = require('cheerio');

function parseSearchListings(html) {
  const $ = cheerio.load(html);
  const products = [];

  const cards = $(
    'div[data-testid="divSRPContentProducts"] div.css-5wh65g'
  );

  cards.each((index, element) => {
    const card = $(element);

    const title = card
      .find('span.OWkG6oHwAppMn1hIBsC3pQ\\=\\=')
      .text()
      .trim();

    const price = card
      .find('div.ELhJqP-Bfiud3i5eBR8NWg\\=\\=')
      .text()
      .trim();

    const shop = card
      .find('span.X6c-fdwuofj6zGvLKVUaNQ\\=\\=')
      .text()
      .trim();

    // Rating and sold count sit in small text rows under the price
    const rating = card.find('span.nBBbPk2cBpbZJ2nFPN8jKA\\=\\=').text().trim();
    const sold = card.find('span.eLNb-rRDe6X9p64ZsQAx9w\\=\\=').text().trim();

    const link = card.find('a.Nq8NlC5Hk9KgVBJzMYBUsg\\=\\=').attr('href');

    if (title) {
      products.push({
        title: title,
        price: price || 'N/A',
        shop: shop || 'N/A',
        rating: rating || 'N/A',
        sold: sold || 'N/A',
        link: link || 'N/A',
      });
    }
  });

  return products;
}

有几个细节让这段代码忠实于页面。商品卡片位于divSRPContentProducts test id下,每张卡片的标题在类名为OWkG6oHwAppMn1hIBsC3pQ==span中,价格在类名为ELhJqP-Bfiud3i5eBR8NWg==div中,店铺名在类名为X6c-fdwuofj6zGvLKVUaNQ==span中。商品链接从卡片的锚点href读取。这些类名包含字面量==字符,因此在cheerio选择器字符串中需转义为\\=\\=。评分和销量选择器遵循价格下方小字文本行的相同模式。

选择器会漂移

Tokopedia的哈希类名(如OWkG6oHwAppMn1hIBsC3pQ==及其他)是自动生成的,会在无通知的情况下变更。请将上述选择器视为起始模板而非合同。当某个字段返回空时,在浏览器开发者工具中重新检查实时页面并更新选择器。定期进行选择器维护是任何生产级爬虫的正常操作,并不意味着出了问题。

第3步:处理分页

Tokopedia将搜索结果分布在多个页面上,每一页都可以通过在URL后附加page参数来访问,例如&page=2。从第一页循环到您想要的最后一页,通过Crawling API获取每一页,用第2步中的函数解析,并将所有内容收集到一个数组中。如果某页获取失败,提前停止而不是推送空结果。

javascript
async function fetchHtml(url) {
  const response = await api.get(url, options);
  if (response.statusCode === 200) return response.body;
  console.error(`Failed to fetch page: ${response.statusCode}`);
  return null;
}

async function scrapeMultiplePages(baseUrl, maxPages) {
  const allProducts = [];

  for (let page = 1; page <= maxPages; page++) {
    const paginatedUrl = `${baseUrl}&page=${page}`;
    const html = await fetchHtml(paginatedUrl);
    if (!html) break;

    const products = parseSearchListings(html);
    allProducts.push(...products);
    console.log(`Page ${page}: ${products.length} products`);
  }

  return allProducts;
}

这段代码遍历您指定的页面,从每页抓取列表,并汇总结果。由于每个搜索页面共享相同的卡片结构,您在第2步编写的解析器无需任何修改即可跨页面使用。

第4步:组装带JSON和CSV导出的完整脚本

现在将获取、解析器和分页整合为一个可运行的脚本,然后将记录写入磁盘,同时生成JSON和CSV格式。JSON保留完整的嵌套结构;CSV可以直接在电子表格中打开。

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

const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' });
const options = { ajax_wait: 'true', page_wait: '5000' };

// Paste parseSearchListings, fetchHtml, and scrapeMultiplePages here

function toCsv(rows) {
  const headers = ['title', 'price', 'shop', 'rating', 'sold', 'link'];
  const escape = (value) =>
    `"${String(value).replace(/"/g, '""')}"`;
  const lines = [headers.join(',')];
  for (const row of rows) {
    lines.push(headers.map((h) => escape(row[h])).join(','));
  }
  return lines.join('\n');
}

async function main() {
  const baseUrl = 'https://www.tokopedia.com/search?q=headset';
  const maxPages = 5;

  const products = await scrapeMultiplePages(baseUrl, maxPages);

  fs.writeFileSync(
    'tokopedia_search_results.json',
    JSON.stringify(products, null, 2)
  );
  fs.writeFileSync('tokopedia_search_results.csv', toCsv(products));

  console.log(`Saved ${products.length} products to JSON and CSV`);
}

main();

parseSearchListingsfetchHtmlscrapeMultiplePages函数粘贴到同一文件中,以便main能够调用它们。用node tokopedia-scraper.js运行,您会得到两个文件:包含完整结构化记录的tokopedia_search_results.json和可在电子表格中打开的tokopedia_search_results.csvtoCsv辅助函数对每个字段加引号并将内嵌的引号双写,这在Tokopedia标题通常很长且经常包含逗号的情况下至关重要。

输出示例

JSON文件按搜索顺序保存每个商品的对象,每个对象包含标题、价格、店铺、评分、销量和链接。

json
[
  {
    "title": "Ipega PG-R008 Gaming Headset for P4 /X1 series/N-Switch Lite/Mobile",
    "price": "Rp178.000",
    "shop": "ipegaofficial",
    "rating": "4.9",
    "sold": "250+ terjual",
    "link": "https://www.tokopedia.com/ipegaofficial/ipega-pg-r008-gaming-headset"
  },
  {
    "title": "Hippo Toraz Handsfree Earphone Stereo Sound - headset, Putih",
    "price": "Rp13.000",
    "shop": "HippoCenter",
    "rating": "4.8",
    "sold": "1rb+ terjual",
    "link": "https://www.tokopedia.com/hippocenter88/hippo-toraz-handsfree-earphone"
  }
]

CSV以带标题行的相同行格式展现,可直接在Excel、Google Sheets或任何读取分隔符文件的数据管道中使用。

csv
title,price,shop,rating,sold,link
"Ipega PG-R008 Gaming Headset for P4 /X1 series","Rp178.000","ipegaofficial","4.9","250+ terjual","https://www.tokopedia.com/ipegaofficial/ipega-pg-r008-gaming-headset"
"Hippo Toraz Handsfree Earphone Stereo Sound - headset, Putih","Rp13.000","HippoCenter","4.8","1rb+ terjual","https://www.tokopedia.com/hippocenter88/hippo-toraz-handsfree-earphone"

抓取单个商品页面

搜索列表给您带来广度;商品页面给您带来深度。一旦您从搜索爬虫获得链接,就可以通过同一个Crawling API获取商品页面,并提取更丰富的字段。在Tokopedia商品页面上,名称位于带data-testid="lblPDPDetailProductName"h1中,价格位于带data-testid="lblPDPDetailProductPrice"div中,店铺位于带data-testid="llbPDPFooterShopName"a中,描述位于带data-testid="lblPDPDescriptionProduk"div中,缩略图位于data-testid="PDPImageThumbnail"按钮内的img标签中。

javascript
async function scrapeProductPage(url) {
  const html = await fetchHtml(url);
  if (!html) return null;

  const $ = cheerio.load(html);

  const images = $('button[data-testid="PDPImageThumbnail"] img')
    .map((i, el) => $(el).attr('src'))
    .get();

  return {
    name: $('h1[data-testid="lblPDPDetailProductName"]').text().trim(),
    price: $('div[data-testid="lblPDPDetailProductPrice"]').text().trim(),
    shop: $('a[data-testid="llbPDPFooterShopName"]').text().trim(),
    description: $('div[data-testid="lblPDPDescriptionProduk"]').text().trim(),
    images: images,
  };
}

这复用了搜索爬虫中相同的fetchHtml辅助函数,因此商品页面爬虫无需任何额外设置即可继承渲染和IP轮换能力。向它传入搜索爬虫收集到的任何链接,它会返回一个结构化对象,您可以用JSON.stringify或CSV中的一行来存储它。

保持不被封锁

即使处理了渲染,Tokopedia仍会监控爬虫形态的流量。以下几个习惯能让抓取运行保持健康,它们适用于任何防御严密的商业目标。

  • 控制请求节奏。 在页面获取之间引入延迟,而不是紧密循环地连续请求搜索结果。分散请求是保持在平台速率限制之下的最重要因素。
  • 依赖轮换。 住宅IP池将请求分散到众多真实用户地址上,使单个地址不会触发限制。Crawling API为您处理这一切;如果您自己搭建,这是最需要做对的部分。
  • 关注状态码。 运行开始返回非200响应时,说明当前速率或IP级别已不够用。将此视为信号需要退缩,而非可以忽略的噪音。

更广泛的操作手册,请参阅如何在不被封锁的情况下抓取网站以及JavaScript网站爬取更深入的指南,两者都更详细地介绍了渲染和轮换方面的内容。

抓取Tokopedia合法吗?

抓取Tokopedia是否被允许,取决于Tokopedia的服务条款、您所在的司法管辖区以及您对数据的使用方式。Tokopedia的条款限制自动化访问,因此无论您的工具多么谨慎,抓取行为都可能违反这些条款。这里的代码并不改变这一点,它只是让技术部分得以实现。请阅读Tokopedia的使用条款及其robots.txt,将两者都视为您采集范围的边界。

有几条值得坚守的底线。仅采集公开商品数据:任何人无需账号即可在搜索或商品页面看到的标题、价格、店铺名、评分、销量和商品链接。遵守Tokopedia的速率预期,将请求量控制在不对其服务器造成压力的水平。避免个人数据,包括任何与可识别买家或评论者相关的信息,超出页面上公开显示的评论文本和星级计数。不要将Tokopedia受版权保护的媒体(如商家商品摄影)重新分发为您自己的内容。

本指南有意限定在公开搜索和商品列表范围内,因为这是保持工作可辩护的边界。它不涵盖登录后的任何内容、买家或卖家个人数据、订单历史、聊天,或任何绕过身份验证或不应通过的CAPTCHA的尝试。如果您的项目需要超出公开列表的数据,正确的路径是获得授权的数据协议或Tokopedia提供的任何官方API渠道,而不是更聪明的爬虫。如有疑问,宁可取得许可,也不要假定沉默就是同意。

回顾

核心要点

  • Tokopedia在客户端渲染列表,且限速严格。 普通请求返回空白外壳,因此您必须在解析之前用JavaScript令牌在受信任IP背后渲染页面。
  • Crawling API一次调用完成两件事。 它通过ajax_waitpage_wait渲染页面,轮换住宅IP,并将完整HTML交给您用cheerio解析。
  • cheerio提取字段。 选取每张商品卡片,然后读取标题、价格、店铺、评分、销量和链接,同时对哈希的==类名进行转义,并预期它们会随时间漂移。
  • 分页和商品页面扩展了爬虫。 循环page参数获取广度,然后重用相同的获取辅助函数,从单个商品页面提取名称、价格、店铺、描述和图片。
  • 坚守公开数据。 遵守Tokopedia的服务条款和robots.txt,控制请求节奏,将结果导出为JSON和CSV,避免登录后的内容或任何个人数据。

常见问题

从Tokopedia抓取数据合法吗?

当您限于公开商品数据并遵循Tokopedia的服务条款时,抓取可能是允许的。请先查阅网站规则和robots.txt,保持合理的请求量,避免个人或需要登录的数据。将您采集的数据用于研究、价格追踪或分析等合法目的,不要做任何违反Tokopedia政策或当地法律的事情。

为什么我需要Crawling API来抓取Tokopedia?

Tokopedia通过JavaScript加载商品,因此原始HTTP请求通常返回空白外壳而非商品卡片。Crawling API在真实浏览器中渲染页面,通过ajax_waitpage_wait等待延迟加载的内容,并轮换住宅IP,让您无需管理浏览器集群或代理池就能获取完整列表。

我可以从Tokopedia提取哪些数据点?

从搜索列表中,您可以提取商品标题、价格、店铺名、评分、销量和链接。从单个商品页面,您可以提取名称、价格、店铺、完整描述和图片URL。这些合在一起涵盖了大多数团队进行价格监控、竞争对手研究和商品趋势分析所需的公开字段。

为什么我的Tokopedia选择器返回空值?

几乎可以肯定是标记发生了变化。Tokopedia的哈希类名(如OWkG6oHwAppMn1hIBsC3pQ==)是自动生成的,会在无通知的情况下变更,因此上个月还能用的选择器可能就失效了。在浏览器开发者工具中重新检查实时页面并更新选择器。定期进行选择器维护是任何生产级爬虫的正常操作。

如何处理Tokopedia搜索结果的分页?

在搜索URL后附加page参数,例如&page=2,并从第一页循环到您想要的最后一页。通过Crawling API获取每一页,用相同的cheerio函数解析,并将结果收集到一个数组中,然后再导出。如果某页获取失败,提前停止以免用空结果填充数据。

我可以从Tokopedia抓取买家或卖家个人数据吗?

不行,本指南也不涵盖这些内容。买家账户、订单历史、聊天以及任何需要登录的内容都不是公开数据。抓取需要登录的内容、买家或评论者超出公开评论文本范围的个人数据,或绕过身份验证,均超出本文范围且违反Tokopedia的条款。如有更广泛的需求,请参阅电商网络爬虫指南或网络爬取在价格情报中的应用概述,并对超出公开列表的需求优先寻求授权的数据协议。

开始构建

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

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

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