Casos de Uso

Monitoreo de precios de comercio electrónico con manejo de CAPTCHA

El monitoreo de precios casi siempre se rompe por la misma razón: la página de producto devuelve un CAPTCHA en lugar del HTML con el precio, y tu scraper guarda un valor vacío sin avisar. La solución no es reintentar más rápido, sino resolver ese desafío dentro del propio pipeline. Con la API de CaptchaAI detectas el CAPTCHA, lo envías a resolver y vuelves a cargar la página con el token válido, de modo que tu histórico de precios quede completo aunque la tienda active protección antibot a mitad de la jornada.

Antes de escribir una línea de código, conviene tener claros los tres puntos que definen un monitor fiable:

  • Detección: reconocer si la respuesta trae precio o un CAPTCHA.
  • Resolución: obtener un token válido sin intervención manual.
  • Cadencia: repetir la comprobación con la frecuencia justa para no disparar más bloqueos.

Este patrón es el mismo tanto si sigues precios en Amazon.es como en un marketplace regional tipo MercadoLibre: cambia el selector del precio, no la mecánica de resolución.

Por qué el CAPTCHA rompe el monitoreo de precios

Las tiendas grandes muestran un CAPTCHA en cuanto detectan tráfico automatizado, y cada plataforma usa una tecnología distinta. Un monitor que no sepa reconocer y resolver estos desafíos irá acumulando huecos en los datos justo cuando más importa: durante una oferta o un cambio de precio de la competencia.

Plataforma Tipo de CAPTCHA Qué lo activa
Amazon Image CAPTCHA, reCAPTCHA Volumen alto de solicitudes
Walmart Cloudflare Turnstile Detección de bots
eBay reCAPTCHA v2 Patrones sospechosos
Best Buy Cloudflare Challenge Todo el tráfico automatizado
Tiendas Shopify reCAPTCHA v3 Según la configuración de la tienda

El problema de fondo es el silencio: sin manejo de CAPTCHA, el scraper no lanza un error claro, simplemente registra un precio nulo o desactualizado y tú te enteras tarde.

Qué plan necesitas según el volumen

Antes de programar nada conviene dimensionar el coste, porque marca cuántos workers puedes permitirte. CaptchaAI no cobra por CAPTCHA resuelto, sino por thread (cada resolución en vuelo), con solves ilimitados dentro del plan. Por eso lo que importa no es cuántos CAPTCHA resuelves al día, sino cuántos workers concurrentes mantienes; para un monitor de precios eso se traduce casi directamente en el número de threads:

Escenario Workers concurrentes Plan orientativo
50 productos, cada 30 min ~5 BASIC ($15/mes, 5 threads)
200 productos, cada 15 min ~15 STANDARD ($30/mes, 15 threads)
1.000 productos, por hora ~50 ADVANCE ($90/mes, 50 threads)

No todas las cargas de página activan un CAPTCHA, así que en la práctica sueles resolver bastante por debajo del máximo teórico. Para agencias y freelancers que facturan en monedas locales volátiles, este costo mensual fijo en USD resulta más predecible que un modelo de pago por resolución.

Cómo se conecta CaptchaAI a tu pipeline

La idea es intercalar un paso de resolución entre "descargar la página" y "extraer el precio". Cuando el worker detecta un desafío, envía el sitekey y la URL a CaptchaAI, recibe el token y reintenta la carga; si no hay CAPTCHA, extrae el precio directamente.

Scheduler (every 30 min)
    → URL Queue
        → Scraper Workers (5-10 concurrent)
            → Fetch page
            → CAPTCHA detected?
                → Yes → CaptchaAI → Solve → Retry page
                → No → Parse prices
            → Store in database
        → Alert on price changes

El flujo tiene tres puntos de decisión que conviene aislar en el código:

  • El scheduler encola las URLs y los workers las procesan en paralelo.
  • Cada worker decide si la página trae precio o CAPTCHA y, solo en el segundo caso, llama a CaptchaAI.
  • El resultado se almacena y una comparación posterior dispara las alertas de cambio de precio.

