Token de captcha expirado: causas e como resolver
Um token de captcha é uma prova de curta duração de que um desafio foi cumprido. Os tokens do PopularCaptcha da Scarnox expiram cerca de dois minutos depois de emitidos. Se você enviar um token depois dessa janela, o site rejeita o formulário, muitas vezes com uma mensagem dizendo que o captcha expirou ou é inválido. A correção quase sempre é uma questão de tempo: resolva o captcha o mais perto possível do envio, use cada token uma única vez e tente de novo apenas quando souber que a primeira tentativa não foi aceita.
Por que os tokens expiram
Um token não é uma credencial permanente. O site quer saber se uma pessoa (ou um solver agindo por ela) cumpriu o desafio recentemente, então dá ao token uma vida curta. O relógio começa quando o token é emitido, não quando você chama a API pela primeira vez. Tudo o que acontece entre a resolução e o envio consome esses dois minutos.
Causas comuns
- Resolver cedo demais. Pré-resolver um lote de tokens e guardá-los para depois é a causa mais frequente. Quando um token é usado, ele já está vencido.
- Código lento entre a resolução e o envio. Uma escrita longa no banco, uma fila ou um
time.sleepcolocado antes do envio do formulário podem empurrar o token para fora da janela. - Reutilizar um token. Um token que já foi aceito, ou que uma tentativa com falha já usou, não funciona de novo. Trate cada token como de uso único.
- Recarregar a página. Se a página for atualizada depois da resolução, o formulário pode emitir um novo desafio e invalidar o token que você tem.
A correção: resolva logo antes de enviar
O padrão mais simples e confiável é transformar a resolução e o envio em um único passo. Peça um token, coloque-o no formulário, envie o formulário e não faça nada lento no meio:
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, # a chamada fica bloqueada até o token estar 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) # resolve logo antes de enviar
return requests.post(
page_url,
data={**form, "h-captcha-response": token, "g-recaptcha-response": token},
timeout=30,
)
Repare que submit_form não mantém um token em cache ou em uma fila. Ele pede um, envia e segue em frente. Resoluções com falha não são cobradas, então uma requisição que falha não custa nada do lado da resolução.
Repetindo com segurança
Alguns sites rejeitam um token expirado com uma resposta clara. Outros retornam uma página normal com uma mensagem de erro dentro dela, então o código de status sozinho não diz nada. Observe a resposta que o site realmente devolve para um token expirado e construa a sua verificação em torno dela. Um loop de retentativa poderia ficar assim:
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 # aceito, pelo menos segundo a resposta deste site
raise RuntimeError("captcha token rejected after retries")
Tenha cuidado com retentativas em formulários que criam algo. Se a primeira tentativa foi de fato aceita, mas o seu código interpretou mal a resposta, uma nova tentativa pode criar uma conta, um pedido ou uma mensagem duplicada. Repita somente quando tiver certeza de que o primeiro envio não foi aceito, e torne a verificação específica para o site com que você está trabalhando.
Mantenha o tempo honesto
- Não resolva em um loop de tokens que você pretende usar mais tarde. Resolva por envio.
- Mantenha o trabalho entre a resolução e o envio no mínimo.
- Use um timeout de cerca de 185 segundos na requisição de resolução, porque a chamada fica bloqueada até o token estar pronto. Um cronômetro separado para o envio do seu formulário deve ser bem menor que a vida do token.
- Se precisar de um pipeline mais rápido, veja velocidade de resolução de captcha, que cobre resoluções em paralelo sem guardar tokens.
Erros comuns
- Guardar tokens em um dicionário ou no Redis para depois. O cache parece inofensivo até que um token seja usado depois da janela e todos os envios falhem.
- Resolver uma vez por sessão. Uma sessão de longa duração ainda precisa de um token novo para cada envio de formulário, porque cada envio pode executar a sua própria verificação do desafio.
- Dormir entre a resolução e o envio. Um limitador de taxa ou uma pausa deliberada colocada depois da resolução consome a vida do token. Coloque qualquer pausa antes da resolução.
- Tratar todas as falhas da mesma forma. Um timeout do seu lado, um token rejeitado e um erro do site precisam de tratamentos diferentes. Registre-os separadamente.
Registre o suficiente para diagnosticar
Quando um envio falha, registre a hora em que o token foi emitido, a hora em que o formulário foi enviado, o status HTTP e os primeiros caracteres da resposta. Com esses quatro valores você consegue ver de imediato se o token envelheceu ou se o site o rejeitou por outro motivo. Não registre o token completo em logs compartilhados, já que um token válido é uma credencial para aquele desafio.
O que verificar se ainda falhar
- Imprima o tempo entre o token ser devolvido e o envio do formulário.
- Confirme que o
site_urlcorresponde à página que mostra o captcha. - Confirme que o sitekey foi lido da página atual, não de uma cópia antiga.
- Confirme os nomes dos campos do formulário. Os tokens do PopularCaptcha vão em
h-captcha-response, e muitas vezes também emg-recaptcha-response.
Para a lista completa de respostas de erro e como tratar cada uma, veja tratamento de erros do solver de captcha.