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.
¿Es legal monitorear precios de la competencia?
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
- Manejar CAPTCHA en flujos de web scraping
- Scraping fiable sin bloqueos
- Recopilación de datos para investigación de mercado