DevOps y Escalado

Monitoreo de tasas de resolución CAPTCHA con Prometheus y Grafana

Si tu tasa de resolución cae a las tres de la mañana, quieres enterarte por una alerta en tu teléfono, no por un cliente enfadado al día siguiente. La respuesta corta es instrumentar tu solver: Prometheus recoge las métricas, Grafana las dibuja en un panel y las reglas de alerta te avisan cuando algo se sale de rango. Con menos de cien líneas de Python ya tienes visibilidad completa sobre cuántos CAPTCHA resuelves, cuánto tardan y cuánto saldo te queda.

Este es el patrón que usan los equipos que corren resolución de CAPTCHA en producción, y encaja igual de bien si automatizas la extracción de datos de un marketplace regional que si monitorizas un portal de cita previa protegido por reCAPTCHA.


Antes de empezar: lo que necesitas

El montaje asume tres cosas en tu máquina o servidor:

  • Docker y Docker Compose instalados, para levantar todo el stack de una sola vez.
  • Una clave API de CaptchaAI activa (el plan BASIC basta para empezar).
  • Python 3.9 o superior con la biblioteca prometheus_client.

Qué métricas vale la pena vigilar

Antes de escribir código, decide qué quieres medir. Con seis métricas cubres casi todas las preguntas operativas del día a día:

  • ¿Está resolviendo? Volumen total y resoluciones exitosas.
  • ¿Va rápido? Distribución del tiempo de resolución.
  • ¿Falla mucho? Errores desglosados por código.
  • ¿Me queda saldo? Saldo de la cuenta y cola pendiente.

Estas son las seis métricas que exponemos:

Métrica Tipo Propósito
captcha_solves_total Counter Total de intentos de resolución
captcha_solves_success Counter Resoluciones exitosas
captcha_solves_errors Counter Resoluciones fallidas (por código de error)
captcha_solve_duration Histogram Distribución del tiempo de resolución
captcha_balance Gauge Saldo actual de la cuenta
captcha_queue_length Gauge Tareas pendientes en cola

La distinción entre Counter y Gauge importa: un Counter solo sube (y se reinicia al reiniciar el proceso, por eso siempre lo consultas con rate()), mientras que un Gauge sube y baja, ideal para el saldo o la longitud de la cola.


Exportador de métricas en Python

El corazón del sistema es un envoltorio alrededor de tu solver que incrementa los contadores y observa la duración en cada llamada. Fíjate en la etiqueta method: gracias a ella podrás desglosar cada métrica por tipo de CAPTCHA (reCAPTCHA v2, Turnstile, GeeTest v3) sin duplicar código. El servidor HTTP arranca en el puerto 8000 y expone todo en /metrics.

# metrics.py
import time
import requests
from prometheus_client import (
    Counter, Histogram, Gauge, start_http_server,
)


# Define metrics
SOLVES_TOTAL = Counter(
    "captcha_solves_total",
    "Total CAPTCHA solve attempts",
    ["method"],
)

SOLVES_SUCCESS = Counter(
    "captcha_solves_success",
    "Successful CAPTCHA solves",
    ["method"],
)

SOLVES_ERRORS = Counter(
    "captcha_solves_errors",
    "Failed CAPTCHA solves",
    ["method", "error_code"],
)

SOLVE_DURATION = Histogram(
    "captcha_solve_duration_seconds",
    "CAPTCHA solve duration in seconds",
    ["method"],
    buckets=[5, 10, 15, 20, 30, 45, 60, 90, 120],
)

BALANCE = Gauge(
    "captcha_balance_usd",
    "Current CaptchaAI account balance in USD",
)

QUEUE_LENGTH = Gauge(
    "captcha_queue_length",
    "Number of pending CAPTCHA tasks",
)


