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:
- El propio captcha. Algunos tipos de desafío tardan más que otros. Es una propiedad del captcha del sitio, no de tu código.
- La carga del solver. Un periodo de mucha actividad puede hacer que una sola llamada sea más lenta. La lógica de reintentos debe gestionarlo, no abandonar la llamada pronto.
- Tu propio pipeline. Resolver demasiado pronto, serializar todas las peticiones y esperar trabajo sin relación con la resolución añaden tiempo que puedes eliminar.
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
- Mantén una sesión HTTP por sitio para no reconectar en cada petición.
- No ejecutes parseo lento ni trabajo de base de datos entre la resolución y el envío.
- Usa un timeout razonable en tus propias peticiones. La llamada de resolución usa unos 185 segundos porque se bloquea hasta que el token está listo. Tus otras llamadas deben ser mucho más cortas.
- Envía un proxy solo cuando la tarea lo necesite. Los tipos de tarea con proxy aceptan un campo
proxycon el formatohttp://user:pass@host:port, pero añadir un proxy no acelera nada por sí mismo.
Reutiliza lo que no caduca
Parte del trabajo puede compartirse con seguridad entre resoluciones. Saca estos elementos de la ruta crítica:
- Sitekeys. Lee la sitekey una vez por plantilla de página y guárdala. No caduca como un token. Vuelve a leerla solo cuando el formulario empiece a fallar.
- Conexiones HTTP. Una sesión con keep-alive evita un nuevo handshake TLS en cada llamada. Crea una sesión por hilo o por worker, no una por petición.
- Configuración ya parseada. Carga tu lista de objetivos, tu lista de proxies y tus ajustes una sola vez al arrancar, en lugar de leer archivos dentro de la función de resolución.
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.