Назад к блогу

Load balancing через прокси: как масштабировать парсинг и API-запросы до 10000+ RPS

Полное руководство по распределению нагрузки через прокси-пулы для парсинга маркетплейсов, работы с API и автоматизации: архитектура, алгоритмы балансировки и практические примеры настройки.

📅7 февраля 2026 г.

Когда вы парсите тысячи страниц маркетплейсов, отправляете массовые API-запросы или автоматизируете работу с сотнями аккаунтов, правильное распределение нагрузки через прокси становится критически важным. Без грамотной балансировки вы столкнётесь с блокировками, таймаутами и падением производительности. В этом руководстве разберём архитектуру распределения нагрузки, алгоритмы балансировки и практические стратегии для высоконагруженных систем.

Материал рассчитан на разработчиков и технических специалистов, которые работают с парсингом данных, автоматизацией API-запросов или управляют большими пулами прокси для бизнес-задач.

Зачем нужна балансировка нагрузки через прокси

Балансировка нагрузки через прокси решает несколько критических проблем высоконагруженных систем. Первая и главная — защита от блокировок. Когда вы отправляете тысячи запросов к одному ресурсу (маркетплейс, API социальной сети, поисковая система), целевой сервер видит аномально высокую активность с одного IP-адреса и блокирует его. Распределение запросов между десятками или сотнями прокси делает вашу активность похожей на действия обычных пользователей.

Вторая проблема — производительность. Один прокси-сервер имеет ограниченную пропускную способность (обычно 100-1000 Мбит/с) и может обрабатывать ограниченное количество одновременных соединений. При парсинге 10000 страниц в минуту или массовой отправке API-запросов один прокси станет узким местом системы. Балансировка позволяет горизонтально масштабировать пропускную способность, добавляя новые прокси в пул.

Третья задача — надёжность. Если один из прокси выходит из строя (технический сбой, блокировка, истечение срока аренды), система автоматически перенаправляет трафик на работающие прокси. Без механизма балансировки и health checks отказ одного прокси может остановить всю систему.

Реальный пример: При парсинге Wildberries с целью мониторинга цен конкурентов вам нужно обрабатывать 50000 товаров каждый час. Это примерно 14 запросов в секунду. Один прокси выдержит такую нагрузку, но Wildberries заблокирует IP после 100-200 запросов с одного адреса. Распределяя запросы между 20 резидентными прокси, вы снижаете нагрузку на каждый IP до 0.7 запросов в секунду — это выглядит как активность обычного пользователя.

Четвёртая причина — географическое распределение. Многие сервисы показывают разный контент или цены в зависимости от региона пользователя. Балансировка между прокси из разных стран и городов позволяет одновременно собирать данные из всех целевых регионов. Например, для мониторинга цен на Ozon вам нужны прокси из Москвы, Санкт-Петербурга, Екатеринбурга и других крупных городов.

Архитектура системы распределения нагрузки

Классическая архитектура системы балансировки нагрузки через прокси состоит из нескольких компонентов. На верхнем уровне находится балансировщик (load balancer) — программный модуль, который принимает входящие задачи (запросы на парсинг, API-вызовы) и распределяет их между доступными прокси. Балансировщик может работать как отдельный сервис или быть встроенным в приложение.

Второй компонент — менеджер пула прокси (proxy pool manager). Он хранит список всех доступных прокси с их характеристиками: IP-адрес, порт, протокол (HTTP/SOCKS5), географическое расположение, тип (резидентный, мобильный, дата-центр), текущее состояние (активен, заблокирован, на проверке). Менеджер пула отвечает за добавление новых прокси, удаление неработающих и периодическую проверку доступности.

Третий элемент — система мониторинга и health checks. Она постоянно проверяет работоспособность каждого прокси: отправляет тестовые запросы, измеряет время отклика, проверяет успешность соединения. Если прокси не отвечает или возвращает ошибки, система помечает его как недоступный и временно исключает из ротации.

Компонент Функция Технологии
Load Balancer Распределение запросов между прокси HAProxy, Nginx, Python/Node.js библиотеки
Proxy Pool Manager Управление списком прокси Redis, PostgreSQL, in-memory хранилища
Health Check System Проверка доступности прокси Scheduled tasks, Celery, cron
Rate Limiter Контроль частоты запросов Token bucket, leaky bucket алгоритмы
Monitoring & Metrics Сбор метрик производительности Prometheus, Grafana, ELK Stack

