Para bajar el tiempo que tu proceso espera por cada token, dos cambios rinden más que cualquier otro: solapa el envío de la tarea con el trabajo que ya estás haciendo y deja de esperar en bloques fijos de 5 segundos. Ninguno toca el solver: la ganancia está en tu lado de la conexión.
Los cuatro tramos de una resolución
Cada token atraviesa cuatro tramos y solo uno queda fuera de tu alcance:
- Envío de la tarea a
in.php: milisegundos, salvo que abras una conexión nueva cada vez. - Espera en cola: según cuántos threads de tu plan estén ocupados.
- Ejecución del solver: el único tramo que no controlas.
- Recuperación del resultado desde
res.phpo víapingback: aquí pierde tiempo la mayoría de los pipelines.
Tres de los cuatro son tuyos.
Por dónde empezar si solo vas a tocar una cosa
Ordena el trabajo por lo que devuelve cada cambio, no por lo rápido de escribir:
- Solapa la resolución con tu procesamiento. El prefetch elimina casi toda la espera percibida.
- Cambia cómo recuperas el resultado. Sondeo adaptativo o
pingbacken vez del bloque fijo de 5 segundos. - Afina transporte y envío. Keep-alive, parámetros por tipo de CAPTCHA y proxy solo si hace falta.
1. Ajusta el intervalo de sondeo en vez de fijarlo en 5 s
El intervalo fijo de 5 segundos desperdicia tiempo cuando la resolución termina justo después de un sondeo. Consulta cada 3 segundos y amplía el intervalo si la tarea se alarga.
Python
import time
import requests
API_KEY = "YOUR_API_KEY"
RESULT_URL = "https://ocr.captchaai.com/res.php"
def adaptive_poll(task_id, timeout=120):
"""Start polling at 3s, increase to 5s after 4 polls."""
start = time.time()
interval = 3 # start aggressive
polls = 0
while time.time() - start < timeout:
time.sleep(interval)
polls += 1
resp = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get",
"id": task_id, "json": "1"
}).json()
if resp["status"] == 1:
elapsed = time.time() - start
print(f"Solved in {elapsed:.1f}s ({polls} polls)")
return resp["request"]
if resp["request"] != "CAPCHA_NOT_READY":
raise Exception(resp["request"])
# Back off after initial fast polls
if polls >= 4:
interval = 5
raise TimeoutError(f"Task {task_id} timed out")
JavaScript
async function adaptivePoll(taskId, apiKey, timeout = 120000) {
const start = Date.now();
let interval = 3000;
let polls = 0;
while (Date.now() - start < timeout) {
await new Promise(r => setTimeout(r, interval));
polls++;
const resp = await fetch(
`https://ocr.captchaai.com/res.php?key=${apiKey}&action=get&id=${taskId}&json=1`
);
const data = await resp.json();
if (data.status === 1) {
console.log(`Solved in ${((Date.now() - start) / 1000).toFixed(1)}s (${polls} polls)`);
return data.request;
}
if (data.request !== 'CAPCHA_NOT_READY') {
throw new Error(data.request);
}
if (polls >= 4) interval = 5000;
}
throw new Error(`Task ${taskId} timed out`);
}
- Ahorro típico: entre 1 y 4 segundos por tarea frente al intervalo fijo.
- Suelo recomendado: 3 segundos. Por debajo no adelantas nada y te acercas al límite.
2. Reutiliza conexiones con keep-alive
Cada sondeo que abre una conexión nueva paga handshakes TCP y TLS completos. Con una sesión reutilizable esa factura desaparece:
Python
session = requests.Session()
# Use session.get() and session.post() instead of requests.get/post
# The session reuses TCP connections automatically
JavaScript (Node.js)
const { Agent } = require('http');
const axios = require('axios');
const client = axios.create({
httpAgent: new Agent({ keepAlive: true, maxSockets: 10 }),
timeout: 10000,
});
// Use client.get() and client.post() for all API calls
Son 50–100 ms por solicitud: poco, hasta multiplicarlo por los cinco o seis sondeos de cada tarea.
3. Envía la tarea antes de necesitar el token
El prefetch es la optimización con mejor relación esfuerzo/resultado: mientras tu scraper procesa la página N, envía el CAPTCHA de la N+1.
Python
from concurrent.futures import ThreadPoolExecutor
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
def prefetch_submit(sitekey, page_url):
resp = session.post(SUBMIT_URL, data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1",
})
data = resp.json()
if data["status"] == 1:
return data["request"]
raise Exception(data["request"])
# Submit next page's CAPTCHA while processing current page
with ThreadPoolExecutor(max_workers=2) as pool:
# Submit CAPTCHA for page 2 while processing page 1
future_task = pool.submit(prefetch_submit, "6Le-SITEKEY", "https://example.com/page/2")
# Process page 1...
process_page(current_data)
# Now get the pre-submitted task ID and poll
task_id = future_task.result()
token = adaptive_poll(task_id)
- La resolución corre en paralelo con tu procesamiento: en flujos secuenciales la espera percibida tiende a cero.
- No adelantes tanto el envío que el token caduque antes de usarlo.
4. Elige el método de envío adecuado
En algunos escenarios hay una vía más corta que la genérica:
| Escenario | Método lento | Alternativa más rápida |
|---|---|---|
| reCAPTCHA v2 con callback conocido | userrecaptcha + sondeo |
userrecaptcha con pingback (URL de callback) |
| CAPTCHA de texto en imagen | base64 a máxima resolución |
base64 con numeric=1 si solo hay dígitos |
Acotar el espacio de respuesta reduce el trabajo del solver sin tocar tu código.
5. Manda parámetros de proxy solo cuando el sitio lo exija
Enrutar por un proxy añade latencia. Manda esos parámetros solo si el destino exige una IP concreta:
Python
# Without proxy — faster for most use cases
data = {
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1",
}
# With proxy — only when required
data["proxy"] = "user:[email protected]:8080"
data["proxytype"] = "HTTP"
6. Sustituye el sondeo por un pingback
La forma más rápida de recuperar un resultado es no ir a buscarlo: con el parámetro pingback, CaptchaAI lo entrega a tu endpoint en cuanto termina la resolución.
Python
resp = session.post(SUBMIT_URL, data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1",
"pingback": "https://your-server.com/captcha-callback",
})
Desaparece el bucle de sondeo, pero necesitas un endpoint público: tras un firewall corporativo el resultado no llega y conviene seguir con el sondeo adaptativo.
7. Mide antes y después
Ninguna de estas técnicas se justifica sin números propios: instrumenta, aplica un cambio y vuelve a medir.
Python
import statistics
def benchmark(solve_func, iterations=20):
times = []
for i in range(iterations):
start = time.time()
try:
solve_func()
times.append(time.time() - start)
except Exception:
pass
if times:
print(f"Samples: {len(times)}/{iterations}")
print(f"Mean: {statistics.mean(times):.1f}s")
print(f"Median: {statistics.median(times):.1f}s")
print(f"P95: {sorted(times)[int(len(times)*0.95)]:.1f}s")
print(f"Min: {min(times):.1f}s")
print(f"Max: {max(times):.1f}s")
- Mira la mediana y el P95: esta latencia tiene cola larga y el promedio la esconde.
- Anota cuántos sondeos consumió cada tarea: delata un intervalo mal ajustado.
Techos de tiempo por tipo de CAPTCHA
Contrasta tus mediciones con los límites de servicio publicados:
| Tipo de CAPTCHA | Techo publicado | Rango realista optimizado |
|---|---|---|
| Imagen/OCR | <0.5 s | 0.2–0.4 s |
| reCAPTCHA v2 | <60 s | 10–20 s |
| reCAPTCHA v3 | <4 s | 1–3 s |
| Cloudflare Turnstile | <10 s | 4–8 s |
| GeeTest v3 | <12 s | 6–10 s |
La segunda columna es lo que suele verse tras aplicar lo anterior. Si superas el techo con holgura, el problema rara vez está en el solver: está en tu bucle de recuperación.
Cómo se ve esto en un pipeline real
Un equipo en Ciudad de México monitorea precios en marketplaces regionales desde un servidor en São Paulo. Su scraper se bloqueaba en cada reCAPTCHA v2 hasta recibir el token, y abría una conexión nueva en cada sondeo.
Al enviar el CAPTCHA de la página siguiente antes de terminar la actual, sondear a 3 segundos con retroceso y compartir una sola sesión HTTP, la resolución ocurre mientras el proceso ya hace otra cosa. Siguen en ADVANCE ($90/mes, 50 threads); lo que cambió es cuántos threads están ocupados a la vez.
- Mide desde la región donde corre tu carga real: la distancia hasta los endpoints añade una ida y vuelta a cada llamada.
- Respeta los términos de servicio del sitio y la normativa de protección de datos aplicable (GDPR y LOPDGDD en España, LFPDPPP en México).
Diagnóstico rápido cuando la latencia no baja
Si aplicaste todo y el reloj no se mueve, revisa estos cuatro sospechosos:
| Síntoma | Causa habitual | Qué hacer |
|---|---|---|
| El sondeo tarda igual que antes | Sigues llamando a requests.get() sin sesión |
Cambia a session.get() |
| El token del prefetch caduca antes de usarlo | El procesamiento intermedio dura demasiado | Acorta la ventana |
La URL de pingback no recibe nada |
El endpoint no es público | Publícalo y revisa las reglas del firewall |
| Aparecen errores de límite de solicitudes | Sondeo por debajo de 2 s | Mantén el mínimo en 3 s |
Preguntas frecuentes
¿Contratar más threads reduce la latencia de una resolución?
No. Los threads determinan cuántas tareas tienes en vuelo a la vez, no lo que tarda cada una. Si hay espera en cola, más threads ayudan; si la resolución es lenta, mira el sondeo y el prefetch.
¿Cuál es el intervalo de sondeo mínimo razonable?
Tres segundos. Por debajo multiplicas llamadas sin adelantar la resolución y te expones al límite.
¿Influye la región de mi servidor en el tiempo total?
Sí, aunque es el factor menor: la distancia añade una ida y vuelta a cada llamada, y con keep-alive ese coste se paga muchas menos veces.
¿Puedo combinar pingback y sondeo en el mismo pipeline?
Sí, y es lo más robusto: usa pingback como vía principal y deja un sondeo de respaldo, con intervalo amplio, para los callbacks que no lleguen.
Empieza a medir tu latencia real
Obtén tu clave API en captchaai.com, corre el benchmark de arriba y compara mediana y P95 antes y después del prefetch.