🎯 一款可定制、具备反检测功能的云浏览器,由自主研发的 Chromium驱动,专为网页爬虫AI 代理设计。👉立即试用
返回博客

如何使用代理浏览器抓取家得宝产品数据

Ava Wilson
Ava Wilson

Expert in Web Scraping Technologies

04-May-2026

主要收获:

  • 一个产品详情页抓取,完整的产品架构。 家得宝的产品详情页HTML在服务器端嵌入了一个JSON-LD Product 块——名称、品牌、SKU、型号、GTIN、图片、描述、优惠(价格、货币、可用性)、综合评分以及前十条评论。这是快路径:没有等待水合,没有React外壳的困扰。
  • 水合字段填补其余部分。 销售标识、履行选项(运输/自取/配送)、每店可用性文本以及产品详情页上的评论轮播在JavaScript运行后到达。Scrapeless Scraping Browser——本指南使用的代理就绪云浏览器——在真实的云浏览器中呈现它们,因此单个CLI调用返回填充的DOM。
  • 每店库存是区分因素。 通过位置选择器模态框设置家得宝商店会返回特定商店的可用性和提货预计时间,与标准产品详情页字段并列——这是通用搜索API未暴露的信号。
  • 大规模搜索和分类。 /s/<query>/b/<category> 列表页面通过 Nao URL 偏移进行分页。驱动产品详情页抓取的相同发现→提取模式返回分页卡片,包含productId、标题、品牌、价格、评分、评论数、图片和规范产品详情页URL。
  • 清洁的评论架构。 每条评论提取器发出扁平的、语义化的形状——idtitletextratingbadgesreviewer.nametimeoriginal_source.nameimages[].linktotal_positive_feedbacktotal_negative_feedback——可以直接映射到典型的评论智能管道,无需重命名。
  • 美国代理出口是必需的——100%。 家得宝仅为美国零售网站。每次会话都必须使用 --proxy-country US
  • 使用规范产品URL。 可靠的产品详情页目标是 /p/<slug>/<productId>(带有slug);仅包含ID的裸URL,如 /p/<productId> 在验证中返回家得宝的通用错误页面。评论页面目标是 /p/reviews/<slug>/<productId>/<page>
  • 发现 → 提取。 首先使用 get html 从语义锚点(data-testidaria-label[itemprop]、语义ID)读取实时DOM,然后对实际渲染的内容编写 eval 选择器。类名会轮换;语义锚点不会。

引言:使用代理浏览器抓取家得宝产品数据

家得宝是美国最大的家居改善零售商,按收入计算,提供的公共目录涵盖家电、工具、建筑材料和服务。对于竞争定价团队、最低广告价格合规监测、通过家得宝销售的品牌所有者、库存管道和以评论为驱动力的产品研究人员,产品详情页、搜索/分类和每店库存表面是驱动管道的数据。

目录是客户端水合的。典型的产品详情页以一个薄的HTML外壳到达,内含一个服务器渲染的JSON-LD Product 块,而React在加载包后填补其余内容——销售价格、当前促销、履行选项、商店可用性、评分直方图以及产品详情页上的评论轮播在渲染后均被填充。纯HTTP抓取返回外壳;数据在一次渲染周期后存在。

这篇文章讲述了基于Scrapeless Scraping Browser的终端优先工作流——一个能够处理JavaScript渲染、住宅代理出口和会话绑定状态以进行每店库存检查的代理就绪云浏览器。以下步骤1-8涵盖完整的产品详情页提取(JSON-LD快路径 + 水合字段)、搜索/分类分页、解锁特定商店可用性的选择器流程,以及评论管道(来自JSON-LD的前10条评论,加上渲染DOM的分页、排序和过滤)。

每个步骤都涉及使用scrapeless-scraping-browser CLI文档。重点是代理浏览器体验:以自然语言描述您需要的产品数据形状,让技能在底层驱动CLI。

有关其他零售商上相同的发现→提取模式,请参阅亚马逊抓取文章


您可以用它做什么

  • 竞争定价。 在滚动周期中跟踪竞争SKU上的价格、销售标志和促销文本;警报最低广告价格违规。
  • 最低广告价格合规监测。 品牌所有者跟踪目录中第三方卖家的定价,并标记低于最低广告价格的列表以进行强制执行。
  • 库存和履行智能。 通过位置选择器流程提供的每店库存和提货预计时间信号揭示了区域可用性,而通用搜索API则未能提供。
  • 目录采集。 搜索和分类列表向下游管道提供标准化的产品架构(productId、标题、品牌、价格、评分、评论数、图片、规范URL)。
  • 评论智能。 情感分析、投诉聚类、验证购买者比例追踪、照片证据收集——扁平的每条评论架构适应现有评论管道。
  • 品牌监测。 跟踪第一方和竞争品牌在各类别中的评论,以检测发布时间的评论轰炸或持续的质量回退。
  • 产品开发。 将评论中的重复负面主题转化为产品路线图输入、支持文档、替换部件更改或列表页面改进。

为什么选择无刮擦抓取浏览器

无刮擦抓取浏览器是一个可定制的、反检测的云浏览器,专为网络爬虫和AI代理而设计。专门针对Home Depot,它提供:

  • 美国住宅代理 通过 --proxy-country US — Home Depot所需。
  • 云端JavaScript渲染,确保价格、履行、店内可用性、评论轮播、排序下拉框、照片过滤器和分页条都以填充状态到达,而不是作为React壳。
  • 会话持久性 通过 --session-id,在快照 → 点击 → 填充操作过程中,为商店定位器、排序/过滤和分页流程保持一致的cookie和应用状态。
  • 反检测指纹识别 在每个会话上,使PDP和列表页面与自然流量呈相同的样子。
  • 单一CLI接口 — 所有用于发现、提取和分页步骤的操作 (open, wait, snapshot, eval, get, click, fill, cookies) 只需一个CLI调用。

通过 注册Scrapeless 获取免费的API密钥,并加入我们的官方社区。完整的CLI接口文档请参见 skill-dev/SKILL.md代理解决方案 页面涵盖支持云浏览器的住宅代理计划。
Scrapeless 官方Discord社区
Scrapeless 官方Telegram社区


先决条件

  • Node.js 18或更新版本。
  • 一个Scrapeless账户和API密钥 — 在 app.scrapeless.com 注册。
  • jq(可选,用于shell脚本中的JSON解析 — 下面提供了一个可移植的grep备用方案)。
  • 对终端有基本的了解。

安装

下面的配方在 scrapeless-scraping-browser CLI上运行。设置分为三个步骤 — CLI用户和AI代理用户都需要#1和#2;AI代理用户还需完成#3。

1. 安装CLI包

