When scraping thousands of marketplace pages, sending mass API requests, or automating work with hundreds of accounts, proper load distribution through proxies becomes critically important. Without effective balancing, you will encounter blocks, timeouts, and performance drops. In this guide, we will explore the architecture of load distribution, balancing algorithms, and practical strategies for high-load systems.
This material is intended for developers and technical specialists who work with data scraping, API request automation, or manage large proxy pools for business tasks.
Why load balancing through proxies is needed
Load balancing through proxies addresses several critical issues of high-load systems. The first and foremost is protection against blocks. When you send thousands of requests to a single resource (marketplace, social media API, search engine), the target server sees abnormally high activity from a single IP address and blocks it. Distributing requests among dozens or hundreds of proxies makes your activity resemble that of regular users.
The second issue is performance. A single proxy server has limited bandwidth (usually 100-1000 Mbps) and can handle a limited number of simultaneous connections. When scraping 10,000 pages per minute or sending mass API requests, a single proxy will become a bottleneck in the system. Balancing allows for horizontal scaling of bandwidth by adding new proxies to the pool.
The third task is reliability. If one of the proxies fails (technical failure, block, lease expiration), the system automatically redirects traffic to working proxies. Without a balancing mechanism and health checks, the failure of one proxy can halt the entire system.
Real example: When scraping Wildberries for competitor price monitoring, you need to process 50,000 products every hour. Thatβs about 14 requests per second. One proxy can handle this load, but Wildberries will block the IP after 100-200 requests from a single address. By distributing requests among 20 residential proxies, you reduce the load on each IP to 0.7 requests per second β this looks like the activity of a regular user.
The fourth reason is geographical distribution. Many services show different content or prices depending on the user's region. Balancing among proxies from different countries and cities allows you to collect data from all target regions simultaneously. For example, to monitor prices on Ozon, you need proxies from Moscow, St. Petersburg, Yekaterinburg, and other major cities.
Architecture of the load distribution system
The classic architecture of a load balancing system through proxies consists of several components. At the top level is the load balancer β a software module that accepts incoming tasks (scraping requests, API calls) and distributes them among available proxies. The load balancer can operate as a separate service or be integrated into the application.
The second component is the proxy pool manager. It maintains a list of all available proxies with their characteristics: IP address, port, protocol (HTTP/SOCKS5), geographical location, type (residential, mobile, data center), current status (active, blocked, under review). The pool manager is responsible for adding new proxies, removing non-working ones, and periodically checking availability.
The third element is the monitoring and health check system. It constantly checks the operability of each proxy: sends test requests, measures response time, checks connection success. If a proxy does not respond or returns errors, the system marks it as unavailable and temporarily excludes it from rotation.
| Component | Function | Technologies |
|---|---|---|
| Load Balancer | Distributing requests among proxies | HAProxy, Nginx, Python/Node.js libraries |
| Proxy Pool Manager | Managing the proxy list | Redis, PostgreSQL, in-memory storage |
| Health Check System | Checking proxy availability | Scheduled tasks, Celery, cron |
| Rate Limiter | Controlling request frequency | Token bucket, leaky bucket algorithms |
| Monitoring & Metrics | Collecting performance metrics | Prometheus, Grafana, ELK Stack |
The fourth component is the rate limiter. It ensures that each proxy does not exceed the allowable request frequency to the target resource. For example, if you are scraping Instagram and know that the platform blocks IPs after 60 requests per minute, the rate limiter will not allow sending more than 50 requests per minute through one proxy, leaving a safety margin.
The fifth element is the metrics and analytics system. It collects data on the performance of each proxy: the number of successful requests, the number of errors, average response time, and the block percentage. This data is used to optimize balancing algorithms and identify problematic proxies.
Balancing algorithms: Round Robin, Least Connections, Weighted
The Round Robin algorithm is the simplest and most common balancing method. It sequentially iterates through the proxies in the list: the first request goes through proxy #1, the second through proxy #2, the third through proxy #3, and so on. When the list ends, the balancer returns to the beginning. This algorithm evenly distributes the load if all proxies have the same performance.
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
# Usage
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}")
The Least Connections algorithm selects the proxy with the fewest active connections at the moment. This is useful when requests have different execution durations. For example, while scraping, one request may take 100 ms, while another takes 5 seconds (if the page loads slowly). Least Connections automatically directs new requests to less loaded proxies.
class LeastConnectionsBalancer:
def __init__(self, proxies):
self.proxies = {proxy: 0 for proxy in proxies}
def get_next_proxy(self):
# Select the proxy with the least connections
proxy = min(self.proxies.items(), key=lambda x: x[1])[0]
self.proxies[proxy] += 1
return proxy
def release_proxy(self, proxy):
# Decrease the counter when the request is completed
self.proxies[proxy] -= 1
# Usage with context manager
balancer = LeastConnectionsBalancer(proxies)
def make_request(url):
proxy = balancer.get_next_proxy()
try:
# Execute the request through the selected proxy
response = requests.get(url, proxies={"http": proxy})
return response
finally:
balancer.release_proxy(proxy)
Weighted Round Robin expands the classic Round Robin by assigning each proxy a weight based on its performance or quality. Proxies with higher weights receive more requests. This is useful when you have proxies of varying quality: for example, premium residential proxies with high speed and cheap data center proxies with limitations.
class WeightedRoundRobinBalancer:
def __init__(self, weighted_proxies):
# weighted_proxies = [(proxy, weight), ...]
self.proxies = []
for proxy, weight in weighted_proxies:
# Add the proxy to the list weight times
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
# Usage with weights
weighted_proxies = [
("http://premium-proxy1.com:8080", 5), # high quality
("http://premium-proxy2.com:8080", 5),
("http://cheap-proxy1.com:8080", 2), # low quality
("http://cheap-proxy2.com:8080", 1) # very low quality
]
balancer = WeightedRoundRobinBalancer(weighted_proxies)
The Random algorithm selects a proxy randomly from the list of available ones. It is simpler to implement than Round Robin and works well with a large number of proxies (100+). Random distribution automatically balances out with a sufficient volume of requests. The downside is that there may be short periods of uneven load.
IP Hash is an algorithm that selects a proxy based on the hash of the target URL or another parameter. This ensures that requests to the same resource always go through the same proxy. This is useful for sites that use sessions or require sequential requests from a single IP (for example, authorization + data retrieval).
Managing proxy pools: rotation and health checks
Effective management of a proxy pool requires constant monitoring of the status of each proxy and automatic rotation. A health check is a periodic check of proxy availability by sending a test request. Typically, a simple GET request is used to a reliable service (for example, httpbin.org or your own endpoint) that returns information about the IP address.
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):
"""Checks the operability of the proxy"""
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()
}
# Usage
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)")
The proxy pool management system should automatically mark non-working proxies as unavailable and periodically repeat the check. A common strategy is the circuit breaker pattern: after three consecutive failed checks, the proxy is excluded from the pool for 5-10 minutes, then checked again. If the check is successful, the proxy returns to the active pool.
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):
"""Adds a proxy to the pool"""
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):
"""Returns a list of active proxies"""
now = datetime.now()
active = []
for proxy_url, info in self.proxies.items():
# Check if the proxy is in 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):
"""Marks a failed attempt to use the proxy"""
if proxy_url not in self.proxies:
return
info = self.proxies[proxy_url]
info["failures"] += 1
if info["failures"] >= self.max_failures:
# Move to 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):
"""Marks a successful use of the proxy"""
if proxy_url not in self.proxies:
return
info = self.proxies[proxy_url]
info["failures"] = 0
info["status"] = "active"
info["cooldown_until"] = None
Proxy rotation is a strategy for automatically changing proxies at certain intervals or after a certain number of requests. There are several approaches: time-based rotation (changing every 5-10 minutes), request count rotation (changing after 100-500 requests), session-based rotation (one proxy per scraping session). The choice of strategy depends on the requirements of the target site.
Tip: For scraping marketplaces (Wildberries, Ozon), the optimal strategy is request count rotation: 50-100 requests per proxy, then switch. For working with social media APIs (Instagram, Facebook), it is better to use session-based rotation: one proxy for the entire cycle of authorization β actions β logout.
Rate limiting and request frequency control
Rate limiting is a critically important component of the load balancing system. It prevents exceeding the allowable request frequency to the target resource, which could lead to blocking the proxies. There are two main algorithms: Token Bucket and Leaky Bucket.
Token Bucket works as follows: each proxy has a virtual "bucket" of tokens. Each token grants the right to one request. The bucket gradually fills with tokens at a specified rate (for example, 10 tokens per second). The maximum capacity of the bucket is limited (for example, 50 tokens). When a request comes in, the system checks for available tokens: if there are any, it takes one token and executes the request; if not, it waits for a new token to appear.
import time
from threading import Lock
class TokenBucketRateLimiter:
def __init__(self, rate, capacity):
"""
rate: number of tokens per second
capacity: maximum number of tokens in the bucket
"""
self.rate = rate
self.capacity = capacity
self.tokens = capacity
self.last_update = time.time()
self.lock = Lock()
def _add_tokens(self):
"""Adds tokens based on elapsed time"""
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):
"""Attempts to get the specified number of tokens"""
with self.lock:
self._add_tokens()
if self.tokens >= tokens:
self.tokens -= tokens
return True
return False
def wait_and_acquire(self, tokens=1):
"""Waits for tokens to become available and acquires them"""
while not self.acquire(tokens):
# Calculate wait time
wait_time = (tokens - self.tokens) / self.rate
time.sleep(wait_time)
# Usage for each proxy
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() # Wait for token availability
response = requests.get(url, proxies={"http": proxy_url})
return response
Leaky Bucket works differently: requests are placed in a queue (bucket), which "leaks" at a constant rate. If the bucket overflows, new requests are rejected or put on hold. This algorithm provides a more even distribution of load over time but can lead to delays during spikes in activity.
Different target platforms require different limits. The Instagram API allows about 200 requests per hour per IP (about 3.3 requests per minute). Wildberries blocks after 100-200 requests per minute from a single IP. Google Search allows 10-20 requests per minute. Your rate limiting system should take these restrictions into account and add a safety margin (usually 20-30%).
| Platform | Request Limit | Recommended Setting |
|---|---|---|
| ~200 requests/hour | 2-3 requests/minute (with margin) | |
| Wildberries | 100-200 requests/minute | 60-80 requests/minute |
| Google Search | 10-20 requests/minute | 8-12 requests/minute |
| Ozon | 50-100 requests/minute | 30-50 requests/minute |
| Facebook API | 200 requests/hour | 2-3 requests/minute |
Performance monitoring and automatic scaling
An effective load balancing system requires constant monitoring of key metrics. The first group of metrics is the performance of proxies: average response time, percentage of successful requests, number of timeouts, number of 4xx and 5xx errors. This data allows you to identify problematic proxies and optimize balancing algorithms.
class ProxyMetrics:
def __init__(self):
self.metrics = {} # {proxy_url: metrics_dict}
def record_request(self, proxy_url, response_time, status_code, success):
"""Records request metrics"""
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):
"""Returns the percentage of successful requests"""
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):
"""Returns the average response time"""
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):
"""Returns a full report on the proxy"""
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)
}
The second group of metrics is the overall system performance: total requests per second (RPS), average latency, request queue size, number of active proxies, percentage of proxy pool usage. These metrics indicate whether the system is coping with the load or requires scaling.
Automatic scaling allows dynamically adding or removing proxies based on load. A simple strategy: if the average load of the pool exceeds 80% for 5 minutes, the system automatically adds new proxies. If the load falls below 30% for 15 minutes, the system removes excess proxies to save resources.
Example of monitoring setup: To scrape 100,000 products from Wildberries per hour (about 28 requests per second), you will need at least 30-40 proxies with a limit of 60 requests per minute per proxy. Set up alerts: if the success rate drops below 85% or the average response time exceeds 3 seconds, the system should automatically add 10-15 backup proxies from the pool.
For visualizing metrics, use Grafana with Prometheus or ELK Stack. Create dashboards with graphs: RPS over time, response time distribution, top 10 fastest/slowest proxies, error map by type. This will allow you to quickly identify problems and optimize the system.
Practical implementation in Python and Node.js
Let's consider a complete implementation of a load balancing system in Python using popular libraries. This example combines all described components: balancer, pool manager, health checks, rate limiting, and monitoring.
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: list of proxies [{"url": "...", "weight": 1}, ...]
algorithm: round_robin, least_connections, weighted, random
rate_limit: maximum number of requests per second per proxy
"""
self.proxies = proxies
self.algorithm = algorithm
self.rate_limit = rate_limit
# Load balancer state
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
})
# Initialize 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()
# Start background health check
self.health_check_thread = Thread(target=self._health_check_loop, daemon=True)
self.health_check_thread.start()
def get_next_proxy(self):
"""Selects the next proxy according to the algorithm"""
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):
"""Executes a request through the balancer"""
proxy_url = self.get_next_proxy()
# Wait for rate limit availability
self.rate_limiters[proxy_url].wait_and_acquire()
# Increase connection counter
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
# Record metrics
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:
# Decrease connection counter
with self.lock:
self.connections[proxy_url] -= 1
def _record_metrics(self, proxy_url, response_time, success, status_code):
"""Records request metrics"""
with self.lock:
m = self.metrics[proxy_url]
m["total"] += 1
if success:
m["success"] += 1
m["response_times"].append(response_time)
# Store only the last 1000 values
if len(m["response_times"]) > 1000:
m["response_times"].pop(0)
else:
m["failed"] += 1
def _health_check_loop(self):
"""Background health check for proxies"""
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) # Check every minute
def get_stats(self):
"""Returns statistics for all proxies"""
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
Using this balancer for scraping a marketplace:
# Setting up the balancer
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 requests per minute per proxy
)
# Scraping product list
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)
# Process response
results.append({"url": url, "status": "success", "data": response.text})
except Exception as e:
results.append({"url": url, "status": "error", "error": str(e)})
# Every 100 requests, print statistics
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")
# Final statistics
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']}")
For Node.js, a similar architecture can be used with axios for HTTP requests and node-rate-limiter for rate control:
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();
// Initialize 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);
// Wait for rate limit availability
await limiter.removeTokens(1);
// Increase connection counter
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;
}
}
// Usage
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();
Optimization for specific tasks: scraping, APIs, automation
Different tasks require different load balancing strategies. For scraping marketplaces (Wildberries, Ozon, Avito), the optimal strategy is to rotate based on the number of requests and geographical distribution. Use residential proxies from different cities in Russia, change proxies every 50-100 requests, and add random delays between requests (1-3 seconds) to simulate human behavior.
For working with social media APIs (Instagram, Facebook, VK), the stability of the IP address within a session is critical. Use the IP Hash algorithm or sticky sessions: all requests from one account should go through the same proxy. This prevents suspicious changes in geolocation that could lead to account blocking. Mobile proxies are recommended, as social networks trust mobile IPs more than residential or data center ones.
# Example of sticky sessions for 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):
"""Returns a fixed proxy for the account"""
if account_id in self.session_map:
return self.session_map[account_id]
# Select the least loaded proxy
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):
"""Releases the proxy when done with the account"""
if account_id in self.session_map:
proxy = self.session_map[account_id]
self.proxy_usage[proxy] -= 1
del self.session_map[account_id]
# Usage for Instagram
balancer = StickySessionBalancer([