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 :
- Le captcha lui-même. Certains types de défis prennent plus de temps que d'autres. C'est une propriété du captcha du site, pas de votre code.
- La charge du solveur. Une période chargée peut ralentir un appel. La logique de réessai doit gérer cela, et non abandonner l'appel trop tôt.
- Votre propre pipeline. Résoudre trop tôt, sérialiser chaque requête et attendre un travail sans rapport ajoutent du temps que vous pouvez supprimer.
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
- Gardez une seule session HTTP par site pour ne pas vous reconnecter à chaque requête.
- N'exécutez pas de longs traitements d'analyse ou de base de données entre la résolution et l'envoi.
- Utilisez un délai d'attente raisonnable pour vos propres requêtes. L'appel de résolution utilise environ 185 secondes, car il bloque jusqu'à ce que le token soit prêt. Vos autres appels doivent être bien plus courts.
- N'envoyez un proxy que si la tâche en a besoin. Les types de tâches avec proxy prennent un champ
proxyau formathttp://user:pass@host:port, mais ajouter un proxy n'accélère rien en soi.
Réutiliser ce qui n'expire pas
Certains travaux peuvent être partagés sans risque entre plusieurs résolutions. Sortez-les du chemin critique :
- Sitekeys. Lisez la sitekey une fois par modèle de page et stockez-la. Elle n'expire pas comme un token. Relisez-la seulement lorsque le formulaire commence à échouer.
- Connexions HTTP. Une session avec keep-alive évite une nouvelle négociation TLS à chaque appel. Créez une session par thread ou par worker, et non une par requête.
- Configuration analysée. Chargez votre liste de cibles, votre liste de proxys et vos réglages une seule fois au démarrage, au lieu de lire des fichiers à l'intérieur de la fonction de résolution.
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.