Casos de Uso

Monitoreo de inventario minorista con manejo CAPTCHA

Seguir el precio y el stock de miles de productos en tiendas online es sencillo hasta que aparece el primer CAPTCHA y tu scraper se detiene. La respuesta no es dejar de automatizar, sino resolver ese CAPTCHA de forma programática: CaptchaAI devuelve el token de reCAPTCHA v2, reCAPTCHA v3 o Cloudflare Turnstile en segundos y tu monitor de precios continúa sin intervención manual.

Este es el flujo que usa, por ejemplo, una agencia que vigila precios en Amazon.es o en marketplaces regionales tipo MercadoLibre para su cliente de electrónica: comprobaciones periódicas, detección de cambios de precio y alertas de reposición, con el captcha solver integrado dentro del ciclo de scraping. Respeta siempre los términos de servicio del sitio y la normativa de protección de datos aplicable. Abajo tienes el código en Python listo para adaptar, además de las cadencias recomendadas y una guía de resolución de problemas.


Qué CAPTCHA aparece en cada plataforma de retail

Antes de escribir una línea de código conviene saber qué te vas a encontrar. Estos son los tipos más habituales por plataforma y qué dato suele interesar extraer:

Plataforma Tipo de CAPTCHA Qué lo activa Datos objetivo
Amazon reCAPTCHA v2 acceso de alto volumen precio, stock, reseñas
Walmart reCAPTCHA v3 + Cloudflare detección de bots inventario, precios
Best Buy reCAPTCHA v2 validación de "añadir al carrito" stock, precios
Target Cloudflare Turnstile acceso automatizado disponibilidad
Tiendas Shopify Cloudflare Turnstile límite de solicitudes datos de producto
eBay reCAPTCHA v2 búsqueda y acceso a listados listados, precios

CaptchaAI resuelve los tres tipos que dominan esta tabla —reCAPTCHA v2, reCAPTCHA v3 y Cloudflare Turnstile—, así que el mismo monitor cubre casi todo el retail sin cambiar de proveedor.


Qué necesitas antes de empezar

Reúne estas piezas y el resto es adaptar los selectores a cada tienda:

  • una clave de API de CaptchaAI (el YOUR_API_KEY del código);
  • Python con requests y BeautifulSoup instalados;
  • una salida de red autorizada por producto o categoría;
  • una cadencia de comprobación acorde a cada tipo de producto (la tienes justo debajo).

Con qué frecuencia comprobar cada tipo de producto

La cadencia decide cuántos CAPTCHA vas a disparar y qué salida de red te conviene. Productos volátiles piden intervalos cortos; los estables toleran comprobaciones espaciadas y salidas más baratas:

Tipo de producto Frecuencia de comprobación Salida de red recomendada
Electrónica cada 30 minutos residencial rotativa
Alimentación cada 4 horas residencial rotativa
Moda cada 2 horas residencial
Lanzamientos limitados cada 1 minuto red móvil autorizada
Artículos para el hogar cada 6 horas residencial
Productos básicos cada 12 horas centro de datos (a menudo suficiente)

Cómo monitorear un producto con manejo de CAPTCHA

El núcleo es una clase reutilizable: pide la página, detecta si vino un CAPTCHA, lo resuelve con CaptchaAI, reenvía el token y luego extrae precio, disponibilidad y estado de stock. La misma clase sirve para una comprobación puntual o para un bucle continuo.

import requests
import time
import re
import json
from datetime import datetime
from bs4 import BeautifulSoup

CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"


def solve_captcha(method, sitekey, pageurl, **kwargs):
    data = {
        "key": CAPTCHAAI_KEY, "method": method,
        "googlekey": sitekey, "pageurl": pageurl, "json": 1,
    }
    data.update(kwargs)
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data=data)
    task_id = resp.json()["request"]
    for _ in range(60):
        time.sleep(5)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        r = result.json()
        if r["request"] != "CAPCHA_NOT_READY":
            return r["request"]
    raise TimeoutError("Timeout")