Четвёртый компонент — rate limiter (ограничитель частоты запросов). Он следит за тем, чтобы каждый прокси не превышал допустимую частоту обращений к целевому ресурсу. Например, если вы парсите Instagram и знаете, что платформа блокирует IP после 60 запросов в минуту, rate limiter не позволит отправить через один прокси больше 50 запросов в минуту, оставляя запас безопасности.

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

Алгоритмы балансировки: Round Robin, Least Connections, Weighted

Алгоритм Round Robin — самый простой и распространённый метод балансировки. Он последовательно перебирает прокси из списка: первый запрос идёт через прокси №1, второй через прокси №2, третий через прокси №3, и так далее. Когда список заканчивается, балансировщик возвращается к началу. Этот алгоритм равномерно распределяет нагрузку, если все прокси имеют одинаковую производительность.

class RoundRobinBalancer:
    def __init__(self, proxies):
        self.proxies = proxies
        self.current_index = 0
    
    def get_next_proxy(self):
        proxy = self.proxies[self.current_index]
        self.current_index = (self.current_index + 1) % len(self.proxies)
        return proxy

# Использование
proxies = [
    "http://proxy1.example.com:8080",
    "http://proxy2.example.com:8080",
    "http://proxy3.example.com:8080"
]

balancer = RoundRobinBalancer(proxies)
for i in range(10):
    proxy = balancer.get_next_proxy()
    print(f"Request {i+1} → {proxy}")

Алгоритм Least Connections (наименьшее количество соединений) выбирает прокси с минимальным количеством активных соединений в текущий момент. Это полезно, когда запросы имеют разную длительность выполнения. Например, при парсинге один запрос может обрабатываться 100 мс, а другой — 5 секунд (если страница загружается медленно). Least Connections автоматически направляет новые запросы на менее загруженные прокси.

class LeastConnectionsBalancer:
    def __init__(self, proxies):
        self.proxies = {proxy: 0 for proxy in proxies}
    
    def get_next_proxy(self):
        # Выбираем прокси с минимальным количеством соединений
        proxy = min(self.proxies.items(), key=lambda x: x[1])[0]
        self.proxies[proxy] += 1
        return proxy
    
    def release_proxy(self, proxy):
        # Уменьшаем счётчик при завершении запроса
        self.proxies[proxy] -= 1

# Использование с контекстным менеджером
balancer = LeastConnectionsBalancer(proxies)

def make_request(url):
    proxy = balancer.get_next_proxy()
    try:
        # Выполняем запрос через выбранный прокси
        response = requests.get(url, proxies={"http": proxy})
        return response
    finally:
        balancer.release_proxy(proxy)

Weighted Round Robin (взвешенный циклический перебор) расширяет классический Round Robin, присваивая каждому прокси вес в зависимости от его производительности или качества. Прокси с большим весом получают больше запросов. Это полезно, когда у вас есть прокси разного качества: например, премиум резидентные прокси с высокой скоростью и дешёвые прокси дата-центров с ограничениями.

class WeightedRoundRobinBalancer:
    def __init__(self, weighted_proxies):
        # weighted_proxies = [(proxy, weight), ...]
        self.proxies = []
        for proxy, weight in weighted_proxies:
            # Добавляем прокси в список weight раз
            self.proxies.extend([proxy] * weight)
        self.current_index = 0
    
    def get_next_proxy(self):
        proxy = self.proxies[self.current_index]
        self.current_index = (self.current_index + 1) % len(self.proxies)
        return proxy

# Использование с весами
weighted_proxies = [
    ("http://premium-proxy1.com:8080", 5),  # высокое качество
    ("http://premium-proxy2.com:8080", 5),
    ("http://cheap-proxy1.com:8080", 2),    # низкое качество
    ("http://cheap-proxy2.com:8080", 1)     # очень низкое качество
]

balancer = WeightedRoundRobinBalancer(weighted_proxies)

Алгоритм Random (случайный выбор) выбирает прокси случайным образом из списка доступных. Он проще в реализации, чем Round Robin, и хорошо работает при большом количестве прокси (100+). Случайное распределение автоматически выравнивается при достаточном объёме запросов. Недостаток — возможны короткие периоды неравномерной нагрузки.

