2 puntos por GN⁺ 2024-08-07 | 1 comentarios | Compartir por WhatsApp
  • En diciembre de 2022, en medio de una alta inflación, se construyó el pipeline de pricewatcher.gr para seguir a diario los cambios de precios de las 3 principales cadenas de supermercados de Grecia
  • Como el listado de productos estaba detrás de renderizado con JavaScript y scroll infinito, se usó Playwright para controlar el navegador y extraer datos del DOM en lugar de curl o requests.get()
  • Como el entorno de ejecución era lento con solo una laptop vieja y AWS EC2 resultaba caro, se cambió a un esquema de crear servidores de Hetzner solo cuando se necesitan, ejecutar 3 scrapers en paralelo y luego apagarlos
  • Para el supermercado donde Akamai bloqueaba IPs no residenciales, se hizo pasar el tráfico por la IP de casa con un exit node de Tailscale, y se detectaban fallas con alertas por correo, validación estricta y heurísticas sobre la cantidad de productos
  • Con un servidor de 8 vCPU y bloqueando solicitudes de imágenes se redujo el tiempo de ejecución; en las 31 ejecuciones más recientes, el costo fue de €4.94 por servidores de Hetzner, €0.09 por IPv4 y €0.00 por almacenamiento en Cloudflare R2

Scraping de sitios de supermercados basados en JavaScript

  • En diciembre de 2022, en un contexto de alta inflación, se construyó pricewatcher.gr para seguir los cambios de precios de las 3 principales cadenas de supermercados de Grecia
  • Las e-shops de los tres supermercados usaban renderizado con JavaScript, y en algunas secciones cargaban más productos al hacer scroll, como el scroll infinito de las redes sociales
  • Como no era posible obtener los datos de productos con un simple curl o requests.get(), se usó Playwright
  • Playwright controla el navegador por programación y ofrece una API para tareas como:
    • abrir una nueva pestaña y navegar a una URL
    • inspeccionar el DOM
    • consultar detalles de elementos
    • interceptar e inspeccionar solicitudes
  • Playwright soporta Chromium, Safari y Firefox, y puede usarse desde Node, Java, .NET y Python
  • El scraper buscaba el elemento load more del scroll infinito, seguía desplazándose y luego excluía del listado li.product-item los productos marcados como agotados
  • Al final, en cada producto se parseaban nombre, precio, foto y enlace del producto, y el mismo proceso se repetía para la siguiente categoría

Elegir un entorno de ejecución diario

  • En una M1 MacBook Pro, procesar por completo un solo supermercado tomaba entre 50 minutos y 2 horas 30 minutos, y ejecutar los 3 scrapers en paralelo no mostraba una diferencia notable
  • Para desarrollo y pruebas la laptop era suficiente, pero hacía falta un entorno permanente para correrlo todos los días
  • Ejecutarlo solo en una laptop vieja

    • El primer intento fue con una laptop vieja de 2013
    • Tenía un procesador serie M de doble núcleo a 2.20 GHz y 4 GB de RAM, que luego se ampliaron a 12 GB
    • Pero incluso el supermercado “rápido” tardaba más de 2 horas, así que el rendimiento no cumplió las expectativas
  • Ejecución en la nube

    • Se evaluó una instancia EC2 de AWS con 4 núcleos y 8 GB de RAM, pero el costo era alto para un proyecto paralelo
    • Al momento de escribirlo, c5a.xlarge en eu-north-1 costaba $0.1640 por hora, unos $118.08 al mes o $1,416.96 al año
    • Un servidor equivalente de Hetzner, cpx31, costaba $17.22 (€15.72) al mes, o $206.64 al año, unas 7 veces más barato que AWS
    • Al final se eligió Hetzner como entorno de ejecución

Pipeline diario armado con Concourse

  • La laptop vieja no hacía el scraping directamente, sino que actuaba como servidor de CI delegando el trabajo a un servidor de Hetzner
  • Como herramienta de CI se usó Concourse
    • Concourse se presenta como “a continuous thing-doer”
    • Apunta a builds reproducibles mediante un modelo de pipeline declarativo y control de versiones de entradas
  • El pipeline corría cada noche en este orden:
    • crear el servidor de scraping
    • ejecutar en paralelo los trabajos de scraping de los 3 supermercados
    • apagar el servidor al terminar todos los trabajos para ahorrar costos
    • pasar la salida cruda de cada scraper al trabajo de transformación
    • cargar los datos transformados en pricewatcher.gr
    • enviar una alerta por correo si falla cualquier etapa

