← Назад к блогу

Retry-логика парсера: как повторные запросы съедают до 40% трафика и как это исправить

Повторные запросы могут съедать до 40% трафика прокси. Показываем, как считать реальные потери и сокращать их через правильный backoff, circuit breaker и умную ротацию прокси.

📅27 сентября 2026 г.

Если счёт за прокси растёт быстрее, чем объём собранных данных, — проблема почти всегда в retry-логике. Парсер молча повторяет неудачные запросы по несколько раз, тратит трафик на таймауты и капчи, а разработчик даже не видит эти расходы в логах. Разберём, как посчитать реальные потери и сократить их без потери качества данных.

Почему retry-логика съедает трафик

Большинство парсеров написаны с наивной retry-логикой: если запрос не прошёл — повторить, и так до 3-5 раз. Проблема в том, что каждый повторный запрос — это не только новый HTTP-запрос, но и полный цикл: TCP-хендшейк, TLS-negotiation, загрузка страницы целиком (даже если нужен только один блок данных), а иногда и повторная загрузка изображений или JS-файлов, если парсер использует headless-браузер вместо простого HTTP-клиента.

Особенно дорого обходятся повторы при работе через резидентные прокси, где трафик тарифицируется по объёму, а не по количеству запросов. Один неудачный запрос на страницу товара Wildberries с картинками и скриптами может стоить 300-500 КБ. Если парсер делает 3 повтора при таймауте, вы платите за один и тот же неудачный запрос четыре раза подряд — и это без гарантии, что четвёртая попытка окажется успешной.

Вторая причина — retry по ошибкам, которые нельзя исправить повтором. Если сайт вернул 403 из-за обнаружения бота, повторный запрос с тем же fingerprint и с той же сессии cookies почти наверняка получит тот же ответ. Парсер тратит трафик на попытки, которые математически не могут закончиться успехом, пока не изменится IP-адрес или отпечаток браузера.

Сколько трафика реально уходит на повторы

Чтобы понять масштаб проблемы, возьмём простой пример. Парсер собирает карточки товаров с маркетплейса, средний размер ответа — 250 КБ (HTML + JSON API + частично статика). При стабильной работе без блокировок доля неудачных запросов держится на уровне 5-8%. Но при агрессивном парсинге через дешёвые прокси дата-центров этот показатель может подниматься до 25-35%, потому что провайдер-цель быстро распознаёт паттерн и начинает возвращать капчи или временные баны по IP.

Посчитаем на цифрах. Допустим, нужно собрать 100 000 карточек товаров:

Доля неудачных запросов Повторов на 1 запрос (в среднем) Итоговый трафик Перерасход
5% 0.15 28.75 ГБ +15%
15% 0.45 36.25 ГБ +45%
30% 0.90 47.5 ГБ +90%

Как видно, при доле сбоев 30% и retry-стратегии «повторить до 3 раз» реальный трафик почти удваивается относительно теоретического минимума в 25 ГБ. Именно эти лишние 20+ ГБ — прямые потери бюджета на прокси, которые можно сократить, если пересмотреть логику повторов.

Типичные ошибки retry-логики парсеров

Прежде чем чинить retry-логику, стоит распознать типичные антипаттерны, которые встречаются в 90% самописных парсеров:

  • Retry без разбора кода ошибки. Повтор запускается на любую нештатную ситуацию — 403, 429, 500, таймаут, разрыв соединения — хотя стратегия обработки для них должна отличаться.
  • Фиксированная задержка между повторами. Например, 2 секунды между попытками независимо от того, первая это попытка или пятая — это либо слишком агрессивно для сайта, либо слишком медленно для больших объёмов.
  • Повтор с тем же IP и той же сессией. Если сайт заблокировал запрос по фингерпринту, повтор с идентичными параметрами не меняет исход, но тратит трафик.
  • Нет верхнего предела попыток. Некоторые парсеры зацикливаются на «мёртвых» URL и делают десятки повторов, прежде чем сдаться.
  • Отсутствие различия между временными и постоянными ошибками. 404 (страница не существует) и 503 (сервер временно недоступен) требуют разной логики — но часто обрабатываются одинаково.

