Resposta direta: quando a resolução de CAPTCHA falha, sua automação não deve parar — ela deve decidir, em código, o que fazer a seguir. Tempo limite, sitekey errada, saldo zerado ou limite de requisições são eventos esperados em qualquer pipeline em escala, não exceções raras. Se o primeiro erro derruba o processo inteiro, você perde o progresso de centenas de tarefas por causa de uma única falha transitória. Degradação graciosa é o conjunto de padrões que mantém o pipeline vivo: pular o item problemático, reagendar para depois ou reduzir temporariamente o que o sistema tenta fazer.
Mapeie os modos de falha antes de programar a recuperação
Cada código de erro da API da CaptchaAI pede uma resposta diferente — tratar todos como a mesma falha genérica é o erro mais comum em pipelines que "simplesmente param" na primeira exceção.
| Falha | Código de erro | Estratégia de recuperação |
|---|---|---|
| Tempo limite | CAPCHA_NOT_READY (excedeu as consultas) |
Tente novamente com um desafio novo |
| Parâmetros incorretos | ERROR_BAD_PARAMETERS |
Registrar e pular — corrigir a extração |
| Sitekey errada | ERROR_WRONG_GOOGLEKEY |
Extrair a sitekey novamente |
| Saldo zerado | ERROR_ZERO_BALANCE |
Pausar, alertar, aguardar recarga |
| Limite de requisições | ERROR_TOO_MUCH_REQUESTS |
Backoff exponencial |
| API fora do ar | Erro de conexão | Circuit breaker + retry |
Falhas transitórias (tempo limite, limite de requisições, API fora do ar) pedem nova tentativa; falhas estruturais (parâmetros incorretos, sitekey desatualizada) pedem correção na extração, não repetição; e saldo zerado exige pausa com intervenção humana. Programar essa distinção logo no início evita que a fila de retry fique tentando resolver, para sempre, uma tarefa que nunca vai funcionar.
Matriz de decisão: quando vale tentar de novo
- Tente de novo somente quando a falha for transitória e o estado da sessão em volta continuar válido.
- Repita a etapa quando a requisição puder ser reconstruída de forma determinística, sem duplicar uma ação que o usuário já realizou.
- Pule ou pause o fluxo quando a nova tentativa só for gastar saldo à toa ou aumentar o risco de o site marcar o comportamento como suspeito.
Padrão 1: pular e seguir adiante
O padrão mais simples serve para lotes em que perder um item pontual é aceitável. Tente resolver duas vezes; se falhar, registre o motivo e siga para a próxima URL — no fim, você processa o que deu certo e sabe o que ficou de fora, sem reiniciar o lote inteiro.
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_or_skip(captcha_type, sitekey, page_url, max_retries=2):
"""Try to solve; return None on failure instead of crashing."""
for attempt in range(max_retries):
try:
token = solve_captcha(captcha_type, sitekey, page_url)
if token:
return token
except Exception as e:
print(f"Attempt {attempt + 1} failed: {e}")
return None # Skip this item
def process_urls(urls):
results = []
skipped = []
for url in urls:
sitekey = extract_sitekey(url)
if not sitekey:
skipped.append({"url": url, "reason": "no_sitekey"})
continue
token = solve_or_skip("recaptcha_v2", sitekey, url)
if token:
data = submit_form(url, token)
results.append({"url": url, "data": data})
else:
skipped.append({"url": url, "reason": "solve_failed"})
print(f"Processed: {len(results)}, Skipped: {len(skipped)}")
return results, skipped
Repare que solve_or_skip devolve None em vez de propagar a exceção — essa troca é que impede que uma falha isolada derrube o laço que processa a lista inteira de URLs.
Padrão 2: fila de retentativas com backoff exponencial
Quando pular não é opção — porque cada item da fila representa um formulário que ainda precisa ser enviado —, a tarefa que falhou volta para uma fila e é tentada mais tarde, com um intervalo cada vez maior entre uma tentativa e outra. Isso evita sobrecarregar a API numa instabilidade pontual sem descartar tarefas que teriam funcionado na segunda ou terceira tentativa.
from collections import deque
import json
class RetryQueue:
def __init__(self, max_retries=3, backoff_base=60):
self.queue = deque()
self.max_retries = max_retries
self.backoff_base = backoff_base
def add(self, task):
task["retry_count"] = task.get("retry_count", 0) + 1
if task["retry_count"] <= self.max_retries:
task["retry_after"] = time.time() + (
self.backoff_base * task["retry_count"]
)
self.queue.append(task)
return True
return False # Exceeded max retries
def get_ready(self):
"""Get tasks ready for retry."""
ready = []
remaining = deque()
now = time.time()
while self.queue:
task = self.queue.popleft()
if task["retry_after"] <= now:
ready.append(task)
else:
remaining.append(task)
self.queue = remaining
return ready
def save(self, filepath="retry_queue.json"):
with open(filepath, "w") as f:
json.dump(list(self.queue), f)
def load(self, filepath="retry_queue.json"):
try:
with open(filepath) as f:
self.queue = deque(json.load(f))
except FileNotFoundError:
pass
# Usage
retry_q = RetryQueue()
def process_with_retry(task):
try:
token = solve_captcha(task["type"], task["sitekey"], task["url"])
if token:
return submit_form(task["url"], token)
else:
retry_q.add(task)
except Exception:
retry_q.add(task)
# Process retry queue periodically
def drain_retry_queue():
ready = retry_q.get_ready()
for task in ready:
process_with_retry(task)
Se a fila guardar dados de formulário com informações pessoais — nome, e-mail, CPF —, trate esse armazenamento sob a LGPD: limite as tentativas (o max_retries do exemplo já faz esse papel), evite manter o payload além do necessário e persista a fila em disco ou banco, não só em memória.
Padrão 3: modo degradado com recuperação automática
Depois de um número configurável de falhas seguidas, vale parar de chamar a API por um tempo em vez de insistir erro atrás de erro — é o que o modo degradado faz. Pense numa equipe de automação em São Paulo rodando workers em sa-east-1 para monitorar preços numa campanha de vendas: se a resolução de CAPTCHA ficar instável, o CaptchaSolver do exemplo detecta cinco falhas seguidas, entra em modo degradado por 5 minutos e decide localmente se pula a página, enfileira para depois ou tenta um solver alternativo — em vez de cada worker bater na API individualmente e amplificar o problema.
class CaptchaSolver:
def __init__(self, api_key):
self.api_key = api_key
self.degraded = False
self.failure_count = 0
self.failure_threshold = 5
self.recovery_time = None
def solve(self, captcha_type, sitekey, page_url):
if self.degraded:
if time.time() < self.recovery_time:
return self._degraded_action(page_url)
else:
self.degraded = False
self.failure_count = 0
try:
token = self._solve_api(captcha_type, sitekey, page_url)
self.failure_count = 0
return token
except Exception as e:
self.failure_count += 1
if self.failure_count >= self.failure_threshold:
self._enter_degraded_mode()
raise
def _enter_degraded_mode(self):
self.degraded = True
self.recovery_time = time.time() + 300 # 5 min
print("Entering degraded mode for 5 minutes")
# Send alert
def _degraded_action(self, url):
"""What to do when solving is unavailable."""
# Option A: Skip CAPTCHA pages entirely
return None
# Option B: Queue for later
# retry_queue.add({"url": url, ...})
# return None
# Option C: Try alternative solver
# return self._solve_with_backup_api(...)
def _solve_api(self, captcha_type, sitekey, page_url):
# Normal CaptchaAI API call
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1",
}).json()
if resp["status"] != 1:
raise Exception(resp["request"])
task_id = resp["request"]
for _ in range(24):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": self.api_key, "action": "get",
"id": task_id, "json": "1"
}).json()
if result["status"] == 1:
return result["request"]
if result["request"] != "CAPCHA_NOT_READY":
raise Exception(result["request"])
raise Exception("TIMEOUT")
A recuperação é automática: passado o recovery_time, solve() zera o contador e volta a tentar a chamada normal, sem reiniciar o processo.
Node.js: combinando os três padrões
A versão em Node.js junta os três padrões numa única classe: tenta resolver, cai para a fila de retry em qualquer falha e entra em modo degradado por excesso de erros ou por saldo zerado — que recebe recuperação mais longa (10 minutos), pois normalmente exige recarga manual.
class ResilientSolver {
constructor(apiKey) {
this.apiKey = apiKey;
this.retryQueue = [];
this.failureCount = 0;
this.degraded = false;
}
async solve(type, sitekey, pageUrl) {
if (this.degraded) {
this.retryQueue.push({ type, sitekey, pageUrl, addedAt: Date.now() });
return null;
}
try {
const token = await this._callApi(type, sitekey, pageUrl);
this.failureCount = 0;
return token;
} catch (err) {
this.failureCount++;
if (err.message === 'ERROR_ZERO_BALANCE') {
this._enterDegraded(600000); // 10 min
return null;
}
if (this.failureCount >= 5) {
this._enterDegraded(300000); // 5 min
}
this.retryQueue.push({ type, sitekey, pageUrl, addedAt: Date.now() });
return null;
}
}
_enterDegraded(durationMs) {
this.degraded = true;
console.warn(`Degraded mode for ${durationMs / 1000}s`);
setTimeout(() => {
this.degraded = false;
this.failureCount = 0;
this.drainRetryQueue();
}, durationMs);
}
async drainRetryQueue() {
const tasks = this.retryQueue.splice(0);
for (const task of tasks) {
await this.solve(task.type, task.sitekey, task.pageUrl);
}
}
async _callApi(type, sitekey, pageUrl) {
// Standard submit + poll
const axios = require('axios');
const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: { key: this.apiKey, method: 'userrecaptcha', googlekey: sitekey, pageurl: pageUrl, json: 1 },
});
if (submit.data.status !== 1) throw new Error(submit.data.request);
const taskId = submit.data.request;
for (let i = 0; i < 24; i++) {
await new Promise(r => setTimeout(r, 5000));
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: this.apiKey, action: 'get', id: taskId, json: 1 },
});
if (poll.data.status === 1) return poll.data.request;
if (poll.data.request !== 'CAPCHA_NOT_READY') throw new Error(poll.data.request);
}
throw new Error('TIMEOUT');
}
}
Solução de problemas
Os problemas abaixo costumam ter a mesma causa: limites copiados de outro projeto, não calibrados para o seu volume real.
| Problema | Causa | Correção |
|---|---|---|
| Todas as tarefas estão sendo puladas | Modo degradado acionado de forma muito agressiva | Aumentar o failure_threshold |
| A fila de retry só cresce | As tarefas nunca dão certo | Definir um máximo de tentativas; mover para uma dead letter queue |
| Recuperação lenta demais | Tempo de modo degradado longo demais | Reduzir o recovery_time; adicionar uma sonda de health check |
| Tarefas da fila somem no restart | Fila só existe em memória | Persistir a fila em arquivo ou banco de dados |
Perguntas frequentes
Qual é a diferença entre modo degradado e um circuit breaker?
Um circuit breaker interrompe as chamadas assim que detecta falhas seguidas. O modo degradado é mais amplo: inclui comportamentos alternativos, lógica de pular tarefas e fluxos de reserva. Na prática, os dois funcionam bem combinados.
Quantas falhas seguidas devo tolerar antes de entrar em modo degradado?
Não existe um número universal — comece com failure_threshold = 5, como no exemplo, e ajuste observando os logs. Baixo demais, o sistema pula tarefas que dariam certo; alto demais, insiste em chamadas que já sabem que vão falhar.
Toda tarefa que falha deve entrar na fila de retry?
Não. ERROR_BAD_PARAMETERS e ERROR_WRONG_GOOGLEKEY não vão ter sucesso numa nova tentativa — enfileirá-las só adia o mesmo erro. Reserve a fila para falhas transitórias, como tempo limite e limite de requisições.
A fila de retries sobrevive se o processo reiniciar?
Só se você persistir. Uma deque em memória, como no exemplo de RetryQueue, some no restart — use save()/load() para gravar a fila em arquivo ou banco antes de encerrar o processo.
O que fazer quando o erro é saldo zerado (ERROR_ZERO_BALANCE)?
Pause os envios, dispare um alerta e recarregue o saldo — tentar de novo sem recarga só repete o erro. No exemplo em Node.js, esse caso entra num modo degradado com recuperação mais longa (10 minutos), porque normalmente depende de ação manual.
Crie automação CAPTCHA resiliente com a CaptchaAI
Obtenha sua chave de API em captchaai.com e comece a aplicar esses três padrões no seu pipeline ainda hoje.