class RetailMonitor:
    def __init__(self, proxy=None):
        self.session = requests.Session()
        if proxy:
            self.session.proxies = {"http": proxy, "https": proxy}
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
            "Accept": "text/html,application/xhtml+xml,*/*;q=0.8",
            "Accept-Language": "en-US,en;q=0.9",
        })

    def check_product(self, url):
        """Check single product's price and availability."""
        resp = self.session.get(url, timeout=30)

        # Handle CAPTCHA
        if self._has_captcha(resp.text):
            resp = self._solve_and_retry(resp.text, url)

        soup = BeautifulSoup(resp.text, "html.parser")
        return {
            "url": url,
            "title": self._text(soup, "h1, .product-title, #productTitle"),
            "price": self._text(soup, ".price, .a-price .a-offscreen, .prod-price"),
            "availability": self._text(soup, "#availability, .stock-status, .fulfillment"),
            "in_stock": self._check_stock(soup),
            "timestamp": datetime.now().isoformat(),
        }

    def monitor_products(self, product_urls, interval_sec=1800):
        """Continuously monitor products for changes."""
        history = {}

        while True:
            for url in product_urls:
                try:
                    current = self.check_product(url)

                    # Check for changes
                    prev = history.get(url)
                    if prev:
                        changes = self._detect_changes(prev, current)
                        if changes:
                            self._alert(current["title"], changes)

                    history[url] = current
                    time.sleep(3)

                except Exception as e:
                    print(f"Error checking {url}: {e}")

            print(f"Cycle complete: {len(product_urls)} products checked")
            time.sleep(interval_sec)

    def track_prices(self, product_urls, output_file="prices.json"):
        """Single price check across all products."""
        results = []
        for url in product_urls:
            try:
                data = self.check_product(url)
                results.append(data)
                time.sleep(3)
            except Exception as e:
                results.append({"url": url, "error": str(e)})

        with open(output_file, "w") as f:
            json.dump(results, f, indent=2)
        print(f"Tracked {len(results)} products → {output_file}")
        return results

    def _has_captcha(self, html):
        return any(tag in html.lower() for tag in [
            'data-sitekey', 'g-recaptcha', 'cf-turnstile', 'captcha',
        ])

    def _solve_and_retry(self, html, url):
        match = re.search(r'data-sitekey="([^"]+)"', html)
        if not match:
            return self.session.get(url)

        sitekey = match.group(1)
        if 'cf-turnstile' in html:
            token = solve_captcha("turnstile", sitekey, url)
            return self.session.post(url, data={"cf-turnstile-response": token})
        else:
            token = solve_captcha("userrecaptcha", sitekey, url)
            return self.session.post(url, data={"g-recaptcha-response": token})

    def _text(self, soup, selector):
        el = soup.select_one(selector)
        return el.get_text(strip=True) if el else ""

    def _check_stock(self, soup):
        stock_el = soup.select_one("#availability, .stock-status")
        if stock_el:
            text = stock_el.get_text(strip=True).lower()
            return "in stock" in text or "available" in text
        return None

    def _detect_changes(self, prev, current):
        changes = []
        if prev["price"] != current["price"]:
            changes.append(f"Price: {prev['price']} → {current['price']}")
        if prev["in_stock"] != current["in_stock"]:
            status = "In Stock" if current["in_stock"] else "Out of Stock"
            changes.append(f"Stock: → {status}")
        return changes

    def _alert(self, title, changes):
        print(f"ALERT [{title}]: {', '.join(changes)}")


# Usage
monitor = RetailMonitor(
    proxy="http://user:pass@residential.proxy.com:5000"
)

products = [
    "https://store.example.com/product/abc123",
    "https://store.example.com/product/def456",
    "https://store.example.com/product/ghi789",
]

# One-time price check
results = monitor.track_prices(products)

# Or continuous monitoring (every 30 min)
# monitor.monitor_products(products, interval_sec=1800)

El método _solve_and_retry elige el method correcto según lo que detecta en el HTML: turnstile cuando encuentra cf-turnstile y userrecaptcha para reCAPTCHA. El token que devuelve CaptchaAI se reenvía en el campo esperado (cf-turnstile-response o g-recaptcha-response) y la sesión sigue su curso.


Escaneo de stock de toda una categoría

Para vigilar un catálogo completo en lugar de URLs sueltas, recorre las páginas de una categoría y resuelve el CAPTCHA solo cuando aparezca. El bucle se detiene solo en cuanto una página deja de devolver artículos:

