← Todos los artículos

Scraping de precios de supermercados: ¿qué tienda, qué comprador?

El scraping de precios de supermercados falla en silencio: una cookie de tienda perdida devuelve un precio real de la tienda incorrecta. Cómo verificar la tienda y por qué un solo comprador no basta.

Una docena de huevos Lucerne en un Safeway de Washington, D.C. se ofrecía en Instacart a $3.99, $4.28, $4.59, $4.69 y $4.79. La misma tienda, el mismo momento, diferentes compradores. ¿Cuál de esos cinco números pertenece a tu panel de precios?

El desafío

El sector de los supermercados es donde la inteligencia de precios deja de ser "obtener la página del producto, parsear el precio". El precio de un producto de supermercado pertenece a una tienda, a menudo a una zona de entrega y, cada vez más, a quien lo está mirando.

Empieza por la tienda. La guía de comparación de precios de supermercados de Scrapfly sitúa un galón de leche a $3.98 en un Walmart y a $4.29 en otro a 20 millas de distancia, y advierte que una sesión sin ubicación de tienda obtiene datos por defecto que podrían no coincidir con ninguna tienda real. La guía de entrega de supermercados de 2026 de ScrapeInsight observa lo mismo un nivel más abajo: $3.99 en una zona de entrega, $4.49 a pocas millas de distancia. El caso de estudio de ShopGrok de Bright Data describe cómo los precios minoristas australianos pasaron a depender del código postal a lo largo de los casi cuatro años de actividad de esa empresa.

Luego, el comprador. En diciembre de 2025, Groundwork Collaborative, Consumer Reports y More Perfect Union sometieron a 437 compradores a pruebas en vivo en cuatro ciudades, llenando carritos de Instacart idénticos en las mismas tiendas y al mismo tiempo. El 74% de los artículos apareció con más de un precio. Donde los precios diferían, la brecha entre el más bajo y el más alto promedió un 13%, y las cestas completas variaron alrededor de un 7%.

Combina todo esto y obtendrás el fallo que hace que los datos de supermercados sean costosos. Pierde el contexto de la tienda y nada se rompe. El sitio no devuelve un error. Devuelve un precio perfectamente válido para una tienda por la que no preguntaste, con un SKU, un número y un timestamp que superan cada comprobación de nulos que tengas.

Ya hemos escrito sobre la versión en la que un bloqueo parece un punto de datos. Esta es más difícil de detectar, porque la página realmente es una página de precios. Solo que no es la tuya.

Valida la tienda dentro de la respuesta

La mayoría de los colectores envían la selección de la tienda (una cookie, un query parameter, un header) y confían en ella. El hábito más robusto es hacer que la respuesta lo demuestre. Si la página nombra la tienda para la que se renderizó, por ejemplo un store ID en los datos embebidos o el nombre de la tienda en el banner de recogida, ese marcador se convierte en tu definición de éxito. En FourA eso se reduce a una sola llamada de Proxy Finder:

import requests

r = requests.post(
    "https://api.foura.ai/api/proxy",
    headers={"X-API-Key": "pk_live_..."},
    json={
        "maxTries": 6,
        "request": {
            "method": "GET",
            "url": "https://grocer.example/product/0001234",
            "headers": [["Cookie", "store=1234"]],
            "validate": {
                "status": {"accept": [200]},
                "data": {
                    "accept": ["\"storeId\":\"1234\""],
                    "fail": ["Just a moment", "Access Denied"]
                }
            }
        }
    }
).json()

if "error" in r:
    report = r.get("attemptReport", {})
    print(report.get("summary", r["error"]))
    if report.get("contentRejected", 0) > report.get("defense", 0) + report.get("noResponse", 0):
        print("the site answered for another store: check the store selection")
else:
    page, exit_id = r["data"], r["proxy"]

Hay tres detalles en esa request que son fáciles de pasar por alto.

accept se aprueba cuando cualquiera de sus cadenas aparece en la página. Si colocas un marcador de precio junto al marcador de tienda, una página de la tienda incorrecta pasará sin problemas, ya que también contiene un precio. Mantén únicamente el marcador de tienda en accept y coloca las cadenas de validación en fail.

