Scarnox · Blog

Risoluzione captcha veloce: come ridurre la latenza nella pipeline

Un solver captcha veloce è uno che puoi chiamare nel momento giusto, in parallelo, senza sprecare richieste. La chiamata di risoluzione di Scarnox ritorna quando il token è pronto, quindi la sua durata dipende dal tipo di captcha, dal sito di destinazione e dal carico del momento. Questa guida non promette un tempo fisso. Mostra ciò che controlli nel tuo codice, come misurarlo e come eseguire molte risoluzioni insieme, così il tempo che spendi è speso bene.

Cosa determina il tempo di risoluzione

Tre cose contano, e solo una è nelle tue mani:

Concentra lo sforzo sul terzo punto. È la parte che puoi misurare e cambiare.

Misura prima di ottimizzare

Avvolgi la chiamata con un cronometro e registra il risultato. Ti servono sia il tempo di risoluzione sia il tempo che il tuo codice spende intorno ad essa:

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")

Eseguilo sulle pagine che ti interessano, negli orari in cui fai girare i tuoi job. Conserva i numeri. Ti dicono se una modifica ha davvero aiutato.

Esegui le risoluzioni in parallelo

Le risoluzioni sono indipendenti. Se hai dieci sitekey da gestire, aspettarle una alla volta spreca tempo che potresti usare per le altre. Un thread pool basta per la maggior parte degli script, perché ogni risoluzione è una richiesta HTTP che passa la maggior parte del tempo in attesa:

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)

# Inizia con poco. Aumenta il numero di worker solo finché gli errori restano bassi.
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}")

Se quando aumenti il numero di worker vedi errori che sembrano problemi di frequenza o di capacità, abbassalo. Il parallelismo aiuta solo quando il server riesce a tenere il passo.

Non risolvere in anticipo

Il codice più veloce è quello che non aspetta un token che non userà ancora. Richiedere token in anticipo e conservarli è l'errore più comune. I token scadono circa due minuti dopo l'emissione, quindi un token memorizzato spesso è già scaduto quando ti serve. Risolvi quando il form sta per essere inviato. Il ragionamento dettagliato è in token captcha scaduto: cause e rimedi.

Mantieni corto il resto del percorso

Riusa ciò che non scade

Alcuni lavori possono essere condivisi in sicurezza tra più risoluzioni. Tienili fuori dal percorso critico:

Queste modifiche costano poco e rimuovono un overhead che compare in ogni chiamata.

Misura la coda, non la media

Un tempo medio nasconde le chiamate lente che bloccano un batch. Registra il tempo di ciascuna risoluzione, poi guarda la mediana e il 95° percentile. Una pipeline che termina nel tempo del suo job più lento è limitata dalla coda, quindi una piccola modifica al modo in cui riprovi le chiamate lente può contare più di qualsiasi modifica alla mediana:

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]))

Confronta il riepilogo prima e dopo ogni modifica. Se il p95 quasi non si muove, il tempo viene speso altrove nel tuo codice.

Controlla il risultato, non solo la velocità

Un fallimento veloce è comunque un fallimento. Conta le risoluzioni che restituiscono un errore e considera un tasso di errore in crescita come un segnale per rallentare. Le risoluzioni fallite non vengono addebitate, ma il tempo speso per loro è comunque tempo che la pipeline ha perso. Il pattern completo di retry è in gestione degli errori del solver captcha.

Prossimi passi