Когда парсер или бот отправляет сотни запросов в минуту с одного IP, сервер быстро включает rate limit, а дальше — капча или бан. Решение — очередь задач, которая распределяет запросы между воркерами и прокси. В этой статье разберём, как устроена такая архитектура в Celery, RQ и BullMQ, и как грамотно управлять лимитом запросов на каждый IP-адрес.
Зачем очередь задач при работе с прокси
Если задача — спарсить 50 000 карточек товаров с Wildberries или проверить статус 10 000 аккаунтов Instagram, последовательный запуск запросов займёт часы, а параллельный запуск без контроля быстро словит бан по IP. Очередь задач (task queue) решает обе проблемы: она разбивает большой объём работы на мелкие задачи, распределяет их между несколькими воркерами и позволяет привязать к каждому воркеру отдельный прокси.
Архитектура обычно выглядит так: брокер сообщений (Redis или RabbitMQ) хранит очередь задач, воркеры забирают задачи из очереди и выполняют их, каждый воркер использует свой прокси или ротирует прокси из пула. Это позволяет держать нагрузку на каждый IP в безопасных пределах, даже если общий объём запросов огромный.
Такой подход применяют не только разработчики парсеров, но и команды, которые автоматизируют проверку рекламных кабинетов Facebook Ads, мониторят цены на Ozon или собирают данные с десятков сайтов одновременно. Без очереди и грамотного распределения по IP любая такая система обречена на блокировки в первый же день работы.
Проблема лимита запросов на один IP
У большинства сайтов и API есть защита от чрезмерной нагрузки. Она может выражаться по-разному: сервер возвращает HTTP 429 (Too Many Requests), показывает капчу, временно банит IP на 10-60 минут или навсегда добавляет его в чёрный список. У разных платформ лимиты сильно отличаются — например, Wildberries API пропускает ограниченное число запросов в секунду с одного адреса, а Instagram реагирует на подозрительную активность уже после 15-20 действий подряд.
Главная ошибка при построении очереди — запускать всех воркеров через один и тот же IP. Даже если задачи распределены между 20 воркерами, но все они используют один прокси или вообще его не используют, лимит на этот IP наступит так же быстро, как при последовательном выполнении. Поэтому количество воркеров должно соотноситься с количеством доступных IP-адресов в пуле прокси.
Для задач с высокой частотой запросов на один домен лучше использовать прокси дата-центров — они дают низкую задержку и подходят для масштабного параллельного парсинга, где важна скорость, а не маскировка под обычного пользователя. Если же цель — обход более жёсткой защиты вроде антибот-систем маркетплейсов или соцсетей, нужны IP, похожие на обычных пользователей, то есть резидентные или мобильные.
Celery: архитектура и ротация прокси в коде
Celery — самая зрелая и популярная очередь задач в экосистеме Python. Она работает поверх брокера (чаще всего Redis или RabbitMQ) и поддерживает гибкую настройку конкурентности, повторных попыток и приоритетов задач. Для ротации прокси удобно хранить пул IP в Redis и назначать прокси каждой задаче при постановке в очередь.
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 через Redis, чтобы не бить по одному 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("Rate limited"))
return response.status_code
except requests.exceptions.RequestException as exc:
raise self.retry(exc=exc)
Ключевой момент — функция get_proxy(), которая через Redis-счётчик выдаёт следующий прокси из пула по кругу. Это гарантирует, что нагрузка равномерно распределяется между всеми IP, а не концентрируется на одном. Параметр max_retries и обработка кода 429 позволяют задаче автоматически переотправляться с другим прокси, если текущий IP получил ограничение.
Для контроля конкурентности на уровне воркеров Celery позволяет ограничивать количество одновременных задач через параметр --concurrency при запуске воркера, а также через очереди с разным приоритетом (например, отдельная очередь для медленных задач с жёстким rate limit).
RQ (Redis Queue): простая альтернатива
RQ — более лёгкая библиотека, чем Celery, без брокера RabbitMQ и с минимальной конфигурацией. Она подходит, если проект небольшой и не требует сложной маршрутизации задач, а нужна просто очередь с воркерами и возможностью привязать прокси к задаче.
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:
# Повторная постановка в очередь с задержкой
q.enqueue_in(60, fetch_with_proxy, url)
return None
return response.status_code
# Постановка задач в очередь
urls = ["https://example.com/page1", "https://example.com/page2"]
for url in urls:
q.enqueue(fetch_with_proxy, url)
В RQ ротация через itertools.cycle работает внутри процесса воркера — это проще, чем Redis-счётчик, но хуже масштабируется при множестве параллельных воркеров, потому что каждый воркер начинает цикл с начала. Для небольших проектов с 1-5 воркерами этого достаточно, для более крупных лучше вернуться к централизованному счётчику в Redis, как в примере с Celery.
RQ хорошо подходит разработчикам, которые уже используют Redis в проекте и не хотят поднимать отдельный брокер сообщений. Минус — меньше встроенных механизмов приоритизации и мониторинга по сравнению с Celery, поэтому для production-систем с сотнями тысяч задач в день чаще выбирают именно Celery.
BullMQ: очередь на Node.js с ограничением по IP
BullMQ — стандарт для очередей задач в экосистеме Node.js, построен на Redis и имеет встроенный rate limiter, что особенно удобно для контроля лимита запросов на IP. Вместо того чтобы вручную считать запросы, можно указать лимитер прямо на уровне очереди.
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 });
// Лимитер: не более 5 задач в секунду на очередь
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('Rate limited, retrying with another 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 },
});
}
Параметр limiter в BullMQ — это встроенный механизм, который ограничивает общее число обработанных задач за промежуток времени, что удобно для соблюдения лимита запросов на уровне всей системы. А параметр concurrency, равный размеру пула прокси, гарантирует, что одновременно работает ровно столько воркеров, сколько есть свободных IP.
Такой подход часто используют команды, которые пишут собственных ботов для автоматизации действий в TikTok или Telegram — BullMQ хорошо интегрируется с остальным Node.js стеком и не требует переключения на Python ради одной задачи.
Сравнение Celery, RQ и BullMQ
| Параметр | Celery | RQ | BullMQ |
|---|---|---|---|
| Язык | Python | Python | Node.js |
| Брокер | Redis / RabbitMQ | Только Redis | Только Redis |
| Встроенный rate limiter | Нет (пишется вручную) | Нет | Да, из коробки |
| Порог входа | Средний | Низкий | Средний |
| Подходит для | Крупные production-системы | Небольшие проекты, прототипы | Node.js-стек, боты, автоматизация |
Стратегии распределения нагрузки по IP
Round-robin, который используется в примерах выше, — самая простая стратегия, но не всегда оптимальная. Вот несколько вариантов, которые стоит рассмотреть в зависимости от задачи:
- Round-robin — каждый следующий запрос идёт через следующий прокси по кругу. Подходит, когда все IP равнозначны и лимиты на них одинаковые.
- Sticky session — один прокси закрепляется за конкретным аккаунтом или сессией на всё время работы с ним. Критично для соцсетей и маркетплейсов, где смена IP посреди сессии выглядит подозрительно.
- Weighted distribution — часть запросов направляется на более дорогие и надёжные IP (например, мобильные), часть — на дешёвые дата-центровые, в зависимости от того, какой домен обрабатывается.
- Adaptive throttling — скорость отправки задач автоматически снижается, если растёт процент ошибок 429 или капч, и восстанавливается, когда ошибки пропадают.
Для задач, где важна маскировка под живого пользователя — например, прогрев и проверка аккаунтов в Instagram или TikTok через антидетект-браузеры вроде Dolphin Anty или AdsPower — лучше применять sticky session с мобильными прокси: они реже попадают под подозрение антибот-систем и ведут себя как обычные пользователи мобильного интернета.
Для массового сбора данных с сайтов, где важна скорость и объём, а не доверие алгоритма, round-robin с резидентными прокси даёт баланс между стабильностью и ценой: IP выглядят как настоящие пользователи, но их можно менять часто без риска для одной конкретной сессии.
Мониторинг ошибок 429 и банов
Очередь задач без мониторинга — это чёрный ящик: сложно понять, почему часть задач проваливается и какие IP уже находятся под подозрением. Минимальный набор метрик, который стоит отслеживать:
- Процент ответов с кодом 429 и 403 по каждому прокси отдельно
- Среднее время ответа — рост задержки часто предшествует бану
- Количество повторных попыток (retries) на задачу
- Длина очереди задач в реальном времени — если она растёт, воркеры не справляются
В Celery для этого удобно использовать Flower — веб-интерфейс для мониторинга задач и воркеров в реальном времени. В BullMQ есть Bull Board с аналогичным функционалом. Для RQ существует RQ Dashboard. Все три инструмента показывают статус задач, количество неудачных попыток и позволяют быстро понять, какой прокси из пула стоит временно исключить из ротации.
Хорошая практика — автоматически помечать прокси как "в карантине" на 15-30 минут после нескольких подряд идущих ошибок 429 с этого IP, и возвращать его в пул только после успешного тестового запроса. Это предотвращает ситуацию, когда забаненный IP продолжает получать задачи и тратить впустую время воркеров.
Заключение
Очередь задач решает проблему масштабирования запросов, но сама по себе не защищает от лимита на IP — для этого нужна продуманная ротация прокси внутри Celery, RQ или BullMQ. Выбор инструмента зависит от стека: Python-проекты чаще берут Celery для production и RQ для простых задач, Node.js-разработчики — BullMQ с его встроенным rate limiter.
Независимо от выбранной очереди, результат сильно зависит от качества самого пула прокси. Если задача — быстрый массовый парсинг, присмотритесь к прокси дата-центров для скорости, а для работы с соцсетями и маркетплейсами, где важна лояльность антибот-систем, подойдут резидентные или мобильные IP — они снижают процент ошибок 429 и бана, что напрямую сокращает число повторных попыток и ускоряет выполнение всей очереди задач.