Un pipeline de résolution CAPTCHA se dégrade avant de tomber : le taux de réussite glisse, la durée P95 double, la file s'allonge — et le job nocturne rend des pages vides. Instrumentez trois séries — réussites sur tentatives, temps de résolution P95, solde du compte — et la dégradation devient visible avant l'incident.
Prometheus les collecte sur un endpoint /metrics exposé par votre worker, Grafana les affiche, une poignée de règles d'alerte vous préviennent avant vos utilisateurs. Voici la chaîne complète pour surveiller vos taux de résolution avec l'API CaptchaAI.
Six métriques qui décrivent votre taux de résolution
Chacune répond à une question précise.
| Métrique | Type | Question |
|---|---|---|
captcha_solves_total |
Compteur | Volume et débit |
captcha_solves_success |
Compteur | Résolutions abouties |
captcha_solves_errors |
Compteur | Codes d'erreur dominants |
captcha_solve_duration |
Histogramme | Répartition des durées |
captcha_balance |
Jauge | Solde restant |
captcha_queue_length |
Jauge | Tâches en attente |
Le compteur donne le volume, l'histogramme donne la forme : c'est la différence entre « le solveur est lent » et « seule la queue de distribution s'allonge ».
L'exporteur Python
L'instrumentation se place au plus près de l'appel réseau : compteur avant la soumission sur in.php, mesure de la durée, puis succès ou erreur après l'interrogation de res.php. Le label method porte le type de CAPTCHA.
# 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")
Attention à la cardinalité : error_code dérive ici du message d'exception tronqué. Si celui-ci contient un identifiant de tâche, vous créez une série temporelle par erreur. Normalisez sur les codes de l'API (CAPCHA_NOT_READY, ERROR_ZERO_BALANCE).
Configurer Prometheus
Une seule cible, collecte toutes les 10 secondes : descendre plus bas n'apporte rien sur des résolutions de plusieurs secondes.
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: "captcha-solver"
static_configs:
- targets: ["solver-app:8000"]
scrape_interval: 10s
La stack Docker Compose
Trois conteneurs : votre solveur, Prometheus, Grafana. La clé API passe par une variable d'environnement, jamais dans l'image ni dans un fichier versionné.
# 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:
Piège classique : dans prometheus.yml, la cible est le nom de service Docker (solver-app) ; depuis ce conteneur, localhost désigne Prometheus lui-même.
Les requêtes PromQL du tableau de bord Grafana
Cinq panneaux couvrent l'essentiel ; ajoutez by (method) pour obtenir la même vue par type de CAPTCHA.
Taux de réussite
rate(captcha_solves_success[5m])
/ rate(captcha_solves_total[5m]) * 100
Temps de résolution moyen
rate(captcha_solve_duration_seconds_sum[5m])
/ rate(captcha_solve_duration_seconds_count[5m])
Erreurs par code
sum by (error_code) (
rate(captcha_solves_errors[5m])
)
Solde du compte
captcha_balance_usd
Durée de résolution P95
histogram_quantile(0.95,
rate(captcha_solve_duration_seconds_bucket[5m])
)
Lisez la moyenne et la P95 ensemble : une moyenne stable avec une P95 qui grimpe signale une minorité de tâches en timeout, souvent sur un seul type de CAPTCHA.
Alerter avant que le taux de résolution ne s'effondre
Trois règles suffisent au départ : solde bas, taux d'erreur anormal, résolutions lentes. Les fenêtres for: absorbent les micro-pics.
# 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"
Le seuil de solde à 5 dollars est un plancher : calez-le sur votre consommation réelle.
Seuils de résolution normaux, par type de CAPTCHA
Un seuil unique de 60 secondes convient à reCAPTCHA v2 mais reste aveugle pour Turnstile. Découpez vos règles avec le label method.
Les plafonds ci-dessous proviennent des pages solveur publiques de CaptchaAI. Les résultats varient selon l'environnement, le volume et le moment de la journée.
| Type de CAPTCHA | Plafond annoncé | Seuil P95 de départ |
|---|---|---|
| Image / OCR | < 0,5 s | 3 s |
| Cloudflare Turnstile | < 10 s | 20 s |
| GeeTest v3 | < 12 s | 25 s |
| reCAPTCHA v2 | < 60 s | 90 s |
Reprenez ensuite votre P95 réelle sur une période calme, puis ajoutez une marge.
Du tableau de bord au dimensionnement des threads
La facturation CaptchaAI se fait par thread concurrent, avec des résolutions illimitées par thread : la question n'est pas le volume du mois, mais le nombre de résolutions simultanées au pic.
Le calcul tient en une ligne : threads ≈ débit au pic × durée moyenne. Un worker à 30 résolutions par minute avec une durée moyenne de 10 s occupe environ 5 threads en continu, soit toute la capacité de BASIC ($15/mois, 5 threads) sans marge ; STANDARD ($30/mois, 15 threads) absorbe le pic, et ADVANCE ($90/mois, 50 threads) devient pertinent quand captcha_queue_length reste au-dessus de zéro alors que la durée par résolution ne bouge pas : une file qui s'allonge à durée constante signale des tâches en attente d'un thread libre.
Scénario : un pipeline de scraping hébergé chez OVHcloud
Une équipe QA lyonnaise exécute ses workers sur trois instances OVHcloud, avec un Prometheus mutualisé et une seule instance Grafana. Deux réglages ont tout changé.
D'abord, deux labels et pas un de plus : instance par machine, method par type. Ni URL cible, ni identifiant de compte testé : au-delà de la cardinalité, ces valeurs finissent dupliquées dans une base que personne ne purge — exactement ce que le RGPD vous demande d'éviter.
Ensuite, un seuil distinct pour le job nocturne, dont la P95 est structurellement plus haute : SlowSolveTime sonnait chaque nuit à 2 h. Une alerte ignorée par habitude ne protège plus rien.
Dépannage
| Problème | Cause probable | Correctif |
|---|---|---|
/metrics répond vide |
start_http_server(8000) jamais appelé |
Démarrez le serveur dans le processus qui résout |
| Cible « down » dans Prometheus | Mauvais hôte ou port | Visez le nom de service Docker, pas localhost |
| Grafana sans données | Source Prometheus non déclarée | Ajoutez http://prometheus:9090, puis rechargez |
| Compteurs à zéro après un déploiement | Redémarrage du worker, attendu | Interrogez rate(), jamais le compteur brut |
FAQ
Pourquoi le taux de réussite chute-t-il aux heures de pointe ?
Le plus souvent, ce sont les threads et non le solveur : si captcha_queue_length grossit alors que la durée par résolution reste stable, vos tâches attendent un thread libre.
À quelle fréquence interroger le solde du compte ?
Une fois par minute suffit. Appelez update_balance() depuis une boucle dédiée : la jauge répond ensuite aux scrapes sans appel supplémentaire.
Quel seuil P95 choisir pour l'alerte de lenteur ?
Un seuil par type. Les 60 s du fichier d'alertes conviennent à reCAPTCHA v2 ; sur Cloudflare Turnstile, dont le plafond annoncé est inférieur à 10 s, une P95 au-delà de 20 s mérite un coup d'œil.
Combien de temps faut-il conserver ces métriques ?
Quinze jours couvrent le diagnostic courant : c'est la rétention par défaut de Prometheus. Pour comparer d'un mois sur l'autre, exportez vos agrégats vers un stockage longue durée.
Puis-je suivre hCaptcha ou FunCaptcha avec ces métriques ?
Non — hCaptcha et FunCaptcha (Arkose Labs) ne sont pas pris en charge par CaptchaAI. Le label method couvre reCAPTCHA v2 et v3, Cloudflare Turnstile et Challenge, GeeTest v3, l'image/OCR, la grille et BLS, plus CaptchaFox (bêta), Friendly Captcha (bêta) et Lemin (bêta). GeeTest v4 est annoncé comme à venir.
Guides connexes
Vos taux de résolution méritent mieux qu'un fichier de logs : ouvrez un compte CaptchaAI et branchez votre premier /metrics.