¿Conviene resolver los CAPTCHA de uno en uno o lanzarlos todos a la vez? La respuesta depende de un solo número: cuántas resoluciones necesitas al día. Por debajo de unas 500 al día, el enfoque secuencial te da un código simple y fácil de depurar. Por encima, la resolución en paralelo deja de ser un lujo y pasa a ser la única forma de vaciar la cola antes de que se acumule. En esta comparativa verás ambas estrategias con CaptchaAI: cifras de rendimiento, memoria y costo, más ejemplos en Python y JavaScript listos para copiar.
Comparación rápida: secuencial frente a paralelo
| Factor | Secuencial | Paralelo |
|---|---|---|
| Rendimiento (reCAPTCHA v2, mediana 15 s) | ~240/hora | ~10.000+/hora |
| Complejidad del código | Sencillo | De moderado a complejo |
| Manejo de errores | Directo | Requiere aislar errores concurrentes |
| Uso de memoria | Mínimo (~30 MB) | Escala con la concurrencia (~100–500 MB) |
| Costo API por resolución | Igual | Igual |
| Dificultad de depuración | Fácil | Mayor (condiciones de carrera, timing) |
| Conservación del orden | Automática | Requiere seguimiento explícito |
| Ideal para | < 500 resoluciones/día | > 500 resoluciones/día |
Si tuvieras que decidir en diez segundos, esta es la regla:
- Menos de 500 resoluciones/día: quédate en secuencial; el código simple no te cuesta tiempo.
- De 500 a unos pocos miles al día: pasa a paralelo con concurrencia moderada (10–25 a la vez).
- Decenas de miles al día: paralelo con toda la concurrencia que te permitan tus threads.
Piensa en un equipo que monitorea precios en marketplaces regionales (tipo MercadoLibre o Amazon.es) para varios clientes. Con 200 páginas al día y un CAPTCHA por página, lo secuencial sobra. Pero cuando ese equipo pasa a rastrear miles de fichas de producto cada mañana, el modo secuencial tardaría horas y el informe llegaría tarde: ahí es donde el paralelismo —y los threads de tu plan— marca la diferencia.
Rendimiento según el nivel de concurrencia
| Resoluciones concurrentes | Rendimiento estimado/hora | Memoria (Python) | Complejidad |
|---|---|---|---|
| 1 (secuencial) | 240 | 30 MB | Baja |
| 10 | 2.400 | 50 MB | Baja |
| 25 | 6.000 | 80 MB | Media |
| 50 | 10.000+ | 120 MB | Media |
| 100 | 18.000+ | 200 MB | Alta |
Basado en reCAPTCHA v2 con un tiempo medio de resolución de 15 segundos.
El techo práctico de concurrencia no lo pone solo tu código, sino los threads de tu plan: cada thread es un CAPTCHA en curso. Estos son los límites por plan:
- BASIC ($15/mes, 5 threads): hasta 5 resoluciones simultáneas.
- STANDARD ($30/mes, 15 threads): margen para lotes pequeños.
- ADVANCE ($90/mes, 50 threads): ideal para rastreos diarios de miles de páginas.
- PREMIUM ($170/mes, 100 threads): para picos de decenas de miles al día.
Ajusta max_concurrent para que no supere los threads que tienes contratados; si te pasas, la API responde ERROR_NO_SLOT_AVAILABLE.
Regla práctica: fija
max_concurrental número de threads de tu plan, nunca por encima. La concurrencia de más no acelera nada; solo genera envíos rechazados.
Resolución secuencial: un CAPTCHA a la vez
Cómo funciona
Un CAPTCHA cada vez: envía → espera → consulta → obtén el resultado → siguiente.
# sequential_solver.py
import os
import time
import requests
API_KEY = os.environ.get("CAPTCHAAI_KEY", "YOUR_API_KEY")
def solve_sequential(tasks):
"""Solve CAPTCHAs one by one."""
results = []
session = requests.Session()
for task in tasks:
# Submit
resp = session.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": task["sitekey"],
"pageurl": task["pageurl"],
"json": "1",
})
result = resp.json()
if result.get("status") != 1:
results.append({"error": result.get("request")})
continue
task_id = result["request"]
time.sleep(15)
# Poll
token = None
for _ in range(25):
poll = session.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get",
"id": task_id, "json": "1",
})
poll_result = poll.json()
if poll_result.get("status") == 1:
token = poll_result["request"]
break
if poll_result.get("request") != "CAPCHA_NOT_READY":
break
time.sleep(5)
results.append({"token": token} if token else {"error": "timeout"})
return results
# 10 tasks sequentially → ~150 seconds total
tasks = [{"sitekey": "SITEKEY", "pageurl": "https://example.com"}] * 10
start = time.time()
results = solve_sequential(tasks)
print(f"Completed in {time.time() - start:.0f}s")
Cuándo compensa lo secuencial
- Flujos de una sola página: rellenas un formulario cada vez.
- Desarrollo y depuración: el flujo de ejecución es claro y lineal.
- Volumen bajo: por debajo de 500 resoluciones/día no compensa la complejidad del paralelo.
- Tareas dependientes del orden: cuando cada resultado alimenta el siguiente paso.
Resolución en paralelo: todos a la vez
Cómo funciona
Envía todos los CAPTCHA de golpe y consulta los resultados de forma concurrente:
# parallel_solver.py
import os
import asyncio
import aiohttp
API_KEY = os.environ.get("CAPTCHAAI_KEY", "YOUR_API_KEY")
async def solve_one(session, sitekey, pageurl, semaphore):
"""Solve a single CAPTCHA within concurrency limits."""
async with semaphore:
# Submit
async with session.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY, "method": "userrecaptcha",
"googlekey": sitekey, "pageurl": pageurl, "json": "1",
}) as resp:
result = await resp.json(content_type=None)
if result.get("status") != 1:
return {"error": result.get("request")}
task_id = result["request"]
await asyncio.sleep(15)
# Poll
for _ in range(25):
async with session.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get",
"id": task_id, "json": "1",
}) as resp:
poll_result = await resp.json(content_type=None)
if poll_result.get("status") == 1:
return {"token": poll_result["request"]}
if poll_result.get("request") != "CAPCHA_NOT_READY":
return {"error": poll_result.get("request")}
await asyncio.sleep(5)
return {"error": "timeout"}
async def solve_parallel(tasks, max_concurrent=50):
"""Solve CAPTCHAs in parallel with concurrency control."""
semaphore = asyncio.Semaphore(max_concurrent)
connector = aiohttp.TCPConnector(limit=max_concurrent)
async with aiohttp.ClientSession(connector=connector) as session:
coros = [
solve_one(session, t["sitekey"], t["pageurl"], semaphore)
for t in tasks
]
return await asyncio.gather(*coros)
# 10 tasks in parallel → ~20 seconds total
import time
tasks = [{"sitekey": "SITEKEY", "pageurl": "https://example.com"}] * 10
start = time.time()
results = asyncio.run(solve_parallel(tasks))
print(f"Completed in {time.time() - start:.0f}s")
El Semaphore evita que abras más solicitudes de las que soporta tu plan: las tareas sobrantes esperan turno de forma ordenada, sin saturar la API.
Ejemplo en paralelo con JavaScript
// parallel_solver.js
const axios = require('axios');
const https = require('https');
const API_KEY = process.env.CAPTCHAAI_KEY || 'YOUR_API_KEY';
const agent = new https.Agent({ keepAlive: true, maxSockets: 50 });
const api = axios.create({ baseURL: 'https://ocr.captchaai.com', httpsAgent: agent });
async function solveOne(sitekey, pageurl) {
const submit = await api.get('/in.php', {
params: { key: API_KEY, method: 'userrecaptcha', googlekey: sitekey, pageurl, json: '1' },
});
if (submit.data.status !== 1) return { error: submit.data.request };
await new Promise(r => setTimeout(r, 15000));
for (let i = 0; i < 25; i++) {
const poll = await api.get('/res.php', {
params: { key: API_KEY, action: 'get', id: submit.data.request, json: '1' },
});
if (poll.data.status === 1) return { token: poll.data.request };
if (poll.data.request !== 'CAPCHA_NOT_READY') return { error: poll.data.request };
await new Promise(r => setTimeout(r, 5000));
}
return { error: 'timeout' };
}
(async () => {
const tasks = Array.from({ length: 10 }, () => ({
sitekey: 'SITEKEY', pageurl: 'https://example.com',
}));
const start = Date.now();
const results = await Promise.all(tasks.map(t => solveOne(t.sitekey, t.pageurl)));
console.log(`Completed in ${((Date.now() - start) / 1000).toFixed(0)}s`);
console.log(`Solved: ${results.filter(r => r.token).length}/${tasks.length}`);
agent.destroy();
})();
Cuándo elegir el paralelo
- Volumen alto: por encima de 500 resoluciones/día el secuencial se queda corto.
- Lotes independientes: cuando una tarea no necesita el resultado de la anterior.
- Ventanas de tiempo ajustadas: rastreos o informes que deben cerrarse en minutos, no en horas.
Enfoque híbrido: flujo secuencial, resolución en paralelo
No tienes que elegir un solo modelo para todo el proceso. Un patrón muy práctico es recorrer las URL de forma secuencial —porque el orden importa en tu flujo— pero agrupar la parte lenta, la resolución del CAPTCHA, en un lote paralelo:
# Process 10 URLs sequentially, but solve their CAPTCHAs in a parallel batch
urls = get_next_batch() # 10 URLs
captcha_params = [extract_sitekey(url) for url in urls] # Sequential extraction
tokens = asyncio.run(solve_parallel(captcha_params, max_concurrent=10)) # Parallel solving
for url, result in zip(urls, tokens):
submit_form(url, result.get("token")) # Sequential submission
Así mantienes la lógica de negocio simple y ordenada, y reservas la concurrencia para el cuello de botella real: los 15 segundos de espera de cada resolución. Reserva este patrón para cuando:
- El orden de las URL importa en tu flujo de negocio.
- Extraer el sitekey es rápido, pero resolver el CAPTCHA es lo lento.
- Quieres código legible sin sacrificar rendimiento.
Solución de problemas
Cuando el paralelo no rinde como esperabas, casi siempre es una de estas cuatro causas:
| Problema | Causa | Solución |
|---|---|---|
| El paralelo va más lento de lo esperado | Semáforo demasiado restrictivo | Aumenta max_concurrent |
| Fallos aleatorios en paralelo | Estado compartido corrupto | Aísla el estado por corrutina/promise |
| Resultados desordenados | asyncio.gather conserva el orden |
Usa seguimiento por índice si hace falta |
La API devuelve ERROR_NO_SLOT_AVAILABLE |
Demasiados envíos simultáneos | Añade un pequeño retraso entre envíos |
Preguntas frecuentes
¿Cuántos threads de mi plan necesito para resolver en paralelo?
Tantos como resoluciones simultáneas quieras mantener. Cada thread equivale a un CAPTCHA en curso, así que max_concurrent=50 necesita al menos los 50 threads del plan ADVANCE ($90/mes). Si configuras más concurrencia que threads, los envíos sobrantes esperarán turno.
¿Resolver en paralelo consume más saldo?
No. CaptchaAI factura por thread, con resoluciones ilimitadas por thread dentro de tu plan, no por solicitud simultánea. El paralelo cuesta lo mismo que el secuencial: solo llegas al total antes.
¿Qué pasa si supero los threads que tengo contratados?
La API empieza a devolver ERROR_NO_SLOT_AVAILABLE en los envíos que no caben. La salida es bajar max_concurrent, añadir un pequeño retraso entre envíos o subir de plan si el volumen lo justifica.
¿Cómo mantengo el orden de los resultados en paralelo?
asyncio.gather y Promise.all ya conservan el orden de entrada en la lista de resultados. Si repartes el trabajo por otras vías (colas, workers), guarda un índice con cada tarea y reordena al final.
¿Cuándo no merece la pena el paralelo?
Cuando tu volumen es bajo (menos de 500 resoluciones/día) o cada resultado depende del anterior. En esos casos la concurrencia añade complejidad —condiciones de carrera, depuración más difícil— sin un beneficio real de velocidad.
Próximos pasos
Elige la estrategia según tu volumen y arranca con tu clave API de CaptchaAI.
Guías relacionadas: