Token captcha scaduto: cause e come risolvere
Un token captcha è una prova di breve durata che una sfida è stata superata. I token PopularCaptcha di Scarnox scadono circa due minuti dopo l'emissione. Se invii un token dopo quella finestra, il sito rifiuta il form, spesso con un messaggio che dice che il captcha è scaduto o non valido. La soluzione riguarda quasi sempre il timing: risolvi il captcha il più vicino possibile all'invio, usa ogni token una sola volta e riprova solo quando sai che il primo tentativo non è stato accettato.
Perché i token scadono
Un token non è una credenziale permanente. Il sito vuole sapere che una persona (o un solver che agisce per conto di una persona) ha completato la sfida di recente, quindi gli dà una vita breve. Il conteggio inizia quando il token viene emesso, non quando chiami per la prima volta l'API. Tutto ciò che succede tra la risoluzione e l'invio consuma i due minuti.
Cause comuni
- Risolvere troppo presto. Pre-risolvere un batch di token e tenerli per dopo è la causa più frequente. Quando un token viene usato, è già vecchio.
- Codice lento tra risoluzione e invio. Una scrittura lenta nel database, una coda o un
time.sleeppiazzato prima dell'invio del form possono far superare al token la sua finestra. - Riutilizzo di un token. Un token già accettato, o già usato da un tentativo fallito, non funzionerà di nuovo. Tratta ogni token come monouso.
- Ricaricare la pagina. Se la pagina viene aggiornata dopo la risoluzione, il form potrebbe emettere una nuova sfida e invalidare il token che hai.
La soluzione: risolvi subito prima di inviare
Il pattern più semplice e affidabile è rendere la risoluzione e l'invio un unico passaggio. Richiedi un token, mettilo nel form, invia il form e non fare nulla di lento nel mezzo:
import requests
API_KEY = "scarnox_YOURAPIKEY"
CREATE_URL = "https://api.scarnox.com/api/tasks/create"
def fresh_token(sitekey: str, page_url: str) -> str:
r = requests.post(
CREATE_URL,
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"captcha_type": "PopularCaptchaTaskProxyless",
"site_key": sitekey,
"site_url": page_url,
},
timeout=185, # la chiamata blocca finché il token non è pronto
)
data = r.json()
if data.get("errorId") != 0:
raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
return data["solution"]["token"]
def submit_form(page_url: str, sitekey: str, form: dict) -> requests.Response:
token = fresh_token(sitekey, page_url) # risolvi subito prima dell'invio
return requests.post(
page_url,
data={**form, "h-captcha-response": token, "g-recaptcha-response": token},
timeout=30,
)
Nota che submit_form non tiene un token in una cache o in una coda. Ne chiede uno, lo invia e va avanti. Le risoluzioni fallite non vengono addebitate, quindi una richiesta fallita non ti costa nulla lato risoluzione.
Riprova in sicurezza
Alcuni siti rifiutano un token scaduto con una risposta chiara. Altri restituiscono una pagina normale con un messaggio di errore al suo interno, quindi il solo codice di stato non basta. Guarda la risposta che il sito restituisce davvero per un token scaduto, e costruisci il controllo su quella. Un ciclo di retry potrebbe essere così:
def submit_with_retry(page_url: str, sitekey: str, form: dict, attempts: int = 2):
for _ in range(attempts):
resp = submit_form(page_url, sitekey, form)
if resp.status_code < 400 and "captcha" not in resp.text.lower():
return resp # accettato, per quanto mostra la risposta di questo sito
raise RuntimeError("captcha token rejected after retries")
Fai attenzione ai retry sui form che creano qualcosa. Se il primo tentativo è stato davvero accettato ma il tuo codice ha interpretato male la risposta, un retry può creare un account, un ordine o un messaggio duplicato. Riprova solo quando sei sicuro che il primo invio non sia stato accettato, e rendi il controllo specifico per il sito con cui lavori.
Mantieni onesta la tempistica
- Non risolvere in un ciclo di token che pensi di usare più tardi. Risolvi per ogni invio.
- Mantieni al minimo il lavoro tra la risoluzione e l'invio.
- Usa un timeout di circa 185 secondi sulla richiesta di risoluzione, perché la chiamata blocca finché il token non è pronto. Un timer separato sul tuo invio del form dovrebbe essere molto più corto della vita del token.
- Se ti serve una pipeline più veloce, vedi velocità di risoluzione dei captcha, che tratta le risoluzioni parallele senza memorizzare i token.
Errori comuni
- Mettere i token in una cache, in un dizionario o in Redis per dopo. La cache sembra innocua finché un token non viene usato oltre la sua finestra e ogni invio fallisce.
- Risolvere una volta per sessione. Una sessione di lunga durata ha comunque bisogno di un nuovo token per ogni invio del form, perché ogni POST può eseguire un proprio controllo.
- Dormire tra risoluzione e invio. Un limitatore di frequenza o una pausa voluta messa dopo la risoluzione consuma la vita del token. Metti invece qualsiasi pacing prima della risoluzione.
- Trattare ogni fallimento allo stesso modo. Un timeout dal tuo lato, un token rifiutato e un errore del sito richiedono gestioni diverse. Registrali separatamente.
Registra abbastanza per diagnosticarlo
Quando un invio fallisce, registra l'ora in cui il token è stato emesso, l'ora in cui il form è stato inviato, lo stato HTTP e i primi qualche centinaio di caratteri della risposta. Con questi quattro valori puoi capire a colpo d'occhio se il token è scaduto o se il sito l'ha rifiutato per un altro motivo. Non registrare il token completo nei log condivisi, perché un token valido è una credenziale per quella singola sfida.
Cosa controllare se continua a fallire
- Stampa il tempo tra la restituzione del token e l'invio del form.
- Verifica che
site_urlcorrisponda alla pagina che mostra il captcha. - Verifica che la sitekey sia stata letta dalla pagina attuale, non da una copia vecchia.
- Verifica i nomi dei campi del form. I token PopularCaptcha vanno in
h-captcha-response, e spesso anche ing-recaptcha-response.
Per l'elenco completo delle risposte di errore e come gestire ciascuna, vedi gestione degli errori del solver captcha.