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:
- Il captcha stesso. Alcuni tipi di sfida richiedono più tempo di altri. È una proprietà del captcha del sito, non del tuo codice.
- Il carico del solver. Un periodo di traffico intenso può rallentare una singola chiamata. La logica di retry dovrebbe gestirlo, non abbandonare la chiamata presto.
- La tua pipeline. Risolvere troppo presto, serializzare ogni richiesta e aspettare lavoro non correlato aggiungono tempo che puoi eliminare.
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
- Mantieni una sessione HTTP per sito, così non ti riconnetti a ogni richiesta.
- Non eseguire parsing lento o operazioni sul database tra la risoluzione e l'invio.
- Usa un timeout sensato sulle tue richieste. La chiamata di risoluzione usa circa 185 secondi perché si blocca finché il token non è pronto. Le tue altre chiamate dovrebbero essere molto più brevi.
- Invia un proxy solo quando il task lo richiede. I tipi di task con proxy accettano un campo
proxynel formatohttp://user:pass@host:port, ma aggiungere un proxy non velocizza nulla di per sé.
Riusa ciò che non scade
Alcuni lavori possono essere condivisi in sicurezza tra più risoluzioni. Tienili fuori dal percorso critico:
- Sitekey. Leggi la sitekey una volta per template di pagina e memorizzala. Non scade come un token. Rileggila solo quando il form inizia a fallire.
- Connessioni HTTP. Una sessione con keep-alive evita un nuovo handshake TLS a ogni chiamata. Crea una sessione per thread o per worker, non una per richiesta.
- Configurazione analizzata. Carica la lista dei target, la lista dei proxy e le impostazioni una sola volta all'avvio, invece di leggere i file dentro la funzione di risoluzione.
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.