Los workers son concurrentes, así que la capacidad la marca cuántas resoluciones puedes tener en vuelo a la vez: ese es exactamente el modelo de facturación por threads de CaptchaAI.

Implementación

Monitor de precios en Python

Este script resuelve tanto reCAPTCHA v2 como Cloudflare Turnstile. La función solve_captcha envía la tarea a in.php, sondea res.php cada cinco segundos y devuelve el token; fetch_with_captcha decide qué tipo de desafío hay en la página antes de reenviar la solicitud con el token correcto.

import requests
import time
import re
import json
import os
from datetime import datetime

API_KEY = os.environ["CAPTCHAAI_API_KEY"]
BASE_URL = "https://ocr.captchaai.com"


def solve_captcha(method, params):
    params["key"] = API_KEY
    params["method"] = method

    resp = requests.get(f"{BASE_URL}/in.php", params=params)
    if not resp.text.startswith("OK|"):
        raise Exception(f"Submit failed: {resp.text}")

    task_id = resp.text.split("|")[1]

    for _ in range(60):
        time.sleep(5)
        result = requests.get(f"{BASE_URL}/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id,
        })
        if result.text == "CAPCHA_NOT_READY":
            continue
        if result.text.startswith("OK|"):
            return result.text.split("|", 1)[1]
        raise Exception(f"Solve failed: {result.text}")

    raise TimeoutError("CAPTCHA solve timed out")


def fetch_with_captcha(url, session):
    """Fetch a page, solving CAPTCHAs if encountered."""
    resp = session.get(url)

    # Check for reCAPTCHA
    match = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]+)["\']', resp.text)
    if match:
        site_key = match.group(1)
        token = solve_captcha("userrecaptcha", {
            "googlekey": site_key,
            "pageurl": url,
        })
        resp = session.post(url, data={"g-recaptcha-response": token})

    # Check for Turnstile
    match = re.search(
        r'class="cf-turnstile"[^>]*data-sitekey=["\']([^"\']+)', resp.text
    )
    if match:
        site_key = match.group(1)
        token = solve_captcha("turnstile", {
            "sitekey": site_key,
            "pageurl": url,
        })
        resp = session.post(url, data={"cf-turnstile-response": token})

    return resp


def extract_price(html, selectors):
    """Extract price from HTML using regex patterns."""
    for pattern in selectors:
        match = re.search(pattern, html)
        if match:
            price_str = match.group(1).replace(",", "")
            return float(price_str)
    return None


def monitor_prices(products):
    """Monitor prices for a list of products."""
    session = requests.Session()
    session.headers["User-Agent"] = (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 Chrome/120.0.0.0"
    )

    results = []
    for product in products:
        try:
            resp = fetch_with_captcha(product["url"], session)
            price = extract_price(resp.text, product["selectors"])

            results.append({
                "name": product["name"],
                "url": product["url"],
                "price": price,
                "timestamp": datetime.utcnow().isoformat(),
                "status": "ok",
            })
            print(f"  {product['name']}: ${price}")

        except Exception as e:
            results.append({
                "name": product["name"],
                "url": product["url"],
                "price": None,
                "timestamp": datetime.utcnow().isoformat(),
                "status": f"error: {e}",
            })
            print(f"  {product['name']}: ERROR - {e}")

    return results


# Define products to monitor
products = [
    {
        "name": "Wireless Headphones",
        "url": "https://example.com/product/headphones",
        "selectors": [
            r'class="price"[^>]*>\$?([\d,]+\.?\d*)',
            r'itemprop="price" content="([\d.]+)"',
        ],
    },
    {
        "name": "Bluetooth Speaker",
        "url": "https://example.com/product/speaker",
        "selectors": [
            r'class="price"[^>]*>\$?([\d,]+\.?\d*)',
        ],
    },
]

print("Starting price check...")
results = monitor_prices(products)

# Save results
with open("prices.json", "w") as f:
    json.dump(results, f, indent=2)

Dos detalles del script marcan la diferencia en producción:

  • Cada lectura se guarda con su marca de tiempo en UTC, así comparas el mismo producto entre ejecuciones sin que el huso horario del servidor ensucie el histórico.
  • Los errores no rompen el lote: si un producto falla, se registra con estado de error y el bucle continúa con el resto del catálogo.

Versión en Node.js

Si tu stack ya vive en Node, la lógica es idéntica: enviar la tarea, sondear el resultado y reintentar la página con el token. Aquí cheerio hace de parser ligero del HTML.

const axios = require("axios");
const cheerio = require("cheerio");

const API_KEY = process.env.CAPTCHAAI_API_KEY;

async function solveCaptcha(method, params) {
  params.key = API_KEY;
  params.method = method;

  const submit = await axios.get("https://ocr.captchaai.com/in.php", {
    params,
  });
  const taskId = String(submit.data).split("|")[1];

  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const poll = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId },
    });
    const text = String(poll.data);
    if (text === "CAPCHA_NOT_READY") continue;
    if (text.startsWith("OK|")) return text.split("|").slice(1).join("|");
    throw new Error(text);
  }
  throw new Error("Timeout");
}