IP Hash — алгоритм, который выбирает прокси на основе хеша целевого URL или другого параметра. Это гарантирует, что запросы к одному и тому же ресурсу всегда идут через один и тот же прокси. Полезно для сайтов, которые используют сессии или требуют последовательности запросов с одного IP (например, авторизация + получение данных).

Управление пулами прокси: ротация и health checks

Эффективное управление пулом прокси требует постоянного мониторинга состояния каждого прокси и автоматической ротации. Health check — это периодическая проверка доступности прокси путём отправки тестового запроса. Обычно используется простой GET-запрос к надёжному сервису (например, httpbin.org или собственному эндпоинту), который возвращает информацию о IP-адресе.

import requests
import time
from datetime import datetime

class ProxyHealthChecker:
    def __init__(self, test_url="http://httpbin.org/ip", timeout=10):
        self.test_url = test_url
        self.timeout = timeout
    
    def check_proxy(self, proxy_url):
        """Проверяет работоспособность прокси"""
        try:
            start_time = time.time()
            response = requests.get(
                self.test_url,
                proxies={"http": proxy_url, "https": proxy_url},
                timeout=self.timeout
            )
            response_time = time.time() - start_time
            
            if response.status_code == 200:
                return {
                    "status": "healthy",
                    "response_time": response_time,
                    "timestamp": datetime.now(),
                    "ip": response.json().get("origin")
                }
            else:
                return {
                    "status": "unhealthy",
                    "error": f"HTTP {response.status_code}",
                    "timestamp": datetime.now()
                }
        except requests.exceptions.Timeout:
            return {
                "status": "unhealthy",
                "error": "timeout",
                "timestamp": datetime.now()
            }
        except Exception as e:
            return {
                "status": "unhealthy",
                "error": str(e),
                "timestamp": datetime.now()
            }

# Использование
checker = ProxyHealthChecker()
proxies = ["http://proxy1.com:8080", "http://proxy2.com:8080"]

for proxy in proxies:
    result = checker.check_proxy(proxy)
    print(f"{proxy}: {result['status']} ({result.get('response_time', 'N/A')}s)")

Система управления пулом должна автоматически помечать неработающие прокси как недоступные и периодически повторять проверку. Распространённая стратегия — circuit breaker pattern: после трёх неудачных проверок подряд прокси исключается из пула на 5-10 минут, затем проверяется снова. Если проверка успешна, прокси возвращается в активный пул.

class ProxyPoolManager:
    def __init__(self, health_checker, max_failures=3, cooldown_seconds=300):
        self.health_checker = health_checker
        self.max_failures = max_failures
        self.cooldown_seconds = cooldown_seconds
        
        self.proxies = {}  # {proxy_url: ProxyInfo}
    
    def add_proxy(self, proxy_url, metadata=None):
        """Добавляет прокси в пул"""
        self.proxies[proxy_url] = {
            "url": proxy_url,
            "status": "active",
            "failures": 0,
            "last_check": None,
            "cooldown_until": None,
            "metadata": metadata or {}
        }
    
    def get_active_proxies(self):
        """Возвращает список активных прокси"""
        now = datetime.now()
        active = []
        
        for proxy_url, info in self.proxies.items():
            # Проверяем, не в cooldown ли прокси
            if info["cooldown_until"] and now < info["cooldown_until"]:
                continue
            
            if info["status"] == "active":
                active.append(proxy_url)
        
        return active
    
    def mark_failure(self, proxy_url):
        """Отмечает неудачную попытку использования прокси"""
        if proxy_url not in self.proxies:
            return
        
        info = self.proxies[proxy_url]
        info["failures"] += 1
        
        if info["failures"] >= self.max_failures:
            # Переводим в cooldown
            info["status"] = "cooldown"
            info["cooldown_until"] = datetime.now() + timedelta(seconds=self.cooldown_seconds)
            print(f"Proxy {proxy_url} moved to cooldown until {info['cooldown_until']}")
    
    def mark_success(self, proxy_url):
        """Отмечает успешное использование прокси"""
        if proxy_url not in self.proxies:
            return
        
        info = self.proxies[proxy_url]
        info["failures"] = 0
        info["status"] = "active"
        info["cooldown_until"] = None

Ротация прокси — это стратегия автоматической смены прокси через определённые интервалы или после определённого количества запросов. Существует несколько подходов: ротация по времени (смена каждые 5-10 минут), ротация по количеству запросов (смена после 100-500 запросов), ротация по сессиям (один прокси на одну сессию парсинга). Выбор стратегии зависит от требований целевого сайта.

Совет: Для парсинга маркетплейсов (Wildberries, Ozon) оптимальна ротация по количеству запросов: 50-100 запросов на один прокси, затем смена. Для работы с API социальных сетей (Instagram, Facebook) лучше использовать ротацию по сессиям: один прокси на весь цикл авторизация → действия → выход.

Rate limiting и контроль частоты запросов

Rate limiting — критически важный компонент системы балансировки нагрузки. Он предотвращает превышение допустимой частоты запросов к целевому ресурсу, что могло бы привести к блокировке прокси. Существует два основных алгоритма: Token Bucket (ведро токенов) и Leaky Bucket (дырявое ведро).

Token Bucket работает следующим образом: у каждого прокси есть виртуальное "ведро" с токенами. Каждый токен даёт право на один запрос. Ведро постепенно наполняется токенами с заданной скоростью (например, 10 токенов в секунду). Максимальная вместимость ведра ограничена (например, 50 токенов). Когда приходит запрос, система проверяет наличие токенов: если есть — забирает один токен и выполняет запрос, если нет — ждёт появления нового токена.

import time
from threading import Lock

class TokenBucketRateLimiter:
    def __init__(self, rate, capacity):
        """
        rate: количество токенов в секунду
        capacity: максимальное количество токенов в ведре
        """
        self.rate = rate
        self.capacity = capacity
        self.tokens = capacity
        self.last_update = time.time()
        self.lock = Lock()
    
    def _add_tokens(self):
        """Добавляет токены на основе прошедшего времени"""
        now = time.time()
        elapsed = now - self.last_update
        new_tokens = elapsed * self.rate
        
        self.tokens = min(self.capacity, self.tokens + new_tokens)
        self.last_update = now
    
    def acquire(self, tokens=1):
        """Пытается получить указанное количество токенов"""
        with self.lock:
            self._add_tokens()
            
            if self.tokens >= tokens:
                self.tokens -= tokens
                return True
            return False
    
    def wait_and_acquire(self, tokens=1):
        """Ждёт появления токенов и получает их"""
        while not self.acquire(tokens):
            # Вычисляем время ожидания
            wait_time = (tokens - self.tokens) / self.rate
            time.sleep(wait_time)

# Использование для каждого прокси
proxy_limiters = {
    "http://proxy1.com:8080": TokenBucketRateLimiter(rate=10, capacity=50),
    "http://proxy2.com:8080": TokenBucketRateLimiter(rate=10, capacity=50)
}

def make_request_with_limit(url, proxy_url):
    limiter = proxy_limiters[proxy_url]
    limiter.wait_and_acquire()  # Ждём доступности токена
    
    response = requests.get(url, proxies={"http": proxy_url})
    return response

Leaky Bucket (дырявое ведро) работает иначе: запросы помещаются в очередь (ведро), которая "вытекает" с постоянной скоростью. Если ведро переполняется, новые запросы отклоняются или ставятся в ожидание. Этот алгоритм обеспечивает более равномерное распределение нагрузки во времени, но может приводить к задержкам при всплесках активности.

Для разных целевых платформ требуются разные лимиты. Instagram API допускает примерно 200 запросов в час на один IP (около 3.3 запроса в минуту). Wildberries блокирует после 100-200 запросов в минуту с одного IP. Google Search допускает 10-20 запросов в минуту. Ваша система rate limiting должна учитывать эти ограничения и добавлять запас безопасности (обычно 20-30%).

Платформа Лимит запросов Рекомендуемая настройка
Instagram ~200 запросов/час 2-3 запроса/минуту (с запасом)
Wildberries 100-200 запросов/минуту 60-80 запросов/минуту
Google Search 10-20 запросов/минуту 8-12 запросов/минуту
Ozon 50-100 запросов/минуту 30-50 запросов/минуту
Facebook API 200 запросов/час 2-3 запроса/минуту

Мониторинг производительности и автоматическое масштабирование

Эффективная система балансировки нагрузки требует постоянного мониторинга ключевых метрик. Первая группа метрик — производительность прокси: среднее время отклика (response time), процент успешных запросов (success rate), количество таймаутов, количество ошибок 4xx и 5xx. Эти данные позволяют выявить проблемные прокси и оптимизировать алгоритмы балансировки.

class ProxyMetrics:
    def __init__(self):
        self.metrics = {}  # {proxy_url: metrics_dict}
    
    def record_request(self, proxy_url, response_time, status_code, success):
        """Записывает метрики запроса"""
        if proxy_url not in self.metrics:
            self.metrics[proxy_url] = {
                "total_requests": 0,
                "successful_requests": 0,
                "failed_requests": 0,
                "total_response_time": 0,
                "timeouts": 0,
                "errors_4xx": 0,
                "errors_5xx": 0
            }
        
        m = self.metrics[proxy_url]
        m["total_requests"] += 1
        
        if success:
            m["successful_requests"] += 1
            m["total_response_time"] += response_time
        else:
            m["failed_requests"] += 1
            
            if status_code == 0:  # timeout
                m["timeouts"] += 1
            elif 400 <= status_code < 500:
                m["errors_4xx"] += 1
            elif 500 <= status_code < 600:
                m["errors_5xx"] += 1
    
    def get_success_rate(self, proxy_url):
        """Возвращает процент успешных запросов"""
        m = self.metrics.get(proxy_url, {})
        total = m.get("total_requests", 0)
        if total == 0:
            return 0
        return (m.get("successful_requests", 0) / total) * 100
    
    def get_avg_response_time(self, proxy_url):
        """Возвращает среднее время отклика"""
        m = self.metrics.get(proxy_url, {})
        successful = m.get("successful_requests", 0)
        if successful == 0:
            return 0
        return m.get("total_response_time", 0) / successful
    
    def get_report(self, proxy_url):
        """Возвращает полный отчёт по прокси"""
        m = self.metrics.get(proxy_url, {})
        return {
            "proxy": proxy_url,
            "total_requests": m.get("total_requests", 0),
            "success_rate": self.get_success_rate(proxy_url),
            "avg_response_time": self.get_avg_response_time(proxy_url),
            "timeouts": m.get("timeouts", 0),
            "errors_4xx": m.get("errors_4xx", 0),
            "errors_5xx": m.get("errors_5xx", 0)
        }

Вторая группа метрик — общая производительность системы: общее количество запросов в секунду (RPS — requests per second), средняя задержка (latency), размер очереди запросов, количество активных прокси, процент использования пула прокси. Эти метрики показывают, справляется ли система с нагрузкой или требуется масштабирование.

Автоматическое масштабирование (auto-scaling) позволяет динамически добавлять или удалять прокси в зависимости от нагрузки. Простая стратегия: если средняя загрузка пула превышает 80% в течение 5 минут, система автоматически добавляет новые прокси. Если загрузка падает ниже 30% в течение 15 минут, система удаляет избыточные прокси для экономии ресурсов.

Пример настройки мониторинга: Для парсинга 100000 товаров Wildberries в час (примерно 28 запросов в секунду) вам потребуется минимум 30-40 прокси при лимите 60 запросов в минуту на прокси. Настройте алерты: если success rate падает ниже 85% или средний response time превышает 3 секунды, система должна автоматически добавить 10-15 резервных прокси из пула.

Для визуализации метрик используйте Grafana с Prometheus или ELK Stack. Создайте дашборды с графиками: RPS по времени, распределение response time, топ-10 самых быстрых/медленных прокси, карта ошибок по типам. Это позволит быстро выявлять проблемы и оптимизировать систему.

Практическая реализация на Python и Node.js

Рассмотрим полную реализацию системы балансировки нагрузки на Python с использованием популярных библиотек. Этот пример объединяет все описанные компоненты: балансировщик, менеджер пула, health checks, rate limiting и мониторинг.

import requests
import time
import random
from datetime import datetime, timedelta
from threading import Lock, Thread
from collections import defaultdict

class ProxyLoadBalancer:
    def __init__(self, proxies, algorithm="round_robin", rate_limit=10):
        """
        proxies: список прокси [{"url": "...", "weight": 1}, ...]
        algorithm: round_robin, least_connections, weighted, random
        rate_limit: максимальное количество запросов в секунду на прокси
        """
        self.proxies = proxies
        self.algorithm = algorithm
        self.rate_limit = rate_limit
        
        # Состояние балансировщика
        self.current_index = 0
        self.connections = defaultdict(int)
        self.rate_limiters = {}
        self.metrics = defaultdict(lambda: {
            "total": 0, "success": 0, "failed": 0,
            "response_times": [], "last_check": None
        })
        
        # Инициализация rate limiters
        for proxy in proxies:
            proxy_url = proxy["url"]
            self.rate_limiters[proxy_url] = TokenBucketRateLimiter(
                rate=rate_limit,
                capacity=rate_limit * 5
            )
        
        self.lock = Lock()
        
        # Запуск фонового health check
        self.health_check_thread = Thread(target=self._health_check_loop, daemon=True)
        self.health_check_thread.start()
    
    def get_next_proxy(self):
        """Выбирает следующий прокси согласно алгоритму"""
        with self.lock:
            if self.algorithm == "round_robin":
                return self._round_robin()
            elif self.algorithm == "least_connections":
                return self._least_connections()
            elif self.algorithm == "weighted":
                return self._weighted_random()
            elif self.algorithm == "random":
                return random.choice(self.proxies)["url"]
    
    def _round_robin(self):
        proxy = self.proxies[self.current_index]["url"]
        self.current_index = (self.current_index + 1) % len(self.proxies)
        return proxy
    
    def _least_connections(self):
        min_conn = min(self.connections.values()) if self.connections else 0
        candidates = [p["url"] for p in self.proxies if self.connections[p["url"]] == min_conn]
        return random.choice(candidates)
    
    def _weighted_random(self):
        weights = [p.get("weight", 1) for p in self.proxies]
        return random.choices(self.proxies, weights=weights)[0]["url"]
    
    def make_request(self, url, method="GET", **kwargs):
        """Выполняет запрос через балансировщик"""
        proxy_url = self.get_next_proxy()
        
        # Ждём доступности rate limit
        self.rate_limiters[proxy_url].wait_and_acquire()
        
        # Увеличиваем счётчик соединений
        with self.lock:
            self.connections[proxy_url] += 1
        
        try:
            start_time = time.time()
            response = requests.request(
                method,
                url,
                proxies={"http": proxy_url, "https": proxy_url},
                timeout=kwargs.get("timeout", 30),
                **kwargs
            )
            response_time = time.time() - start_time
            
            # Записываем метрики
            self._record_metrics(proxy_url, response_time, True, response.status_code)
            
            return response
        
        except Exception as e:
            self._record_metrics(proxy_url, 0, False, 0)
            raise
        
        finally:
            # Уменьшаем счётчик соединений
            with self.lock:
                self.connections[proxy_url] -= 1
    
    def _record_metrics(self, proxy_url, response_time, success, status_code):
        """Записывает метрики запроса"""
        with self.lock:
            m = self.metrics[proxy_url]
            m["total"] += 1
            if success:
                m["success"] += 1
                m["response_times"].append(response_time)
                # Храним только последние 1000 значений
                if len(m["response_times"]) > 1000:
                    m["response_times"].pop(0)
            else:
                m["failed"] += 1
    
    def _health_check_loop(self):
        """Фоновая проверка работоспособности прокси"""
        while True:
            for proxy in self.proxies:
                proxy_url = proxy["url"]
                try:
                    response = requests.get(
                        "http://httpbin.org/ip",
                        proxies={"http": proxy_url},
                        timeout=10
                    )
                    with self.lock:
                        self.metrics[proxy_url]["last_check"] = datetime.now()
                        proxy["status"] = "healthy" if response.status_code == 200 else "unhealthy"
                except:
                    with self.lock:
                        proxy["status"] = "unhealthy"
            
            time.sleep(60)  # Проверка каждую минуту
    
    def get_stats(self):
        """Возвращает статистику по всем прокси"""
        stats = []
        with self.lock:
            for proxy in self.proxies:
                proxy_url = proxy["url"]
                m = self.metrics[proxy_url]
                
                avg_response_time = (
                    sum(m["response_times"]) / len(m["response_times"])
                    if m["response_times"] else 0
                )
                
                success_rate = (
                    (m["success"] / m["total"] * 100)
                    if m["total"] > 0 else 0
                )
                
                stats.append({
                    "proxy": proxy_url,
                    "status": proxy.get("status", "unknown"),
                    "total_requests": m["total"],
                    "success_rate": round(success_rate, 2),
                    "avg_response_time": round(avg_response_time, 3),
                    "active_connections": self.connections[proxy_url]
                })
        
        return stats

Использование этого балансировщика для парсинга маркетплейса:

# Настройка балансировщика
proxies = [
    {"url": "http://proxy1.example.com:8080", "weight": 5},
    {"url": "http://proxy2.example.com:8080", "weight": 5},
    {"url": "http://proxy3.example.com:8080", "weight": 3},
    {"url": "http://proxy4.example.com:8080", "weight": 2}
]

balancer = ProxyLoadBalancer(
    proxies=proxies,
    algorithm="weighted",
    rate_limit=60  # 60 запросов в минуту на прокси
)

# Парсинг списка товаров
product_urls = [f"https://www.wildberries.ru/catalog/{i}/detail.aspx" for i in range(1000)]

results = []
for url in product_urls:
    try:
        response = balancer.make_request(url)
        # Обработка ответа
        results.append({"url": url, "status": "success", "data": response.text})
    except Exception as e:
        results.append({"url": url, "status": "error", "error": str(e)})
    
    # Каждые 100 запросов выводим статистику
    if len(results) % 100 == 0:
        stats = balancer.get_stats()
        for stat in stats:
            print(f"{stat['proxy']}: {stat['success_rate']}% success, "
                  f"{stat['avg_response_time']}s avg response")

# Итоговая статистика
print("\n=== Final Statistics ===")
for stat in balancer.get_stats():
    print(f"{stat['proxy']}:")
    print(f"  Total requests: {stat['total_requests']}")
    print(f"  Success rate: {stat['success_rate']}%")
    print(f"  Avg response time: {stat['avg_response_time']}s")
    print(f"  Status: {stat['status']}")

Для Node.js можно использовать похожую архитектуру с библиотеками axios для HTTP-запросов и node-rate-limiter для контроля частоты:

const axios = require('axios');
const { RateLimiter } = require('limiter');

class ProxyLoadBalancer {
  constructor(proxies, algorithm = 'round_robin', rateLimit = 10) {
    this.proxies = proxies;
    this.algorithm = algorithm;
    this.currentIndex = 0;
    this.connections = new Map();
    this.limiters = new Map();
    this.metrics = new Map();
    
    // Инициализация rate limiters
    proxies.forEach(proxy => {
      this.limiters.set(proxy.url, new RateLimiter({ 
        tokensPerInterval: rateLimit, 
        interval: 'second' 
      }));
      this.connections.set(proxy.url, 0);
      this.metrics.set(proxy.url, {
        total: 0,
        success: 0,
        failed: 0,
        responseTimes: []
      });
    });
  }
  
  getNextProxy() {
    if (this.algorithm === 'round_robin') {
      const proxy = this.proxies[this.currentIndex].url;
      this.currentIndex = (this.currentIndex + 1) % this.proxies.length;
      return proxy;
    } else if (this.algorithm === 'least_connections') {
      let minConn = Infinity;
      let selectedProxy = null;
      
      this.connections.forEach((count, proxy) => {
        if (count < minConn) {
          minConn = count;
          selectedProxy = proxy;
        }
      });
      
      return selectedProxy;
    }
  }
  
  async makeRequest(url, options = {}) {
    const proxyUrl = this.getNextProxy();
    const limiter = this.limiters.get(proxyUrl);
    
    // Ждём доступности rate limit
    await limiter.removeTokens(1);
    
    // Увеличиваем счётчик соединений
    this.connections.set(proxyUrl, this.connections.get(proxyUrl) + 1);
    
    try {
      const startTime = Date.now();
      const response = await axios({
        url,
        proxy: this.parseProxyUrl(proxyUrl),
        timeout: options.timeout || 30000,
        ...options
      });
      
      const responseTime = (Date.now() - startTime) / 1000;
      this.recordMetrics(proxyUrl, responseTime, true);
      
      return response;
    } catch (error) {
      this.recordMetrics(proxyUrl, 0, false);
      throw error;
    } finally {
      this.connections.set(proxyUrl, this.connections.get(proxyUrl) - 1);
    }
  }
  
  parseProxyUrl(proxyUrl) {
    const url = new URL(proxyUrl);
    return {
      host: url.hostname,
      port: parseInt(url.port)
    };
  }
  
  recordMetrics(proxyUrl, responseTime, success) {
    const m = this.metrics.get(proxyUrl);
    m.total++;
    
    if (success) {
      m.success++;
      m.responseTimes.push(responseTime);
      if (m.responseTimes.length > 1000) {
        m.responseTimes.shift();
      }
    } else {
      m.failed++;
    }
  }
  
  getStats() {
    const stats = [];
    
    this.proxies.forEach(proxy => {
      const m = this.metrics.get(proxy.url);
      const avgResponseTime = m.responseTimes.length > 0
        ? m.responseTimes.reduce((a, b) => a + b, 0) / m.responseTimes.length
        : 0;
      
      const successRate = m.total > 0 ? (m.success / m.total * 100) : 0;
      
      stats.push({
        proxy: proxy.url,
        totalRequests: m.total,
        successRate: successRate.toFixed(2),
        avgResponseTime: avgResponseTime.toFixed(3),
        activeConnections: this.connections.get(proxy.url)
      });
    });
    
    return stats;
  }
}

// Использование
const proxies = [
  { url: 'http://proxy1.example.com:8080', weight: 5 },
  { url: 'http://proxy2.example.com:8080', weight: 5 }
];

const balancer = new ProxyLoadBalancer(proxies, 'round_robin', 60);

async function parseProducts() {
  const urls = Array.from({ length: 1000 }, (_, i) => 
    `https://www.wildberries.ru/catalog/${i}/detail.aspx`
  );
  
  for (const url of urls) {
    try {
      const response = await balancer.makeRequest(url);
      console.log(`Success: ${url}`);
    } catch (error) {
      console.error(`Error: ${url} - ${error.message}`);
    }
  }
  
  console.log('\n=== Statistics ===');
  console.log(balancer.getStats());
}

parseProducts();

Оптимизация для конкретных задач: парсинг, API, автоматизация

Разные задачи требуют разных стратегий балансировки нагрузки. Для парсинга маркетплейсов (Wildberries, Ozon, Авито) оптимальна стратегия с ротацией по количеству запросов и географическим распределением. Используйте резидентные прокси из разных городов России, меняйте прокси каждые 50-100 запросов, добавляйте случайные задержки между запросами (1-3 секунды) для имитации поведения человека.

Для работы с API социальных сетей (Instagram, Facebook, VK) критична стабильность IP-адреса в рамках одной сессии. Используйте алгоритм IP Hash или sticky sessions: все запросы одного аккаунта должны идти через один и тот же прокси. Это предотвращает подозрительную смену геолокации, которая может вызвать блокировку аккаунта. Рекомендуются мобильные прокси, так как социальные сети доверяют мобильным IP больше, чем резидентным или дата-центрам.

# Пример sticky sessions для Instagram
class StickySessionBalancer:
    def __init__(self, proxies):
        self.proxies = proxies
        self.session_map = {}  # {account_id: proxy_url}
        self.proxy_usage = defaultdict(int)
    
    def get_proxy_for_account(self, account_id):
        """Возвращает фиксированный прокси для аккаунта"""
        if account_id in self.session_map:
            return self.session_map[account_id]
        
        # Выбираем наименее загруженный прокси
        proxy = min(self.proxies, key=lambda p: self.proxy_usage[p])
        self.session_map[account_id] = proxy
        self.proxy_usage[proxy] += 1
        
        return proxy
    
    def release_account(self, account_id):
        """Освобождает прокси при завершении работы с аккаунтом"""
        if account_id in self.session_map:
            proxy = self.session_map[account_id]
            self.proxy_usage[proxy] -= 1
            del self.session_map[account_id]

# Использование для Instagram
balancer = StickySessionBalancer([