Scarnox · Blog

Resolución de captchas rápida: cómo reducir la latencia en tu pipeline

Un solver de captchas rápido es aquel que puedes llamar en el momento adecuado, en paralelo, sin desperdiciar peticiones. La llamada de resolución de Scarnox devuelve la respuesta cuando el token está listo, así que su duración depende del tipo de captcha, del sitio de destino y de la carga actual. Esta guía no promete un tiempo fijo. Muestra lo que controlas en tu propio código, cómo medirlo y cómo ejecutar muchas resoluciones a la vez, para que el tiempo que sí inviertes se aproveche bien.

Qué determina el tiempo de resolución

Tres cosas importan, y solo una está en tus manos:

Concentra tu esfuerzo en el tercer punto. Es la parte que puedes medir y cambiar.

Mide antes de optimizar

Envuelve la llamada con un temporizador y registra el resultado. Quieres saber el tiempo de resolución y el tiempo que tu código dedica alrededor de ella:

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")

Ejecuta esto contra las páginas que te interesan, a las horas en que ejecutas tus trabajos. Guarda los números. Te dirán si un cambio realmente ayudó.

Ejecuta las resoluciones en paralelo

Las resoluciones son independientes. Si tienes diez sitekeys que gestionar, esperar a cada una por turno desperdicia tiempo que podrías dedicar a las demás. Un pool de hilos basta para la mayoría de los scripts, porque cada resolución es una petición HTTP que pasa la mayor parte del tiempo 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)

# Start small. Raise the worker count only while errors stay low.
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}")

Si al subir el número de workers ves errores que parecen problemas de límite o de capacidad, bájalo. El paralelismo solo ayuda cuando el servidor puede seguir el ritmo.

No resuelvas antes de tiempo

El código más rápido es el que no espera un token que todavía no va a usar. Pedir tokens por adelantado y guardarlos es el error habitual. Los tokens caducan unos dos minutos después de emitirse, así que un token guardado suele estar caducado cuando lo necesitas. Resuelve cuando el formulario esté a punto de enviarse. El razonamiento detallado está en token de captcha caducado: causas y soluciones.

Mantén corto el resto del camino

Reutiliza lo que no caduca

Parte del trabajo puede compartirse con seguridad entre resoluciones. Saca estos elementos de la ruta crítica:

Estos cambios cuestan poco y eliminan una sobrecarga que aparece en cada llamada.

Mide la cola, no la media

El tiempo medio de resolución oculta las llamadas lentas que retienen un lote. Registra el tiempo de cada resolución y luego mira la mediana y el percentil 95. Un pipeline que termina en el tiempo de su trabajo más lento está limitado por la cola, así que un pequeño cambio en la forma de reintentar las llamadas lentas puede importar más que cualquier cambio en la 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]))

Compara el resumen antes y después de cada cambio. Si el p95 apenas se mueve, el tiempo se está gastando en otra parte de tu código.

Comprueba el resultado, no solo la velocidad

Un fallo rápido sigue siendo un fallo. Cuenta las resoluciones que devuelven error, y trata una tasa de errores creciente como señal para ir más despacio. Las resoluciones fallidas no se cobran, pero el tiempo dedicado a ellas sigue siendo tiempo que tu pipeline ha perdido. El patrón completo de reintentos está en gestión de errores del solver de captchas.

Próximos pasos