def scan_category(base_url, category, max_pages=20):
    """Scan an entire product category for stock status."""
    monitor = RetailMonitor(
        proxy="http://user:pass@residential.proxy.com:5000"
    )

    all_products = []
    for page in range(1, max_pages + 1):
        url = f"{base_url}/{category}?page={page}"
        resp = monitor.session.get(url, timeout=30)

        if monitor._has_captcha(resp.text):
            resp = monitor._solve_and_retry(resp.text, url)

        soup = BeautifulSoup(resp.text, "html.parser")
        items = soup.select(".product-card, .s-result-item")

        if not items:
            break

        for item in items:
            all_products.append({
                "name": monitor._text(item, ".product-name, .a-text-normal"),
                "price": monitor._text(item, ".price, .a-price"),
                "stock": monitor._text(item, ".stock, .a-color-success"),
                "url": item.select_one("a")["href"] if item.select_one("a") else "",
            })

        time.sleep(3)

    return all_products

Comparar precios entre tiendas competidoras

El mismo patrón resuelve el caso de negocio más pedido: cuánto cuesta un producto en cada retailer. Se busca el término en cada tienda, se resuelve el CAPTCHA si aparece y se ordenan los resultados por precio.

def compare_product_across_stores(product_name, stores):
    """Compare prices across retailers for the same product."""
    results = []

    for store in stores:
        monitor = RetailMonitor(proxy=store.get("proxy"))
        search_url = f"{store['base_url']}/search?q={product_name}"

        try:
            resp = monitor.session.get(search_url, timeout=30)
            if monitor._has_captcha(resp.text):
                resp = monitor._solve_and_retry(resp.text, search_url)

            soup = BeautifulSoup(resp.text, "html.parser")
            first_result = soup.select_one(".product-card, .s-result-item")

            if first_result:
                results.append({
                    "store": store["name"],
                    "price": monitor._text(first_result, ".price"),
                    "in_stock": "in stock" in first_result.get_text().lower(),
                })
        except Exception as e:
            results.append({"store": store["name"], "error": str(e)})

        time.sleep(5)

    results.sort(key=lambda x: x.get("price", "zzzz"))
    return results

Solución de problemas frecuentes

Problema Causa Solución
Aparece un CAPTCHA en cada página de producto IP marcada o frecuencia excedida aumenta el retraso y rota las IP de salida
Precio incorrecto al extraer precios dinámicos renderizados por JS usa Selenium o Puppeteer en su lugar
Página de "verificación de robot" detección de bots tipo Amazon usa headers realistas y salida de red residencial autorizada
El stock aparece como "no disponible" disponibilidad restringida por región haz coincidir la salida de red con la región objetivo
Faltan datos del producto cambió la estructura de la página actualiza los selectores CSS

Preguntas frecuentes

¿Qué plan de CaptchaAI necesito para monitorear un catálogo grande?

Depende de cuántas comprobaciones quieras lanzar en paralelo, no del número de productos. CaptchaAI factura por thread concurrente con resoluciones ilimitadas: el plan BASIC ($15/mes, 5 threads) sirve para un monitor pequeño, mientras que ADVANCE ($90/mes, 50 threads) mantiene decenas de escaneos simultáneos sin coste por resolución.

¿CaptchaAI resuelve el CAPTCHA de Amazon o el de Cloudflare Turnstile?

Sí. Resuelve reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile y Cloudflare Challenge, que son los tipos que verás en Amazon, Walmart, Target y tiendas Shopify. No resuelve hCaptcha ni FunCaptcha, así que si un retailer los usa necesitarás otro enfoque.

Los precios públicos suelen ser consultables, pero cada sitio tiene sus términos de servicio y aplica normativa de protección de datos (GDPR y LOPDGDD en España, LFPDPPP en México). Limita el scraping a datos públicos, respeta esos términos y no recopiles información personal.

¿Cómo evito que me bloqueen tras unas pocas páginas?

Espacia las solicitudes, rota las IP de salida y usa headers realistas. Comprobar cada producto cada 30–60 minutos es un ritmo seguro para la mayoría de retailers; bajar a 1–5 minutos funciona, pero dispara más CAPTCHA y exige más salidas de red.


Guías relacionadas


Monitorea el inventario minorista en tiempo real: obtén tu clave de CaptchaAI y añade manejo automatizado de CAPTCHA a tu pipeline.

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