bash Copy
npm install -g scrapeless-scraping-browser

这将提供每一步都调用的 scrapeless-scraping-browser 二进制文件。该技能不带有自己的运行时 — 它将命令模式加载到您的AI代理中,但CLI本身必须先安装。

2. 配置您的API密钥

app.scrapeless.com 获取您的令牌,然后将其存储在CLI可以读取的位置:

bash Copy
scrapeless-scraping-browser config set apiKey your_api_token_here
scrapeless-scraping-browser config get apiKey   # 验证

使用AI代理? 该技能的指示要求代理在进行任何会话调用之前进行身份验证。如果在代理首次请求CLI时未设置API密钥,代理将提示您并为您运行 config set apiKey … 命令。

配置文件位于 ~/.scrapeless/config.json,访问权限限制在当前用户,优先于环境变量,并可在代理和CI运行器之间移植。对于CI管道,建议使用:

bash Copy
export SCRAPELESS_API_KEY=your_api_token_here

3. 在您的AI代理中安装Scrapeless技能

这是与步骤1 不同的步骤。步骤1安装了 CLI二进制文件 — 这是您的代理调用的运行时。 技能 是教您代理 如何 正确调用它(选择器、等待、重试模式、发现 → 提取工作流)。这两者是不同的,您需要两者。

技能是一个包含 SKILL.md + skill.json + references/ 的文件夹。规范来源是 scrapeless-ai/scrapeless-agent-browser → skills/scraping-browser-skill GitHub上的仓库。

要在Claude Code、Cursor、VS Code + GitHub Copilot、OpenAI Codex CLI或Gemini CLI中安装它,请遵循 Scrapeless AI代理安装指南 — 它提供每个代理的复制粘贴命令(bash和Windows PowerShell)。安装后重新加载您的代理,以使技能处于活动状态。

技能在您的代理操作上下文中前期加载的内容:

  • 身份验证 — 检查 ~/.scrapeless/config.jsonSCRAPELESS_API_KEY,如果缺失,提示您设置。
  • 发现 → 提取工作流程 — 首先使用 get html "<region>" 读取实时 DOM,识别稳定锚点(data-testidaria-label、语义 ID、[itemprop='review']),然后根据实际渲染情况编写 eval 选择器。
  • Home Depot 的等待陷阱 — 在 open 和任何选择器等待之间使用 wait 1500 以避免冷会话的 chrome://new-tab-page/ 竞争;针对评论卡元素使用 wait '<review-anchor>' 而不是普遍的 networkidle,因为 Home Depot 不断发射从未结束的懒惰信号。
  • 选择器语法 — 何时使用 CSS 选择器与无障碍引用(来自 snapshot -i@e1)。
  • 并行 CLI 工作进程 — 单壳 && 链接,唯一会话名称,每个主机 ≤3 个并发工作进程。
  • 常见陷阱eval 返回 JSON 引用值,open 在成功导航时退出非零状态,连接关闭时会话终止。
  • 完整命令参考new-sessionopenwaitevalgetclickfillsnapshotcookiesrecordingstop 的每个标志。

4. 验证技能是否已连接

在第一次真正的 Home Depot 抓取之前,使用一个安全提示对安装进行烟雾测试:

“使用 Scrapeless 技能,打开 https://example.com 并告诉我页面标题。”

代理应生成一个 Scrapeless 会话,导航并回复 “示例域”。如果这两个词返回,说明技能已加载,API 密钥已设置,云浏览器可达。

如果失败:

症状 可能原因 修复
“我没有工具/技能去做这个” 本代理会话中未加载技能 通过 技能安装指南 重新安装并重新加载代理
身份验证失败 / 401 API 密钥未设置 重新运行 scrapeless-scraping-browser config set apiKey <token>(安装步骤 2)
找不到命令 CLI 二进制文件在 PATH 上缺失 重新运行安装步骤 1
Home Depot 上的 ERR_TUNNEL_CONNECTION_FAILED 代理池在分配时没有可用的住宅 IP 生成一个新会话 — 保持 --proxy-country US 并稍后重试
挂起 / 降落在 chrome://new-tab-page/ 冷会话等待竞争 请代理重试 — 技能知道在 open 和下一个等待之间插入 wait 1500
Home Depot 返回通用错误页面 非美国出口,仅 ID URL,或临时会话指纹标志 确认 --proxy-country US,使用标准的 /p/<slug>/<productId> URL,生成一个新会话,并重试
每次新会话调用时显示 Scrapeless session has been terminated and cannot be reconnected 本地守护进程缓存了一个现已终止的会话 ID 并不断尝试重新连接 结束守护进程并清除其 pid 文件,然后生成一个新会话:Stop-Process -Id (Get-Content "$env:USERPROFILE\.scrapeless-scraping-browser\default.pid") -Force; Remove-Item "$env:USERPROFILE\.scrapeless-scraping-browser\default.pid","$env:USERPROFILE\.scrapeless-scraping-browser\default.port" -Force(Windows 上的 PowerShell)。在 Linux/macOS 上路径为 ~/.scrapeless-scraping-browser/

如何实际使用这个:提示你的代理

安装后,通过 与代理交谈 来抓取 Home Depot 产品数据 — 而不是复制粘贴 bash。技能将选择器、等待、重试分类器以及发现 → 提取模式加载到代理上下文中,因此一行自然语言提示就足以返回结构化 JSON。

你可以粘贴的提示

你对代理说 你得到的反馈
“获取 Home Depot 产品 204279858 的完整产品架构(价格、品牌、型号、图片、可用性)。” 第 2 步 JSON-LD 产品架构 + 第 3 步充填的价格/履行负载
“跟踪此 Home Depot PDP 的价格 + 销售标志。” pricewasPriceonSalepromotioncurrency
“在 Home Depot 搜索 '无绳钻' 并返回前 5 页的结果。” 分页列出卡片:productId、标题、品牌、价格、评级、评论数、图片、产品 URL
“检查 ZIP 90015 下产品 204279858 的库存。” storeavailabilityTextpickupEtastockCount(每个商店的负载)
“对于这 20 个产品 ID,获取价格 + ZIP 33101 的每个商店可用性。” 每个 ID 返回一个产品数据 JSON,带有商店集的履行负载
“将 Home Depot 产品 ID 326716329 解析到其标准评论 URL,然后返回评论 JSON。” 标准 URL 加上产品级摘要和评论数组
“从这个 /p/reviews/... URL 获取前 100 条 Home Depot 评论,最新的在前。” 包含页面和每页索引元数据的分页评论
“仅获取此 Home Depot 产品的照片评论。” 过滤的评论,条件为 images.length > 0
“对于产品 204279858,仅列出已验证购买者的评论。” 过滤的评论,条件为已验证购买者徽章
“获取最新评论,然后按有用票数排序,返回前 30 条。” 在 UI 排序流程之后的评论,按有用票数排名
"打开产品页面,设置商店邮政编码90015,然后抓取商店上下文评论。" 从设置的商店浏览会话收集的评论(见第5步)

