Comparativas

Resolución de CAPTCHA en paralelo vs secuencial: rendimiento

¿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_concurrent al 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:

Los comentarios están deshabilitados para este artículo.