class InstrumentedSolver:
    """Solver with Prometheus metric instrumentation."""

    def __init__(self, api_key):
        self.api_key = api_key
        self.base = "https://ocr.captchaai.com"

    def solve(self, method, **params):
        """Solve CAPTCHA with metric collection."""
        SOLVES_TOTAL.labels(method=method).inc()
        start = time.time()

        try:
            token = self._do_solve(method, params)
            duration = time.time() - start

            SOLVES_SUCCESS.labels(method=method).inc()
            SOLVE_DURATION.labels(method=method).observe(duration)

            return token

        except Exception as e:
            error_code = str(e)[:30]
            SOLVES_ERRORS.labels(
                method=method, error_code=error_code,
            ).inc()
            raise

    def update_balance(self):
        """Fetch and update balance metric."""
        resp = requests.get(f"{self.base}/res.php", params={
            "key": self.api_key,
            "action": "getbalance",
            "json": 1,
        }, timeout=15)
        balance = float(resp.json()["request"])
        BALANCE.set(balance)
        return balance

    def _do_solve(self, method, params, timeout=120):
        data = {"key": self.api_key, "method": method, "json": 1}
        data.update(params)

        resp = requests.post(
            f"{self.base}/in.php", data=data, timeout=30,
        )
        result = resp.json()

        if result.get("status") != 1:
            raise RuntimeError(result.get("request"))

        task_id = result["request"]
        start = time.time()

        while time.time() - start < timeout:
            time.sleep(5)
            resp = requests.get(f"{self.base}/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1,
            }, timeout=15)
            data = resp.json()
            if data["request"] != "CAPCHA_NOT_READY":
                if data.get("status") == 1:
                    return data["request"]
                raise RuntimeError(data["request"])

        raise TimeoutError("Solve timeout")


# Start metrics server on port 8000
start_http_server(8000)
print("Metrics server running on :8000/metrics")

Acuérdate de llamar a update_balance() de forma periódica (por ejemplo, en un hilo que corra cada minuto); de lo contrario el Gauge del saldo se quedará congelado en su último valor.


Configuración de Prometheus

Prometheus funciona al revés de lo que muchos esperan: no recibe métricas, va a buscarlas. Aquí le decimos que consulte tu solver cada 10 segundos. Un intervalo más corto da más resolución temporal, pero también más carga; para resolución de CAPTCHA, entre 10 y 15 segundos es un punto de equilibrio sensato.

# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:

  - job_name: "captcha-solver"
    static_configs:

      - targets: ["solver-app:8000"]
    scrape_interval: 10s

Stack completo con Docker Compose

Para tener el solver, Prometheus y Grafana levantados con un solo comando, este docker-compose.yml los conecta en la misma red. La contraseña de Grafana es solo para arranque local: cámbiala antes de exponer nada fuera de tu máquina.

# docker-compose.yml
version: "3.8"

services:
  solver:
    build: .
    environment:

      - CAPTCHAAI_KEY=${CAPTCHAAI_KEY}
    ports:

      - "8000:8000"

  prometheus:
    image: prom/prometheus:latest
    volumes:

      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:

      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    ports:

      - "3000:3000"
    environment:

      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:

      - grafana-data:/var/lib/grafana

volumes:
  grafana-data:

Consultas PromQL para tu panel de Grafana

Con las métricas fluyendo, estas cinco consultas cubren el panel base. Cópialas tal cual al crear cada panel en Grafana y ajusta la ventana [5m] según el volumen que muevas.

Tasa de éxito

rate(captcha_solves_success[5m])
/ rate(captcha_solves_total[5m]) * 100

Tiempo medio de resolución

rate(captcha_solve_duration_seconds_sum[5m])
/ rate(captcha_solve_duration_seconds_count[5m])

Errores por tipo

sum by (error_code) (
  rate(captcha_solves_errors[5m])
)

Saldo a lo largo del tiempo

captcha_balance_usd

Duración P95 de la resolución

histogram_quantile(0.95,
  rate(captcha_solve_duration_seconds_bucket[5m])
)

El P95 es más honesto que la media: te dice qué experimenta el peor 5 % de tus solicitudes, que suele ser justo lo que dispara los tiempos de espera aguas abajo.


Reglas de alerta que sí sirven

Un panel bonito no despierta a nadie. Estas tres reglas cubren los fallos que de verdad importan: quedarte sin saldo, una racha de errores y una degradación de la latencia. Ajusta los umbrales a tu realidad antes de ponerlas en producción.

# alert_rules.yml
groups:

  - name: captcha-alerts
    rules:

      - alert: LowBalance
        expr: captcha_balance_usd < 5
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "CaptchaAI balance below $5"

      - alert: HighErrorRate
        expr: |
          rate(captcha_solves_errors[5m])
          / rate(captcha_solves_total[5m]) > 0.1
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "CAPTCHA error rate above 10%"

      - alert: SlowSolveTime
        expr: |
          histogram_quantile(0.95,
            rate(captcha_solve_duration_seconds_bucket[5m])
          ) > 60
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "P95 solve time exceeds 60s"

Un caso concreto: agencia con picos nocturnos

Imagina una agencia de datos en Madrid que extrae precios de un marketplace latinoamericano durante la madrugada, cuando la carga es baja. El volumen se dispara de 2:00 a 5:00 y el resto del día es plano. Sin observabilidad, un cambio en el CAPTCHA del sitio objetivo pasaría inadvertido hasta que el informe de la mañana llegara vacío.

Con este stack, la alerta HighErrorRate salta a los diez minutos y el panel muestra, por la etiqueta error_code, si el problema es de red, de saldo o de un tipo de CAPTCHA que empezó a fallar. Como CaptchaAI factura por thread concurrente con resoluciones ilimitadas —el plan BASIC cuesta $15/mes con 5 threads y escala hasta VIP-3 a $7,500/mes con 5.000 threads—, el coste mensual en USD es predecible y encaja bien con equipos que facturan a sus clientes en monedas locales volátiles.


Solución de problemas

Problema Causa Solución
No aparecen métricas en /metrics El servidor no arrancó Llama a start_http_server(8000)
Prometheus marca el target como "down" Dirección de destino incorrecta Revisa la red y el puerto en Docker
Grafana no muestra datos Falta Prometheus como fuente de datos Añade la fuente de datos de Prometheus en Grafana
Las métricas se reinician al reiniciar Comportamiento esperado del Counter Usa rate(), nunca contadores en crudo

Preguntas frecuentes

¿Cada cuánto conviene hacer scrape sin sobrecargar el solver?

Entre 10 y 15 segundos es lo habitual. La biblioteca prometheus_client añade menos de 1 ms por operación, así que la instrumentación es prácticamente gratis; el coste real está en el intervalo de recolección, y a ese ritmo el impacto sobre el solver es imperceptible.

¿Cómo separo las métricas por tipo de CAPTCHA?

Gracias a la etiqueta method. Como el exportador la aplica a cada contador e histograma, en Grafana puedes filtrar o agrupar por method para comparar, por ejemplo, la tasa de éxito de reCAPTCHA v2 frente a la de Cloudflare Turnstile sin tocar el código.

¿Puedo recibir una alerta antes de quedarme sin saldo?

Sí. La regla LowBalance vigila el Gauge captcha_balance_usd y avisa cuando baja de un umbral (5 dólares en el ejemplo). Sube ese número si tu consumo por hora es alto, de modo que la alerta te dé margen para recargar antes de que se detengan las resoluciones.

¿Funciona con workers repartidos en varias máquinas?

Sí. Cada worker expone su propio endpoint /metrics y Prometheus recolecta todos los destinos que declares en scrape_configs. Grafana agrega los datos de todas las instancias de forma automática, así que ves el conjunto sin trabajo extra.


Guías relacionadas


Observabilidad de punta a punta: monitoriza CaptchaAI con Prometheus y duerme tranquilo.

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