DevOps & Scaling

Surveillance CaptchaAI avec Datadog : métriques et alertes

Un pipeline de résolution CAPTCHA tombe rarement en panne d'un seul coup. Le solde s'épuise, la latence dérive, un worker se fige — et vous ne le découvrez que lorsque vos scrapers renvoient des erreurs en masse. Cet article montre comment instrumenter votre intégration CaptchaAI avec Datadog pour transformer ces signaux faibles en métriques et en alertes qui se déclenchent avant la rupture. Vous repartez avec le code d'instrumentation (Python et Node.js), un tableau de bord prêt à importer et un jeu d'alertes calibré pour la production.

Ce que Datadog doit surveiller dans un pipeline CAPTCHA

Avant d'écrire la moindre alerte, décidez de ce que vous mesurez. Sept métriques couvrent l'essentiel d'un solveur de CAPTCHA : elles expliquent, à elles seules, pourquoi un scraper ralentit ou s'arrête.

Métrique Type Ce qu'elle révèle
captcha.solve.count Compteur Nombre total de tâches soumises
captcha.solve.success Compteur Résolutions réussies
captcha.solve.error Compteur Résolutions en échec, ventilées par code d'erreur
captcha.solve.latency Histogramme Temps entre la soumission et la solution
captcha.queue.depth Jauge Tâches en attente dans la file
captcha.balance Jauge Solde API restant
captcha.worker.active Jauge Workers actifs

Bien taguer vos métriques

Taguez chaque métrique par type de CAPTCHA (captcha_type:recaptcha_v2, turnstile, geetest_v3…) pour comparer la latence de reCAPTCHA v2 et de Turnstile sur un même graphique. Quelques règles évitent les mauvaises surprises :

  • Taguez par catégorie (captcha_type, host), jamais par identifiant unique.
  • Gardez des tags strictement techniques ; n'y injectez aucune donnée personnelle issue des pages parcourues (RGPD).
  • Un seul agent DogStatsD par hôte : tous les workers de la machine y envoient leurs métriques.

Exemple concret : avec deux instances OVHcloud en région eu-west taguées par host, vous repérez immédiatement qu'un seul hôte concentre les timeouts — souvent le signe d'un proxy résidentiel défaillant plutôt qu'un problème côté CaptchaAI.

Instrumenter le solveur en Python avec DogStatsD

Le décorateur ci-dessous enveloppe votre fonction de résolution et émet les métriques automatiquement : un compteur par tâche, un incrément de réussite avec histogramme de latence quand la solution arrive, un compteur d'erreur tagué par code sinon. Il fonctionne avec n'importe quel type pris en charge — reCAPTCHA v2, reCAPTCHA v3, Turnstile, GeeTest v3.

import os
import time
import functools
import requests
from datadog import initialize, statsd

# Initialize Datadog
initialize(
    statsd_host=os.environ.get("DD_AGENT_HOST", "localhost"),
    statsd_port=int(os.environ.get("DD_DOGSTATSD_PORT", "8125"))
)

API_KEY = os.environ["CAPTCHAAI_API_KEY"]
session = requests.Session()


def track_captcha_metrics(captcha_type="recaptcha_v2"):
    """Decorator to track solve metrics."""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            tags = [f"captcha_type:{captcha_type}"]
            statsd.increment("captcha.solve.count", tags=tags)

            start = time.time()
            try:
                result = func(*args, **kwargs)
                elapsed = time.time() - start

                if "solution" in result:
                    statsd.increment("captcha.solve.success", tags=tags)
                    statsd.histogram("captcha.solve.latency", elapsed, tags=tags)
                else:
                    error = result.get("error", "unknown")
                    statsd.increment(
                        "captcha.solve.error",
                        tags=tags + [f"error:{error}"]
                    )
                return result
            except Exception as e:
                statsd.increment(
                    "captcha.solve.error",
                    tags=tags + [f"error:{type(e).__name__}"]
                )
                raise
        return wrapper
    return decorator


@track_captcha_metrics(captcha_type="recaptcha_v2")
def solve_recaptcha(sitekey, pageurl):
    resp = session.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": pageurl,
        "json": 1
    })
    data = resp.json()
    if data.get("status") != 1:
        return {"error": data.get("request")}

    captcha_id = data["request"]
    for _ in range(60):
        time.sleep(5)
        result = session.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": captcha_id, "json": 1
        }).json()
        if result.get("status") == 1:
            return {"solution": result["request"]}
        if result.get("request") != "CAPCHA_NOT_READY":
            return {"error": result.get("request")}
    return {"error": "TIMEOUT"}


