Cómo extraer datos de productos de Home Depot con un navegador de agente
Expert in Web Scraping Technologies
Puntos Clave:
- Una búsqueda de PDP, esquema de producto completo. El HTML de la PDP de Home Depot incorpora un bloque
Producten JSON-LD del lado del servidor — nombre, marca, sku, modelo, gtin, imagen, descripción, ofertas (precio, moneda, disponibilidad), calificación agregada y las 10 mejores reseñas. Ese es el camino rápido: sin espera de hidratación, sin un shell de React con el que luchar. - Los campos hidratados completan el resto. Las banderas de venta, opciones de cumplimiento (envío / recogida / entrega), texto de disponibilidad por tienda y el carrusel de reseñas en la PDP llegan después de que se ejecute JavaScript. Scrapeless Scraping Browser — el navegador en la nube listo para agentes que utiliza esta guía — los renderiza en un navegador en la nube real, por lo que una sola llamada CLI devuelve el DOM poblado.
- El stock por tienda es el diferenciador. Establecer una tienda de Home Depot a través del modal de selección de ubicación devuelve disponibilidad específica de la tienda y ETAs de recogida junto a los campos estándar de la PDP — una señal no expuesta a través de APIs de búsqueda genéricas.
- Búsqueda y categoría a escala. Las páginas de listado
/s/<consulta>y/b/<categoría>se paginan a través del offset de URLNao. El mismo patrón de Descubrimiento → Extracción que alimenta la extracción de PDP devuelve tarjetas paginadas con productId, título, marca, precio, calificación, conteo de reseñas, imagen y URL canónica de la PDP. - Esquema de reseñas limpio. El extractor por reseña emite una forma plana y semántica —
id,título,texto,calificación,insignias,nombre.revisor,hora,nombre.fuente_original,imágenes[].enlace,total_feedback_positivo,total_feedback_negativo— que se mapea directamente a canalizaciones de inteligencia de reseñas típicas sin renombrar. - Se requiere salida de proxy de EE. UU. — 100%. Home Depot es un sitio minorista exclusivo de EE. UU.
--proxy-country USes obligatorio en cada sesión. - Usa URLs canónicas de productos. El objetivo confiable de la PDP es
/p/<slug>/<productId>(con el slug); URLs bare ID como/p/<productId>devolvieron la página de error genérica de Home Depot durante la verificación. El objetivo de la página de reseñas es/p/reviews/<slug>/<productId>/<page>. - Descubrimiento → Extracción. Lee el DOM en vivo con
get htmlprimero contra anclas semánticas (data-testid,aria-label,[itemprop], ids semánticos), luego escribe selectoresevalcontra lo que realmente se renderiza. Los nombres de clase giran; las anclas semánticas no.
Introducción: raspado de datos de productos de Home Depot con un navegador de agentes
Home Depot es el minorista de mejoras para el hogar más grande de EE. UU. por ingresos, con un catálogo público que abarca electrodomésticos, herramientas, materiales de construcción y servicios. Para equipos de precios competitivos, monitores de cumplimiento de MAP, propietarios de marcas que venden a través de Home Depot, tuberías de inventario e investigadores de productos impulsados por reseñas, la PDP, búsqueda/categoría y superficies de inventario por tienda son los datos que impulsan la canalización.
El catálogo se hidrata del lado del cliente. Una página típica de detalles del producto llega como un shell HTML delgado con un bloque Product en JSON-LD renderizado del lado del servidor, y React completa el resto una vez que se carga el paquete — precio de venta, promoción actual, opciones de cumplimiento, disponibilidad en la tienda, el histograma de calificaciones y el carrusel de reseñas en la PDP se poblan después de la renderización. El raspado puro por HTTP devuelve el shell; los datos viven un ciclo de renderización después.
Esta publicación recorre un flujo de trabajo centrado en la terminal sobre el Scrapeless Scraping Browser — un navegador en la nube listo para agentes que maneja la renderización de JavaScript, salida de proxy residencial y estado vinculado a la sesión para verificaciones de stock por tienda. Los pasos 1–8 a continuación cubren la extracción completa de PDP (camino rápido de JSON-LD + campos hidratados), paginación de búsqueda/categoría, el flujo de selección de ubicación que desbloquea disponibilidad específica de la tienda y la canalización de reseñas (top-10 del JSON-LD más paginación, orden y filtro de DOM renderizado).
Cada paso se ejecuta a través de la CLI scrapeless-scraping-browser documentada en skill-dev/SKILL.md. El énfasis está en la experiencia del navegador agente: describe la forma de datos de producto que necesitas en lenguaje natural y deja que la habilidad impulse la CLI bajo el capó.
Para el mismo patrón de Descubrimiento → Extracción en otro minorista, consulta el post del scraper de Amazon.
Lo Que Puedes Hacer Con Él
- Precios competitivos. Rastrea precio, banderas de venta y texto de promoción a través de SKUs en competencia en una cadencia continua; alerta sobre violaciones de MAP.
- Monitoreo de cumplimiento de MAP. Los propietarios de marcas rastrean el precio de vendedores de terceros a través del catálogo y marcan listados por debajo de MAP para su aplicación.
- Inteligencia de inventario y cumplimiento. Señales de stock y ETAs de recogida por tienda a través del flujo de selección de ubicación exponen disponibilidad regional que las APIs de búsqueda genéricas no hacen.
- Ingesta de catálogo. Listados de búsqueda y categoría alimentan las canalizaciones aguas abajo con un esquema de producto normalizado (productId, título, marca, precio, calificación, conteo de reseñas, imagen, URL canónica).
- Inteligencia de reseñas. Análisis de sentimiento, agrupamiento de quejas, seguimiento de la proporción de compradores verificados, recopilación de evidencia fotográfica — el esquema plano por reseña se integra en canalizaciones de reseñas existentes.
- Monitoreo de marcas. Realiza un seguimiento de reseñas de marcas propias y de competencia a través de categorías para detectar ataques a las reseñas en el momento del lanzamiento o regresiones sostenidas en la calidad.
- Desarrollo de productos. Convierte temas negativos recurrentes de las reseñas en insumos para la hoja de ruta, documentos de soporte, cambios en piezas de repuesto o mejoras en la página de listado.
Por qué Scrapeless Scraping Browser
Scrapeless Scraping Browser es un navegador en la nube personalizable y a prueba de detección diseñado para rastreadores web y agentes de IA. Para Home Depot específicamente, ofrece:
- Proxies residenciales en EE. UU. a través de
--proxy-country US— requerido para Home Depot. - Renderizado de JavaScript del lado de la nube para que el precio, cumplimiento, disponibilidad en la tienda, el carrusel de reseñas, listas desplegables de orden, filtros de fotos y la barra de paginación lleguen poblados en lugar de como el contenedor React.
- Persistencia de sesión a través de
--session-iden la coreografía de captura instantánea → clic → llenado para los flujos de búsqueda de tienda, orden/filtro y paginación, de modo que las cookies y el estado aplicado se mantengan consistentes a través de comandos de seguimiento dentro de una sola invocación encadenada del shell. - Huella de detección anti-detección en cada sesión, por lo que las PDP y las páginas de listado se renderizan de manera idéntica a la del tráfico orgánico.
- Una única superficie CLI — cada operación necesaria para los pasos de descubrimiento, extracción y paginación (
open,wait,snapshot,eval,get,click,fill,cookies) está a solo una llamada CLI de distancia.
Obtén tu clave API en el plan gratuito registrándote en Scrapeless y únete a nuestra comunidad oficial. La superficie CLI completa está documentada en skill-dev/SKILL.md; la página de Soluciones de Proxy cubre el plan de proxy residencial que respalda el navegador en la nube.
Comunidad Oficial de Discord de Scrapeless
Comunidad Oficial de Telegram de Scrapeless
Requisitos previos
- Node.js 18 o superior.
- Una cuenta Scrapeless y clave API — regístrate en app.scrapeless.com.
jq(opcional, para análisis JSON en scripts de shell; se muestra una alternativa portátil degrepa continuación).- Familiaridad básica con la terminal.
Instalación
Las recetas a continuación se ejecutan en la CLI scrapeless-scraping-browser. La configuración consiste en tres pasos: tanto los usuarios de CLI como los de agente de IA necesitan realizar el #1 y el #2; los usuarios de agente de IA también deben realizar el #3.
1. Instala el paquete CLI
bash
npm install -g scrapeless-scraping-browser
Esto proporciona el binario scrapeless-scraping-browser que cada paso de este documento llama. La habilidad no trae su propio entorno de ejecución; carga patrones de comando en tu agente de IA, pero primero debe instalarse la CLI.
2. Configura tu clave API
Obtén tu token de app.scrapeless.com, luego almacénalo donde la CLI pueda leerlo:
bash
scrapeless-scraping-browser config set apiKey your_api_token_here
scrapeless-scraping-browser config get apiKey # verifica
¿Usando un agente de IA? Las instrucciones de la habilidad indican al agente que se requiere autenticación antes de cualquier llamada de sesión. Si la clave API no está configurada cuando el agente accede por primera vez a la CLI, el agente te lo pedirá y ejecutará el comando config set apiKey … por ti.
El archivo de configuración se encuentra en ~/.scrapeless/config.json con acceso restringido al usuario actual, tiene prioridad sobre la variable de entorno y es portátil entre agentes y ejecutores de CI. Para tuberías de CI, prefiere:
bash
export SCRAPELESS_API_KEY=your_api_token_here
3. Instala la habilidad Scrapeless en tu agente de IA
Este es un paso separado del paso 1. El paso 1 instaló el binario de la CLI — el entorno de ejecución que tu agente invoca. La habilidad es lo que enseña a tu agente cómo invocarla correctamente (selectores, esperas, patrones de reintento, flujo de descubrimiento → extracción). Son dos cosas diferentes y necesitas ambas.
La habilidad es una carpeta que contiene SKILL.md + skill.json + references/. La fuente canónica es el scrapeless-ai/scrapeless-agent-browser → skills/scraping-browser-skill repositorio en GitHub.
Para instalarla en Claude Code, Cursor, VS Code + GitHub Copilot, OpenAI Codex CLI o Gemini CLI, sigue la guía de instalación del Agente de IA Scrapeless — tiene los comandos de copia y pega por agente (bash y Windows PowerShell). Reinicia tu agente después de la instalación para que la habilidad se active.
Lo que la habilidad carga en el contexto operativo de tu agente desde el principio:
- Autenticación — verifica
~/.scrapeless/config.jsonoSCRAPELESS_API_KEYy te pide que lo configures si falta. - Descubrir → Extraer flujo de trabajo — lee el DOM en vivo con
get html "<región>"primero, identifica anclajes estables (data-testid,aria-label, ids semánticos,[itemprop='review']), luego escribe selectoresevalbasados en lo que realmente se está renderizando. - Trucos de espera para Home Depot —
wait 1500entreopeny cualquier espera de selector para evitar la carrera de sesión fríachrome://new-tab-page/;wait '<review-anchor>'contra un elemento de tarjeta de reseña en lugar denetworkidlegeneral, porque Home Depot sigue disparando balizas perezosas que nunca se estabilizan. - Sintaxis de selectores — cuándo usar selectores CSS frente a referencias de accesibilidad (
@e1desnapshot -i). - Trabajadores paralelos de CLI — encadenamiento de un solo shell
&&, nombres de sesión únicos, ≤3 trabajadores concurrentes por host. - Errores comunes —
evaldevuelve valores con comillas JSON,opensale con código diferente de cero en navegación exitosa, las sesiones terminan cuando se cierra la conexión. - Referencia completa de comandos — cada bandera para
new-session,open,wait,eval,get,click,fill,snapshot,cookies,recording,stop.
4. Verifica que la habilidad esté configurada
Antes de la primera extracción real de Home Depot, verifica la instalación con un aviso seguro:
"Usando la habilidad Scrapeless, abre https://example.com y dime el título de la página."
El agente debería crear una sesión Scrapeless, navegar y responder con "Ejemplo de dominio". Si esas dos palabras regresan, la habilidad está cargada, la clave de API está configurada y el navegador en la nube es accesible.
Si falla:
| Síntoma | Causa probable | Solución |
|---|---|---|
| "No tengo una herramienta/habilidad para hacer eso" | Habilidad no cargada en esta sesión de agente | Reinstala a través de la guía de instalación de la habilidad y recarga el agente |
Autenticación fallida / 401 |
Clave de API no configurada | Vuelve a ejecutar scrapeless-scraping-browser config set apiKey <token> (Paso 2 de instalación) |
comando no encontrado |
Binario de CLI faltante en PATH | Vuelve a ejecutar el Paso 1 de instalación |
ERR_TUNNEL_CONNECTION_FAILED en Home Depot |
La piscina de proxies no tenía IP residencial disponible en el momento de la asignación | Crea una nueva sesión — mantén --proxy-country US y vuelve a intentar pronto |
Se cuelga / aterriza en chrome://new-tab-page/ |
Carrera de espera de sesión fría | Pide al agente que vuelva a intentar — la habilidad sabe insertar wait 1500 entre open y la siguiente espera |
| Home Depot devuelve la página de error genérica | Salida no estadounidense, URL solo identificada, o bandera de sesión transitoria | Confirma --proxy-country US, usa una URL canónica /p/<slug>/<productId>, crea una nueva sesión, vuelve a intentar |
La sesión Scrapeless ha sido terminada y no se puede reconectar en cada llamada de nueva sesión |
El demonio local almacenó en caché un ID de sesión que ahora ha terminado y sigue intentando reconectarse a él | Mata el demonio y borrando su archivo pid, luego crea una nueva sesión: 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 (PowerShell en Windows). En Linux/macOS el camino es ~/.scrapeless-scraping-browser/. |
Cómo usar esto: indica a tu agente
Después de la instalación, extraes datos de productos de Home Depot hablando con tu agente — no copiando y pegando bash. La habilidad carga selectores, espera, clasificadores de reintento y el patrón de descubrir → extraer en el contexto del agente, por lo que un aviso de lenguaje natural en una línea es suficiente para obtener un JSON estructurado de vuelta.
Avisos que puedes copiar
| Dices a tu agente | Lo que obtienes |
|---|---|
| "Consigue el esquema completo del producto para el producto 204279858 de Home Depot (precio, marca, modelo, imagen, disponibilidad)." | Esquema JSON-LD del producto del paso 2 + carga de precio/fulfillment hidratada del paso 3 |
| "Rastrear el precio + indicador de venta para este PDP de Home Depot." | price, wasPrice, onSale, promotion, currency |
| "Buscar en Home Depot 'taladro inalámbrico' y devolver las primeras 5 páginas de resultados." | Tarjetas de listado paginadas: productId, título, marca, precio, calificación, conteo de reseñas, imagen, productUrl |
| "Verificar stock para el producto 204279858 en el ZIP 90015." | store, availabilityText, pickupEta, stockCount (carga por tienda) |
| "Para estos 20 ID de productos, obtener precio + disponibilidad por tienda en el ZIP 33101." | Un JSON de datos de producto por ID con la carga de fulfillment por tienda |
| "Resolver el ID de producto de Home Depot 326716329 a su URL canónica de reseñas, luego devolver el JSON de reseñas." | URL canónica más resumen a nivel de producto y array de reseñas |
"Obtener las primeras 100 reseñas de Home Depot desde esta URL /p/reviews/..., las más nuevas primero." |
Reseñas paginadas con metadata de índice de página y por página |
| "Obtener solo las reseñas de fotos para este producto de Home Depot." | Reseñas filtradas por images.length > 0 |
| "Para el producto 204279858 listar solo reseñas de compradores verificados." | Reseñas filtradas por la insignia de comprador verificado |
| "Obtener las reseñas más nuevas y luego ordenar por conteo útil, devolver las 30 principales." | Reseñas después del flujo de ordenación de la interfaz, clasificadas por votos útiles |
| "Abre la página del producto, establece el código ZIP de la tienda 90015, y luego recopila reseñas del contexto de la tienda." | Reseñas recopiladas de una sesión de navegador configurada para la tienda (ver Paso 5) |
Ejemplo trabajado: obtener reseñas del producto 204279858 (taladro DEWALT)
Escribes:
"Obtén el título, texto, calificación y nombre del revisor para las reseñas principales del producto 204279858 de Home Depot. Devuelve JSON."
El plan del agente (en inglés sencillo):
- Resuelve
204279858a la URL PDP canónica/p/<slug>/204279858.- Abre una sesión de egreso en EE. UU. (
--proxy-country US); intenta nuevamente ante un error transitorioos error 10054/ 503.- Abre la URL PDP,
espera 1500, luegoespera 'h1'para esquivar la carrera de sesión fría.evalcontrascript[type="application/ld+json"]para analizar el esquemaProductincrustado y devolver las 10 mejores reseñas + agregado.- Si se necesitan más de 10 reseñas, navega a
/p/reviews/<slug>/204279858/<page>y ejecuta el extractor de DOM renderizado (Paso 6) por página.
Lo que obtienes de vuelta (salida ilustrativa, extractos de cuerpo recortados por longitud):
json
{
"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": "¡Gran herramienta!",
"text": "La he estado usando durante dos semanas, y valen cada centavo. La calidad es excepcionalmente nítida...",
"rating": 5,
"reviewer": { "name": "kevein" },
"time": null,
"original_source": { "name": "homedepot.com" }
},
{
"title": null,
"text": "Compré el taladro/atornillador Dewalt 20v max para reemplazar un antiguo taladro inalámbrico Craftsman...",
"rating": 5,
"reviewer": { "name": "DIYer_Bill" },
"time": null,
"original_source": { "name": "homedepot.com" }
}
// ... 8 reseñas más con la misma estructura
]
}
time regresa null porque Home Depot no incluye datePublished en los objetos de reseña JSON-LD — las marcas de tiempo están presentes en el DOM de la página de reseñas renderizada, así que recógelas a través de la ruta del Paso 6 si se requieren marcas de tiempo de las reseñas.
Esa es toda la superficie de usuario para este raspado. El bash, selectores y esperas en los Pasos 1–8 a continuación son lo que la habilidad hace que el agente ejecute: tú no tienes que teclear nada de eso.
Formulando indicaciones: cómo controlar lo que regresa
| Redacción | Efecto |
|---|---|
| "…devuelve JSON" / "…como CSV" | Formato de salida |
| "…campos: título, texto, calificación, solo tiempo" | Restringe los campos que el agente extrae |
| "…top 25" / "…en las páginas 1–10" | Profundidad de paginación |
| "…más nuevos primero" / "…menor calificación primero" | Activa el flujo de ordenamiento de la UI |
| "…solo reseñas con foto" | Activa el filtro de reseñas con foto |
| "…solo compradores verificados" | Filtra las reseñas extraídas por insignia |
| "…guardar en reviews.jsonl" | Escribe una reseña por línea en el archivo |
| "…entonces resume las 5 principales quejas" | Encadena un análisis posterior a la extracción |
| "…configura primero el código ZIP de la tienda 90015" | Activa el flujo de localizador de tiendas antes de raspar |
Ese es el flujo de trabajo. Los Pasos 1–8 a continuación son la referencia interna: léelos una vez para ver cómo se compone el patrón de descubrimiento → extracción; luego confía en tu agente para aplicarlo.
PRODUCT_SLUG="DEWALT-20V-MAX-Cordless-1-2-in-Drill-Driver-2-20V-1-3Ah-Batteries-Charger-and-Bag-DCD771C2"
PDP_URL="https://www.homedepot.com/p/$PRODUCT_SLUG/$PRODUCT_ID"
scrapeless-scraping-browser --session-id $SESSION open "$PDP_URL"
Pausa breve para que la próxima espera no se resuelva en la pre-carga
chrome://new-tab-page/ antes de que se confirme la navegación.
scrapeless-scraping-browser --session-id $SESSION wait 1500
Espera por el título del producto H1: los bloques JSON-LD son renderizados por el servidor en
el mismo HTML estático y se garantiza que están presentes una vez que el H1 es visible.
(No esperes directamente por script[type="application/ld+json"] — wait predetermina
al estado "visible", y los elementos <script> nunca son visibles.)
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: "no hay JSON-LD de Producto en esta página" });
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,
});
})()
'
Esta única búsqueda de PDP retorna el esquema de producto completo renderizado por el servidor — productId, nombre, marca, sku, modelo, gtin, imagen, descripción, ofertas (precio, moneda, disponibilidad), calificación agregada — además de las 10 mejores reseñas en una estructura semántica plana (`id`, `titulo`, `texto`, `calificación`, `revisor.nombre`, `tiempo`, `fuente_original.nombre`, `imagenes[].enlace`, `total_comentarios_positivos`, `total_comentarios_negativos`). Retorna de manera confiable sin depender de la concha de React para hidratar.
Algunos campos son condicionales. JSON-LD envía lo que schema.org marca el producto — `gtin`, `mpn`, `color`, `precioVálidoHasta`, `vendedor` están presentes en la mayoría de los elementos del catálogo pero no en todos (trátalos como anulables). Para la detección de banderas de venta, texto de promoción actual y disponibilidad por tienda, esos viven en el DOM renderizado en lugar de JSON-LD — el Paso 3 cubre esa superficie. Para el histograma de calificaciones por estrella (contadores de 5★/4★/3★/2★/1★), la región del histograma se renderiza en línea en la página de reseñas cubierta en el Paso 6.
---
## Paso 3 — Campos hidratados de PDP (precio, cumplimiento, imagen, especificaciones)
El paso 2 de JSON-LD retorna el esquema de producto estático. Las anulaciones de precios de venta, texto de promoción actual, opciones de cumplimiento (envío a domicilio, recogida en tienda, entrega programada), la insignia de existencias, la galería de imágenes destacadas y las tablas de especificaciones/características viven en el DOM renderizado y llegan después de que React hidrata. Impulsa el flujo de descubrimiento → extracción en el mismo PDP, contra anclajes semánticos que sobreviven a la rotación de nombres de clase.
Ejecuta un descubrimiento de `get html` contra la región de precios del PDP primero, confirma los nombres de anclaje activos y luego estrecha los selectores de `eval` a lo que la página realmente ofrece. Los nombres de clase cambian; los patrones `data-testid` y `aria-label` son la superficie estable.
```bash
# La sesión aún está en el PDP del Paso 2. Espera a que se renderice la región de precios,
# luego echa un vistazo al HTML circundante para confirmar los anclajes.
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]'
Del HTML descubierto, los anclajes con mayor duración son [data-testid*="price"], [data-testid*="fulfillment"], [data-testid*="availability"], y los aria-labels en los botones de cumplimiento (aria-label*="Enviar a Casa", aria-label*="Recogida en Tienda", aria-label*="Entrega Programada"). Luego eval el extractor:
bash
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*=\"Enviar\" i], [aria-label*=\"Recogida\" i], [aria-label*=\"Entrega\" 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,
});
})()
'
Para la mayoría de las tuberías de inteligencia de precios, ejecuta tanto el Paso 2 como el Paso 3 contra la misma sesión: el Paso 2 captura el esquema JSON-LD canónico (productId, la marca, sku, modelo, gtin, imagen, ofertas), el Paso 3 captura las sobrecargas hidratadas (bandera de venta, promoción, opciones de cumplimiento, insignia en stock, especificaciones/características). Las dos cargas útiles se fusionan limpiamente en productUrl.
Paso 4 — Búsqueda y listados de categorías
Home Depot expone la búsqueda en /s/<query> y las páginas de aterrizaje de categorías en /b/<category-slug>. Ambas se paginan a través del desplazamiento de URL Nao (?Nao=24, ?Nao=48, …). Cada página muestra ~24 tarjetas de producto.
bash
QUERY="taladro sin cable"
# Codifica espacios en la consulta (Home Depot usa codificación estándar de URL)
SEARCH_URL="https://www.homedepot.com/s/${QUERY// /%20}"
scrapeless-scraping-browser --session-id $SESSION open "$SEARCH_URL"
# Las páginas de búsqueda tienen un renderizado en dos fases: primero se monta un esqueleto de marcador de posición de React con
# una sola tarjeta, luego se llena la cuadrícula real de ~24 tarjetas. Un simple
# `wait '[data-testid*="product-pod" i]'` puede resolverse en el marcador de posición, así que
# el extractor se ejecuta contra una cuadrícula vacía. Espera a que la cuadrícula real
# (≥ 5 tarjetas) en lugar de cualquier coincidencia individual.
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,
});
})()
'
Bucle de paginación para las primeras cinco páginas:
bash
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 '/* extractor de listados idénticos */' \
> "search-page-$((offset / 24 + 1)).json"
done
Para una mayor dispersión en docenas de consultas, crea sesiones nuevas para cada consulta y limita la concurrencia a ≤3 trabajadores por host (ver la nota de trabajadores paralelos del Paso 8). Las páginas de categoría (/b/<category-slug>) aceptan el mismo offset Nao y el mismo extractor.
Paso 5 — Verificación de stock por tienda (código ZIP)
La característica distintiva de Home Depot es la disponibilidad por tienda: seleccionar una tienda de Home Depot a través del modal de selección de ubicación devuelve stock específico de la tienda y texto sobre el ETA de recogida en la misma PDP, junto con opciones de cumplimiento nacionales. El mismo flujo también puede cambiar el orden de revisión hacia los votos útiles regionales, por lo que una sesión configurada para una sola tienda cubre tanto la disponibilidad como los casos de uso del contexto de revisión.
Las referencias de accesibilidad
@e15/@e22/@e25en el fragmento son marcadores de posición; Scrapelesssnapshot -iasigna referencias dinámicamente por renderizado de página. Captura el snapshot, encuentra las filas etiquetadas "Cambiar tienda", el input de ZIP, y el botón de Buscar / Confirmar, y sustituye los números@e<n>reales antes de ejecutar la secuencia de clics. Confirma los selectores[data-testid*="store-locator" i]/[data-testid*="store-name" i]contra el DOM en vivo con un paso de descubrimientoget html— estos coinciden con las convenciones generales dedata-testidde Home Depot, pero deben ajustarse a lo que renderiza la página.
bash
ZIP="90015"
# 1. Abre la PDP y muestra las referencias de accesibilidad para el selector de ubicación.
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. Haz clic en "Cambiar tienda" / "Mi tienda" → llena ZIP → confirmar.
# Ajusta las referencias @e<n> a lo que devuelve el snapshot.
scrapeless-scraping-browser --session-id $SESSION click @e15 # Cambiar tienda
scrapeless-scraping-browser --session-id $SESSION wait 800
scrapeless-scraping-browser --session-id $SESSION fill @e22 "$ZIP" # Input de ZIP
scrapeless-scraping-browser --session-id $SESSION click @e25 # Buscar / Confirmar
scrapeless-scraping-browser --session-id $SESSION wait 1500
scrapeless-scraping-browser --session-id $SESSION wait '[data-testid*="fulfillment" i], [data-testid*="availability" i]'
# 3. Extraer disponibilidad por tienda + la insignia de tienda.
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'",
es
availabilityText: text(document.querySelector("[data-testid*=\"availability\" i], [data-testid*=\"in-stock\" i]")),
pickupEta: text(document.querySelector("[data-testid*=\"pickup\" i], [aria-label*=\"Pickup\" i]")),
stockCount: text(document.querySelector("[data-testid*=\"stock-count\" i], [class*=\"stock-count\" i]")),
});
})()
'
El estado de la tienda se conserva en cookies durante el resto de la sesión: las llamadas posteriores (la evaluación de campos hidratados del Paso 3, el extractor de reseñas renderizadas del Paso 6, el flujo de clasificación del Paso 7) devuelven la vista regional sin necesidad de reiniciar el modal.
Fallback establecido por cookies. Cuando el modal del selector de ubicación no es accesible (bloqueador de ventanas emergentes, variante A/B, WAF transitorio), el mismo resultado está disponible a través de cookies: THD_LOCSTORE / THD_PERSIST llevan el ID de la tienda y el código postal. Establecerlos a través de scrapeless-scraping-browser cookies set y el siguiente open devuelve el PDP en el contexto de la tienda sin el flujo de clics.
Paso 6 — Reseñas 11+ a través del carrusel renderizado
Cuando el flujo de trabajo necesita más que el top 10 de JSON-LD (corpus completo de reseñas, ordenamiento/filtro aplicado, rango de fechas personalizado), el widget de reseñas renderizado es la superficie. Las reseñas se encuentran en /p/reviews/<slug>/<productId>/<page> y dentro del carrusel en el PDP; ambos renderizan a través del mismo micro-frontend de reseñas de productos paginadas en assets.thdstatic.com/experiences/paginated-product-reviews/*.
Conduce el flujo de descubrimiento → extracción:
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 NO se establece en Home Depot — espera anclas de tarjetas de reseñas en su lugar.
scrapeless-scraping-browser --session-id $SESSION wait "#reviews, [itemprop='review'], [data-testid*='review' i]"
# Echale un vistazo al HTML de la región de reseñas para confirmar anclas
scrapeless-scraping-browser --session-id $SESSION get html "#reviews, [data-component*='Review' i]"
A partir del HTML devuelto, identifica las anclas estables: las tarjetas de reseñas suelen estar bajo [itemprop='review'], [data-testid*='review' i], o estructuras repetidas <article>/<li>; los widgets de estrellas llevan una aria-label como "5 de 5"; los nombres de los revisores están cerca de patrones de encabezados repetidos; las insignias de compradores verificados exponen contenido de texto estable; los botones de paginación llevan etiquetas accesibles.
Luego eval la extracción:
bash
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 = /(verificado|recomendar|útil|reseña|comprado)/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: /recomendar/i.test(bodyStr)
? !/no recomendar|no recomendaría/i.test(bodyStr) : null,
badges: unique(
[...c.querySelectorAll("[data-testid*=\"badge\" i], [class*=\"badge\" i]")].map(text)
.concat(bodyStr.match(/Comprador Verificado|Comprador|Pro|Selección del Personal|Incentivado/gi) || [])
),
reviewer: { name: text(c.querySelector("[data-testid*=\"author\" i], [class*=\"author\" i]")) },
time: dateEl?.getAttribute("datetime") || text(dateEl),
original_source: { name: "homedepot.com" },
es
imágenes: único([...c.querySelectorAll("img")].map((i) => i.currentSrc || i.src)).map((link) => ({ link })),
total_feedback_positivo: número(helpfulText.match(/(\d+)\s*(?:personas\s*)?(?:encontraron\s*)?(?:útil|sí|arriba)/i)?.[1]),
total_feedback_negativo: número(helpfulText.match(/(\d+)\s*(?:no útil|no|abajo)/i)?.[1]),
verificado: /verificado/i.test(bodyStr),
};
}).filtrar((r) => r.texto || r.titulo);
return JSON.stringify({ productId, productUrl: location.href, reseñasDevueltas: reviews.length, reseñas });
})()
'
El extractor de DOM renderizado es la ruta para la paginación de reseñas (Paso 8), el orden/aplicación de filtros (Paso 7) y cada vez que el flujo de trabajo necesita reseñas 11+. Considera campos ausentes como null o [] en lugar de eliminar la clave — eso mantiene las tuberías posteriores estables cuando Home Depot rota una plantilla de tarjeta de reseña.
Esquema por reseña:
id,título,texto,calificación,insignias,nombre_revisor(anidado),tiempo,nombre_origen_original(anidado),imágenes[].link(array de objetos),total_feedback_positivo,total_feedback_negativo, además de los extras de ScrapelessesRecomendado(booleano derivado del texto de la reseña) yverificado(booleano derivado del texto de la insignia). Mantén campos ausentes comonullo[]para que los consumidores posteriores se mantengan estables a través de las rotaciones de DOM.
Si el pase de reseñas renderizadas devuelve cero tarjetas incluso después de la espera, el micro-frontend no se ha hidratado completamente; vuelve a intentar la espera, intenta con una sesión nueva si se devuelve Acceso Denegado, o retrocede a los 10 principales de JSON-LD del Paso 2 más la ruta de paginación GraphQL (/federation-gateway/graphql?opname=reviewSentiments) para consultas más profundas.
Paso 7 — Ordenar y filtrar reseñas a través de la UI
Home Depot expone opciones de orden (más nuevas, calificación más alta, calificación más baja, más útiles, solo reseñas con fotos) en la página de reseñas. Dirige esos controles dentro de la misma sesión persistente — la instantánea → las coreografías de clics preservan cookies y estado aplicado.
Los refs
@e34/@e35/@e36/@e41en el fragmento son marcadores de posición — Scrapelesssnapshot -iasigna refs dinámicamente por renderizado de página. Captura la instantánea primero, encuentra las filas para el menú desplegable de orden y los botones de filtro, y sustituye los números reales antes de ejecutar la secuencia de clics.
bash
# Superficialmente referencias de accesibilidad para controles interactivos
scrapeless-scraping-browser --session-id $SESSION snapshot -i > /tmp/reviews-refs.txt
# Identifica las referencias del menú desplegable de orden / botón de filtro de la instantánea, luego haz clic.
# En una página típica de reseñas de Home Depot, estas se presentan como algo como:
# @e34 [botón] "Ordenar Por: Más Útil"
# @e35 [botón] "Solo Fotos"
# @e36 [botón] "Comprador Verificado"
# Ajusta los números @ref a continuación a lo que devuelva la instantánea.
scrapeless-scraping-browser --session-id $SESSION click @e34 # abrir menú desplegable de orden
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 # opción "Más Nueva"
scrapeless-scraping-browser --session-id $SESSION wait 1500
scrapeless-scraping-browser --session-id $SESSION wait "#reviews, [data-component*='Review' i]"
# Vuelve a ejecutar la extracción después de que la región de reseñas se vuelva a renderizar
scrapeless-scraping-browser --session-id $SESSION eval '/* mismo cuerpo de extracción de reseñas que el Paso 6 */'
Para un pase solo de fotos, haz clic en el filtro de reseñas de fotos visible; la región de reseñas se renderiza de nuevo con el conjunto filtrado, y el mismo extractor en el Paso 6 devuelve solo reseñas con fotos. Si la página no expone un filtro de fotos para un producto dado, extrae todas las reseñas visibles y filtra posteriormente en images.length > 0.
Paso 8 — Paginación a través de reseñas
Home Depot paginó reseñas a través de /p/reviews/<slug>/<productId>/<page>. Dos patrones funcionan:
Patrón A — controla el control "Siguiente" visible dentro de la misma sesión, útil cuando se han aplicado estados de orden/filtro a través del Paso 7 y deben persistir a través de las páginas:
bash
for page in $(seq 1 5); do
scrapeless-scraping-browser --session-id $SESSION eval '/* extraer esquema de reseñas */' \
> "reviews-page-$page.json"
# Haz clic en el control de paginación "Siguiente" visible
scrapeless-scraping-browser --session-id $SESSION eval '
(() => {
const next = [...document.querySelectorAll("a, button")]
.find((el) => /siguiente/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
Patrón B — paginación directa por URL con una sesión nueva por página, útil para extracción paralela a gran escala:
bash
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
es
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 '/* extracción de reseñas cuerpo */' \
> "reseñas-página-$page.json"
scrapeless-scraping-browser stop $SID
done
Una sesión nueva por página, no una larga sesión. En teoría, reutilizar un --session-id es más eficiente, pero en la práctica, las sesiones Scrapeless comienzan a devolver una página en blanco chrome://new-tab-page/ o terminan después de aproximadamente 3 solicitudes de página hidratada. Las sesiones de corta duración por página son más confiables, fáciles de paralelizar y triviales de razonar.
¿Ejecutando trabajadores en paralelo? El daemon predeterminado
~/.scrapeless-scraping-browser/se comparte entre procesos en el mismo host: múltiples trabajadores pueden apoderarse de las sesiones de cada uno, incluso cuando cada uno pasa su propio--session-id. Lo que funciona: encadena cada llamada CLI para un trabajo como una única invocación de shell con&&(atómico desde la perspectiva del daemon: otros trabajadores no pueden intercalarse entre tus pasos), usa nombres de sesión únicos (el puerto se genera a partir del nombre) y limita la concurrencia a aproximadamente 3 trabajadores por host. Para un mayor desbordamiento, distribúyelo entre hosts. Consultaskill-dev/SKILL.md"Agentes CLI Paralelos" para ver el patrón completo.
Desduplica entre páginas por id cuando está presente; recurre a reviewer.name + time + title + text cuando no se exponga ID. Las reseñas destacadas y los widgets patrocinados a veces se repiten entre páginas.
Lo Que Obtienes de Vuelta
El Navegador de Scraping devuelve un DOM en vivo: el esquema de extracción es lo que el llamador escribe en el paso de eval. Para una sola pasada por la página de reseñas con la plantilla de descubrir → extraer del Paso 6, los campos que regresan hoy se ven así:
json
// El esquema refleja exactamente lo que emite el eval del Paso 6.
// Los valores de los campos son muestras ilustrativas, no un instantáneo congelado de ningún producto hoy.
{
"productId": "326716329",
"productUrl": "https://www.homedepot.com/p/reviews/<slug>/326716329/1",
"reviewsReturned": 1,
"reviews": [
{
"id": "abc123",
"title": "Instalación fácil y calidad sólida",
"text": "El producto se instaló de manera limpia y funcionó como se esperaba.",
"rating": 5,
"isRecommended": true,
"badges": ["Comprador Verificado"],
"reviewer": { "name": "ClienteHomeDepot" },
"time": "2024-04-04",
"original_source": { "name": "homedepot.com" },
"images": [],
"total_positive_feedback": 8,
"total_negative_feedback": 0,
"verified": true
}
]
}
El eval del Paso 6 emite productId, productUrl, reviewsReturned y reviews[]. El histograma de calificaciones (overallRating, ratings[] por estrella), totalReviews agregado y los metadatos a nivel de producto son emitidos por el eval de Paso 2 JSON-LD: obtén tanto el Paso 2 como el Paso 6 en la misma sesión si un flujo de trabajo necesita agregados + corpus juntos. El extractor del Paso 6 puede extenderse con un pase de histograma contra filas de [data-testid*="histogram" i] / [aria-label*="stars" i], pero eso no está en el cuerpo de eval mostrado arriba; agrégalo explícitamente cuando lo necesites.
Algunas observaciones honestas sobre esta salida, que vale la pena conocer antes de ejecutar a gran escala:
- Temporización de hidratación. El micro-frontend de reseñas en
assets.thdstatic.com/experiences/paginated-product-reviews/*se pinta en oleadas: primero el chrome de la página y el H1, luego el histograma y las tarjetas de reseñas. Unwaitcontra un ancla de tarjeta de reseña (Paso 6) es lo que controla la extracción. Si el pase de descubrimiento devuelve solo el chrome de la página, espera un momento más y vuelve a ejecutarobtener htmlantes de ajustar selectores: las tarjetas suelen estar a un ciclo de renderizado de distancia. - Estabilidad del selector.
[itemprop='review'],[data-testid*='review' i]yaria-label*='out of'widgets de estrellas son los anclajes de mayor duración. Los nombres de clase que comienzan con prefijos hasheados rotan entre implementaciones. - Presencia del histograma. El histograma de calificaciones se renderiza en línea en la página de reseñas, pero utiliza diferentes formas de DOM según las categorías de productos. Trata su ausencia como nullable en lugar de volver a intentar: para productos con muy pocas reseñas, puede que no se renderice en absoluto.
- Insignia de comprador verificado. Dos superficies estables: un span con estilo de insignia que lleva el texto literal "Comprador Verificado", y un
data-testiden el contenedor de la insignia. Match cualquiera de los dos. - Fotos. Las reseñas con fotos adjuntas exponen elementos
<img>dentro de la tarjeta de reseña; las reseñas sin fotos simplemente no tienen hijos<img>:images.length > 0es un filtro limpio.
- **Intersticiales de WAF.** Algunas asignaciones llegan a la página de `Acceso Denegado` de Home Depot (`bodyLen` ≪ 1000, sin marcadores de revisión). El script en el Paso 6 debe detectar esa página (por ejemplo, `if (/Access Denied|Error Page/i.test(document.title)) throw`) y el llamador vuelve a intentar con una sesión nueva.
## Reclama tu plan gratuito y comienza a extraer:
Únete a la vibrante comunidad de Scrapeless para reclamar un **plan gratuito** de $5-10 y conectarte con otros innovadores:
[Comunidad Oficial de Discord de Scrapeless](https://discord.gg/stFPK2xKHY)
[Comunidad Oficial de Telegram de Scrapeless](https://t.me/+uELlyZh2JGw1M2Ux)
---
## Preguntas Frecuentes
**P1: ¿Necesito un proxy para extraer datos de Home Depot?**
Sí — 100%. Home Depot es un sitio minorista de EE. UU. y sirve una página de error genérica en egress no estadounidense. `--proxy-country US` es requerido en cada sesión.
**P2: ¿Dónde viven los precios y opciones de cumplimiento — JSON-LD o DOM renderizado?**
Ambos. JSON-LD envía el `offers.price`, `offers.priceCurrency` y `offers.availability` canónicos (Paso 2). Los descuentos, el texto de promoción actual, la insignia de disponibilidad por tienda y los botones de cumplimiento (Envío a Casa / Recogida / Entrega) se rellenan después de que React se hidrate y se extraen del DOM renderizado (Paso 3).
**P3: ¿Cómo extraigo el stock por tienda?**
Utiliza el modal del selector de ubicación: abre el PDP, haz clic en "Cambiar Tienda", llena el ZIP, confirma. Las llamadas posteriores de eval en la misma sesión devuelven la vista regional. Consulta el Paso 5 para la coreografía completa de captura → clic → llenar. La opción de respaldo de configuración de cookies (`THD_LOCSTORE` / `THD_PERSIST`) está documentada al final del Paso 5 para entornos donde el modal no es accesible.
**P4: ¿Por qué a veces mi sesión devuelve `ERR_TUNNEL_CONNECTION_FAILED`?**
La piscina de proxies no tenía IP residencial disponible en el momento de la asignación. Genera una nueva sesión y vuelve a intentar en breve.
**P5: ¿Por qué la página a veces devuelve `Acceso Denegado`?**
El WAF de Home Depot a veces desafía una asignación nueva. Crea una nueva sesión y vuelve a intentar. El extractor del Paso 6 debería detectar el título de la página de error y lanzar una excepción para que el llamador pueda intentar de nuevo con una sesión nueva.
**P6: ¿Cómo se paginan las páginas de búsqueda y categoría?**
A través del offset de URL `Nao` (`?Nao=24`, `?Nao=48`, …) con 24 tarjetas por página. El Paso 4 cubre el extractor de listados y el bucle de paginación. Las páginas de categoría (`/b/<slug>`) aceptan el mismo offset.
**P7: ¿Puedo extraer solo reseñas de fotos?**
Sí. Primero intenta con el filtro de reseñas de fotos visibles (Paso 7). Si ese control no está presente para un producto determinado, extrae todas las reseñas visibles y filtra después sobre `images.length > 0`.
**P8: ¿Cómo obtengo las reseñas más nuevas o las de menor calificación?**
Usa los controles de ordenación de la UI dentro de la misma sesión persistente — la coreografía de captura → clic en el Paso 7. Haz clic en el control relevante, espera a que la región de reseñas se vuelva a renderizar, y luego vuelve a ejecutar la extracción `eval`.
**P09: ¿Qué pasa cuando Home Depot cambia el DOM?**
Vuelve a ejecutar el pase de descubrimiento: `get html "<region>"`, identifica los anclajes estables actuales (`data-testid`, `aria-label`, `[itemprop]`, ids semánticos) y ajusta los selectores de `eval`. No uses nombres de clase encriptados como selectores permanentes.
**P10: ¿Cuántos productos puedo coleccionar en una sesión?**
Para mayor fiabilidad, limita una sesión de Scrapeless a una pequeña extracción lógica (unos pocos PDP o unas pocas páginas de búsqueda). Para trabajos más grandes, genera sesiones nuevas por tarea y mantén la concurrencia ≤ 3 por host (nota de trabajadores paralelos del Paso 8). Distribuye entre hosts para mayor alcance.
**P11: ¿Puede esto funcionar sin un agente de IA?**
Sí. Los comandos CLI en los Pasos 1–8 funcionan de extremo a extremo como bash. El flujo de trabajo impulsado por agentes (habilidad + indicaciones en lenguaje natural) es el camino recomendado porque la habilidad lleva el patrón de descubrimiento → extracción, las trampas de espera y las reglas de trabajadores paralelos, por lo que la indicación puede permanecer en una sola línea.
**P12: ¿Por qué usar una URL canónica `/p/<slug>/<productId>` en lugar de un ID de producto desnudo?**
Las URL solo con ID desnudo como `/p/<productId>` devolvían la página de error genérica de Home Depot en la verificación. La URL canónica de PDP `/p/<slug>/<productId>` y la URL canónica de reseñas `/p/reviews/<slug>/<productId>/<page>` son los objetivos confiables del navegador; resuelve los IDs de producto a esas formas antes de abrir la página.
En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.



