Tratamento de erros do solver de captcha: retentativas, backoff e o que nunca repetir
Uma resolução de captcha é uma chamada de rede para um sistema que você não controla, contra um desafio que pode mudar sem aviso. Tratar as falhas bem é a diferença entre um pipeline que degrada com elegância e um que desperdiça tempo em uma tempestade de retentativas.
Com a Scarnox, a economia ajuda você: resoluções com falha são gratuitas, então uma retentativa custa apenas tempo. Mas repetir a falha errada ainda desperdiça esse tempo, e repetir com insistência demais pode causar problemas próprios. Este guia oferece uma política de retentativas que você pode colar em um job Python.
A Scarnox resolve o PopularCaptcha hoje. O mesmo tratamento de erros se aplica a qualquer solver de captcha, então a política abaixo não depende do tipo de captcha.
Sempre verifique o errorId primeiro
Uma resposta de resolução tem um campo errorId. 0 significa sucesso e o token fica em solution.token. Qualquer outro valor significa que a resolução falhou. Uma resposta com falha também traz um errorCode e um errorDescription. Registre os dois: o código é o que você vai procurar depois, e a descrição é o que você vai ler quando uma execução der errado.
{"errorId": 1, "errorCode": "...", "errorDescription": "..."}
Nunca presuma que a resposta é um sucesso só porque o status HTTP é 200. Verifique o errorId sempre.
Falhas para repetir
Algumas falhas são temporárias. Repita-as com backoff exponencial e um pequeno atraso aleatório:
- Erros de rede: conexões reiniciadas, problemas de DNS, timeouts de leitura do seu lado.
- Erros do servidor: respostas HTTP 5xx, que indicam um problema no lado do serviço.
- Limitação de taxa: HTTP 429, que significa que você está enviando requisições rápido demais. Desacelere.
- Timeouts: a resolução demorou mais do que o seu cliente esperou. Repita com um timeout maior, se você ainda não tiver definido um.
Essas falhas valem uma nova tentativa porque a próxima pode funcionar sem que você precise mudar nada.
Falhas para corrigir antes de repetir
Outras falhas não vão embora esperando. Repetir com a mesma entrada vai falhar do mesmo jeito. Corrija estas primeiro:
- Tipo de tarefa inválido. Confira se
captcha_typeestá escrito exatamente como na documentação. - Sitekey errado ou ausente. Leia o sitekey de novo na página. Um sitekey de um deploy antigo é uma causa comum.
- URL da página errada.
site_urldeve ser a página que mostra o widget. - Problemas com proxy. Um proxy inativo, digitado errado ou bloqueado vai falhar em todas as tentativas. Teste o proxy sozinho primeiro. Veja proxy versus proxyless.
- Problemas de conta. Uma chave inválida ou uma conta sem o saldo necessário vai falhar em todas as tentativas até alguém corrigir a conta. Avise uma pessoa; não repita em loop.
Quando uma falha não estiver em nenhuma das listas, trate-a como uma falha a corrigir primeiro. Leia o texto do erro, decida e adicione o código à lista certa no seu código. Mantenha as listas curtas e revisadas.
Uma política pronta para colar
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:
# Erro de rede ou timeout do nosso lado: seguro repetir.
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"]
# A API informou uma falha. Repita apenas os códigos que você viu serem transitórios.
code = data.get("errorCode", "UNKNOWN")
retryable = code in TRANSIENT_CODES
raise SolveError(code, data.get("errorDescription", ""), retryable=retryable)
# Adicione códigos de erro da API aqui somente depois de ver nos seus logs que são transitórios.
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
print(f"attempt {attempt + 1} failed ({err.code}), retrying in {delay:.1f}s")
time.sleep(delay)
raise RuntimeError("unreachable")
A política nunca repete uma falha que precisa de correção, e para depois de um número fixo de tentativas. O conjunto TRANSIENT_CODES começa vazio de propósito: adicione um código somente depois que os seus logs mostrarem que ele é temporário.
Por que o jitter importa
Se cinquenta workers falharem no mesmo momento e todos dormirem exatamente dois segundos, todos vão tentar de novo juntos e falhar juntos outra vez. Uma fração aleatória de segundo espalha as tentativas. É o mesmo conselho que a maioria dos grandes provedores de API dá para seus próprios clientes.
Timeouts
Defina o timeout HTTP bem acima do tempo típico de resolução, em torno de 185 segundos. A requisição fica bloqueada até o token estar pronto, então um timeout curto no cliente pode transformar um sucesso lento em uma falha que você repete sem ganho nenhum. Um timeout do cliente maior que a resolução é a configuração segura.
Tokens não são reaproveitáveis
Um token expira em cerca de dois minutos. Não guarde tokens entre execuções nem reutilize um para um segundo envio. Se um envio falhar depois que o token foi usado, resolva de novo em vez de reenviar o token antigo. O guia de expiração de token cobre o tempo com mais detalhes.
Concorrência e backoff entre workers
Se muitos workers compartilham uma chave, o backoff de um worker não desacelera os outros. Adicione um limite compartilhado de resoluções simultâneas, como um semáforo ou uma fila, para que uma rajada de retentativas não vire uma rajada de requisições. Um limite simples de concorrência resolve a maioria das falhas de limite de taxa antes de elas começarem.
Logs que ajudam
Para cada tentativa com falha, registre o errorCode, o número da tentativa, o sitekey, o host da página e se a falha foi repetida. Não registre a chave de API nem a senha de um proxy. Um painel que conta os códigos por hora mostra rapidamente se o problema está no seu código, no proxy ou no serviço.
Próximos passos
- Como resolver um PopularCaptcha em Python
- Proxy ou proxyless: qual tipo de tarefa você precisa?
- Mantendo o CI verde quando sua aplicação tem captcha
- Teste grátis do solver de captcha
Teste o seu tratamento de erros com um teste grátis: crie uma conta e comece com crédito de teste grátis, sem cartão.