def report_balance():
    """Send balance as a gauge metric."""
    resp = session.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY, "action": "getbalance", "json": 1
    })
    data = resp.json()
    if data.get("status") == 1:
        balance = float(data["request"])
        statsd.gauge("captcha.balance", balance)
        return balance
    return None


def report_queue_depth(depth):
    """Report current queue depth."""
    statsd.gauge("captcha.queue.depth", depth)


def report_worker_count(active, total):
    """Report worker health."""
    statsd.gauge("captcha.worker.active", active)
    statsd.gauge("captcha.worker.total", total)

report_balance() interroge res.php avec action=getbalance et publie le solde comme jauge. Planifiez-le une fois par minute, hors de la boucle de résolution, pour ne pas gonfler vos appels API.

Faire la même chose en Node.js

Si vos workers tournent sous Node.js, la bibliothèque hot-shots fournit le même client DogStatsD. Le préfixe captcha. et les tags globaux (env:production) sont posés une seule fois à la création du client, ce qui garde le reste du code propre.

const { StatsD } = require("hot-shots");
const axios = require("axios");

const API_KEY = process.env.CAPTCHAAI_API_KEY;

const dogstatsd = new StatsD({
  host: process.env.DD_AGENT_HOST || "localhost",
  port: parseInt(process.env.DD_DOGSTATSD_PORT || "8125", 10),
  prefix: "captcha.",
  globalTags: [`env:${process.env.NODE_ENV || "development"}`],
});

async function solveCaptchaWithMetrics(sitekey, pageurl, captchaType = "recaptcha_v2") {
  const tags = [`captcha_type:${captchaType}`];
  dogstatsd.increment("solve.count", 1, tags);
  const startTime = Date.now();

  try {
    const result = await solveCaptcha(sitekey, pageurl);
    const elapsed = (Date.now() - startTime) / 1000;

    if (result.solution) {
      dogstatsd.increment("solve.success", 1, tags);
      dogstatsd.histogram("solve.latency", elapsed, tags);
    } else {
      dogstatsd.increment("solve.error", 1, [...tags, `error:${result.error}`]);
    }

    return result;
  } catch (err) {
    dogstatsd.increment("solve.error", 1, [...tags, `error:${err.message}`]);
    throw err;
  }
}

async function solveCaptcha(sitekey, pageurl) {
  const submitResp = await axios.post("https://ocr.captchaai.com/in.php", null, {
    params: {
      key: API_KEY,
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: pageurl,
      json: 1,
    },
  });

  if (submitResp.data.status !== 1) {
    return { error: submitResp.data.request };
  }

  const captchaId = submitResp.data.request;
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const pollResp = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
    });
    if (pollResp.data.status === 1) return { solution: pollResp.data.request };
    if (pollResp.data.request !== "CAPCHA_NOT_READY") {
      return { error: pollResp.data.request };
    }
  }
  return { error: "TIMEOUT" };
}

async function reportBalance() {
  try {
    const resp = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "getbalance", json: 1 },
    });
    if (resp.data.status === 1) {
      const balance = parseFloat(resp.data.request);
      dogstatsd.gauge("balance", balance);
      return balance;
    }
  } catch (err) {
    console.error("Balance check failed:", err.message);
  }
  return null;
}

// Report balance every minute
setInterval(reportBalance, 60000);

module.exports = { solveCaptchaWithMetrics, reportBalance };

Ici, reportBalance() s'exécute toutes les 60 secondes via setInterval. Sur un déploiement multi-worker, ne l'activez que dans un seul processus, sinon le solde sera compté en double.

Importer le tableau de bord dans Datadog

Ce modèle crée quatre widgets :

  • taux de résolution (réussite contre erreur) ;
  • latence aux percentiles p50, p95 et p99 ;
  • solde API restant ;
  • profondeur de la file d'attente.

Importez-le via New Dashboard → Import Dashboard JSON, puis ajustez les requêtes si vous avez changé le préfixe.

{
  "title": "CaptchaAI Pipeline",
  "widgets": [
    {
      "definition": {
        "type": "timeseries",
        "title": "Solve Rate (Success vs Error)",
        "requests": [
          {"q": "sum:captcha.solve.success{*}.as_count()"},
          {"q": "sum:captcha.solve.error{*}.as_count()"}
        ]
      }
    },
    {
      "definition": {
        "type": "timeseries",
        "title": "Solve Latency (p50, p95, p99)",
        "requests": [
          {"q": "avg:captcha.solve.latency{*}"},
          {"q": "percentile:captcha.solve.latency{*},0.95"},
          {"q": "percentile:captcha.solve.latency{*},0.99"}
        ]
      }
    },
    {
      "definition": {
        "type": "query_value",
        "title": "API Balance",
        "requests": [{"q": "avg:captcha.balance{*}"}]
      }
    },
    {
      "definition": {
        "type": "timeseries",
        "title": "Queue Depth",
        "requests": [{"q": "avg:captcha.queue.depth{*}"}]
      }
    }
  ]
}

Définir des alertes qui préviennent avant la panne

Une métrique sans alerte ne sert à rien à 3 h du matin. Les seuils ci-dessous couvrent les pannes réelles d'un pipeline de résolution. Adaptez les valeurs à votre volume — un pic à 120 s est anormal pour du Turnstile, banal pour de l'image OCR sous forte charge.

Alerte Condition Gravité
Solde faible captcha.balance < 10 Avertissement
Solde critique captcha.balance < 2 Critique
Taux d'erreur élevé Taux d'erreur > 10 % sur 5 minutes Avertissement
Pic de latence Latence p95 > 120 s sur 10 minutes Avertissement
File qui s'accumule Profondeur de file > 100 en hausse sur 5 minutes Avertissement
Worker à l'arrêt captcha.worker.active == 0 Critique

Créer le monitor via l'API Datadog

Voici l'alerte de solde bas au format attendu par l'API Datadog. Dupliquez le bloc pour chaque condition en ajustant query et message.

# Datadog monitor definition (API create)
- type: metric alert
  name: "CaptchaAI Low Balance"
  query: "avg(last_5m):avg:captcha.balance{*} < 10"
  message: "CaptchaAI balance is low: {{value}}. Top up to avoid solve failures."
  tags:

    - team:scraping
    - service:captcha

Dépannage

Problème Cause Correctif
Les métriques n'apparaissent pas L'agent DogStatsD ne tourne pas Vérifiez DD_AGENT_HOST ; contrôlez docker ps pour le conteneur de l'agent
Histogramme de latence vide Aucune résolution réussie n'a été suivie Vérifiez que statsd.histogram() est bien appelé sur le chemin de réussite
Tags manquants Mauvais format de tag Utilisez le format key:value ; pas d'espaces dans les tags
Métriques dupliquées Plusieurs rapporteurs tournent en parallèle Ne gardez qu'un seul rapporteur de solde par déploiement

FAQ

Par quelles métriques commencer si je débute ?

Commencez par trois : le taux d'erreur (captcha.solve.error rapporté à captcha.solve.count), la latence p95 et le solde. Elles répondent aux questions qui comptent — est-ce que ça marche, est-ce rapide, vais-je tomber en panne de crédit. Ajoutez la file d'attente et la santé des workers ensuite.

Comment fixer un seuil d'alerte de solde adapté ?

Le bon seuil dépend de votre volume, pas d'une valeur universelle. Réglez l'avertissement sur plusieurs jours de marge et le seuil critique sur quelques heures, pour réagir avant l'interruption. Le débit soutenable dépend aussi de votre plan — par exemple ADVANCE ($90/mois, 50 threads).

Datadog ou Prometheus pour surveiller un pipeline CAPTCHA ?

Les deux fonctionnent : DogStatsD pousse les métriques vers un agent, tandis que Prometheus les scrape depuis un endpoint exposé. Choisissez Datadog pour des alertes managées et une corrélation avec le reste de votre stack ; préférez Prometheus et Grafana si vous êtes déjà auto-hébergé et sensible au coût.

Comment éviter une facture Datadog qui explose ?

Datadog facture à la série temporelle, c'est-à-dire à chaque combinaison unique de nom et de tags. Les sept métriques ci-dessus, avec quelques valeurs de captcha_type, restent dans des limites raisonnables. Le piège classique : taguer par captcha_id ou par URL crée une série par tâche. Taguez par catégorie, jamais par identifiant unique.

Ces métriques marchent-elles pour reCAPTCHA v3 et Turnstile ?

Oui. L'instrumentation est indépendante du type résolu : passez le bon captcha_type au décorateur et vous suivez reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile et GeeTest v3 dans le même tableau de bord, filtrés par tag.

Articles connexes

Prochaines étapes

Donnez de la visibilité à votre pipeline CAPTCHA : créez votre clé API CaptchaAI puis connectez vos métriques à Datadog en quelques minutes.

Guides associés :

Les commentaires sont désactivés pour cet article.