Volver al Blog
Ingeniería de Datos Python Automatización Supabase

Web Scraping de Alto Rendimiento y Resiliente

Escrito por Marlon

El Límite de la Extracción de Datos: Superando los Scripts Básicos

Hoy en día, las empresas toman decisiones basadas en datos. Aquellas que lideran el mercado monitorean precios de competidores, extraen información histórica de tendencias o consolidan catálogos masivos de proveedores de forma 100% automatizada. Sin embargo, muchas inician este camino con scripts frágiles (soluciones basadas en librerías básicas de Python que funcionan bien en pruebas locales pero fallan estrepitosamente en producción).

Cuando intentas extraer millones de registros, la web se vuelve inestable y hostil. Los cambios repentinos en la estructura HTML del sitio web destino, los bloqueos de IP, la latencia de red y los sistemas de protección contra bots hacen que los rastreadores mal diseñados se detengan, generen datos duplicados o corrompan la base de datos.

Para que el web scraping profesional aporte valor real a una mediana empresa o startup, debe diseñarse bajo estándares estrictos de ingeniería de datos y arquitectura de software.


Diseñando para la Robustez: Evasión de Bloqueos y Tolerancia a Fallos

Un pipeline de datos resiliente asume que los fallos de red y los intentos de bloqueo van a ocurrir. Para garantizar la continuidad de la extracción de datos de manera automatizada, implementamos tres estrategias clave:

1. Rotación Inteligente de Proxies y Firmas TLS

Utilizar una única dirección IP garantiza un bloqueo inmediato. La arquitectura debe integrar pools de proxies residenciales rotativos. Adicionalmente, los sistemas modernos de detección de bots analizan las huellas TLS y las cabeceras HTTP/2. Configurar cabeceras consistentes y emular comportamientos humanos es obligatorio para evitar bloqueos.

2. Control de Concurrencia y Rate Limiting

Saturar un servidor objetivo no solo es una mala práctica, sino que acelera el bloqueo de tus credenciales o IPs. Controlar la velocidad de extracción mediante semáforos o colas de tareas mantiene el tráfico bajo un patrón seguro.

3. Reintentos Automáticos con Backoff Exponencial

Si el servidor destino responde con un error temporal (como 429 Too Many Requests o 503 Service Unavailable), el pipeline no debe detenerse. Se debe implementar un algoritmo de backoff exponencial con “jitter” (ruido aleatorio) para reintentar la solicitud espaciando el tiempo de espera.

A continuación, un ejemplo en Python de un cliente HTTP asíncrono y altamente resiliente utilizando tenacity e httpx:

import asyncio
import httpx
from tenacity import retry, wait_random_exponential, stop_after_attempt, retry_if_exception_type

# Definición de política de reintento robusta con backoff exponencial y jitter
@retry(
    wait=wait_random_exponential(min=1, max=10),
    stop=stop_after_attempt(5),
    retry=retry_if_exception_type(httpx.HTTPError),
    reraise=True
)
async def fetch_page_with_retry(client: httpx.AsyncClient, url: str, headers: dict) -> str:
    response = await client.get(url, headers=headers, timeout=10.0)

    # Lanza una excepción si hay errores HTTP (4xx/5xx) para disparar el reintento
    response.raise_for_status()
    return response.text

async def main():
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...",
        "Accept-Language": "es-ES,es;q=0.9"
    }

    async with httpx.AsyncClient(proxies="http://your-proxy-pool.com:8000") as client:
        try:
            html = await fetch_page_with_retry(client, "https://api.target.com/data", headers)
            print("Datos extraídos con éxito.")
        except Exception as e:
            print(f"Error al extraer datos tras múltiples intentos: {e}")

if __name__ == "__main__":
    asyncio.run(main())

Ingesta Eficiente de Datos: Optimización de Base de Datos

La extracción es solo la primera parte del flujo. Almacenar millones de registros de forma eficiente en tu base de datos (como una instancia de PostgreSQL o python y supabase) requiere optimizar la escritura:

  1. Inserciones en Lote (Bulk Inserts): Agrupa los datos extraídos en memoria y realiza escrituras en bloques (de 500 a 1000 registros). Esto reduce drásticamente las conexiones abiertas y el tiempo de procesamiento en red.
  2. Operaciones Upsert (ON CONFLICT DO UPDATE): Evita duplicados actualizando los registros existentes o insertando los nuevos de forma nativa en una sola consulta de base de datos a través de una clave única.
  3. Pools de Conexión: Utiliza gestores de conexiones transaccionales para que la alta concurrencia de tus procesos de scraping no sature la base de datos.

Aquí tienes un ejemplo de inserción masiva y actualización (upsert) en lotes usando el cliente de Supabase en Python:

from supabase import create_client, Client
import math

SUPABASE_URL = "https://your-project.supabase.co"
SUPABASE_KEY = "your-service-role-key"
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)

def batch_upsert_data(records: list, batch_size: int = 1000):
    total_records = len(records)
    batches = math.ceil(total_records / batch_size)

    print(f"Iniciando ingesta de {total_records} registros en {batches} lotes...")

    for i in range(batches):
        start_idx = i * batch_size
        end_idx = start_idx + batch_size
        batch = records[start_idx:end_idx]

        try:
            # Alta performance usando upsert masivo mapeado a clave única
            response = supabase.table("extracted_products").upsert(
                batch,
                on_conflict="sku" # Restricción única para actualizar si ya existe el SKU
            ).execute()
            print(f"Lote {i+1}/{batches} guardado con éxito.")
        except Exception as e:
            print(f"Error al escribir el lote {i+1}: {e}")
            # En entornos reales, envía los lotes fallidos a una cola de errores (DLQ)

# Ejemplo de carga de datos
items_extraidos = [{"sku": f"PROD-{idx}", "price": 29.99, "stock": idx} for idx in range(5000)]
batch_upsert_data(items_extraidos)

El Enfoque Senior + IA de Coins5

Diseñar flujos de datos de alto rendimiento requiere criterio arquitectónico profundo. Al estructurar pipelines de datos resilientes bajo Clean Architecture, separamos las reglas de negocio del scraper de los detalles de red y almacenamiento, permitiendo adaptarnos a cambios en los sitios destino en cuestión de minutos.

En Coins5, Marlon combina más de 10 años de experiencia como Arquitecto de Software y desarrollador Full Stack con el uso avanzado de Inteligencia Artificial para la aceleración del código. Esto nos permite estructurar sistemas de extracción masiva altamente robustos y optimizados para consumir el mínimo ancho de banda y hardware de servidor posible, entregando el proyecto hasta 3 veces más rápido.

¿Quieres automatizar la recolección de datos en tu negocio?

  • Agenda una Reunión: Book a Call para conversar sobre tu arquitectura de datos en una llamada de descubrimiento de 15 minutos.
  • Cotiza por WhatsApp: Conversemos directamente por WhatsApp para evaluar alcances, cotizar costos y planificar tiempos de entrega.