Retour au blog

Équilibrage de charge via un proxy : comment scaler le parsing et les requêtes API jusqu'à 10000+ RPS

Guide complet sur la répartition de la charge via des pools de proxy pour le scraping de marketplaces, l'utilisation d'API et l'automatisation : architecture, algorithmes d'équilibrage et exemples pratiques de configuration.

📅7 février 2026
```html

Lorsque vous analysez des milliers de pages de marketplaces, envoyez des requêtes API massives ou automatisez le travail avec des centaines de comptes, une répartition correcte de la charge via des proxies devient critique. Sans un bon équilibrage, vous serez confronté à des blocages, des délais d'attente et une baisse de performance. Dans ce guide, nous examinerons l'architecture de répartition de la charge, les algorithmes d'équilibrage et des stratégies pratiques pour des systèmes à forte charge.

Ce matériel est destiné aux développeurs et aux spécialistes techniques qui travaillent avec l'analyse de données, l'automatisation des requêtes API ou gèrent de grands pools de proxies pour des tâches commerciales.

Pourquoi l'équilibrage de la charge via des proxies est-il nécessaire

L'équilibrage de la charge via des proxies résout plusieurs problèmes critiques des systèmes à forte charge. Le premier et principal est la protection contre les blocages. Lorsque vous envoyez des milliers de requêtes à une seule ressource (marketplace, API de réseau social, moteur de recherche), le serveur cible voit une activité anormalement élevée provenant d'une seule adresse IP et la bloque. La répartition des requêtes entre des dizaines ou des centaines de proxies rend votre activité similaire à celle d'utilisateurs ordinaires.

Le deuxième problème est la performance. Un serveur proxy a une bande passante limitée (généralement 100-1000 Mbit/s) et peut traiter un nombre limité de connexions simultanées. Lors de l'analyse de 10 000 pages par minute ou de l'envoi massif de requêtes API, un seul proxy deviendra le goulet d'étranglement du système. L'équilibrage permet de mettre à l'échelle horizontalement la bande passante en ajoutant de nouveaux proxies au pool.

Le troisième défi est la fiabilité. Si l'un des proxies tombe en panne (défaillance technique, blocage, expiration de la location), le système redirige automatiquement le trafic vers des proxies opérationnels. Sans mécanisme d'équilibrage et de vérification de santé, la défaillance d'un proxy peut arrêter tout le système.

Exemple réel : Lors de l'analyse de Wildberries dans le but de surveiller les prix des concurrents, vous devez traiter 50 000 produits chaque heure. Cela représente environ 14 requêtes par seconde. Un seul proxy peut supporter cette charge, mais Wildberries bloquera l'IP après 100-200 requêtes d'une même adresse. En répartissant les requêtes entre 20 proxies résidentiels, vous réduisez la charge sur chaque IP à 0,7 requêtes par seconde — cela ressemble à l'activité d'un utilisateur ordinaire.

La quatrième raison est la répartition géographique. De nombreux services affichent un contenu ou des prix différents en fonction de la région de l'utilisateur. L'équilibrage entre des proxies de différents pays et villes permet de collecter simultanément des données de toutes les régions cibles. Par exemple, pour surveiller les prix sur Ozon, vous avez besoin de proxies de Moscou, Saint-Pétersbourg, Ekaterinbourg et d'autres grandes villes.

Architecture du système de répartition de la charge

L'architecture classique d'un système d'équilibrage de charge via des proxies se compose de plusieurs composants. Au niveau supérieur se trouve le répartiteur de charge (load balancer) — un module logiciel qui reçoit les tâches entrantes (requêtes d'analyse, appels API) et les répartit entre les proxies disponibles. Le répartiteur peut fonctionner comme un service distinct ou être intégré dans l'application.

Le deuxième composant est le gestionnaire de pool de proxies (proxy pool manager). Il stocke la liste de tous les proxies disponibles avec leurs caractéristiques : adresse IP, port, protocole (HTTP/SOCKS5), localisation géographique, type (résidentiel, mobile, centre de données), état actuel (actif, bloqué, en vérification). Le gestionnaire de pool est responsable de l'ajout de nouveaux proxies, de la suppression de ceux qui ne fonctionnent pas et de la vérification périodique de la disponibilité.

Le troisième élément est le système de surveillance et de vérification de santé. Il vérifie en permanence la fonctionnalité de chaque proxy : envoie des requêtes de test, mesure le temps de réponse, vérifie le succès de la connexion. Si un proxy ne répond pas ou renvoie des erreurs, le système le marque comme indisponible et l'exclut temporairement de la rotation.

Composant Fonction Technologies
Load Balancer Répartition des requêtes entre les proxies HAProxy, Nginx, bibliothèques Python/Node.js
Proxy Pool Manager Gestion de la liste des proxies Redis, PostgreSQL, stockages en mémoire
Health Check System Vérification de la disponibilité des proxies Tâches planifiées, Celery, cron
Rate Limiter Contrôle de la fréquence des requêtes Algorithmes de seau à jetons, seau percé
Monitoring & Metrics Collecte de métriques de performance Prometheus, Grafana, ELK Stack

Le quatrième composant est le limiteur de taux (rate limiter). Il veille à ce qu'aucun proxy ne dépasse la fréquence de requêtes autorisée vers la ressource cible. Par exemple, si vous analysez Instagram et savez que la plateforme bloque les IP après 60 requêtes par minute, le limiteur de taux n'autorisera pas plus de 50 requêtes par minute via un seul proxy, laissant une marge de sécurité.

Le cinquième élément est le système de métriques et d'analytique. Il collecte des données sur la performance de chaque proxy : nombre de requêtes réussies, nombre d'erreurs, temps de réponse moyen, pourcentage de blocages. Ces données sont utilisées pour optimiser les algorithmes d'équilibrage et identifier les proxies problématiques.

Algorithmes d'équilibrage : Round Robin, Least Connections, Weighted

L'algorithme Round Robin est la méthode d'équilibrage la plus simple et la plus répandue. Il parcourt successivement les proxies de la liste : la première requête passe par le proxy n°1, la deuxième par le proxy n°2, la troisième par le proxy n°3, et ainsi de suite. Lorsque la liste se termine, le répartiteur revient au début. Cet algorithme répartit uniformément la charge, si tous les proxies ont des performances identiques.

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

# Utilisation
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}")

L'algorithme Least Connections (le moins de connexions) choisit le proxy avec le nombre minimal de connexions actives à ce moment. Cela est utile lorsque les requêtes ont des durées d'exécution différentes. Par exemple, lors de l'analyse, une requête peut être traitée en 100 ms, tandis qu'une autre peut prendre 5 secondes (si la page se charge lentement). Least Connections dirige automatiquement les nouvelles requêtes vers des proxies moins chargés.

class LeastConnectionsBalancer:
    def __init__(self, proxies):
        self.proxies = {proxy: 0 for proxy in proxies}
    
    def get_next_proxy(self):
        # Choisit le proxy avec le nombre minimal de connexions
        proxy = min(self.proxies.items(), key=lambda x: x[1])[0]
        self.proxies[proxy] += 1
        return proxy
    
    def release_proxy(self, proxy):
        # Diminue le compteur à la fin de la requête
        self.proxies[proxy] -= 1

# Utilisation avec un gestionnaire de contexte
balancer = LeastConnectionsBalancer(proxies)

def make_request(url):
    proxy = balancer.get_next_proxy()
    try:
        # Effectue la requête via le proxy choisi
        response = requests.get(url, proxies={"http": proxy})
        return response
    finally:
        balancer.release_proxy(proxy)

Weighted Round Robin (round robin pondéré) étend le Round Robin classique en attribuant un poids à chaque proxy en fonction de ses performances ou de sa qualité. Les proxies avec un poids plus élevé reçoivent plus de requêtes. Cela est utile lorsque vous avez des proxies de qualité différente : par exemple, des proxies résidentiels premium avec une vitesse élevée et des proxies de centres de données bon marché avec des restrictions.

class WeightedRoundRobinBalancer:
    def __init__(self, weighted_proxies):
        # weighted_proxies = [(proxy, weight), ...]
        self.proxies = []
        for proxy, weight in weighted_proxies:
            # Ajoute le proxy à la liste weight fois
            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

# Utilisation avec des poids
weighted_proxies = [
    ("http://premium-proxy1.com:8080", 5),  # haute qualité
    ("http://premium-proxy2.com:8080", 5),
    ("http://cheap-proxy1.com:8080", 2),    # faible qualité
    ("http://cheap-proxy2.com:8080", 1)     # très faible qualité
]

balancer = WeightedRoundRobinBalancer(weighted_proxies)

L'algorithme Random (choix aléatoire) choisit un proxy au hasard dans la liste des disponibles. Il est plus simple à mettre en œuvre que le Round Robin et fonctionne bien avec un grand nombre de proxies (100+). La répartition aléatoire s'équilibre automatiquement avec un volume suffisant de requêtes. L'inconvénient est qu'il peut y avoir de courtes périodes de charge inégale.

IP Hash est un algorithme qui choisit un proxy en fonction du hachage de l'URL cible ou d'un autre paramètre. Cela garantit que les requêtes vers la même ressource passent toujours par le même proxy. Utile pour les sites qui utilisent des sessions ou nécessitent une séquence de requêtes depuis une seule IP (par exemple, authentification + récupération de données).

Gestion des pools de proxies : rotation et vérifications de santé

Une gestion efficace du pool de proxies nécessite une surveillance constante de l'état de chaque proxy et une rotation automatique. La vérification de santé est une vérification périodique de la disponibilité des proxies en envoyant une requête de test. On utilise généralement une simple requête GET vers un service fiable (par exemple, httpbin.org ou un point de terminaison propre) qui renvoie des informations sur l'adresse 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):
        """Vérifie la fonctionnalité du 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()
            }

# Utilisation
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)")

Le système de gestion du pool doit automatiquement marquer les proxies non fonctionnels comme indisponibles et répéter périodiquement la vérification. Une stratégie courante est le circuit breaker pattern : après trois vérifications infructueuses consécutives, le proxy est exclu du pool pendant 5 à 10 minutes, puis vérifié à nouveau. Si la vérification est réussie, le proxy est réintégré dans le pool actif.

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):
        """Ajoute un proxy au 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):
        """Retourne la liste des proxies actifs"""
        now = datetime.now()
        active = []
        
        for proxy_url, info in self.proxies.items():
            # Vérifie si le proxy est en 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):
        """Marque un échec d'utilisation du proxy"""
        if proxy_url not in self.proxies:
            return
        
        info = self.proxies[proxy_url]
        info["failures"] += 1
        
        if info["failures"] >= self.max_failures:
            # Met en 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):
        """Marque un succès d'utilisation du proxy"""
        if proxy_url not in self.proxies:
            return
        
        info = self.proxies[proxy_url]
        info["failures"] = 0
        info["status"] = "active"
        info["cooldown_until"] = None

La rotation des proxies est une stratégie de changement automatique de proxy à intervalles réguliers ou après un certain nombre de requêtes. Il existe plusieurs approches : rotation par temps (changement toutes les 5-10 minutes), rotation par nombre de requêtes (changement après 100-500 requêtes), rotation par sessions (un proxy pour une session d'analyse). Le choix de la stratégie dépend des exigences du site cible.

Conseil : Pour l'analyse des marketplaces (Wildberries, Ozon), la rotation par nombre de requêtes est optimale : 50-100 requêtes par proxy, puis changement. Pour travailler avec les API des réseaux sociaux (Instagram, Facebook), il est préférable d'utiliser la rotation par sessions : un proxy pour tout le cycle d'authentification → actions → déconnexion.

Limitation de taux et contrôle de la fréquence des requêtes

La limitation de taux est un composant critique du système d'équilibrage de charge. Elle empêche le dépassement de la fréquence de requêtes autorisée vers la ressource cible, ce qui pourrait entraîner le blocage du proxy. Il existe deux algorithmes principaux : Token Bucket (seau à jetons) et Leaky Bucket (seau percé).

Token Bucket fonctionne comme suit : chaque proxy a un "seau" virtuel avec des jetons. Chaque jeton donne droit à une requête. Le seau se remplit progressivement de jetons à une vitesse donnée (par exemple, 10 jetons par seconde). La capacité maximale du seau est limitée (par exemple, 50 jetons). Lorsqu'une requête arrive, le système vérifie la disponibilité des jetons : s'il y en a, il en retire un et exécute la requête, sinon il attend l'apparition d'un nouveau jeton.

import time
from threading import Lock

class TokenBucketRateLimiter:
    def __init__(self, rate, capacity):
        """
        rate: nombre de jetons par seconde
        capacity: nombre maximal de jetons dans le seau
        """
        self.rate = rate
        self.capacity = capacity
        self.tokens = capacity
        self.last_update = time.time()
        self.lock = Lock()
    
    def _add_tokens(self):
        """Ajoute des jetons en fonction du temps écoulé"""
        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):
        """Essaie d'obtenir le nombre spécifié de jetons"""
        with self.lock:
            self._add_tokens()
            
            if self.tokens >= tokens:
                self.tokens -= tokens
                return True
            return False
    
    def wait_and_acquire(self, tokens=1):
        """Attend l'apparition de jetons et les obtient"""
        while not self.acquire(tokens):
            # Calcule le temps d'attente
            wait_time = (tokens - self.tokens) / self.rate
            time.sleep(wait_time)

# Utilisation pour chaque 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()  # Attend la disponibilité d'un jeton
    
    response = requests.get(url, proxies={"http": proxy_url})
    return response

Leaky Bucket (seau percé) fonctionne différemment : les requêtes sont placées dans une file d'attente (seau) qui "s'écoule" à une vitesse constante. Si le seau déborde, les nouvelles requêtes sont rejetées ou mises en attente. Cet algorithme assure une répartition de la charge plus uniforme dans le temps, mais peut entraîner des délais lors de pics d'activité.

Différentes plateformes cibles nécessitent des limites différentes. L'API d'Instagram autorise environ 200 requêtes par heure par IP (environ 3,3 requêtes par minute). Wildberries bloque après 100-200 requêtes par minute depuis une IP. Google Search autorise 10-20 requêtes par minute. Votre système de limitation de taux doit tenir compte de ces restrictions et ajouter une marge de sécurité (généralement 20-30%).

Plateforme Limite de requêtes Configuration recommandée
Instagram ~200 requêtes/heure 2-3 requêtes/minute (avec marge)
Wildberries 100-200 requêtes/minute 60-80 requêtes/minute
Google Search 10-20 requêtes/minute 8-12 requêtes/minute
Ozon 50-100 requêtes/minute 30-50 requêtes/minute
Facebook API 200 requêtes/heure 2-3 requêtes/minute

Surveillance des performances et mise à l'échelle automatique

Un système efficace d'équilibrage de charge nécessite une surveillance constante des métriques clés. Le premier groupe de métriques concerne la performance des proxies : temps de réponse moyen (response time), pourcentage de requêtes réussies (success rate), nombre de délais d'attente, nombre d'erreurs 4xx et 5xx. Ces données permettent d'identifier les proxies problématiques et d'optimiser les algorithmes d'équilibrage.

class ProxyMetrics:
    def __init__(self):
        self.metrics = {}  # {proxy_url: metrics_dict}
    
    def record_request(self, proxy_url, response_time, status_code, success):
        """Enregistre les métriques de la requête"""
        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:  # délai d'attente
                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):
        """Retourne le pourcentage de requêtes réussies"""
        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):
        """Retourne le temps de réponse moyen"""
        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):
        """Retourne le rapport complet pour le 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)
        }

Le deuxième groupe de métriques concerne la performance globale du système : nombre total de requêtes par seconde (RPS — requests per second), latence moyenne (latency), taille de la file d'attente des requêtes, nombre de proxies actifs, pourcentage d'utilisation du pool de proxies. Ces métriques montrent si le système gère la charge ou si une mise à l'échelle est nécessaire.

La mise à l'échelle automatique (auto-scaling) permet d'ajouter ou de supprimer dynamiquement des proxies en fonction de la charge. Une stratégie simple : si la charge moyenne du pool dépasse 80 % pendant 5 minutes, le système ajoute automatiquement de nouveaux proxies. Si la charge tombe en dessous de 30 % pendant 15 minutes, le système supprime les proxies excédentaires pour économiser des ressources.

Exemple de configuration de surveillance : Pour analyser 100 000 produits Wildberries par heure (environ 28 requêtes par seconde), vous aurez besoin d'au moins 30-40 proxies avec une limite de 60 requêtes par minute par proxy. Configurez des alertes : si le taux de réussite tombe en dessous de 85 % ou si le temps de réponse moyen dépasse 3 secondes, le système doit automatiquement ajouter 10-15 proxies de réserve du pool.

Pour visualiser les métriques, utilisez Grafana avec Prometheus ou ELK Stack. Créez des tableaux de bord avec des graphiques : RPS dans le temps, répartition du temps de réponse, top 10 des proxies les plus rapides/lents, carte des erreurs par type. Cela permettra d'identifier rapidement les problèmes et d'optimiser le système.

Mise en œuvre pratique en Python et Node.js

Examinons une mise en œuvre complète d'un système d'équilibrage de charge en Python utilisant des bibliothèques populaires. Cet exemple regroupe tous les composants décrits : répartiteur, gestionnaire de pool, vérifications de santé, limitation de taux et surveillance.

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: liste de proxies [{"url": "...", "weight": 1}, ...]
        algorithm: round_robin, least_connections, weighted, random
        rate_limit: nombre maximal de requêtes par seconde sur le proxy
        """
        self.proxies = proxies
        self.algorithm = algorithm
        self.rate_limit = rate_limit
        
        # État du répartiteur
        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
        })
        
        # Initialisation des limiteurs de taux
        for proxy in proxies:
            proxy_url = proxy["url"]
            self.rate_limiters[proxy_url] = TokenBucketRateLimiter(
                rate=rate_limit,
                capacity=rate_limit * 5
            )
        
        self.lock = Lock()
        
        # Démarrage de la vérification de santé en arrière-plan
        self.health_check_thread = Thread(target=self._health_check_loop, daemon=True)
        self.health_check_thread.start()
    
    def get_next_proxy(self):
        """Choisit le prochain proxy selon l'algorithme"""
        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):
        """Effectue une requête via le répartiteur"""
        proxy_url = self.get_next_proxy()
        
        # Attend la disponibilité de la limite de taux
        self.rate_limiters[proxy_url].wait_and_acquire()
        
        # Augmente le compteur de connexions
        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
            
            # Enregistre les métriques
            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:
            # Diminue le compteur de connexions
            with self.lock:
                self.connections[proxy_url] -= 1
    
    def _record_metrics(self, proxy_url, response_time, success, status_code):
        """Enregistre les métriques de la requête"""
        with self.lock:
            m = self.metrics[proxy_url]
            m["total"] += 1
            if success:
                m["success"] += 1
                m["response_times"].append(response_time)
                # Conserve seulement les 1000 dernières valeurs
                if len(m["response_times"]) > 1000:
                    m["response_times"].pop(0)
            else:
                m["failed"] += 1
    
    def _health_check_loop(self):
        """Vérification de la fonctionnalité des proxies en arrière-plan"""
        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)  # Vérification chaque minute
    
    def get_stats(self):
        """Retourne les statistiques pour tous les 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

Utilisation de ce répartiteur pour analyser un marketplace :

# Configuration du répartiteur
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 requêtes par minute par proxy
)

# Analyse de la liste des produits
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)
        # Traitement de la réponse
        results.append({"url": url, "status": "success", "data": response.text})
    except Exception as e:
        results.append({"url": url, "status": "error", "error": str(e)})
    
    # Chaque 100 requêtes, affiche les statistiques
    if len(results) % 100 == 0:
        stats = balancer.get_stats()
        for stat in stats:
            print(f"{stat['proxy']}: {stat['success_rate']}% succès, "
                  f"{stat['avg_response_time']}s temps de réponse moyen")

# Statistiques finales
print("\n=== Statistiques finales ===")
for stat in balancer.get_stats():
    print(f"{stat['proxy']}:")
    print(f"  Total des requêtes : {stat['total_requests']}")
    print(f"  Taux de réussite : {stat['success_rate']}%")
    print(f"  Temps de réponse moyen : {stat['avg_response_time']}s")
    print(f"  Statut : {stat['status']}")

Pour Node.js, vous pouvez utiliser une architecture similaire avec les bibliothèques axios pour les requêtes HTTP et node-rate-limiter pour le contrôle de la fréquence :

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();
    
    // Initialisation des limiteurs de taux
    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);
    
    // Attend la disponibilité de la limite de taux
    await limiter.removeTokens(1);
    
    // Augmente le compteur de connexions
    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;
  }
}

// Utilisation
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(`Succès : ${url}`);
    } catch (error) {
      console.error(`Erreur : ${url} - ${error.message}`);
    }
  }
  
  console.log('\n=== Statistiques ===');
  console.log(balancer.getStats());
}

parseProducts();

Optimisation pour des tâches spécifiques : analyse, API, automatisation

Différentes tâches nécessitent différentes stratégies d'équilibrage de charge. Pour l'analyse des marketplaces (Wildberries, Ozon, Avito), la stratégie optimale est la rotation par nombre de requêtes et répartition géographique. Utilisez des proxies résidentiels de différentes villes de Russie, changez de proxy toutes les 50-100 requêtes, ajoutez des délais aléatoires entre les requêtes (1-3 secondes) pour simuler le comportement humain.

Pour travailler avec les API des réseaux sociaux (Instagram, Facebook, VK), la stabilité de l'adresse IP est cruciale dans le cadre d'une session. Utilisez l'algorithme IP Hash ou les sessions collantes : toutes les requêtes d'un même compte doivent passer par le même proxy. Cela empêche un changement suspect de géolocalisation, ce qui pourrait entraîner le blocage du compte. Les proxies mobiles sont recommandés, car les réseaux sociaux font davantage confiance aux IP mobiles qu'aux résidentielles ou aux centres de données.

# Exemple de sessions collantes pour 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):
        """Retourne un proxy fixe pour le compte"""
        if account_id in self.session_map:
            return self.session_map[account_id]
        
        # Choisit le proxy le moins chargé
        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):
        """Libère le proxy à la fin du travail avec le compte"""
        if account_id in self.session_map:
            proxy = self.session_map[account_id]
            self.proxy_usage[proxy] -= 1
            del self.session_map[account_id]

Les stratégies d'équilibrage de charge doivent être adaptées aux besoins spécifiques de chaque tâche. En suivant ces recommandations, vous pouvez optimiser vos systèmes d'analyse, d'automatisation et d'interaction avec les API pour garantir des performances élevées et une fiabilité maximale.

```