← Volver al blog

Apio, RQ o BullMQ: cómo construir una cola de tareas con proxy y eludir el límite de solicitudes por IP

Analizamos la arquitectura de colas de tareas con rotación de proxy en Celery, RQ y BullMQ: cómo distribuir la carga entre direcciones IP y evitar el límite de tasa.

📅20 de octubre de 2026

Cuando un parser o bot envía cientos de solicitudes por minuto desde una sola IP, el servidor rápidamente activa el límite de tasa, y luego viene el captcha o el bloqueo. La solución es una cola de tareas que distribuye las solicitudes entre los trabajadores y proxies. En este artículo, analizaremos cómo está estructurada tal arquitectura en Apio, RQ y BullMQ, y cómo gestionar correctamente el límite de solicitudes por cada dirección IP.

¿Por qué una cola de tareas al trabajar con proxies?

Si la tarea es extraer 50,000 tarjetas de productos de Wildberries o verificar el estado de 10,000 cuentas de Instagram, ejecutar las solicitudes de forma secuencial tomará horas, y la ejecución paralela sin control rápidamente resultará en un bloqueo por IP. La cola de tareas (task queue) resuelve ambos problemas: divide un gran volumen de trabajo en tareas más pequeñas, las distribuye entre varios trabajadores y permite asignar un proxy separado a cada trabajador.

La arquitectura generalmente se ve así: un corredor de mensajes (Redis o RabbitMQ) almacena la cola de tareas, los trabajadores toman tareas de la cola y las ejecutan, cada trabajador utiliza su propio proxy o rota proxies de un grupo. Esto permite mantener la carga en cada IP dentro de límites seguros, incluso si el volumen total de solicitudes es enorme.

Este enfoque es utilizado no solo por desarrolladores de parsers, sino también por equipos que automatizan la verificación de cuentas publicitarias de Facebook Ads, monitorean precios en Ozon o recopilan datos de decenas de sitios al mismo tiempo. Sin una cola y una distribución adecuada por IP, cualquier sistema de este tipo está condenado a bloqueos desde el primer día de operación.

Problema del límite de solicitudes por una IP

La mayoría de los sitios y API tienen protección contra cargas excesivas. Esto puede manifestarse de varias maneras: el servidor devuelve HTTP 429 (Demasiadas Solicitudes), muestra un captcha, bloquea temporalmente la IP durante 10-60 minutos o la agrega permanentemente a una lista negra. Los límites varían significativamente entre diferentes plataformas: por ejemplo, la API de Wildberries permite un número limitado de solicitudes por segundo desde una sola dirección, mientras que Instagram reacciona a actividades sospechosas después de 15-20 acciones consecutivas.

El principal error al construir una cola es ejecutar todos los trabajadores a través de la misma IP. Incluso si las tareas están distribuidas entre 20 trabajadores, si todos utilizan un solo proxy o no utilizan ninguno, el límite para esa IP se alcanzará tan rápido como si se ejecutaran secuencialmente. Por lo tanto, el número de trabajadores debe corresponder al número de direcciones IP disponibles en el grupo de proxies.

Para tareas con alta frecuencia de solicitudes a un solo dominio, es mejor usar proxies de centros de datos — ofrecen baja latencia y son adecuados para un scraping paralelo a gran escala, donde la velocidad es importante, no la camuflaje como un usuario normal. Si el objetivo es eludir protecciones más estrictas como sistemas anti-bots de marketplaces o redes sociales, se necesitan IP que se asemejen a usuarios normales, es decir, residenciales o móviles.

Apio: arquitectura y rotación de proxies en el código

Apio es la cola de tareas más madura y popular en el ecosistema de Python. Funciona sobre un corredor (generalmente Redis o RabbitMQ) y soporta una configuración flexible de concurrencia, reintentos y prioridades de tareas. Para la rotación de proxies, es conveniente almacenar un grupo de IP en Redis y asignar un proxy a cada tarea al ser colocada en la cola.

from celery import Celery
import requests
import redis
import random

app = Celery('tasks', broker='redis://localhost:6379/0')
redis_client = redis.Redis(host='localhost', port=6379, db=1)

PROXY_POOL = [
    "http://user:[email protected]:8000",
    "http://user:[email protected]:8000",
    "http://user:[email protected]:8000",
]

def get_proxy():
    # Round-robin a través de Redis, para no golpear una sola IP
    idx = redis_client.incr("proxy_cursor") % len(PROXY_POOL)
    return PROXY_POOL[idx]

@app.task(bind=True, max_retries=3, default_retry_delay=30)
def fetch_url(self, url):
    proxy = get_proxy()
    try:
        response = requests.get(
            url,
            proxies={"http": proxy, "https": proxy},
            timeout=15
        )
        if response.status_code == 429:
            raise self.retry(exc=Exception("Limitado por tasa"))
        return response.status_code
    except requests.exceptions.RequestException as exc:
        raise self.retry(exc=exc)

El punto clave es la función get_proxy(), que a través de un contador de Redis proporciona el siguiente proxy del grupo en un ciclo. Esto garantiza que la carga se distribuye uniformemente entre todas las IP, y no se concentra en una sola. El parámetro max_retries y el manejo del código 429 permiten que la tarea se reenvíe automáticamente con otro proxy si la IP actual recibe una limitación.

Para controlar la concurrencia a nivel de trabajadores, Apio permite limitar el número de tareas simultáneas a través del parámetro --concurrency al iniciar el trabajador, así como a través de colas con diferentes prioridades (por ejemplo, una cola separada para tareas lentas con un límite de tasa estricto).

RQ (Redis Queue): una alternativa simple

RQ es una biblioteca más ligera que Apio, sin el corredor RabbitMQ y con una configuración mínima. Es adecuada si el proyecto es pequeño y no requiere una compleja enrutación de tareas, sino simplemente una cola con trabajadores y la posibilidad de vincular un proxy a la tarea.

from redis import Redis
from rq import Queue
import requests
import itertools

redis_conn = Redis()
q = Queue(connection=redis_conn)

PROXY_POOL = [
    "http://user:[email protected]:8000",
    "http://user:[email protected]:8000",
]
proxy_cycle = itertools.cycle(PROXY_POOL)

def fetch_with_proxy(url):
    proxy = next(proxy_cycle)
    response = requests.get(
        url,
        proxies={"http": proxy, "https": proxy},
        timeout=15
    )
    if response.status_code == 429:
        # Reenviar a la cola con retraso
        q.enqueue_in(60, fetch_with_proxy, url)
        return None
    return response.status_code

# Colocando tareas en la cola
urls = ["https://example.com/page1", "https://example.com/page2"]
for url in urls:
    q.enqueue(fetch_with_proxy, url)

En RQ, la rotación a través de itertools.cycle funciona dentro del proceso del trabajador — es más simple que un contador de Redis, pero escala peor con muchos trabajadores paralelos, porque cada trabajador comienza el ciclo desde el principio. Para proyectos pequeños con 1-5 trabajadores, esto es suficiente; para proyectos más grandes, es mejor volver a un contador centralizado en Redis, como en el ejemplo con Apio.

RQ es adecuado para desarrolladores que ya utilizan Redis en su proyecto y no quieren levantar un corredor de mensajes separado. La desventaja es que tiene menos mecanismos integrados de priorización y monitoreo en comparación con Apio, por lo que para sistemas de producción con cientos de miles de tareas al día, a menudo se elige Apio.

BullMQ: cola en Node.js con limitación por IP

BullMQ es el estándar para colas de tareas en el ecosistema de Node.js, construido sobre Redis y tiene un limitador de tasa incorporado, lo que es especialmente conveniente para controlar el límite de solicitudes por IP. En lugar de contar manualmente las solicitudes, se puede especificar el limitador directamente a nivel de cola.

const { Queue, Worker } = require('bullmq');
const axios = require('axios');

const connection = { host: 'localhost', port: 6379 };

const proxyPool = [
  'http://user:[email protected]:8000',
  'http://user:[email protected]:8000',
  'http://user:[email protected]:8000',
];

const queue = new Queue('fetchQueue', { connection });

// Limitador: no más de 5 tareas por segundo en la cola
const worker = new Worker('fetchQueue', async job => {
  const proxy = proxyPool[job.id % proxyPool.length];
  try {
    const response = await axios.get(job.data.url, {
      proxy: false,
      httpsAgent: new (require('https-proxy-agent'))(proxy),
      timeout: 15000,
    });
    return response.status;
  } catch (err) {
    if (err.response && err.response.status === 429) {
      throw new Error('Limitado por tasa, reintentando con otro proxy');
    }
    throw err;
  }
}, {
  connection,
  limiter: { max: 5, duration: 1000 },
  concurrency: proxyPool.length,
});

for (let i = 0; i < 100; i++) {
  queue.add('fetch', { url: `https://example.com/item/${i}` }, {
    attempts: 3,
    backoff: { type: 'exponential', delay: 5000 },
  });
}

El parámetro limiter en BullMQ es un mecanismo incorporado que limita el número total de tareas procesadas en un intervalo de tiempo, lo que es conveniente para cumplir con el límite de solicitudes a nivel del sistema. Y el parámetro concurrency, igual al tamaño del grupo de proxies, garantiza que exactamente tantos trabajadores estén funcionando al mismo tiempo como IPs libres hay.

Este enfoque es utilizado a menudo por equipos que escriben sus propios bots para automatizar acciones en TikTok o Telegram — BullMQ se integra bien con el resto del stack de Node.js y no requiere cambiar a Python para una sola tarea.

Comparación de Apio, RQ y BullMQ

Parámetro Apio RQ BullMQ
Idioma Python Python Node.js
Corredor Redis / RabbitMQ Solo Redis Solo Redis
Limitador de tasa incorporado No (se escribe manualmente) No Sí, de forma predeterminada
Umbral de entrada Medio Bajo Medio
Adecuado para Grandes sistemas de producción Proyectos pequeños, prototipos Stack de Node.js, bots, automatización

Estrategias de distribución de carga por IP

El round-robin, que se utiliza en los ejemplos anteriores, es la estrategia más simple, pero no siempre es óptima. Aquí hay algunas opciones que vale la pena considerar dependiendo de la tarea:

  • Round-robin — cada solicitud siguiente pasa a través del siguiente proxy en un ciclo. Adecuado cuando todas las IP son equivalentes y los límites son iguales.
  • Sticky session — un proxy se asigna a una cuenta o sesión específica durante todo el tiempo que se trabaja con ella. Crítico para redes sociales y marketplaces, donde cambiar IP a mitad de sesión parece sospechoso.
  • Weighted distribution — parte de las solicitudes se envía a IP más caras y confiables (por ejemplo, móviles), y parte a las baratas de centros de datos, dependiendo de qué dominio se está procesando.
  • Adaptive throttling — la velocidad de envío de tareas se reduce automáticamente si aumenta el porcentaje de errores 429 o captchas, y se restaura cuando los errores desaparecen.

Para tareas donde es importante camuflarse como un usuario real — por ejemplo, calentar y verificar cuentas en Instagram o TikTok a través de navegadores anti-detección como Dolphin Anty o AdsPower — es mejor aplicar sticky session con proxies móviles: son menos sospechosos para los sistemas anti-bots y se comportan como usuarios normales de internet móvil.

Para la recopilación masiva de datos de sitios donde la velocidad y el volumen son más importantes que la confianza del algoritmo, el round-robin con proxies residenciales ofrece un equilibrio entre estabilidad y precio: las IP parecen usuarios reales, pero se pueden cambiar con frecuencia sin riesgo para una sesión específica.

Monitoreo de errores 429 y bloqueos

Una cola de tareas sin monitoreo es una caja negra: es difícil entender por qué parte de las tareas fallan y qué IP ya están bajo sospecha. El conjunto mínimo de métricas que vale la pena rastrear:

  • Porcentaje de respuestas con código 429 y 403 por cada proxy por separado
  • Tiempo medio de respuesta — un aumento en la latencia a menudo precede a un bloqueo
  • Cantidad de reintentos (retries) por tarea
  • Longitud de la cola de tareas en tiempo real — si está creciendo, los trabajadores no están cumpliendo

En Apio, es conveniente utilizar Flower para esto — una interfaz web para monitorear tareas y trabajadores en tiempo real. En BullMQ hay Bull Board con funcionalidad similar. Para RQ existe RQ Dashboard. Las tres herramientas muestran el estado de las tareas, la cantidad de intentos fallidos y permiten entender rápidamente qué proxy del grupo debe ser temporalmente excluido de la rotación.

Una buena práctica es marcar automáticamente los proxies como "en cuarentena" durante 15-30 minutos después de varios errores consecutivos 429 desde esa IP, y devolverlo al grupo solo después de una solicitud de prueba exitosa. Esto evita que una IP bloqueada continúe recibiendo tareas y desperdiciando el tiempo de los trabajadores.

Conclusión

La cola de tareas resuelve el problema de escalar solicitudes, pero por sí sola no protege contra el límite en la IP — para eso se necesita una rotación de proxies bien pensada dentro de Apio, RQ o BullMQ. La elección de la herramienta depende del stack: los proyectos de Python suelen optar por Apio para producción y RQ para tareas simples, mientras que los desarrolladores de Node.js eligen BullMQ con su limitador de tasa incorporado.

Independientemente de la cola elegida, el resultado depende en gran medida de la calidad del propio grupo de proxies. Si la tarea es un scraping masivo rápido, considere proxies de centros de datos para velocidad, y para trabajar con redes sociales y marketplaces, donde la lealtad de los sistemas anti-bots es importante, se adaptan mejor las IP residenciales o móviles — reducen el porcentaje de errores 429 y bloqueos, lo que directamente disminuye el número de reintentos y acelera la ejecución de toda la cola de tareas.