Когда вы парсите тысячи страниц маркетплейсов, отправляете массовые 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%).
| Платформа | Лимит запросов | Рекомендуемая настройка |
|---|---|---|
| ~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([