Scarnox · Blog

Résolution de captcha rapide : comment réduire la latence de votre pipeline

Un solveur de captcha rapide est celui que vous appelez au bon moment, en parallèle, sans gaspiller de requêtes. L'appel de résolution de Scarnox renvoie la réponse lorsque le token est prêt, sa durée dépend donc du type de captcha, du site cible et de la charge du moment. Ce guide ne promet aucun délai fixe. Il montre ce que vous contrôlez dans votre propre code, comment le mesurer et comment lancer de nombreuses résolutions à la fois, pour que le temps que vous y consacrez soit bien employé.

Ce qui fixe le temps de résolution

Trois éléments comptent, et un seul est entre vos mains :

Concentrez vos efforts sur le troisième point. C'est la partie que vous pouvez mesurer et modifier.

Mesurer avant d'optimiser

Entourez l'appel d'un chronomètre et journalisez le résultat. Vous voulez connaître le temps de résolution et le temps que votre code passe autour :

import time
import requests

API_KEY = "scarnox_YOURAPIKEY"
URL = "https://api.scarnox.com/api/tasks/create"

def timed_solve(sitekey: str, page_url: str) -> dict:
    started = time.perf_counter()
    r = requests.post(
        URL,
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={
            "captcha_type": "PopularCaptchaTaskProxyless",
            "site_key": sitekey,
            "site_url": page_url,
        },
        timeout=185,
    )
    elapsed = time.perf_counter() - started
    data = r.json()
    if data.get("errorId") != 0:
        return {"ok": False, "error": data.get("errorCode"), "seconds": elapsed}
    return {"ok": True, "token": data["solution"]["token"], "seconds": elapsed}

result = timed_solve("10000000-ffff-ffff-ffff-000000000001", "https://example.com/signup")
print(result["ok"], round(result["seconds"], 2), "s")

Lancez ceci sur les pages qui vous intéressent, aux moments où vous exécutez vos travaux. Conservez les chiffres. Ils vous diront si un changement a réellement aidé.

Lancer les résolutions en parallèle

Les résolutions sont indépendantes. Si vous avez dix sitekeys à traiter, attendre chacune à tour de rôle gaspille du temps que vous pourriez consacrer aux autres. Un pool de threads suffit pour la plupart des scripts, car chaque résolution est une requête HTTP qui passe l'essentiel de son temps à attendre :

from concurrent.futures import ThreadPoolExecutor

jobs = [
    ("10000000-ffff-ffff-ffff-000000000001", "https://example.com/signup"),
    ("10000000-ffff-ffff-ffff-000000000001", "https://example.com/register"),
    ("10000000-ffff-ffff-ffff-000000000001", "https://example.com/contact"),
]

def run(job):
    sitekey, page_url = job
    return timed_solve(sitekey, page_url)

# Commencez petit. N'augmentez le nombre de workers que tant que les erreurs restent faibles.
with ThreadPoolExecutor(max_workers=5) as pool:
    results = list(pool.map(run, jobs))

for r in results:
    status = "ok" if r["ok"] else r["error"]
    print(f"{r['seconds']:.2f} s  {status}")

Si vous voyez apparaître des erreurs qui ressemblent à des problèmes de débit ou de capacité lorsque vous augmentez le nombre de workers, réduisez-le. Le parallélisme n'aide que lorsque le serveur suit le rythme.

Ne résolvez pas trop tôt

Le code le plus rapide est celui qui n'attend pas un token dont il n'a pas encore besoin. Demander des tokens à l'avance et les stocker est l'erreur habituelle. Les tokens expirent environ deux minutes après leur émission, un token stocké est donc souvent périmé au moment où vous en avez besoin. Résolvez lorsque le formulaire est sur le point d'être envoyé. Le raisonnement détaillé se trouve dans token de captcha expiré : causes et solutions.

Gardez le reste du chemin court

Réutiliser ce qui n'expire pas

Certains travaux peuvent être partagés sans risque entre plusieurs résolutions. Sortez-les du chemin critique :

Ces changements coûtent peu et suppriment un surcoût présent à chaque appel.

Mesurer la queue de distribution, pas la moyenne

Une durée moyenne masque les appels lents qui retardent un lot. Enregistrez le temps de chaque résolution, puis regardez la médiane et le 95e centile. Un pipeline qui se termine dans le temps de son job le plus lent est limité par cette queue de distribution. Une petite modification de la façon dont vous relancez les appels lents peut donc compter davantage que n'importe quel changement sur la médiane :

import statistics

def summarise(seconds: list[float]) -> str:
    ordered = sorted(seconds)
    p95 = ordered[int(0.95 * (len(ordered) - 1))]
    return f"median {statistics.median(ordered):.2f} s, p95 {p95:.2f} s, n={len(ordered)}"

print(summarise([r["seconds"] for r in results]))

Comparez le résumé avant et après chaque changement. Si le p95 bouge à peine, c'est que le temps est dépensé ailleurs dans votre code.

Vérifiez le résultat, pas seulement la vitesse

Un échec rapide reste un échec. Comptez les résolutions qui renvoient une erreur, et considérez une hausse du taux d'erreur comme un signal pour ralentir. Les résolutions échouées ne sont pas facturées, mais le temps passé à les traiter reste du temps perdu pour votre pipeline. Le schéma complet de réessai se trouve dans la gestion des erreurs du solveur de captcha.

Étapes suivantes