Экспоненциальный backoff с кодом на Python

Простое, но эффективное решение — экспоненциальная задержка с джиттером (случайным разбросом), которая уменьшает количество бессмысленных повторов и распределяет нагрузку во времени. Вместо фиксированной паузы между попытками задержка растёт экспоненциально, что даёт сайту время «остыть» после блокировки, а самому парсеру — не тратить трафик на запросы, которые почти наверняка провалятся.

import time
import random
import requests

def fetch_with_backoff(url, proxies, max_retries=4, base_delay=1.0):
    retryable_codes = {429, 500, 502, 503, 504}
    non_retryable_codes = {404, 410}

    for attempt in range(max_retries + 1):
        try:
            response = requests.get(url, proxies=proxies, timeout=10)

            if response.status_code == 200:
                return response

            if response.status_code in non_retryable_codes:
                # нет смысла повторять — страница физически не существует
                return None

            if response.status_code not in retryable_codes:
                return None

        except (requests.exceptions.Timeout,
                requests.exceptions.ConnectionError):
            pass  # временная сетевая ошибка — можно повторить

        if attempt == max_retries:
            return None

        # экспоненциальная задержка с джиттером
        delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
        time.sleep(delay)

    return None

Ключевая идея этого кода — разделение ошибок на три категории: те, что нельзя исправить повтором (404, 410), те, что можно повторить с задержкой (429, 500-504, таймауты), и всё остальное, что сразу считается провалом без трат на дополнительные попытки. Такое разделение само по себе снижает лишний трафик на 20-30% по сравнению с наивным «повторить всё подряд».

Совет: добавляйте Retry-After заголовок в обработку — многие сайты сами подсказывают, сколько секунд подождать перед повтором. Игнорирование этого заголовка — частая причина лишних банов и трафика.

Circuit breaker: когда стоит остановиться

Экспоненциальный backoff помогает на уровне одного запроса, но не защищает от ситуации, когда весь домен или конкретный прокси-узел временно недоступен для сотен URL подряд. Здесь нужен паттерн circuit breaker — "автоматический выключатель", который отслеживает долю ошибок за последний период и временно прекращает попытки, если она превышает порог, вместо того чтобы продолжать долбить в закрытую дверь.

class CircuitBreaker:
    def __init__(self, failure_threshold=0.5, window_size=50, cooldown=60):
        self.failure_threshold = failure_threshold
        self.window_size = window_size
        self.cooldown = cooldown
        self.results = []
        self.open_until = 0

    def is_open(self):
        return time.time() < self.open_until

    def record(self, success: bool):
        self.results.append(success)
        if len(self.results) > self.window_size:
            self.results.pop(0)

        if len(self.results) == self.window_size:
            failure_rate = 1 - sum(self.results) / self.window_size
            if failure_rate > self.failure_threshold:
                self.open_until = time.time() + self.cooldown
                self.results.clear()

Логика простая: если из последних 50 запросов больше половины провалились, парсер приостанавливает попытки на этот домен или прокси на 60 секунд. За это время можно сменить IP, снизить скорость запросов или переключиться на другой пул прокси. Это особенно важно при работе с целевыми сайтами, которые временно банят диапазон IP после превышения частоты запросов — продолжать биться в закрытую дверь означает просто сжигать трафик впустую.

Умная ротация прокси при повторах

Одна из самых эффективных мер против лишних retry — не повторять запрос с того же IP, который уже получил отказ. Логика проста: если ошибка связана с блокировкой по IP (403, 429, редирект на капчу), смена прокси перед повтором кардинально повышает шанс успеха и снижает число попыток.

Для парсинга маркетплейсов типа Wildberries, Ozon или Авито хорошо работает связка: обычные запросы идут через прокси дата-центров — они быстрее и дешевле, а как только детектор блокировки срабатывает несколько раз подряд, парсер переключается на резидентные прокси, которые реже попадают под фильтры антибот-систем. Такой гибридный подход снижает общий расход трафика, потому что дорогие резидентные IP используются только там, где это действительно нужно, а не для всех запросов подряд.

Тип ошибки Стратегия повтора Смена IP нужна?
Таймаут соединения Backoff, 1-2 повтора Нет
403 / капча Немедленная ротация Да, обязательно
429 (rate limit) Backoff по Retry-After Желательно
500-503 Backoff, 2-3 повтора Нет
404 / 410 Без повторов —

Для парсеров, которые эмулируют мобильный трафик (например, сбор данных из мобильных версий приложений маркетплейсов или соцсетей), имеет смысл использовать мобильные прокси — они реже вызывают подозрение у систем защиты именно потому, что операторы связи выдают одни и те же IP тысячам реальных пользователей одновременно, и точечная блокировка становится нецелесообразной для сайта-цели.

Мониторинг retry-метрик

Без метрик оптимизация retry-логики превращается в гадание. Минимальный набор показателей, которые стоит логировать при каждом запросе:

  • Retry rate — доля запросов, потребовавших хотя бы один повтор.
  • Success after retry — какой процент повторов в итоге завершился успехом (если этот показатель низкий, повторы просто сжигают трафик).
  • Трафик на успешный результат — общий объём переданных данных, делённый на число успешно собранных записей. Это ключевая метрика эффективности.
  • Распределение ошибок по кодам — помогает понять, где происходит основная утечка трафика: таймауты, 403, 429 или что-то ещё.
  • Retry rate по конкретным прокси-узлам — если один IP даёт retry rate 80%, а остальные 10%, проблема локальная и решается сменой конкретного узла, а не всей логики.

Даже простая таблица в Google Sheets или лог в CSV с этими пятью метриками, обновляемая раз в час, даёт достаточно данных, чтобы увидеть аномалии и вовремя скорректировать стратегию — например, снизить частоту запросов к конкретному разделу сайта или увеличить долю резидентных IP в пуле.

Чек-лист оптимизации трафика на retry

  1. Разделите коды ошибок на retryable и non-retryable — не повторяйте 404/410.
  2. Внедрите экспоненциальный backoff с джиттером вместо фиксированной задержки.
  3. Уважайте заголовок Retry-After, если сайт его присылает.
  4. Меняйте IP перед повтором при 403 и подозрении на детект бота.
  5. Установите жёсткий лимит на число попыток (обычно 3-4 достаточно).
  6. Внедрите circuit breaker для доменов и прокси-узлов с высоким failure rate.
  7. Логируйте retry rate и трафик на успешный результат — без метрик оптимизация невозможна.
  8. Разделяйте пул прокси: дешёвые дата-центры для стабильных участков, резидентные или мобильные IP — для проблемных.

Заключение

Retry-логика — это не мелкая деталь парсера, а один из главных факторов, влияющих на стоимость сбора данных. Наивная стратегия «повторить всё подряд» может увеличивать реальный трафик на 40-90% по сравнению с теоретическим минимумом, при этом большая часть повторов заканчивается тем же провалом, что и первая попытка. Разделение ошибок по типам, экспоненциальный backoff, circuit breaker и умная ротация IP позволяют сократить эти потери в разы без снижения полноты собираемых данных.

Если ваш парсер работает с сайтами, которые агрессивно детектируют ботов — маркетплейсами, соцсетями, рекламными платформами — стоит комбинировать несколько типов прокси в зависимости от задачи. Для базовых операций подойдут прокси дата-центров, а там, где нужна максимальная устойчивость к блокировкам, — резидентные прокси с реальными IP-адресами обычных пользователей. Такой гибридный подход в связке с грамотной retry-логикой даёт заметное снижение расхода трафика при том же объёме собранных данных.