async function monitorPrice(url) {
  const resp = await axios.get(url);
  const $ = cheerio.load(resp.data);

  // Check for reCAPTCHA
  const siteKey = $(".g-recaptcha").attr("data-sitekey");
  if (siteKey) {
    const token = await solveCaptcha("userrecaptcha", {
      googlekey: siteKey,
      pageurl: url,
    });
    // Re-fetch with token
    const formResp = await axios.post(url, { "g-recaptcha-response": token });
    return cheerio.load(formResp.data);
  }

  const price = $('[itemprop="price"]').attr("content") || $(".price").text();
  return parseFloat(price.replace(/[^0-9.]/g, ""));
}

Programación de las comprobaciones

Un monitor de precios vive de la cadencia: comprobar demasiado seguido dispara más CAPTCHA, y comprobar poco te hace perder cambios. Un intervalo de 30 minutos es un buen punto de partida. Con cron:

# crontab -e
*/30 * * * * cd /opt/monitor && python price_monitor.py >> /var/log/prices.log 2>&1

Si prefieres no depender de cron y mantener la lógica dentro del propio proceso, la biblioteca schedule de Python funciona igual de bien:

import schedule

schedule.every(30).minutes.do(lambda: monitor_prices(products))

while True:
    schedule.run_pending()
    time.sleep(60)

Un par de reglas mantienen el monitor por debajo del radar antibot:

  • Reparte las comprobaciones a lo largo del intervalo en lugar de lanzarlas todas a la vez; las ráfagas son justo lo que activa la detección de bots.
  • Reserva los intervalos cortos para productos en oferta y usa comprobaciones más espaciadas para el resto del catálogo.

Preguntas frecuentes

¿Qué plan de CaptchaAI necesito para monitorear precios?

Depende de tu concurrencia, no del total diario. Si corres 5 workers en paralelo, el plan BASIC ($15/mes, 5 threads) suele bastar; para 15 workers, STANDARD ($30/mes, 15 threads). Como los solves son ilimitados dentro del plan, puedes ampliar el catálogo de productos sin pagar por cada resolución.

¿CaptchaAI resuelve el reCAPTCHA v3 de las tiendas Shopify?

Sí. CaptchaAI cubre reCAPTCHA v2 y v3, Cloudflare Turnstile y Challenge, GeeTest v3 e imagen/OCR, que son los tipos que verás en la mayoría de tiendas. Ten en cuenta que no resuelve hCaptcha ni FunCaptcha, así que revisa qué protección usa tu objetivo antes de montar el monitor.

Los precios públicos no suelen ser confidenciales, pero cada sitio impone sus propios términos de servicio y hay normativa de protección de datos aplicable (GDPR y LOPDGDD en España, LFPDPPP en México). Respeta los términos del sitio, limita la frecuencia y no recolectes datos personales; esto no es asesoría legal.

¿Cada cuánto conviene comprobar los precios?

Para la mayoría de catálogos, entre 15 y 30 minutos equilibra frescura y coste. Reserva los intervalos cortos para productos en oferta o de rotación rápida, y usa comprobaciones horarias para el resto del catálogo.

Guías relacionadas

Los comentarios están deshabilitados para este artículo.