示例:获取产品204279858(DEWALT钻头)的评论

您输入:

"获取家得宝产品204279858的顶部评论的标题、文本、评级和评论者名称。返回JSON。"

代理的计划(用简单的英语):

  1. 204279858解析为规范PDP URL /p/<slug>/204279858
  2. 创建一个美国出口会话(--proxy-country US);在暂时的os error 10054 / 503上重试一次。
  3. 打开PDP URL,等待1500,然后等待'h1'以避免冷会话竞争。
  4. eval针对script[type="application/ld+json"]解析嵌入的Product架构,并返回前10条评论和汇总。
  5. 如果需要超过10条评论,请导航到/p/reviews/<slug>/204279858/<page>,并对每页运行渲染的DOM提取器(步骤6)。

您得到的返回内容(示例输出,正文部分为长度修剪):

json Copy
{
  "productId": "204279858",
  "productUrl": "https://www.homedepot.com/p/DEWALT-20V-MAX-Cordless-1-2-in-Drill-Driver-2-20V-1-3Ah-Batteries-Charger-and-Bag-DCD771C2/204279858",
  "productName": "DEWALT 20V MAX Cordless 1/2 in. Drill/Driver, (2) 20V 1.3Ah Batteries, Charger and Bag DCD771C2",
  "brand": "DEWALT",
  "sku": "1000014677",
  "modelNumber": "DCD771C2",
  "overallRating": 4.7,
  "totalReviews": 11168,
  "reviewsReturned": 10,
  "reviews": [
    {
      "title": "很好的工具!",
      "text": "我使用这个已经两个礼拜了,值得每一分钱。质量非常出色...",
      "rating": 5,
      "reviewer": { "name": "kevein" },
      "time": null,
      "original_source": { "name": "homedepot.com" }
    },
    {
      "title": null,
      "text": "我购买了德瓦尔特20V最大钻/驱动器来替代一款古老的克拉夫茨曼无绳钻...",
      "rating": 5,
      "reviewer": { "name": "DIYer_Bill" },
      "time": null,
      "original_source": { "name": "homedepot.com" }
    }
    // ... 还有8条相似的评论
  ]
}

time返回为null是因为家得宝在JSON-LD评论对象中不包含datePublished — 时间戳存在于渲染的评论页面DOM中,因此如果需要评论时间戳,则通过第6步路径获取它们。

这就是这个抓取的用户界面。步骤1-8中的bash、选择器和等待是技能要求代理执行的 — 您不必输入它们。

形状提示:如何控制返回内容

表述 效果
"…返回JSON" / "…作为CSV" 输出格式
"…字段:标题、文本、评级、时间仅" 限制代理提取的字段
"…前25条" / "…第1–10页" 分页深度
"…最新优先" / "…最低评级优先" 触发UI排序流程
"…仅照片评论" 触发照片评论过滤
"…仅验证过的购买者" 按徽章过滤提取的评论
"…保存到reviews.jsonl" 将每条评论写入文件
"…然后总结前5条投诉" 链接提取后的分析过程
"…先设置商店邮政编码90015" 在抓取之前触发商店定位流程

这就是工作流程。 下面的步骤1-8是幕后的参考 — 阅读它们一次以了解发现 → 提取模式是如何组合的;然后相信您的代理来应用它。


步骤1 — 连接到无抓取浏览器

创建一个美国出口会话。

bash Copy
SESSION=$(scrapeless-scraping-browser new-session \
  --name "homedepot-product-data" \
  --ttl 1800 \
  --recording true \
  --proxy-country US \
  --json | jq -r '.data.taskId')

echo "会话: $SESSION"

没有jq的便携式后备:

bash Copy
SESSION=$(scrapeless-scraping-browser new-session \
  --name "homedepot-product-data" --ttl 1800 --recording true \
  --proxy-country US --json \
  | grep -oE '"taskId":"[^"]*"' | cut -d'"' -f4)

住宅代理分配偶尔会在第一次尝试时返回瞬时503 — 重试一次。如果出现ERR_TUNNEL_CONNECTION_FAILED,表示代理池在分配时间没有可用的IP;创建一个新会话并稍后重试。


步骤2 — 快速路径:从JSON-LD提取产品架构 + 前10条评论

家得宝PDP HTML嵌入了一个服务器端JSON-LD Product块 — 名称、品牌、SKU、型号、条形码、图片、描述、报价(价格、货币、可用性)、汇总评级,以及前10条评论 — 所有这些都不需要等待加载。产品不提供的Schema.org符合的字段(例如,availabilitysellerpriceValidUntilmpncolor)返回为null,应视为可空。

打开规范的PDP(带有slug — 仅有ID的URL返回通用错误页面),然后eval JSON-LD解析器:

bash Copy
PRODUCT_ID="204279858"

产品_slug="DEWALT-20V-MAX-无绳-1-2英寸-电钻-驱动器-2-20V-1-3Ah-电池-充电器和包-DCD771C2"
PDP_URL="https://www.homedepot.com/p/$PRODUCT_SLUG/$PRODUCT_ID"

scrapeless-scraping-browser --session-id $SESSION open "$PDP_URL"

简短暂停,以确保下一个等待不会在导航提交前解析到预热的

chrome://new-tab-page/。

scrapeless-scraping-browser --session-id $SESSION wait 1500

等待 H1 产品标题 - JSON-LD 块在

相同的静态 HTML 中由服务器渲染,并在 H1 可见时保证存在。

(不要直接等待 script[type="application/ld+json"] - wait 默认

是“可见”状态,<script> 元素从不可见。)

scrapeless-scraping-browser --session-id $SESSION wait 'h1'