Tu propio header Cookie cambia el comportamiento de los reintentos. Cuando un sitio rechaza el navegador que presentamos, Proxy Finder normalmente cambia a otra familia de navegadores en el siguiente intento. Esto no ocurre si adjuntas tu cookie. Una sesión queda vinculada a la firma que la originó, por lo que una request que lleva su propia cookie mantiene el navegador con el que inició. Si el sitio de una cadena resulta ser estricto con los navegadores, elige uno explícitamente con browser profiles en lugar de depender de la rotación.

Además, una tarea fallida te indica qué tipo de fallo ocurrió. Cada respuesta fallida de Proxy Finder incluye un attemptReport. defense cuenta las respuestas donde se detectó un bot check, noResponse cuenta los nodos de salida que nunca llegaron al sitio y contentRejected cuenta las páginas que devolvieron HTTP 200 sin bot check y fueron descartadas únicamente por tu regla de contenido. Para un recolector de datos de supermercados, ese último conteo significa algo específico: el sitio respondió, pero no para tu tienda. Añadir más nodos de salida no solucionará eso. La guía del reporte de intentos cubre los demás conteos.

Mantén fija la sesión del comprador o mide la dispersión

Los hallazgos de Instacart añaden una segunda variable, y existen dos formas transparentes de gestionarla.

Para un panel de precios, mantén la recolección de cada tienda bajo una sola identidad. Reutiliza el nodo de salida que funcionó (r["proxy"], un ID opaco) a través de Single con la misma cookie, y guarda ese ID junto con el header de respuesta X-FourA-Request-Id en cada fila. Cuando un precio varíe bruscamente, podrás distinguir entre un cambio real en la tienda y un cambio de sesión.

Para estudios de precios, haz intencionalmente lo contrario. Muestrea el mismo SKU en la misma tienda a través de varias sesiones independientes y conserva la distribución, no solo la primera respuesta. Un panel que reporta un único valor donde los compradores ven cinco no es preciso. Solo tuvo suerte.

Resultados

Considera una cadena regional que evalúa 120 tiendas de la competencia sobre 2,500 SKUs con actualización diaria (escenario ilustrativo basado en referencias del sector). Eso representa 300,000 lecturas de página al día, y en cualquier noche algunas de ellas devolverán la tienda equivocada: cambió el formato de una cookie, cerró una tienda o el sitio comenzó a priorizar la ubicación por IP sobre la cookie.

  • Las páginas de la tienda incorrecta fallan en la recolección. Nunca se convierten en filas, por lo que una variación de precio en el panel es una variación en esa tienda específica.
  • Los fallos llegan clasificados. Un valor alto de contentRejected va para quien gestione la selección de tiendas, un valor alto de defense es un problema de acceso y un valor alto de noResponse corresponde a las salidas. Tres responsables, tres soluciones y nadie pierde una mañana intentando adivinar cuál es el problema.
  • Cada fila es trazable. Un ID de salida y un ID de request por observación convierten la duda de "¿es real este pico?" en una simple búsqueda.
  • La dispersión se convierte en un dato real. Para los SKU que muestreas entre sesiones, puedes reportar el rango exacto que ven los compradores en lugar de una sola lectura aislada.

Dónde se queda corto esto: los precios para miembros y programas de lealtad están detrás de cuentas de usuario, y los experimentos vinculados a un comprador con sesión iniciada no aparecerán en sesiones anónimas. Recolectar esos datos es una cuestión de términos de servicio antes que un problema de ingeniería. Además, un marcador de tienda es tan bueno como la elección que hagas de él. Extráelo del código fuente de la página, no de memoria, y revísalo de nuevo cada vez que la cadena rediseñe su sitio.

Conclusión clave

El precio de un producto de supermercado solía ser un dato directo sobre el artículo. Ahora es un dato sobre el producto, la tienda y el comprador, y un recolector que solo registra el primero está produciendo ruido con un esquema muy ordenado.

Si los precios personalizados por comprador se siguen extendiendo, la pregunta que los clientes hacen sobre los datos de supermercados pasa de "¿cuánto cuesta?" a "¿cuánto cuesta aquí y qué tan amplio es el rango?". Por lo tanto, los recolectores confiables no serán los que tengan más salidas. Serán aquellos en los que cada request pueda indicar para qué tienda se realizó y demostrarlo.