Cómo esquivar restricciones de IP con un exit node de Tailscale

  • Los supermercados usados en pruebas funcionaban bien, pero otro supermercado estaba detrás de Akamai y tenía reglas de firewall que bloqueaban solicitudes desde IPs no residenciales
  • La estructura necesaria era lo contrario de una VPN normal: hacer que las solicitudes salieran como si vinieran de la IP real de casa
  • Con Tailscale se unieron varios dispositivos como si estuvieran en la misma red
  • En Tailscale, si se designa un dispositivo como exit node, puede configurarse para que las solicitudes de otros dispositivos salgan a través de ese nodo
  • La laptop vieja también cumplía el rol de exit node para el tráfico de scraping
  • También quedaba como variable el hecho de que el ISP en uso empleaba CGNAT, por lo que la IP pública no estaba asociada solo a una persona sino compartida con otros clientes del proveedor

Tipos de fallas y cómo detectarlas

  • Esta configuración estuvo en operación durante un año y medio, y en general funcionó de forma confiable
  • Los proyectos de scraping inevitablemente se ven afectados por cambios hechos por los desarrolladores de los sitios objetivo
  • Las fallas se dividían en dos grandes tipos
  • Cambios que rompen

    • Son casos donde un cambio en el sitio hace que el scraper falle de inmediato
    • Ejemplos:
      • se agrega una encuesta y hay que presionar un botón adicional
      • cambia por completo el layout y hay que refactorizar mucho el scraper
  • Cambios que no rompen

    • Son más difíciles porque el scraper sigue corriendo, pero la interpretación de los datos se desajusta
    • Por ejemplo, si cambia la forma de mostrar el precio y la parte decimal se separa en ``, unas papas fritas de €1.99 podrían terminar parseadas como €199
    • Para detectar estos cambios, la etapa de transformación se configuró para validar la entrada de la forma más estricta posible
    • Como corre todos los días, hay tiempo para revisar problemas, pero si se rompe durante vacaciones sigue siendo un factor de preocupación

Optimización de tiempo de ejecución y confiabilidad

  • La arquitectura general se ha mantenido casi igual desde el principio, pero se cambiaron varias partes para mejorar la confiabilidad y reducir el trabajo manual
  • Entre las mejoras aplicadas estuvieron:
    • alertas por correo al fallar
    • heurísticas que envían alertas cuando la cantidad de productos de cierto supermercado es demasiado alta o demasiado baja
    • timeouts
    • reintentos que no reinician todo desde cero
  • El mayor cuello de botella era el tiempo de ejecución del scraping
    • cuanto más tarda, mayor es el costo
    • también aumenta la molestia si, tras un fallo, hay que reintentar desde el principio
  • Usar un servidor más grande

    • El servidor pasó de 4 vCPU y 16 GB de RAM a 8 vCPU y 16 GB de RAM
    • El tiempo de ejecución bajó alrededor de 20% y quedó en un nivel similar al rendimiento obtenido en la MBP
    • Como el servidor de scraping solo se usa unas 2 horas, la diferencia de precio era prácticamente despreciable
  • Traer menos cosas

    • Se usó page.route de Playwright para bloquear solicitudes de imágenes
    • Las solicitudes .png y .jpg se abortaban para no descargar imágenes mientras cargaban los productos
    • Esto aceleró el scraping y además redujo un poco el ancho de banda y el costo del sitio objetivo

Costos según 31 ejecuciones

  • Según la factura más reciente de Hetzner, los costos fueron:
    • 31 servidores creados: €4.94
    • 31 direcciones IPv4 asignadas a esos servidores: €0.09
  • Los datos del scraping se almacenan en Cloudflare R2
  • Como todavía no se supera el nivel gratuito de 10 GB de Cloudflare R2, el costo de almacenamiento es de €0.00
  • El pipeline completo funciona combinando Playwright, Hetzner, Concourse, Tailscale y Cloudflare R2 para seguir a diario los cambios de precios en supermercados