scrapeless-scraping-browser --session-id $SESSION eval '
(() => {
const ld = [...document.querySelectorAll("script[type="application/ld+json"]")]
.map((s) => { try { return JSON.parse(s.textContent); } catch { return null; } })
.filter(Boolean);
const product = ld.find((o) => o["@type"] === "Product");
if (!product) return JSON.stringify({ error: "此页面上没有产品 JSON-LD" });

Copy
const productId =  
  product.productID || product.sku ||  
  location.pathname.match(/\/(\d+)(?:\/\d+)?(?:[/?#]|$)/)?.[1] || null;  

const offers = Array.isArray(product.offers) ? product.offers[0] : product.offers || null;  
const offerPrice = offers  
  ? (offers.price ?? offers.priceSpecification?.price ?? offers.lowPrice ?? null)  
  : null;  
const availability = offers?.availability  
  ? String(offers.availability).replace(/^https?:\/\/schema\.org\//, "")  
  : null;  

const images = []  
  .concat(product.image || [])  
  .flat()  
  .filter(Boolean);  

const reviews = (Array.isArray(product.review) ? product.review : product.review ? [product.review] : [])  
  .map((r) => ({  
    title: r.headline || null,  
    text: r.reviewBody || null,  
    rating: Number(r.reviewRating?.ratingValue) || null,  
    reviewer: { name: r.author?.name || null },  
    time: r.datePublished || null,  
    original_source: { name: "homedepot.com" },  
  }));  

return JSON.stringify({  
  productId,  
  productUrl: location.href,  
  productName: product.name,  
  brand: product.brand?.name || product.brand || null,  
  sku: product.sku || null,  
  modelNumber: product.model || null,  
  gtin: product.gtin13 || product.gtin14 || product.gtin12 || product.gtin8 || product.gtin || null,  
  mpn: product.mpn || null,  
  category: product.category || null,  
  description: product.description || null,  
  color: product.color || null,  
  image: images[0] || null,  
  images,  
  offers: {  
    price: offerPrice != null ? Number(offerPrice) : null,  
    currency: offers?.priceCurrency || null,  
    availability,  
    priceValidUntil: offers?.priceValidUntil || null,  
    itemCondition: offers?.itemCondition  
      ? String(offers.itemCondition).replace(/^https?:\/\/schema\.org\//, "")  
      : null,  
    seller: offers?.seller?.name || null,  
  },  
  overallRating: Number(product.aggregateRating?.ratingValue) || null,  
  totalReviews: Number(product.aggregateRating?.reviewCount) || null,  
  reviewsReturned: reviews.length,  
  reviews,  
});  

})()
'

Copy
此单个 PDP 获取返回完整的服务器渲染产品架构 - productId、名称、品牌、sku、型号、gtin、图像、描述、报价(价格、货币、可用性)、聚合评级 - 以及前 10 条评论以扁平、语义化的形式(`id`、`标题`、`文本`、`评级`、`评论者.名称`、`时间`、`original_source.name`、`images[].link`、`total_positive_feedback`、`total_negative_feedback`)。它可靠地返回,而不依赖于 React 外壳进行水合作业。

某些字段是有条件的。JSON-LD 提供 schema.org 标记的产品 - `gtin`、`mpn`、`color`、`priceValidUntil`、`seller` 在大多数目录项中存在,但并非全部(将其视为可为空)。有关折扣标识、当前促销文本和每家商店的可用性,这些信息存储在渲染的 DOM 中,而不是 JSON-LD - 第 3 步覆盖了该表面。至于每个星级的评分直方图(5★/4★/3★/2★/1★ 计数),直方图区域在第 6 步中覆盖的评论页面中内联渲染。

---

## 第 3 步 - PDP 水合字段(价格、履行、图像、规格)

第 2 步 JSON-LD 传递返回静态产品架构。折扣价覆盖、当前促销文本、履行选项(送货到家、店内取货、定期交付)、库存徽章、主图像库以及规格/功能表格都存储在渲染的 DOM 中,并在 React 水合后到达。在相同的 PDP 上推动发现 → 提取流,以语义锚点为目标,这些锚点在类名旋转期间存活。
首先对PDP的价格区域运行`get html`发现通行证,确认活动锚名称,然后将`eval`选择器收紧到页面实际呈现的内容。类名会变化;`data-testid`和`aria-label`模式是稳定的表面。

```bash
# 会话仍然在步骤2的PDP上。等待价格区域呈现,
# 然后查看周围的HTML以确认锚点。
scrapeless-scraping-browser --session-id $SESSION wait '[data-testid*="price" i], [class*="price" i], [data-component*="Price"]'
scrapeless-scraping-browser --session-id $SESSION get html '[data-testid*="price" i], [data-testid*="fulfillment" i]'

从发现的HTML中,存活时间最长的锚点是[data-testid*="price"][data-testid*="fulfillment"][data-testid*="availability"],以及关于履行按钮的aria-label(aria-label*="Ship to Home"aria-label*="Store Pickup"aria-label*="Scheduled Delivery")。然后eval提取器:

bash Copy
scrapeless-scraping-browser --session-id $SESSION eval '
  (() => {
    const text = (el) => (el ? el.textContent.replace(/\s+/g, " ").trim() : null);
    const number = (s) => {
      if (!s) return null;
      const m = String(s).replace(/,/g, "").match(/-?\d+(?:\.\d+)?/);
      return m ? Number(m[0]) : null;
    };

    const priceText =
      text(document.querySelector("[data-testid*=\"price\" i]")) ||
      text(document.querySelector("[class*=\"price\" i]"));
    const wasPriceText = text(document.querySelector(
      "[data-testid*=\"was-price\" i], [class*=\"was-price\" i], [data-testid*=\"strike\" i]"
    ));
    const onSale = !!wasPriceText && priceText !== wasPriceText;

    const promotion = text(document.querySelector("[data-testid*=\"promo\" i], [data-testid*=\"saving\" i]"));

    const fulfillment = [...document.querySelectorAll(
      "[data-testid*=\"fulfillment\" i] button, [aria-label*=\"Ship\" i], [aria-label*=\"Pickup\" i], [aria-label*=\"Delivery\" i]"
    )].map((btn) => {
      const label = btn.getAttribute("aria-label") || text(btn);
      return label ? { option: label, available: !btn.matches("[disabled], [aria-disabled=\"true\"]") } : null;
    }).filter(Boolean);

    const availabilityText = text(document.querySelector(
      "[data-testid*=\"availability\" i], [data-testid*=\"in-stock\" i]"
    ));

    const heroImg = document.querySelector(
      "img[data-testid*=\"main-image\" i], [data-testid*=\"image-gallery\" i] img, picture img"
    );

    const features = [...document.querySelectorAll(
      "[data-testid*=\"feature\" i] li, [class*=\"feature\" i] li, [data-testid*=\"highlights\" i] li"
    )].map(text).filter(Boolean).slice(0, 20);

    const specs = Object.fromEntries(
      [...document.querySelectorAll("[data-testid*=\"specs\" i] tr, [class*=\"specs\" i] tr")]
        .map((row) => {
          const cells = [...row.querySelectorAll("th, td")].map(text);
          return cells.length >= 2 ? [cells[0], cells.slice(1).join(" ")] : null;
        })
        .filter(Boolean)
    );

    return JSON.stringify({
      productUrl: location.href,
      price: number(priceText),
      priceText,
      wasPrice: number(wasPriceText),
      onSale,
      promotion,
      currency: "USD",
      fulfillment,
      availabilityText,
      image: heroImg?.currentSrc || heroImg?.src || null,
      features,
      specs,
    });
  })()
'

对于大多数定价智能管道,对同一会话运行步骤2和步骤3:步骤2捕获规范的JSON-LD架构(productId,brand,sku,model,gtin,image,offers),步骤3捕获水合覆盖(销售标志,促销,履行选项,库存徽章,规格/特性)。两个有效负载在productUrl上无缝合并。


第4步 — 搜索和类别列表

Home Depot通过/s/<query>公开搜索,通过/b/<category-slug>公开类别着陆页。两个都通过Nao URL偏移进行分页(?Nao=24?Nao=48,…)。每页约显示24个产品卡片。

bash Copy
QUERY="cordless drill"
# 在查询中编码空格(Home Depot使用标准URL编码)
SEARCH_URL="https://www.homedepot.com/s/${QUERY// /%20}"

scrapeless-scraping-browser --session-id $SESSION open "$SEARCH_URL"
# 搜索页面有两个阶段的渲染:一个React占位符骨架
# 首先挂载一个卡片,随后真实的约24卡片网格填充。一个裸
# `wait '[data-testid*="product-pod" i]'`可能会在占位符上解析,因此
# 提取器会在空网格上运行。等待真实网格
# (≥ 5张卡片)而不是任何单个匹配项。
scrapeless-scraping-browser --session-id $SESSION wait 3000
scrapeless-scraping-browser --session-id $SESSION eval \
  'document.querySelectorAll("[data-testid*=\"product-pod\" i], [data-pod-position]").length >= 5'

scrapeless-scraping-browser --session-id $SESSION eval '
  (() => {
    const text = (el) => (el ? el.textContent.replace(/\s+/g, " ").trim() : null);
    const number = (s) => {
      if (!s) return null;
```javascript
const m = String(s).replace(/,/g, "").match(/-?\d+(?:\.\d+)?/);
return m ? Number(m[0]) : null;
};
const cards = [...document.querySelectorAll("[data-testid*=\"product-pod\" i], [data-pod-position]")];
const results = cards.map((c) => {
  const link = c.querySelector("a[href*=\"/p/\"]");
  const href = link?.getAttribute("href");
  const productId = href?.match(/\/(\d{6,})(?:[/?#]|$)/)?.[1] || null;
  const titleEl = c.querySelector("[data-testid*=\"product-title\" i], [class*=\"product-title\" i], h2, h3");
  const ratingEl = c.querySelector("[aria-label*=\"out of\" i]");
  const reviewCountEl = c.querySelector("[data-testid*=\"review-count\" i], [class*=\"review-count\" i]");
  const priceEl = c.querySelector("[data-testid*=\"price\" i], [class*=\"price\" i]");
  const brandEl = c.querySelector("[data-testid*=\"brand\" i], [class*=\"brand\" i]");
  const img = c.querySelector("img");
  return {
    productId,
    productUrl: href ? new URL(href, location.origin).href : null,
    title: text(titleEl),
    brand: text(brandEl),
    price: number(text(priceEl)),
    priceText: text(priceEl),
    rating: number(ratingEl?.getAttribute("aria-label")),
    reviewCount: number(text(reviewCountEl)),
    image: img?.currentSrc || img?.src || null,
  };
}).filter((r) => r.productId || r.productUrl);
const offset = Number(new URLSearchParams(location.search).get("Nao") || 0);
return JSON.stringify({
  query: location.href,
  page: Math.floor(offset / 24) + 1,
  pageSize: results.length,
  results,
});
})()
'

分页循环 前五页:

bash Copy
for offset in 0 24 48 72 96; do
  scrapeless-scraping-browser --session-id $SESSION open "$SEARCH_URL?Nao=$offset"
  scrapeless-scraping-browser --session-id $SESSION wait 1500
  scrapeless-scraping-browser --session-id $SESSION wait '[data-testid*="product-pod" i], [data-pod-position]'
  scrapeless-scraping-browser --session-id $SESSION eval '/* 相同的列表提取器 */' \
    > "search-page-$((offset / 24 + 1)).json"
done

对于多个查询的更高并发,在每个查询中生成新会话,并将每台主机的并发限制为≤3个工作线程(参见步骤8的并行工作者说明)。类别页面(/b/<category-slug>)接受相同的Nao偏移和相同的提取器。


步骤5 — 每店库存检查(邮政编码)

Home Depot 的独特特点是 每店可用性:通过位置选择器模态设置 Home Depot 商店,会在同一产品详细页面上返回特定于商店的库存和取货预计时间文本,以及全国范围的履行选项。相同的流程也可以将“最有帮助”的评论排序向地方性的有用投票倾斜,因此单个商店设置的会话同时涵盖库存和评论上下文用例。

代码片段中的@e15 / @e22 / @e25 可访问性引用是占位符 — Scrapeless snapshot -i 在每个页面渲染时动态分配引用。捕获快照,找到标记为“更改商店”的行、邮政编码输入和搜索/确认按钮,然后在运行点击序列之前替换为真实的@e<n>数字。使用get html发现通行证确认[data-testid*="store-locator" i] / [data-testid*="store-name" i]选择器与实时DOM — 这些符合Home Depot的一般data-testid约定,但应收紧到页面渲染的内容。

bash Copy
ZIP="90015"

# 1. 打开 PDP并显示位置选择器的可访问性引用。
scrapeless-scraping-browser --session-id $SESSION open \
  "https://www.homedepot.com/p/$PRODUCT_SLUG/$PRODUCT_ID"
scrapeless-scraping-browser --session-id $SESSION wait 1500
scrapeless-scraping-browser --session-id $SESSION wait '[data-testid*="store-locator" i], [aria-label*="store" i]'
scrapeless-scraping-browser --session-id $SESSION snapshot -i > /tmp/pdp-refs.txt

# 2. 点击“更改商店”/“我的商店” → 填写邮政编码 → 确认。
#    根据快照返回调整下面的@e<n>引用。
scrapeless-scraping-browser --session-id $SESSION click @e15            # 更改商店
scrapeless-scraping-browser --session-id $SESSION wait 800
scrapeless-scraping-browser --session-id $SESSION fill @e22 "$ZIP"      # 邮政编码输入
scrapeless-scraping-browser --session-id $SESSION click @e25            # 搜索/确认
scrapeless-scraping-browser --session-id $SESSION wait 1500
scrapeless-scraping-browser --session-id $SESSION wait '[data-testid*="fulfillment" i], [data-testid*="availability" i]'

# 3. 提取每店可用性 + 商店标签。
scrapeless-scraping-browser --session-id $SESSION eval '
(() => {
  const text = (el) => (el ? el.textContent.replace(/\s+/g, " ").trim() : null);
  return JSON.stringify({
    productUrl: location.href,
    store: text(document.querySelector("[data-testid*=\"store-name\" i], [data-testid*=\"my-store\" i]")),
    zip: "'$ZIP'",

可用性文本:文本(document.querySelector("[data-testid*="availability" i], [data-testid*="in-stock" i]")),
取货预计时间:文本(document.querySelector("[data-testid*="pickup" i], [aria-label*="Pickup" i]")),
库存数量:文本(document.querySelector("[data-testid*="stock-count" i], [class*="stock-count" i]")),
});
})()
'

Copy
商店设置状态在会话期间保存在 cookies 中 - 后续调用(步骤 3 充水字段评估,步骤 6 渲染评论提取,步骤 7 排序流程)在不重新驱动模态窗口的情况下返回区域视图。

**Cookie 设置回退。** 当位置选择器模态不可访问时(弹出窗口拦截器,A/B 变体,瞬态 WAF),可以通过 cookies 获取相同结果 - `THD_LOCSTORE` / `THD_PERSIST` 储存商店 ID 和邮政编码。通过 `scrapeless-scraping-browser cookies set` 设置它们,并且下一个 `open` 将直接返回商店上下文 PDP,而无需点击流程。

---

## 步骤 6 - 通过渲染的轮播获得 11+ 评论

当工作流程需要超过 JSON-LD 前 10 条(完整评论库,应用的排序/过滤,自定义日期范围)时,渲染的评论小部件就是表面。评论位于 `/p/reviews/<slug>/<productId>/<page>` 和 PDP 轮播内;两者通过 `assets.thdstatic.com/experiences/paginated-product-reviews/*` 的同一分页产品评论微前端渲染。

驱动发现 → 提取流程:

```bash
REVIEWS_URL="https://www.homedepot.com/p/reviews/$PRODUCT_SLUG/$PRODUCT_ID/1"

scrapeless-scraping-browser --session-id $SESSION open "$REVIEWS_URL"
scrapeless-scraping-browser --session-id $SESSION wait 1500
# networkidle 对 Home Depot 不适用 - 用评论卡锚点代替。
scrapeless-scraping-browser --session-id $SESSION wait "#reviews, [itemprop='review'], [data-testid*='review' i]"

# 检查评论区域 HTML 以确认锚点
scrapeless-scraping-browser --session-id $SESSION get html "#reviews, [data-component*='Review' i]"

从返回的 HTML 中,识别稳定的锚点:评论卡通常位于 [itemprop='review'][data-testid*='review' i],或重复的 <article>/<li> 结构;星级小部件带有 aria-label,例如 "5 out of 5";评论者姓名靠近重复的标题模式;经过验证的购买者徽章展示稳定的文本内容;分页按钮具有可访问标签。

然后进行提取评估:

bash Copy
scrapeless-scraping-browser --session-id $SESSION eval '
  (() => {
    const text = (el) => (el ? el.textContent.replace(/\s+/g, " ").trim() : null);
    const number = (v) => {
      if (v == null) return null;
      const m = String(v).replace(/,/g, "").match(/-?\d+(?:\.\d+)?/);
      return m ? Number(m[0]) : null;
    };
    const unique = (xs) => [...new Set(xs.filter(Boolean))];

    const productId =
      location.pathname.match(/\/(\d+)(?:\/\d+)?(?:[/?#]|$)/)?.[1] || null;

    const root =
      document.querySelector("#reviews") ||
      document.querySelector("[data-component*=\"Review\" i]") ||
      document.querySelector("[data-testid*=\"review\" i]") ||
      document.body;

    const cards = [...root.querySelectorAll(
      "[itemprop=\"review\"], [data-testid*=\"review\" i], article, li"
    )].filter((c) => {
      const bodyStr = text(c) || "";
      const hasRating = !!c.querySelector("[aria-label*=\"out of\" i]");
      const looksReviewish = /(verified|recommend|helpful|review|purchased)/i.test(bodyStr);
      return bodyStr.length > 40 && (hasRating || looksReviewish);
    });

    const reviews = cards.map((c) => {
      const bodyStr = text(c) || "";
      const ratingLabel =
        c.querySelector("[aria-label*=\"out of\" i]")?.getAttribute("aria-label") ||
        text(c.querySelector("[data-testid*=\"rating\" i]"));
      const dateEl = c.querySelector("time, [datetime], [data-testid*=\"date\" i]");
      const helpfulText = unique(
        [...c.querySelectorAll("button, [aria-label*=\"helpful\" i]")]
          .map((el) => el.getAttribute("aria-label") || text(el))
      ).join(" ");
      return {
        id: c.getAttribute("id") || c.getAttribute("data-review-id") || null,
        title: text(c.querySelector("[data-testid*=\"title\" i], h3, h4")),
        text: text(c.querySelector("[data-testid*=\"body\" i], p")),
        rating: number(ratingLabel),
        isRecommended: /recommend/i.test(bodyStr)
          ? !/not recommend|would not recommend/i.test(bodyStr) : null,
        badges: unique(
          [...c.querySelectorAll("[data-testid*=\"badge\" i], [class*=\"badge\" i]")].map(text)
            .concat(bodyStr.match(/Verified Purchaser|Verified Buyer|Pro|Staff Pick|Incentivized/gi) || [])
        ),
        reviewer: { name: text(c.querySelector("[data-testid*=\"author\" i], [class*=\"author\" i]")) },
        time: dateEl?.getAttribute("datetime") || text(dateEl),
        original_source: { name: "homedepot.com" },
images: unique([...c.querySelectorAll("img")].map((i) => i.currentSrc || i.src)).map((link) => ({ link })),
        total_positive_feedback: number(helpfulText.match(/(\d+)\s*(?:people\s*)?(?:found\s*)?(?:helpful|yes|up)/i)?.[1]),
        total_negative_feedback: number(helpfulText.match(/(\d+)\s*(?:not helpful|no|down)/i)?.[1]),
        verified: /verified/i.test(bodyStr),
      };
    }).filter((r) => r.text || r.title);

    return JSON.stringify({ productId, productUrl: location.href, reviewsReturned: reviews.length, reviews });
  })()
'

呈现的 DOM 提取器是评论分页的路径(步骤 8),应用的排序/过滤(步骤 7),以及任何时间工作流程需要 11 条以上的评论。将缺失字段视为 null[] 而不是删除键 — 这样可以在家得宝轮换评论卡片模板时保持下游管道的稳定性。

每条评论的 schema: idtitletextratingbadgesreviewer.name(嵌套),timeoriginal_source.name(嵌套),images[].link(对象数组),total_positive_feedbacktotal_negative_feedback,加上不含抓取的额外项 isRecommended(从评论文本派生的布尔值)和 verified(从徽章文本派生的布尔值)。保持缺失字段为 null[],以确保下游消费者在 DOM 轮换时保持稳定。

如果呈现评论的传递返回零张卡片,即使经过等待,微前端尚未完全水合;重试等待,如果返回 Access Denied,请重试新会话,或退回到步骤 2 的 JSON-LD 前 10 个和 GraphQL 分页路径(/federation-gateway/graphql?opname=reviewSentiments)以进行更深的查询。


步骤 7 — 通过用户界面对评论进行排序和过滤

家得宝在评论页面上公开排序选项(最新、最高评分、最低评分、最有帮助、仅照片评论)。在同一持久会话内驱动这些控件 — 快照 → 点击编排保留了 cookies 和应用状态。

在代码片段中的 @e34 / @e35 / @e36 / @e41 引用是占位符 — Scrapeless snapshot -i 根据页面渲染动态分配引用。首先捕获快照,查找排序下拉菜单和过滤按钮的行,然后在运行点击序列之前替换真实数字。

bash Copy
# 提取交互控件的可访问性引用
scrapeless-scraping-browser --session-id $SESSION snapshot -i > /tmp/reviews-refs.txt

# 从快照中识别排序下拉菜单 / 过滤按钮的引用,然后点击。
# 在典型的家得宝评论页面上,这些通常显示为类似:
#   @e34 [button] "排序按:最有帮助"
#   @e35 [button] "仅照片"
#   @e36 [button] "已验证购买者"
# 根据快照返回的内容调整下面的 @ref 数字。

scrapeless-scraping-browser --session-id $SESSION click @e34   # 打开排序下拉菜单
scrapeless-scraping-browser --session-id $SESSION wait 800
scrapeless-scraping-browser --session-id $SESSION snapshot -i > /tmp/sort-options.txt
scrapeless-scraping-browser --session-id $SESSION click @e41   # "最新"选项
scrapeless-scraping-browser --session-id $SESSION wait 1500
scrapeless-scraping-browser --session-id $SESSION wait "#reviews, [data-component*='Review' i]"

# 重新运行提取,等待评论区域重新渲染
scrapeless-scraping-browser --session-id $SESSION eval '/* 与步骤 6 相同的评论提取体 */'

对于仅照片的传递,点击可见的照片评论过滤器;评论区域使用过滤后的集合重新渲染,步骤 6 中的相同提取器仅返回照片评论。如果页面未为特定产品提供照片过滤器,则提取所有可见评论,并在 images.length > 0 上进行后期过滤。


步骤 8 — 通过评论进行分页

家得宝通过 /p/reviews/<slug>/<productId>/<page> 进行评论分页。有两种模式可以使用:

模式 A — 在同一会话内驱动可见的“下一步”控件,在步骤 7 应用过排序/过滤状态并且必须在页面之间保持时很有用:

bash Copy
for page in $(seq 1 5); do
  scrapeless-scraping-browser --session-id $SESSION eval '/* 提取评论 schema */' \
    > "reviews-page-$page.json"

  # 点击可见的“下一步”分页控件
  scrapeless-scraping-browser --session-id $SESSION eval '
    (() => {
      const next = [...document.querySelectorAll("a, button")]
        .find((el) => /next/i.test(el.getAttribute("aria-label") || el.textContent || ""));
      if (!next) return false;
      next.scrollIntoView({ block: "center" });
      next.click();
      return true;
    })()
  '
  scrapeless-scraping-browser --session-id $SESSION wait 1500
  scrapeless-scraping-browser --session-id $SESSION wait "#reviews, [data-component*='Review' i]"
done

模式 B — 每页使用新的会话进行直接 URL 分页,适用于大规模并行提取:

bash Copy
PRODUCT_ID="326716329"
SLUG="NextWall-31-35-sq-ft-Off-White-Faux-Shiplap-Vinyl-Paintable-Peel-and-Stick-Wallpaper-Roll-PP10000"

for page in $(seq 1 10); do
Copy
SID=$(scrapeless-scraping-browser new-session \
    --name "hd-reviews-p$page" --ttl 300 --proxy-country US --json \
    | jq -r '.data.taskId')

  URL="https://www.homedepot.com/p/reviews/$SLUG/$PRODUCT_ID/$page"
  scrapeless-scraping-browser --session-id $SID open "$URL"
  scrapeless-scraping-browser --session-id $SID wait 1500
  scrapeless-scraping-browser --session-id $SID wait "#reviews, [data-component*='Review' i]"
  scrapeless-scraping-browser --session-id $SID eval '/* review-extraction body */' \
    > "reviews-page-$page.json"
  scrapeless-scraping-browser stop $SID
done

每个页面一个新会话,而不是一个长会话。 在理论上,重复使用 --session-id 更有效,但在实践中,Scrapeless 会话在大约 3 次填充页面请求后会开始返回空的 chrome://new-tab-page/ 或终止。每个页面的短 TTL 会话更可靠,易于并行化,并且更容易推理。

运行并行工作者? 默认的 ~/.scrapeless-scraping-browser/ 守护进程在同一主机上的进程之间共享 — 即使每个进程都传递自己的 --session-id,多个工作者也可能会劫持彼此的会话。有效的方法:将一个作业的每个 CLI 调用连接为一个单一的 shell && 调用(从守护进程的角度是原子性的 — 其他工作者无法在您的步骤之间插入),使用唯一的会话名称(端口是从名称中哈希得出的),并将每个主机的并发数量限制在 3 个工作者左右。对于更高的并行度,可以在多个主机之间进行分片。请参阅 skill-dev/SKILL.md 中的“并行 CLI 代理”以获取完整模式。

当存在时按 id 在页面之间去重;当没有 ID 暴露时,回退到 reviewer.name + time + title + text。固定的评论和赞助的小部件偶尔会在页面之间重复。


返回的内容

抓取浏览器返回一个实时 DOM — 提取模式是调用者在 eval 步骤中编写的内容。对于使用第 6 步的发现 → 提取模板进行单次评论页面访问,今天返回的字段看起来像这样:

json Copy
// 模式完全反映第 6 步 eval 输出的内容。
// 字段值是说明性样本,而不是当前任何产品的冻结快照。
{
  "productId": "326716329",
  "productUrl": "https://www.homedepot.com/p/reviews/<slug>/326716329/1",
  "reviewsReturned": 1,
  "reviews": [
    {
      "id": "abc123",
      "title": "易于安装和坚固的质量",
      "text": "该产品安装顺利,运行正常。",
      "rating": 5,
      "isRecommended": true,
      "badges": ["验证购买者"],
      "reviewer": { "name": "HomeDepotCustomer" },
      "time": "2024-04-04",
      "original_source": { "name": "homedepot.com" },
      "images": [],
      "total_positive_feedback": 8,
      "total_negative_feedback": 0,
      "verified": true
    }
  ]
}

第 6 步的 eval 输出 productIdproductUrlreviewsReturnedreviews[]。评分直方图(每星的 overallRatingratings[])、汇总 totalReviews 和产品级元数据由 第 2 步 JSON-LD eval 输出 — 如果工作流需要聚合 + 语料库一起使用,请将第 2 步和第 6 步都放入同一会话中。第 6 步提取器可以通过对 [data-testid*="histogram" i] / [aria-label*="stars" i] 行进行直方图传递进行扩展,但在上面显示的 eval 主体中没有该内容;当您需要时请显式添加。

在大规模运行之前,值得了解一些关于此输出的诚实观察:

  • 水合定时。 位于 assets.thdstatic.com/experiences/paginated-product-reviews/* 的评论微前端分层加载:首先是页面的界面和 H1,然后是直方图和评论卡。如果发现传递仅返回页面界面,请稍等片刻并重新运行 get html,然后再收紧选择器 — 评论卡通常只有一个渲染周期的距离。
  • 选择器稳定性。 [itemprop='review'][data-testid*='review' i]aria-label*='out of' 星级小部件是生命周期最长的锚点。以哈希前缀开头的类名在部署过程中会轮换。
  • 直方图的存在。 评分直方图在评论页面内联呈现,但在不同产品类别中使用不同的 DOM 结构。将其缺失视为可为空,而不是重试 — 对于评论很少的产品,可能根本不会渲染。
  • 验证购买者徽章。 有两个稳定的表面:一个带有徽章样式的 span,携带文字 “验证购买者”,以及徽章包装上的 data-testid。匹配任意一个即可。
  • 照片。 附有照片的评论会在评论卡内暴露 <img> 元素;没有照片的评论就没有 <img> 子元素 — images.length > 0 是一个干净的过滤条件。
Copy
- **WAF 中介页面。** 一些请求会落到 Home Depot 的 `访问被拒绝` 页面上(`bodyLen` ≪ 1000,没有审查标记)。第 6 步中的脚本应检测该页面(例如,`if (/Access Denied|Error Page/i.test(document.title)) throw`),然后调用者会以新会话重试。

## 领取您的免费计划并开始抓取:
加入 Scrapeless 的活跃社区,领取 5-10 美元的 **免费计划**,与其他创新者连接:

[Scrapeless 官方 Discord 社区](https://discord.gg/stFPK2xKHY)
[Scrapeless 官方 Telegram 社区](https://t.me/+uELlyZh2JGw1M2Ux)

---

## 常见问题解答


**Q1: 我需要代理来抓取 Home Depot 吗?**
是的 — 100%。Home Depot 是一个美国零售网站,并在非美国出口上提供通用错误页面。在每个会话中都需要 `--proxy-country US`。

**Q2: 价格和履行选项在哪里?JSON-LD 还是渲染后的 DOM?**
两者都有。JSON-LD 提供了规范的 `offers.price`、`offers.priceCurrency` 和 `offers.availability`(第 2 步)。销售价格覆盖、当前促销文本、每家商店的可用性标志和履行按钮(送货上门 / 自提 / 送货)在 React 重新水化后填充,并从渲染后的 DOM 中提取(第 3 步)。

**Q3: 如何抓取每家商店的库存?**
操作位置选择器模态:打开产品详情页,点击“更改商店”,填写邮政编码,确认。后续在同一会话中的 eval 调用会返回区域视图。有关完整快照的详细信息,请参见第 5 步 → 点击 → 填充编排。cookie 设置回退(`THD_LOCSTORE` / `THD_PERSIST`)在第 5 步末尾提供文档,适用于无法访问模态的环境。

**Q4: 为什么我的会话有时返回 `ERR_TUNNEL_CONNECTION_FAILED`?**
代理池在分配时没有可用的住宅 IP。请生成一个新会话并稍后重试。

**Q5: 为什么页面有时返回 `访问被拒绝`?**
Home Depot 的 WAF 偶尔会对新的分配提出挑战。生成一个新会话并重试。第 6 步提取器应检测错误页面标题并抛出,以便调用者可以使用新会话重试。

**Q6: 搜索和类别页面如何分页?**
通过 `Nao` URL 偏移(`?Nao=24`,`?Nao=48`,……)每页 24 张卡片。第 4 步涵盖了列表提取器和分页循环。类别着陆页面(`/b/<slug>`)接受相同的偏移。

**Q7: 我可以仅抓取照片评论吗?**
可以。首先尝试可见的照片评论过滤器(第 7 步)。如果某个产品缺少该控制项,则提取所有可见评论,并在 `images.length > 0` 上进行后过滤。

**Q8: 如何获得最新或评分最低的评论?**
在同一持久会话内使用 UI 排序控件 — 第 7 步中的快照 → 点击编排。点击相关控件,等待评论区域重新渲染,然后重新运行提取 `eval`。

**Q09: 当 Home Depot 更改 DOM 时会发生什么?**
重新运行发现过程:`get html "<region>"`,识别当前稳定的锚点(`data-testid`、`aria-label`、`[itemprop]`、语义 ID),并调整 `eval` 选择器。不要将哈希类名称作为永久选择器发送。

**Q10: 我可以在一个会话中收集多少产品?**
为了可靠性,保持一个 Scrapeless 会话为小型逻辑抓取(少量 PDP 或少量搜索页面)。对于较大的任务,按任务生成新会话,并保持每个主机的并发 ≤ 3(第 8 步平行工作者注释)。在主机之间进行分片以实现更高的分发。

**Q11: 这可以在没有 AI 代理的情况下运行吗?**
可以。第 1-8 步中的 CLI 命令作为 bash 端到端工作。推荐的路径是代理驱动的工作流(技能 + 自然语言提示),因为该技能携带发现 → 提取模式、等待陷阱和并行工作者规则,从而使提示保持在一行。

**Q12: 为什么使用规范的 `/p/<slug>/<productId>` URL 而不是仅仅使用产品 ID?**
仅包含 ID 的 URL,如 `/p/<productId>` 在验证中返回 Home Depot 的通用错误页面。规范 PDP URL `/p/<slug>/<productId>` 和规范评论 URL `/p/reviews/<slug>/<productId>/<page>` 是可靠的浏览器目标;在打开页面之前将产品 ID 解析到这些形状。

在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。

最受欢迎的文章

目录