Resolução de captcha rápida: como reduzir a latência no seu pipeline
Um solver de captcha rápido é aquele que você consegue chamar no momento certo, em paralelo, sem desperdiçar requisições. A chamada de resolução da Scarnox retorna quando o token está pronto, então a duração depende do tipo de captcha, do site alvo e da carga atual. Este guia não promete um tempo fixo. Ele mostra o que você controla no seu próprio código, como medir e como rodar muitas resoluções ao mesmo tempo, para que o tempo que você gasta seja bem aproveitado.
O que define o tempo de resolução
Três coisas importam, e só uma delas está nas suas mãos:
- O próprio captcha. Alguns tipos de desafio demoram mais que outros. Isso é uma propriedade do captcha do site, não do seu código.
- A carga no solver. Um período de muito movimento pode deixar uma única chamada mais lenta. A lógica de retentativa deve lidar com isso, e não abandonar a chamada cedo demais.
- O seu próprio pipeline. Resolver cedo demais, serializar todas as requisições e esperar por trabalhos não relacionados são fontes de tempo que você pode eliminar.
Concentre o seu esforço no terceiro item. É a parte que você consegue medir e mudar.
Meça antes de otimizar
Envolva a chamada com um cronômetro e registre o resultado. Você quer saber o tempo de resolução e o tempo que o seu código gasta ao redor dela:
import time
import requests
API_KEY = "scarnox_YOURAPIKEY"
URL = "https://api.scarnox.com/api/tasks/create"
def timed_solve(sitekey: str, page_url: str) -> dict:
started = time.perf_counter()
r = requests.post(
URL,
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"captcha_type": "PopularCaptchaTaskProxyless",
"site_key": sitekey,
"site_url": page_url,
},
timeout=185,
)
elapsed = time.perf_counter() - started
data = r.json()
if data.get("errorId") != 0:
return {"ok": False, "error": data.get("errorCode"), "seconds": elapsed}
return {"ok": True, "token": data["solution"]["token"], "seconds": elapsed}
result = timed_solve("10000000-ffff-ffff-ffff-000000000001", "https://example.com/signup")
print(result["ok"], round(result["seconds"], 2), "s")
Rode isso nas páginas que importam, nos horários em que você executa os seus jobs. Guarde os números. Eles mostram se uma mudança realmente ajudou.
Rode resoluções em paralelo
As resoluções são independentes. Se você tem dez sitekeys para tratar, esperar cada uma em sequência desperdiça tempo que poderia ser usado nas outras. Um pool de threads basta para a maioria dos scripts, porque cada resolução é uma requisição HTTP que passa a maior parte do tempo esperando:
from concurrent.futures import ThreadPoolExecutor
jobs = [
("10000000-ffff-ffff-ffff-000000000001", "https://example.com/signup"),
("10000000-ffff-ffff-ffff-000000000001", "https://example.com/register"),
("10000000-ffff-ffff-ffff-000000000001", "https://example.com/contact"),
]
def run(job):
sitekey, page_url = job
return timed_solve(sitekey, page_url)
# Comece pequeno. Aumente o número de workers somente enquanto os erros continuarem baixos.
with ThreadPoolExecutor(max_workers=5) as pool:
results = list(pool.map(run, jobs))
for r in results:
status = "ok" if r["ok"] else r["error"]
print(f"{r['seconds']:.2f} s {status}")
Se você vir erros que parecem problemas de limite de taxa ou de capacidade ao aumentar o número de workers, reduza-o. O paralelismo só ajuda quando o servidor acompanha.
Não resolva cedo demais
O código mais rápido é aquele que não espera por um token que ainda não vai usar. Pedir tokens com antecedência e guardá-los é o erro mais comum. Os tokens expiram cerca de dois minutos depois de emitidos, então um token guardado costuma estar vencido quando você precisa dele. Resolva quando o formulário estiver prestes a ser enviado. O raciocínio detalhado está em token de captcha expirado: causas e soluções.
Mantenha o resto do caminho curto
- Mantenha uma sessão HTTP por site, para não reconectar a cada requisição.
- Não execute processamento lento nem operações de banco entre a resolução e o envio.
- Use um timeout razoável nas suas próprias requisições. A chamada de resolução usa cerca de 185 segundos porque fica bloqueada até o token estar pronto. As suas outras chamadas devem ser bem mais curtas.
- Envie um proxy apenas quando a tarefa precisar. Os tipos de tarefa com proxy recebem um campo
proxyno formatohttp://user:pass@host:port, mas adicionar um proxy não acelera nada por si só.
Reaproveite o que não expira
Parte do trabalho pode ser compartilhada com segurança entre as resoluções. Tire estes itens do caminho crítico:
- Sitekeys. Leia o sitekey uma vez por modelo de página e guarde-o. Ele não expira como um token. Releia somente quando o formulário começar a falhar.
- Conexões HTTP. Uma sessão com keep-alive evita um novo handshake TLS a cada chamada. Crie uma sessão por thread ou por worker, não uma por requisição.
- Configuração carregada. Carregue a sua lista de alvos, a lista de proxies e as configurações uma vez na inicialização, em vez de ler arquivos dentro da função de resolução.
Essas mudanças custam pouco e eliminam um custo que aparece em todas as chamadas.
Meça a cauda, não a média
Um tempo médio de resolução esconde as chamadas lentas que seguram um lote. Registre o tempo de cada resolução e depois observe a mediana e o percentil 95. Um pipeline que termina no tempo do seu job mais lento é limitado pela cauda, então uma pequena mudança na forma como você refaz as chamadas lentas pode importar mais do que qualquer mudança na mediana:
import statistics
def summarise(seconds: list[float]) -> str:
ordered = sorted(seconds)
p95 = ordered[int(0.95 * (len(ordered) - 1))]
return f"median {statistics.median(ordered):.2f} s, p95 {p95:.2f} s, n={len(ordered)}"
print(summarise([r["seconds"] for r in results]))
Compare o resumo antes e depois de cada mudança. Se o p95 quase não se mover, o tempo está sendo gasto em outro ponto do seu código.
Verifique o resultado, não só a velocidade
Uma falha rápida continua sendo uma falha. Conte as resoluções que retornam erro e trate uma taxa de erro crescente como um sinal para desacelerar. Resoluções com falha não são cobradas, mas o tempo gasto nelas continua sendo tempo que o seu pipeline perdeu. O padrão completo de retentativas está em tratamento de erros do solver de captcha.