Gestione errori del solver captcha: retry, backoff e cosa non ritentare mai
Una risoluzione captcha è una chiamata di rete verso un sistema che non controlli, per una sfida che può cambiare senza preavviso. Gestire bene i fallimenti è la differenza tra una pipeline che degrada con grazia e una che spreca tempo in una tempesta di retry.
Con Scarnox l'economia ti aiuta: le risoluzioni fallite sono gratuite, quindi un retry costa solo tempo. Ma riprovare l'errore sbagliato spreca comunque quel tempo, e riprovare con troppa insistenza può creare problemi propri. Questa guida ti dà una politica di retry da incollare in un job Python.
Scarnox risolve PopularCaptcha oggi. La stessa gestione degli errori vale per qualsiasi solver captcha, quindi la politica qui sotto non dipende dal tipo di captcha.
Controlla sempre prima errorId
Una risposta di risoluzione ha un campo errorId. 0 significa successo e il token si trova in solution.token. Qualsiasi altro valore significa che la risoluzione è fallita. Una risposta fallita porta anche errorCode e errorDescription. Registra entrambi: il codice è quello che cercherai dopo, e la descrizione è quella che leggerai quando un'esecuzione va storta.
{"errorId": 1, "errorCode": "...", "errorDescription": "..."}
Non considerare mai una risposta un successo perché lo stato HTTP è 200. Controlla errorId ogni volta.
Errori da ritentare
Alcuni errori sono temporanei. Riprovali con backoff esponenziale e un piccolo ritardo casuale:
- Errori di rete: reset di connessione, problemi DNS, timeout di lettura dal tuo lato.
- Errori del server: risposte HTTP 5xx, che indicano un problema dal lato del servizio.
- Limitazione di frequenza: HTTP 429, che significa che stai inviando richieste troppo in fretta. Rallenta.
- Timeout: la risoluzione è durata più del tempo che il client ha atteso. Riprova con un timeout più lungo, se non ne hai già impostato uno.
Questi errori meritano un nuovo tentativo perché quello successivo potrebbe riuscire senza alcun cambiamento da parte tua.
Errori da correggere prima di riprovare
Altri errori non spariscono aspettando. Un nuovo tentativo con lo stesso input fallirà allo stesso modo. Correggili prima:
- Tipo di task non valido. Controlla che
captcha_typesia scritto esattamente come nella documentazione. - Sitekey errata o mancante. Rileggi la sitekey dalla pagina. Una sitekey di un deploy vecchio è una causa comune.
- URL della pagina errato.
site_urldeve essere la pagina che mostra il widget. - Problemi di proxy. Un proxy non raggiungibile, scritto male o bloccato farà fallire ogni tentativo. Testa prima il proxy da solo. Vedi proxy o proxyless.
- Problemi dell'account. Una chiave non valida o un account senza il saldo necessario farà fallire ogni tentativo finché qualcuno non sistema l'account. Avvisa una persona; non riprovare in loop.
Quando un errore non rientra in nessuna delle due liste, trattalo come un errore da correggere prima. Leggi il testo dell'errore, decidi, e aggiungi il codice alla lista giusta nel tuo codice. Tieni le liste brevi e riviste.
Una politica da incollare
import os
import random
import time
import requests
API_URL = "https://api.scarnox.com/api/tasks/create"
HEADERS = {"Authorization": f"Bearer {os.environ['SCARNOX_KEY']}"}
MAX_ATTEMPTS = 4
class SolveError(Exception):
def __init__(self, code: str, description: str, retryable: bool):
super().__init__(f"{code}: {description}")
self.code = code
self.retryable = retryable
def solve_once(payload: dict) -> str:
try:
r = requests.post(API_URL, headers=HEADERS, json=payload, timeout=185)
except requests.RequestException as exc:
# Errore di rete o timeout lato nostro: si può ritentare.
raise SolveError("NETWORK", str(exc), retryable=True)
if r.status_code == 429 or r.status_code >= 500:
raise SolveError(f"HTTP_{r.status_code}", r.text[:200], retryable=True)
try:
data = r.json()
except ValueError:
raise SolveError(f"HTTP_{r.status_code}", r.text[:200], retryable=False)
if data.get("errorId") == 0:
return data["solution"]["token"]
# L'API ha segnalato un errore. Ritenta solo i codici che hai visto essere temporanei.
code = data.get("errorCode", "UNKNOWN")
retryable = code in TRANSIENT_CODES
raise SolveError(code, data.get("errorDescription", ""), retryable=retryable)
# Aggiungi qui i codici di errore API solo dopo averli visti essere temporanei nei log.
TRANSIENT_CODES: set[str] = set()
def solve(payload: dict) -> str:
for attempt in range(MAX_ATTEMPTS):
try:
return solve_once(payload)
except SolveError as err:
last_attempt = attempt == MAX_ATTEMPTS - 1
if not err.retryable or last_attempt:
raise
delay = min(30, 2 ** attempt) + random.random() # backoff + jitter casuale
print(f"attempt {attempt + 1} failed ({err.code}), retrying in {delay:.1f}s")
time.sleep(delay)
raise RuntimeError("unreachable")
La politica non riprova mai un errore da correggere, e si ferma dopo un numero fisso di tentativi. L'insieme TRANSIENT_CODES parte vuoto di proposito: aggiungi un codice solo quando i tuoi log mostrano che è temporaneo.
Perché il jitter conta
Se cinquanta worker falliscono nello stesso momento e dormono tutti esattamente due secondi, riproveranno tutti insieme e falliranno di nuovo insieme. Una frazione casuale di secondo li disperde. È lo stesso consiglio che danno la maggior parte dei grandi provider di API ai propri client.
Timeout
Imposta il timeout HTTP ben al di sopra del tempo tipico di risoluzione, circa 185 secondi. La richiesta si blocca finché il token non è pronto, quindi un timeout client corto può trasformare un successo lento in un fallimento che poi riprovi inutilmente. Un timeout client più lungo della risoluzione è l'impostazione sicura.
I token non sono riutilizzabili
Un token scade in circa due minuti. Non memorizzare i token tra un'esecuzione e l'altra, e non riusarne uno per un secondo invio. Se un invio fallisce dopo che il token è stato usato, risolvi di nuovo invece di reinviare il vecchio token. La guida alla scadenza del token approfondisce la tempistica.
Concorrenza e backoff tra worker
Se più worker condividono una chiave, il backoff di un worker non rallenta gli altri. Aggiungi un limite condiviso alle risoluzioni concorrenti, come un semaforo o una coda, così una raffica di retry non diventa una raffica di richieste. Un semplice limite di concorrenza risolve la maggior parte dei problemi di limitazione prima che nascano.
Log utili
Per ogni tentativo fallito, registra il errorCode, il numero del tentativo, la sitekey, l'host della pagina e se l'errore è stato ritentato. Non registrare la chiave API né la password del proxy. Una dashboard che conta i codici per ora ti dice rapidamente se il problema è nel tuo codice, nel proxy o nel servizio.
Prossimi passi
- Come risolvere un PopularCaptcha in Python
- Proxy o proxyless: quale tipo di task ti serve?
- Mantenere verde la CI quando la tua app ha un captcha
- Prova gratuita del solver captcha
Testa la tua gestione con una prova gratuita: crea un account e inizia con crediti di prova gratuiti, senza carta di credito.