1 comentarios

 
GN⁺ 2024-08-07
Comentarios en Hacker News
  • Desde principios de este año estoy haciendo algo parecido para Nueva Zelanda. Extraigo datos con Playwright/Typescript, los guardo en archivos Parquet en almacenamiento en la nube, y todavía no los muestro en una interfaz
    La mayor parte del trabajo se va en esquivar servicios de proxy inverso como Akamai y Cloudflare. Cuando empecé pensé que nadie más lo estaba haciendo, pero ahora sé que hay al menos 3 startups en NZ haciendo lo mismo. Parece que la inflación ha impulsado bastante innovación por aquí
    El patrón es el esperado. Los supermercados usan sus trucos habituales para volver los precios lo más complejos posible, y con variaciones de precios en “diente de sierra” separan a la gente con poco tiempo de la gente con poco dinero. También suelen dividir entre clientes leales a una marca y clientes sensibles al precio; si hay 3 marcas populares de chocolate, cada semana solo una de ellas se vende a un precio razonable

    • Me intriga cómo los supermercados abusarían de la segmentación de clientes mediante actualizaciones de precios. También me gustaría saber cómo suelen reaccionar las personas con poco tiempo y las que de verdad tienen poco dinero
      Me interesa especialmente la parte de “dividen entre clientes leales a una marca y clientes sensibles al precio. Si hay 3 marcas populares de chocolate, cada semana solo una de ellas se vende a un precio razonable”
    • En Australia la legalidad de este tipo de cosas es ambigua. Imagino que en NZ será parecido
      En AU aparecen y desaparecen muchos scrapers de este tipo, pero normalmente las grandes cadenas de supermercados los bloquean. Es un ciclo repetido de utilidad y de “¿por qué no existe esto?”, cuando en realidad ya ha existido varias veces
    • Hice https://bbdeals.in/ para India. Lo uso sobre todo para comprar fruta, y me ayuda a ahorrar alrededor de 20% del gasto, que en estos tiempos no está nada mal
      Construir el crawler y la infraestructura que lo sostiene me tomó menos de 20 horas
    • Como kiwi, me pregunto si puedes publicar este proyecto o el tuyo propio. Me interesa bastante
    • Quienes piden el súper a domicilio por internet pueden beneficiarse de la comparación de precios. Eso es porque pueden hacer pedidos en varias tiendas al mismo tiempo
      Además, resulta más útil porque solo hay un marketplace que tiene los precios de varias tiendas
  • Buen artículo. En mi sitio de comparación de precios de lentes de contacto https://lenspricer.com/ he pasado por problemas parecidos, y opero en unos 30 países. Coincido totalmente en que cuando los sitios web cambian el HTML es un dolor de cabeza
    Uno de los mayores obstáculos al principio fue emparejar el mismo producto entre más de 100 sitios web. Uno pensaría que los nombres de producto serían únicos, pero todos los escriben con pequeñas variaciones a su manera. La mayoría se resuelve con expresiones regulares, pero una buena parte hubo que mapearla manualmente, y aunque usé algo de IA, verifiqué todo a mano
    Crear los scrapers y la infraestructura es relativamente fácil. Lo difícil es dar mantenimiento a todos los scrapers y distinguir, cuando un producto desaparece de un sitio, si fue un error del scraper, un bloqueo, un cambio en el sitio o que el sitio estaba en mantenimiento justo al momento del rastreo
    Es un proyecto divertido, pero a veces es difícil y tiene muchos problemas molestos de arreglar

    • Estás haciendo un trabajo necesario. Cada año sufro por culpa de la aseguradora al comprar lentes de contacto, que son un artículo básico
      Los precios están por todos lados, y la cobertura normalmente termina siendo algo como un 30% por reembolso por correo
    • Me pregunto si puedes usar lentes de contacto mientras trabajas. Yo noto que se me cansan los ojos al mirar el monitor durante mucho tiempo, y me gustaría saber si encontraste alguna solución
    • En la página de Alemania dice debajo de los precios “algunos enlaces pueden ser enlaces patrocinados”, pero no indica cuáles son. Me pregunto si eso es legal
      También parece haber muy pocas tiendas; quizá todos los enlaces sean patrocinados. En idealo.de encuentro precios más bajos
    • En EE. UU., al menos los grandes minoristas intentan dificultar la comparación de precios pidiéndoles a los proveedores que creen SKU apenas distintos
      Costco es bastante famoso por eso: casi todos los electrónicos que vende en tienda y muchos otros productos tienen SKU personalizados. A menudo la configuración del producto también cambia un poco
    • Siento que este sí es un caso de uso donde un LLM podría ayudar de verdad
  • Hice un sitio web parecido que recibió bastante atención en mi ciudad. Extraigo datos de la app y del sitio web, y uso un único servidor de Linode con 2 GB de RAM, 5 IPv4 y 1000 IPv6 gratis
    Recolecto todos los productos con un intervalo máximo de 40 minutos, y el promedio es de unos 25 minutos. Uso curl-impersonate y extraigo JSON siempre que puedo. El 90% del mercado entrega los precios mediante llamadas Ajax, y el 10% restante permite parsear HTML fácilmente con expresiones regulares
    Se puede ver en https://www.economizafloripa.com.br

    • Extraen datos de la app y del sitio web, y luego intentan vendérselos de vuelta a los comerciantes; incluso proponen usar esos datos para entrenar IA. Hasta hacen que parezca que un equipo procesa todo manualmente
      https://www.economizafloripa.com.br/?q=parceria-comercial
      Al ver esa página, el proyecto pasa de parecer “una herramienta útil para que la gente recupere el control frente a las empresas que venden artículos de primera necesidad” a “otro intento más de hacer dinero”. Claro, están en su derecho, pero al leer la página principal esperaba una motivación más ética
    • Me pregunto cómo funciona la rotación de IPv6 en este contexto
  • Buen texto.
    Me parece que lo mejor es separar el scraping y el parsing en procesos distintos. Si guardas el JSON o HTML original, siempre puedes volver atrás, corregir el parser y aplicarlo de nuevo.
    Como parte de mi proyecto de maestría, hice un sistema y sitio web similar para Países Bajos: https://www.superprijsvergelijker.nl/
    La mayor parte del scraping de mi proyecto consiste en hacer simples llamadas HTTP a una API JSON. Algunos sitios web usan una instancia de Playwright para obtener cookies de sesión válidas y saltarse la protección anti-bots y los CAPTCHA. El resto de los crawlers/scrapers, parsers y la API están hechos en Haskell y corren en AWS ECS. El sitio web es NextJS.
    El principal reto que sigo intentando resolver es vincular productos entre distintos supermercados para listar sus precios en una sola pantalla. Aquí hay un ejemplo: https://www.superprijsvergelijker.nl/supermarkt-aanbieding/6...
    Si al menos se proporciona un número de código de barras correcto para el producto, la mayoría de las veces funciona bien.

    • Sí, yo hago exactamente eso, y me ha ayudado más veces de las que me da orgullo admitir. Guardar el JSON/HTML original es realmente útil.
    • Genial. Justo estaba buscando algo así.
  • Creo que las dos grandes cadenas de supermercados de Australia podrían crear una estructura de duopolio con precios anticompetitivos simplemente operando algoritmos de IA para análisis de precios en ambos lados. Lo más probable es que los algoritmos terminen colaborando para maximizar las ganancias.
    Se puede hacer legalmente solo con precios obtenidos públicamente, e ilegalmente si además comparten costos de suministro o datos de ganancias por producto. El resultado probablemente sería parecido.
    Dos IA entrenadas harían una especie de maximización extraña de ganancias mediante regresión multidimensional, quizá hiperdimensional, y los consumidores terminarían pagando las ganancias maximizadas de empresas que en apariencia compiten entre sí. Si puedes obtener datos de precios de esta forma, no hace falta mucho más para correr dos implementaciones de aprendizaje automático centradas en un duopolio.

    • La “autoridad de competencia” de Noruega (https://konkurransetilsynet.no/norwegian-competition-authori...) suele ser crítica de la información pública y transparente sobre precios de alimentos precisamente por eso.
      Su argumento es que, si todos los precios son públicos, los consumidores podrían terminar pagando más. La idea es que los supermercados alinean sus precios en el punto donde todos obtienen la máxima ganancia.
      Los supermercados aquí llevan años contratando “cazadores de precios”, personas que van a las tiendas de la competencia y anotan el precio de todos los productos.
      En Noruega, a menudo se ve que si el supermercado A pone en oferta un artículo una semana, el supermercado B lanza una oferta similar la semana siguiente o la otra para atraer clientes.
    • La palabra que buscabas era colusión. Solo que no es colusión entre personas, sino colusión por software.
  • Parece que se podrían agregar verificaciones automáticas para detectar este tipo de cambios. Por ejemplo, si falla una verificación de sentido común, no se sincroniza el cambio de precio/producto.
    Podrías poner reglas como que ningún precio cambie más de 100% y que la cantidad de productos activos no cambie más de 20%.

    • Las verificaciones de sentido común en programación están subestimadas. Tienen bajo costo en rendimiento y detectan temprano bugs que, si no, terminarían contaminando el estado.
    • Yo también lo pensé, pero sí he visto casos donde el precio de un producto sube más de 100%.
      En cambio, usé ese tipo de heurísticas para comprobar si el scraping tuvo éxito. Por ejemplo, verificar si la cantidad de productos scrapeados hoy está dentro de aproximadamente un 10% del promedio de los últimos 7 días.
  • Lo difícil no es el scraping en sí, sino esquivar medidas de bloqueo cada vez más sofisticadas.
    Hay que rotar proxies residenciales constantemente, usar unos de buena reputación y evitar revelar patrones de scraping de datos. Algunos supermercados ni siquiera muestran las solicitudes de red en la pestaña de red, así que no basta con intentar capturar solo las respuestas de la API.
    Incluso si intentas hacer un ataque de intermediario contra la app móvil para ver las solicitudes y los datos de red, te bloquean si no te disfrazas bien.
    Lo intenté, pero decidí que no valía la pena por el costo y el trabajo de desarrollo continuo. De hecho, algunos servicios de comparación de precios de supermercados simplemente usan mano de obra barata para hacer el scraping.

    • Estaría bien que la ley exigiera publicar los datos de precios mediante una API. Así no habría que pelear con las medidas de bloqueo.
    • Por suerte, aún no hemos llegado a ese punto.
      Como esto es un proyecto paralelo, si empieza a exigir demasiado tiempo, probablemente simplemente lo abandone y publique todo el código y los datos.
      Pero me intriga cómo es posible que las solicitudes de red no aparezcan en la pestaña de red.
      Para mí, la parte más difícil es vincular y comparar productos entre supermercados.
    • ¿No serviría usar OCR y capturar solo imágenes de la lista de productos? No es ideal, pero dependiendo del método podría ser difícil o imposible de rastrear.
    • Hazlo por crowdsourcing con una extensión de navegador.
  • Sería bueno tener transparencia de precios para los productos. Así sería mucho más fácil hacer este seguimiento por tienda y por región.
    Por ejemplo, podrías comparar el precio de la leche de avena, como sustituto de la leche, por código postal y por supermercado. También se podría rastrear la “reduflación”, cuando el precio es el mismo pero el contenido disminuye.
    En ese sentido, ya que parece que haces seguimiento de precios, me pregunto si también revisas el costo por gramo o por onza. Los fabricantes o las tiendas podrían dejar el mismo precio pero dar menos cantidad al consumidor, y me pregunto si la herramienta podría detectar eso.

    • También rastreo el precio por unidad, como kg o L. Dudé entre mostrar y graficar ese número o mostrar el precio real que la gente paga en caja, pero elegí lo segundo para ajustarme a lo que resulta más “familiar” para la gente.
      Aun así, es una función que se puede agregar fácilmente, y si hubiera reduflación, se podría mostrar cuándo ocurrió.
    • Personalmente, me molesta muchísimo cuando una tienda no pone el precio por unidad en la etiqueta. Me cuesta imaginar un motivo que no sea hostil hacia el cliente.
    • Sería interesante imaginar que se exigiera revelar el costo base del producto. Quisiera ver que el agricultor recibió X, el fabricante recibió Y y la tienda agregó Z.
  • En el mercado sueco llevan más de 8 años haciendo esto. Existe un sitio web llamado https://www.matspar.se/ donde los clientes pueden revisar todos los productos de las principales tiendas en línea, comparar precios y luego agregar a su carrito los productos que quieran
    Al final, pueden comparar el total del carrito incluyendo el costo de envío y exportarlo a la tienda que prefieran para hacer el pedido
    Soy cofundador y actual CTO, así que durante todo este tiempo he trabajado bastante en scraping y mantenimiento. Extraemos más de 30 millones de precios al día

    • Desde el lado del negocio, tengo curiosidad por el modelo de negocio. Cómo generan ingresos y cuál es su objetivo a largo plazo
      Según datos públicos, la empresa factura alrededor de 400 mil dólares y tiene 6 empleados: https://www.allabolag.se/5590076351/matspar-i-sverige-ab
    • Me pregunto si existe algún artículo técnico sobre su enfoque de scraping. Me gustaría leer más sobre qué dificultades tuvieron y cómo las resolvieron
  • Cuando me mudé a una zona nueva sí hacía seguimiento de precios, pero ahora siento que es mucho más fácil comprar siempre en el mercado o en dos tiendas grandes que mantienen precios bajos de forma constante
    En Europa probablemente serían Aldi/Lidl
    En Estados Unidos podrían ser Costco/Trader Joe's
    En línea está CamelCamelCamel/Amazon. No para alimentos, sino más bien para salud, belleza y algunos electrónicos
    Si se puede comprar directo al fabricante, a veces eso también resulta mejor. Por ejemplo, compré al por mayor una marca específica de jabón que me gusta en un sitio mayorista y me salió a menos de la mitad del precio minorista. Con el shampoo, comprarlo por galón directamente también fue mucho más barato que en cualquier tienda minorista

    • En mi experiencia, en Estados Unidos son Costco/Walmart/Aldi
      Trader Joe's tiene mejor calidad, pero por lo general es más caro
    • En Estados Unidos también hay ALDI, aunque depende de la región. Trader Joe’s pertenece a la misma familia que ALDI y, hasta hace unos 10 años, era raro ver ambas en la misma zona