三星的网上商城是网络上最整洁的消费电子公开目录之一。每条智能手机列表都包含型号名称、价格、用户评分和简短规格表,任何打开页面的人都可以看到。这使其成为价格追踪、竞争研究和趋势分析的有价值来源:公开目录告诉你三星在卖什么、以什么价格出售,以及购物者如何评价。
本指南将向你展示如何使用 Node.js 抓取三星产品,借助 Crawlbase Crawling API 获取渲染后的页面,并用 cheerio 从 HTML 中提取每个字段。整个演示仅限于公开目录数据:商城向每位访客展示的产品名称、型号、价格、评分、评论数、主要规格和产品 URL。本文不涉及账户、订单或任何需要登录的内容。
你将构建什么
一个小型 Node.js 脚本,请求三星产品列表页面,获取完整渲染的 HTML,遍历每张产品卡片,并为每部手机写入一条整洁的记录到 JSON 文件。每条记录包含:
- 产品名称。卡片上显示的型号系列,例如 Galaxy S24 Ultra。
- 型号与配置。该产品列出的存储容量和配置选项。
- 价格。商城为该列表显示的目录价格。
- 评分。平均用户评分分数。
- 评论数。该评分所基于的评论数量。
- 主要规格。卡片上的简短规格要点,如显示屏尺寸、摄像头和电池。
- 产品 URL。指向完整产品页面的链接。
为什么普通请求在三星上会失败
如果你将普通 HTTP 客户端指向三星产品列表 URL,很少能得到你在浏览器中看到的目录。商城的大量内容在客户端渲染:产品网格、价格和评分在初始 HTML 到达后通过 JavaScript 加载。原始请求返回带有数据槽仍为空的页面外壳,因此 cheerio 找不到任何可以解析的内容。
此外,大型零售网站监控自动化流量。来自数据中心 IP 且请求模式不像真实浏览器的访问,在到达有用标记之前就会被质询或限速。因此,一个有效的三星爬虫需要在一次请求中同时具备两样东西:一个真正渲染页面的浏览器,以及一个被商城识别为真实访客的 IP。你可以自己用 Puppeteer 等无头浏览器加轮换住宅代理池来拼凑这一切,但将两者整合并保持健康运行就是大部分工作量。Crawling API 将两者合并为一次调用:你发送 URL,它在可信 IP 后面渲染页面,返回处理完毕的 HTML 供解析。
Crawlbase 提供两种 token 类型。普通 token 抓取静态 HTML;JavaScript (JS) token 先在真实浏览器中渲染页面。三星的目录依赖客户端渲染,因此 JS token 才是正确选择。如果字段在普通 token 下返回为空,切换到 JS token,渲染后的标记将包含这些字段。
前提条件
编写任何代码之前,需要准备几样东西。这些都不需要太长时间。
Node.js 18 或更高版本。用 node --version 检查你的版本。如果没有安装,从 nodejs.org 安装当前 LTS 版本。Node 让你在浏览器外运行 JavaScript,并通过 npm 安装包。
基本 JavaScript 知识。你应该能熟练编写脚本、从终端运行,以及用 npm 安装包。对 promise 和 then 链的工作理解会让代码读起来更清晰。
Crawlbase 账号和 token。注册后,打开控制台,从账号文档页面复制你的 token。对三星商城使用 JavaScript (JS) token。将 token 视为密码:它用于验证你的请求,因此请勿放入版本控制。
配置项目
创建目录,初始化项目,并安装爬虫需要的两个依赖。
mkdir scrape-samsung-products && cd scrape-samsung-products npm init -y npm install crawlbase cheerio
两个依赖各司其职:crawlbase 是 Crawling API 的官方客户端,cheerio 是一个快速轻量的解析器,通过类 jQuery 的选择器处理返回的 HTML,无需附带浏览器。在目录中创建 index.js 文件;以下代码均放入该文件。
第一步:获取渲染后的页面
从获取完整目录开始。从 crawlbase 包导入 CrawlingAPI,用你的 JS token 初始化,然后请求三星产品列表 URL。两个等待选项在这里很重要:ajax_wait 告知 API 等待异步内容加载完成,page_wait 在加载后再等待固定毫秒数,确保延迟渲染的网格在页面捕获之前出现。在解析之前检查状态码,可让失败明显暴露而不是悄无声息。
const { CrawlingAPI } = require("crawlbase"); const api = new CrawlingAPI({ token: "YOUR_CRAWLBASE_JS_TOKEN" }); const samsungProductsURL = "https://www.samsung.com/levant/smartphones/all-smartphones/"; api .get(samsungProductsURL, { ajax_wait: true, page_wait: 10000 }) .then((response) => { if (response.statusCode === 200) { console.log(response.body.slice(0, 500)); } else { throw new Error(`Failed to fetch HTML. Status: ${response.statusCode}`); } }) .catch(console.error);
运行 node index.js,你应该看到真实的目录标记,而不是精简的外壳。对于这个较重的网格,十秒的 page_wait 较为充裕;一旦确认产品已存在,可适当降低。该输出证明在你编写任何一个选择器之前,渲染已正常工作。如果你想了解为何客户端页面需要这个步骤,参见如何爬取 JavaScript 网站。
你刚才运行的那次 api.get 调用同时完成了两件事:三星产品列表需要在可信 IP 后面渲染页面,Crawling API 接受你的 token,在真实浏览器中运行页面,在服务器端轮换住宅 IP,并返回处理完毕的 HTML。你省去了自己运行无头浏览器集群和代理池的麻烦。先在免费套餐上试试公开列表页面。
第二步:用 cheerio 解析产品字段
拿到渲染后的 HTML,将其加载到 cheerio 并遍历产品网格。模式是:找到重复的产品卡片,然后对每张卡片用 CSS 选择器提取你需要的字段。三星商城以可预期的方式排列每张卡片,因此你可以将产品名称、颜色、配置、评分、规格和 URL 映射到各自的选择器。以下选择器直接来自实时目录标记。
const cheerio = require("cheerio"); function scrapeProducts(html) { const $ = cheerio.load(html); const products = []; $(".js-pfv2-content-wrap .js-pfv2-product-card").each((_, element) => { const card = $(element); const name = card .find(".pd03-product-card__product-name-text") .text() .trim(); const color = card .find(".option-selector-v2__color-name-text-in") .text() .trim(); const variants = card .find(".option-selector-v2__size-text") .map((_, el) => $(el).text().trim()) .get(); const rating = card .find(".rating__point span:last-child") .text() .trim(); const reviewCount = card .find(".rating__count") .text() .replace(/[^0-9]/g, ""); const specifications = card .find(".pd03-product-card__spec-list .pd03-product-card__spec-item") .map((_, el) => $(el).text().trim()) .get(); const url = card .find(".pd03-product-card__product-image-link") .attr("href"); products.push({ name, model: [color, ...variants].filter(Boolean).join(", "), rating, reviewCount, specifications: specifications.join(", "), url: url && (url.startsWith("http") ? url : `https://www.samsung.com${url}`), }); }); return products; }
每个字段都映射到一个你可以自行验证的选择器:在实时三星产品列表页面上右键单击元素,选择"检查",从高亮节点上读取类名。.text() 读取可见标签;.attr("href") 读取链接。.map().get() 对将一组匹配元素(配置选项和规格要点)转换为普通数组。由于商城上的 href 有时是相对路径,当 URL 不以 http 开头时,代码会加上域名前缀。更多关于构建健壮选择器的内容,参见XPath 和 CSS 选择器指南。
第三步:完整脚本
现在将抓取和解析器整合在一起:请求页面,将响应体传给 scrapeProducts,记录结果,并写入 JSON 文件。这是完整的可直接复制粘贴的脚本。
const { CrawlingAPI } = require("crawlbase"); const cheerio = require("cheerio"); const fs = require("fs"); const api = new CrawlingAPI({ token: "YOUR_CRAWLBASE_JS_TOKEN" }); const samsungProductsURL = "https://www.samsung.com/levant/smartphones/all-smartphones/"; function scrapeProducts(html) { const $ = cheerio.load(html); const products = []; const totalResults = $(".js-pfv2-result-total-count").text().trim(); $(".js-pfv2-content-wrap .js-pfv2-product-card").each((_, element) => { const card = $(element); const color = card .find(".option-selector-v2__color-name-text-in") .text() .trim(); const variants = card .find(".option-selector-v2__size-text") .map((_, el) => $(el).text().trim()) .get(); const url = card .find(".pd03-product-card__product-image-link") .attr("href"); products.push({ name: card.find(".pd03-product-card__product-name-text").text().trim(), model: [color, ...variants].filter(Boolean).join(", "), rating: card.find(".rating__point span:last-child").text().trim(), reviewCount: card.find(".rating__count").text().replace(/[^0-9]/g, ""), specifications: card .find(".pd03-product-card__spec-list .pd03-product-card__spec-item") .map((_, el) => $(el).text().trim()) .get() .join(", "), url: url && (url.startsWith("http") ? url : `https://www.samsung.com${url}`), }); }); return { totalResults, products }; } api .get(samsungProductsURL, { ajax_wait: true, page_wait: 10000 }) .then((response) => { if (response.statusCode !== 200) { throw new Error(`Failed to fetch HTML. Status: ${response.statusCode}`); } const data = scrapeProducts(response.body); console.log(data); fs.writeFileSync("samsung-scraped.json", JSON.stringify(data, null, 2)); }) .catch(console.error);
再次运行 node index.js。脚本获取渲染后的目录,解析每张产品卡片,打印结果,并保存到 samsung-scraped.json。totalResults 字段捕获商城显示的数量,让你可以将页面报告的产品数量与你实际解析到的卡片数量进行比对。
输出示例
每条条目是一部手机,字段映射到整洁的键名。JSON 文件的精简示例:
{ "totalResults": "42 results", "products": [ { "name": "Galaxy S24 Ultra", "model": "Titanium Yellow, 256 GB, 512 GB, 1 TB", "rating": "4.3", "reviewCount": "128", "specifications": "Industry-leading hardware meets world-changing AI, Made with titanium. Built to last, 200MP high-resolution photography", "url": "https://www.samsung.com/levant/smartphones/galaxy-s24-ultra/" }, { "name": "Galaxy A04s", "model": "Black, 64 GB, 128 GB", "rating": "5.0", "reviewCount": "17", "specifications": "6.5\" Infinity-V Display, 50MP Camera, 5000mAh Battery", "url": "https://www.samsung.com/levant/smartphones/galaxy-a/galaxy-a04s-black-64gb-sm-a047fzkgmeb/" } ] }
从这里,JSON 可以直接导入数据库或 notebook。如果你更想要电子表格格式的平铺文件,将数组映射成行并写入 CSV;字段集保持不变。
跨分类和页面扩展
一个目录页面是基本构建块。真实任务会收集多个页面。三星按区域和分类(所有智能手机、可折叠屏、平板等)发布独立的产品列表 URL,较长的列表会分页或随滚动加载更多卡片。要扩展,维护一个产品列表 URL 的清单,对每个 URL 运行同样的抓取和解析,然后合并结果。
const listingURLs = [ "https://www.samsung.com/levant/smartphones/all-smartphones/", "https://www.samsung.com/levant/smartphones/galaxy-z/", ]; async function scrapeAll(urls) { const all = []; for (const url of urls) { const response = await api.get(url, { ajax_wait: true, page_wait: 10000 }); if (response.statusCode === 200) { all.push(...scrapeProducts(response.body).products); } await new Promise((r) => setTimeout(r, 2000)); } return all; }
请求之间的短暂 setTimeout 控制运行节奏,避免对商城造成压力。由于所有产品列表共享相同的卡片布局,同一个 scrapeProducts 解析器可跨所有分类工作。如果某个分类使用无限滚动网格而非分散的页面,Crawling API 可以在捕获前滚动页面,让额外的卡片出现在你解析的 HTML 中。
保持不被封锁
Crawling API 处理了避免被封锁的困难部分:它在真实浏览器中渲染每个页面,并通过轮换住宅 IP 路由请求,使你的流量看起来像普通访客而非单个轰炸地址。保持合理的请求频率,如上所示在批次之间加入短暂延迟,并监控状态码,以便在出现挑战时及时退出。如果你自己用 Puppeteer 构建方案,代理轮换和 IP 健康是需要投入的部分。更广泛的内容请参见如何在不被封锁的情况下抓取网站。
抓取三星合法吗?
抓取公开目录数据的风险远小于抓取登录后的内容,但答案仍取决于你收集什么、如何使用,以及网站的规则。本指南故意将范围限定在公开产品信息:三星商城向每位无账号访客展示的型号名称、价格、评分、评论数、主要规格和产品 URL。这是购物者看到的数据,为价格追踪或研究目的收集这些数据是合法性最强的情形。
在正式大量运行之前,阅读三星的服务条款并检查其 robots.txt,了解哪些路径被要求爬虫绕过,并遵守这些要求。将请求频率保持在不会给其服务器造成压力的范围内。本演示不涉及登录后的内容、个人或账户数据、订单历史,或你要以自己名义转载的版权媒体(如产品图片)。这些超出了本文范围,触碰它们违反三星的条款。
对于商业或大规模使用,优先选择官方渠道。如果你需要批量目录或定价数据用于转售或分发,三星的合作伙伴和开发者计划是正确的渠道,寻求许可或数据源比大规模抓取更清晰。当公开目录确实是你所需内容的唯一来源时,将工作限定在公开数据范围内,遵守网站明示的规则,如果你的使用场景是商业性质或不确定,请咨询法律专业人士。
核心要点
- 解析前先渲染。三星目录在客户端加载,普通请求返回空壳;带 JS token 的 Crawling API 先渲染网格。
-
一次调用涵盖渲染和可信 IP。带
ajax_wait和page_wait的api.get等待延迟加载的网格,并在服务器端通过住宅 IP 路由。 - cheerio 完成提取。将名称、型号、评分、评论数、规格和 URL 映射到商城卡片的选择器,并预期这些类名会随时间变化。
- 通过循环产品列表 URL 并控制节奏来扩展。同一解析器跨所有分类工作,因此真实任务只需一个产品列表加上请求之间的短暂延迟。
-
坚守公开数据。只收集任何人都能看到的目录字段,遵守三星的服务条款和
robots.txt,商业或大规模需求优先选择官方渠道。
常见问题
为什么普通请求从三星返回不到产品?
因为商城在客户端用 JavaScript 渲染其产品网格。原始 HTTP 请求返回页面外壳,但目录槽仍为空,因为产品、价格和评分只有在浏览器中运行页面脚本后才会填充。Crawling API 的 JS token 先渲染页面,确保 cheerio 解析时卡片已存在。
三星需要普通 token 还是 JS token?
使用 JS token。普通 token 抓取静态 HTML,在三星商城上会导致产品网格为空。JS token 先在真实浏览器中渲染页面,确保名称、型号、价格、评分、评论数、规格和 URL 在解析时均已存在。
我可以从三星产品列表页面抓取哪些数据?
公开目录字段:产品名称、型号和配置选项、价格、平均评分、评论数、每张卡片上的主要规格要点,以及产品 URL。本指南将范围限定在这些公开数据。它不涉及账户信息、订单历史或任何需要登录的内容。
我的 cheerio 选择器返回空字符串,是什么变了?
几乎可以肯定是三星的标记发生了变化。js-pfv2-product-card、pd03-product-card__product-name-text 和 rating__point 等类名随时可能无通知地改变,因此上个月还能用的选择器可能已经失效。在浏览器开发工具中重新检查实时产品列表并更新选择器。定期进行选择器维护对任何生产爬虫来说都是正常的。
我能在不被封锁的情况下抓取多个三星页面吗?
可以,在合理范围内。Crawling API 通过轮换住宅 IP 并在真实浏览器中渲染每个页面,使单次请求看起来像真实访问。保持较低的每 IP 请求频率,如扩展部分所示在请求之间加入短暂延迟,并变换目标而不是循环一个路径。监控状态码,一旦开始遇到挑战就退出。
三星有获取产品数据的官方方式吗?
对于商业或大规模使用,三星的合作伙伴和开发者计划是正确渠道,申请许可或数据源比大规模抓取更清晰。只有当公开目录确实是你所需公开字段的唯一来源时才选择抓取,并将工作范围限定在这些公开数据内。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

