El dilema API vs. HTML en Walmart
Si tu equipo de inteligencia retail necesita datos de Walmart, te enfrentas a una decisión inmediata: ¿usar la API interna de Walmart o parsear el HTML? La API de Walmart (developer.walmart.com) ofrece endpoints estables para precios e inventario, pero requiere aprobación, tiene límites de tasa estrictos y no cubre datos de vendedores Marketplace. Por eso, la mayoría de equipos de CPG y retail intel terminan scrapeando.
El problema: Walmart protege su sitio con una de las stacks anti-bot más agresivas de la web. Scrapear Walmart sin la estrategia correcta significa captchas interminables, bloques de IP y datos incompletos. Esta guía te muestra cómo hacerlo de forma pragmática: qué extraer, cómo evadir los bloqueos, y cómo estructurar tu pipeline.
Estructura del catálogo de Walmart
Walmart organiza su catálogo en tres tipos de páginas principales. Entender la URL y la estructura de cada una es el primer paso para cualquier scraper.
Páginas de producto: /ip/{slug}/{itemId}
Cada producto tiene una URL canónica con el formato:
https://www.walmart.com/ip/Apple-AirPods-Pro-2nd-Gen/1752698499
El itemId (numérico al final) es el identificador estable. El slug del título puede cambiar, pero el itemId no. Siempre extrae y almacena el itemId como clave primaria. Puedes incluso omitir el slug y cargar directamente https://www.walmart.com/ip/1752698499 — Walmart redirige a la URL canónica.
Páginas de categoría
Las categorías usan el formato:
https://www.walmart.com/cp/electronics/3944
https://www.walmart.com/cp/food/976759
Cada página de categoría carga un JSON de productos paginados. Los enlaces de paginación usan el parámetro ?page=2. Las subcategorías se anidan: /cp/{nombre}/{categoryId}.
Páginas de búsqueda
Las búsquedas usan:
https://www.walmart.com/search?q=airpods+pro&sort=price_low
Parámetros útiles: sort (price_low, price_high, best_seller, rating), facet para filtros, y page para paginación. Cada página de búsqueda devuelve ~40 productos.
El muro anti-bot: Akamai + PerimeterX
Walmart despliega una defensa en dos capas que detecta y bloquea la mayoría de scrapers:
- Akamai Bot Manager: fingerprinting a nivel de red (TLS fingerprint, orden de cabeceras HTTP, JA3 hash). Detecta librerías como
requestsoaxiospor su TLS fingerprint. También analiza patrones de tráfico por IP — demasiados requests desde un rango /24 levanta alertas. - PerimeterX (ahora HUMAN): fingerprinting del navegador. Inyecta scripts JavaScript que generan un token (
_px3) basado en comportamiento del mouse, tiempo de renderizado, y capacidades del navegador. Sin este token, el servidor devuelve una página de captcha o un bloque 403.
Por qué necesitas proxies residenciales: Las IPs de datacenter son bloqueadas casi inmediatamente por Akamai. Walmart mantiene listas de ASN de datacenter (OVH, DigitalOcean, AWS, etc.) y desafía o bloquea esas IPs al primer request. Los proxies residenciales rotativos usan IPs de ISPs reales, lo que hace que cada request parezca de un usuario doméstico legítimo.
Umbral empírico: Walmart permite ~30-80 requests por IP residencial antes de mostrar captcha. Con IPs de datacenter, el bloqueo ocurre en 1-5 requests. Rotar IPs residenciales cada 20-30 requests mantiene tasas de éxito superiores al 95%.
Comparativa de tipos de proxy para Walmart
| Tipo de proxy | Tasa de éxito (est.) | Latencia promedio | Costo por GB | Recomendación |
|---|---|---|---|---|
| Datacenter | 5-15% | Baja (~100ms) | $0.50-1.00 | No viable para Walmart |
| Residencial rotativo | 90-97% | Media (~800ms) | $3-8 | Ideal para scraping masivo |
| Residencial sticky | 92-98% | Media (~800ms) | $3-8 | Ideal para sesiones largas |
| Móvil | 95-99% | Alta (~1.5s) | $8-15 | Máxima confianza, alto costo |
El camino fácil: __NEXT_DATA__
Walmart renderiza sus páginas con Next.js, lo que significa que inyecta todo el estado de la aplicación en un tag <script id="__NEXT_DATA__"> dentro del HTML. Este JSON contiene todos los datos del producto: precio, inventario, ratings, vendedor, variantes, descripción — sin necesidad de parsear el DOM.
Selector CSS para encontrarlo:
script#__NEXT_DATA__
XPath equivalente:
//script[@id='__NEXT_DATA__']
Este es el JSON más completo y confiable de la página. Los selectores CSS del DOM (como [data-automation-id="product-price"]) cambian con frecuencia y son frágiles. __NEXT_DATA__ es estable porque es la fuente de datos interna de la aplicación.
Estructura del JSON
La ruta principal dentro del JSON es:
props.pageProps.initialData.data.product
Los campos clave que encontrarás:
priceInfo.currentPrice.price— precio actualpriceInfo.wasPrice— precio anterior (si hay descuento)availabilityStatus— estado de inventarioaverageRating— rating promedionumberOfReviews— número de reseñassellerId— ID del vendedorsellerName— nombre del vendedorproductType— tipo de producto
Ejemplo en Python: fetch + parse de __NEXT_DATA__
Aquí tienes un script completo que usa ProxyHat para obtener una página de producto y extraer los datos clave:
import requests
import json
from urllib.parse import quote
PROXY_URL = "http://user-country-US:PASSWORD@gate.proxyhat.com:8080"
PROXIES = {"http": PROXY_URL, "https": PROXY_URL}
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/125.0.0.0 Safari/537.36"
),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
}
def fetch_product(item_id: str) -> dict:
url = f"https://www.walmart.com/ip/{item_id}"
resp = requests.get(url, headers=HEADERS, proxies=PROXIES, timeout=30)
resp.raise_for_status()
return resp.text
def extract_next_data(html: str) -> dict:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
script = soup.find("script", id="__NEXT_DATA__")
if not script:
raise ValueError("__NEXT_DATA__ no encontrado")
return json.loads(script.string)
def parse_product(next_data: dict) -> dict:
product = next_data["props"]["pageProps"]["initialData"]["data"]["product"]
price_info = product.get("priceInfo", {})
current = price_info.get("currentPrice", {}).get("price", None)
was = price_info.get("wasPrice", {}).get("price", None) if price_info.get("wasPrice") else None
return {
"item_id": product.get("id"),
"name": product.get("name"),
"current_price": current,
"was_price": was,
"availability": product.get("availabilityStatus"),
"avg_rating": product.get("averageRating"),
"num_reviews": product.get("numberOfReviews"),
"seller_id": product.get("sellerId"),
"seller_name": product.get("sellerName"),
"product_type": product.get("productType"),
}
# Ejemplo de uso
if __name__ == "__main__":
html = fetch_product("1752698499")
next_data = extract_next_data(html)
result = parse_product(next_data)
print(json.dumps(result, indent=2, ensure_ascii=False))
Respuesta de ejemplo (truncada)
{
"item_id": "1752698499",
"name": "Apple AirPods Pro 2nd Generation",
"current_price": 189.00,
"was_price": 249.00,
"availability": "IN_STOCK",
"avg_rating": 4.5,
"num_reviews": 12847,
"seller_id": "0",
"seller_name": "Walmart.com",
"product_type": "1P"
}
Marketplace (3P) vs. catálogo 1P
Walmart opera dos modelos de venta simultáneamente, y los datos se estructuran de forma diferente para cada uno:
Productos 1P (Walmart como vendedor)
sellerId="0"o"F55CBD31AB464BB8B3C59010B21C10E6"(Walmart.com)- Datos de inventario más confiables —
availabilityStatusrefleja almacenes de Walmart - Precios directamente de la base de datos de Walmart
- Un solo precio por itemId
Productos 3P (vendedores Marketplace)
sellerIdes un hash alfanumérico único por vendedor- Múltiples ofertas para el mismo itemId — necesitas iterar
offersdentro del JSON - Los campos de inventario pueden ser menos confiables
- Precios y envío varían por vendedor
Para extraer todas las ofertas de un producto 3P, busca la clave offers dentro de product:
def parse_all_offers(product: dict) -> list:
offers = product.get("offers", [])
results = []
for offer in offers:
results.append({
"seller_id": offer.get("sellerId"),
"seller_name": offer.get("sellerName"),
"price": offer.get("priceInfo", {}).get("currentPrice", {}).get("price"),
"availability": offer.get("availabilityStatus"),
"shipping": offer.get("shippingOptions", [{}])[0].get("shippingPrice"),
})
return results
Dato clave: cuando un producto tiene tanto oferta 1P como 3P, Walmart muestra la oferta 1P por defecto en la página principal. Las ofertas 3P aparecen en la sección "More sellers" o dentro de
offersen__NEXT_DATA__.
Programación con conciencia de rate limits
Walmart no publica sus rate limits, pero la experiencia empírica muestra estos umbrales:
- Por IP residencial: ~30-80 requests antes de captcha. Varía por reputación del IP.
- Por IP de datacenter: 1-5 requests antes del bloqueo.
- Por sesión de navegador: PerimeterX rastrea el token de sesión. Rotar IP sin rotar cookie no funciona.
- Velocidad de requests: Más de 2 requests/segundo desde una IP dispara alertas de Akamai.
Estrategia de scheduling recomendada
import time
import random
import requests
PROXY_URL = "http://user-country-US:PASSWORD@gate.proxyhat.com:8080"
PROXIES = {"http": PROXY_URL, "https": PROXY_URL}
def scrape_item_list(item_ids: list, max_retries: int = 3):
results = []
for i, item_id in enumerate(item_ids):
# Rotar sesión cada 25 requests para evitar captcha
session_id = f"session-walmart-{i // 25}"
proxy = f"http://user-country-US-session-{session_id}:PASSWORD@gate.proxyhat.com:8080"
proxies = {"http": proxy, "https": proxy}
for attempt in range(max_retries):
try:
url = f"https://www.walmart.com/ip/{item_id}"
resp = requests.get(url, headers=HEADERS, proxies=proxies, timeout=30)
if resp.status_code == 403:
print(f" Bloqueado en {item_id}, rotando sesión...")
time.sleep(random.uniform(5, 15))
continue
resp.raise_for_status()
next_data = extract_next_data(resp.text)
product = parse_product(next_data)
results.append(product)
break
except Exception as e:
print(f" Error en {item_id}: {e}")
time.sleep(random.uniform(3, 8))
# Delay aleatorio entre requests: 1.5-3.5 segundos
delay = random.uniform(1.5, 3.5)
time.sleep(delay)
print(f" [{i+1}/{len(item_ids)}] {item_id} OK")
return results
Buenas prácticas para evitar bloqueos
- Rotación de sesiones sticky: Usa sesiones de 20-30 requests antes de rotar. Las sesiones nuevas cada request levantan más sospechas que sesiones estables.
- Delays aleatorios: Nunca uses un intervalo fijo. Agrega jitter entre 1.5 y 4 segundos.
- Headers realistas: Incluye
Accept,Accept-Language,Accept-Encoding. No uses el User-Agent por defecto derequests. - Geo-targeting: Si scrapeas Walmart US, usa IPs de EE.UU. Las IPs internacionales pueden redirigir a walmart.ca o mostrar contenido diferente. Con ProxyHat:
user-country-US. - Manejo de errores: Si recibes un 403, no reintentes inmediatamente. Espera 5-15 segundos y rota la sesión.
- Limita la concurrencia: No más de 2-3 threads por IP residencial. Si necesitas más throughput, usa más IPs, no más threads.
Cuándo usar la API de Walmart vs. scraping
La API oficial de Walmart tiene sentido si:
- Solo necesitas precios e inventario de productos 1P
- Tu volumen es bajo (<100K requests/mes)
- Puedes esperar la aprobación de Walmart (días a semanas)
- No necesitas datos de vendedores 3P
El scraping es necesario si:
- Necesitas datos de vendedores Marketplace (precios, disponibilidad por vendedor)
- Necesitas cobertura masiva (millones de SKUs)
- Quieres datos de búsqueda (ranking, posición, filtros)
- La API no ofrece los campos que necesitas
- Necesitas velocidad — no puedes esperar aprobación
Para la mayoría de equipos de inteligencia retail, la respuesta es ambos: la API para datos estables de 1P, y scraping para cobertura 3P y datos de búsqueda que la API no proporciona.
Consideraciones legales y éticas
Antes de implementar cualquier scraper de Walmart, considera:
- robots.txt: Walmart permite crawling de páginas de producto pero bloquea
/searchy/browsepara ciertos bots. Revisahttps://www.walmart.com/robots.txtregularmente. - Términos de servicio: Los ToS de Walmart prohíben scraping. Si tienes una relación comercial con Walmart, esto puede tener consecuencias. Evalúa el riesgo legal con tu equipo.
- GDPR/CCPA: No extraigas datos personales (reseñas con nombres, ubicaciones). Limítate a datos de producto.
- Rate limits razonables: No satures la infraestructura de Walmart. Un scraper bien diseñado no debería impactar la experiencia de otros usuarios.
Puntos clave
__NEXT_DATA__es tu mejor amigo: No parsees el DOM. Extrae el JSON del tagscript#__NEXT_DATA__— es más completo, estable y fácil de procesar.- Proxies residenciales son obligatorios: Las IPs de datacenter son bloqueadas por Akamai en 1-5 requests. Usa proxies residenciales rotativos con sesiones sticky.
- Diferencia 1P de 3P: Los productos 1P tienen
sellerId = "0". Los productos 3P requieren iterarofferspara obtener todos los vendedores. - Rotación inteligente: Rota sesiones cada 20-30 requests, no cada request. Usa delays aleatorios de 1.5-4 segundos.
- El itemId es la clave: Usa el ID numérico como identificador estable, no el slug del título.
- Geo-targeting importa: Usa IPs de EE.UU. para Walmart.com. IPs de otros países pueden mostrar contenido diferente.
Si necesitas proxies residenciales confiables para scrapear Walmart, ProxyHat ofrece rotación por país y sesión con tasas de éxito superiores al 95% contra Akamai. Configura tu primer proxy en minutos con gate.proxyhat.com:8080.






