Scarnox · Blog

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:

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

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:

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.

Próximos passos