Retour au blog

Comment corriger les erreurs de timeout avec un proxy

Les erreurs de timeout via proxy sont un problème courant lors du scraping et de l'automatisation. Nous analysons les causes et proposons des solutions efficaces avec des exemples de code.

📅15 décembre 2025

Comment corriger les erreurs de timeout lors de l'utilisation d'un proxy

La requête s'est figée, le script s'est arrêté avec l'erreur TimeoutError, les données n'ont pas été reçues. Une situation familière ? Les erreurs de timeout via proxy sont l'un des problèmes les plus courants lors du scraping et de l'automatisation. Analysons les causes et fournissons des solutions concrètes.

Pourquoi les erreurs de timeout se produisent

Le timeout n'est pas un seul problème, mais un symptôme. Avant de traiter, il faut comprendre la cause :

Serveur proxy lent. Un serveur surchargé ou un proxy géographiquement éloigné ajoute un délai à chaque requête. Si votre timeout est de 10 secondes et que le proxy répond en 12 secondes — erreur.

Blocage du côté du site cible. Le site peut intentionnellement « suspendre » les requêtes suspectes au lieu de les refuser explicitement. C'est une tactique contre les bots — maintenir la connexion ouverte indéfiniment.

Problèmes DNS. Le proxy doit résoudre le domaine. Si le serveur DNS du proxy est lent ou indisponible — la requête se fige à l'étape de connexion.

Configuration incorrecte des timeouts. Un seul timeout global pour tout — erreur courante. Connect timeout et read timeout sont des choses différentes et doivent être configurés séparément.

Problèmes réseau. Perte de paquets, connexion proxy instable, problèmes de routage — tout cela conduit à des timeouts.

Types de timeouts et leur configuration

La plupart des bibliothèques HTTP supportent plusieurs types de timeouts. Comprendre la différence entre eux est la clé d'une configuration correcte.

Connect timeout

Temps pour établir une connexion TCP avec le proxy et le serveur cible. Si le proxy est indisponible ou le serveur ne répond pas — ce timeout s'activera. Valeur recommandée : 5-10 secondes.

Read timeout

Temps d'attente des données après l'établissement de la connexion. Le serveur s'est connecté mais reste silencieux — le read timeout s'activera. Pour les pages ordinaires : 15-30 secondes. Pour les API lourdes : 60+ secondes.

Total timeout

Temps total pour toute la requête du début à la fin. Protection contre les connexions figées. Généralement : connect + read + marge.

Exemple de configuration en Python avec la bibliothèque requests :

import requests

proxies = {
    "http": "http://user:pass@proxy.example.com:8080",
    "https": "http://user:pass@proxy.example.com:8080"
}

# Tuple : (connect_timeout, read_timeout)
timeout = (10, 30)

try:
    response = requests.get(
        "https://target-site.com/api/data",
        proxies=proxies,
        timeout=timeout
    )
except requests.exceptions.ConnectTimeout:
    print("Impossible de se connecter au proxy ou au serveur")
except requests.exceptions.ReadTimeout:
    print("Le serveur n'a pas envoyé les données à temps")

Pour aiohttp (Python asynchrone) :

import aiohttp
import asyncio

async def fetch_with_timeout():
    timeout = aiohttp.ClientTimeout(
        total=60,      # Timeout total
        connect=10,    # Pour la connexion
        sock_read=30   # Pour la lecture des données
    )
    
    async with aiohttp.ClientSession(timeout=timeout) as session:
        async with session.get(
            "https://target-site.com/api/data",
            proxy="http://user:pass@proxy.example.com:8080"
        ) as response:
            return await response.text()

Logique de retry : la bonne approche

Le timeout n'est pas toujours une erreur fatale. Souvent, une requête répétée réussit. Mais le retry doit être fait intelligemment.

Délai exponentiel

Ne bombardez pas le serveur avec des requêtes répétées sans pause. Utilisez un backoff exponentiel : chaque tentative suivante — avec un délai croissant.

import requests
import time
import random

def fetch_with_retry(url, proxies, max_retries=3):
    """Requête avec retry et délai exponentiel"""
    
    for attempt in range(max_retries):
        try:
            response = requests.get(
                url,
                proxies=proxies,
                timeout=(10, 30)
            )
            response.raise_for_status()
            return response
            
        except (requests.exceptions.Timeout, 
                requests.exceptions.ConnectionError) as e:
            
            if attempt == max_retries - 1:
                raise  # Dernière tentative — on relève l'erreur
            
            # Délai exponentiel : 1s, 2s, 4s...
            # + jitter aléatoire pour éviter les vagues de requêtes
            delay = (2 ** attempt) + random.uniform(0, 1)
            print(f"Tentative {attempt + 1} échouée : {e}")
            print(f"Nouvelle tentative dans {delay:.1f} secondes...")
            time.sleep(delay)

Bibliothèque tenacity

Pour le code en production, il est plus pratique d'utiliser des solutions prêtes à l'emploi :

from tenacity import retry, stop_after_attempt, wait_exponential
from tenacity import retry_if_exception_type
import requests

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=1, max=10),
    retry=retry_if_exception_type((
        requests.exceptions.Timeout,
        requests.exceptions.ConnectionError
    ))
)
def fetch_data(url, proxies):
    response = requests.get(url, proxies=proxies, timeout=(10, 30))
    response.raise_for_status()
    return response.json()

Rotation de proxy lors de timeouts

Si un proxy donne constamment des timeouts — le problème vient de lui. Solution logique : basculer sur un autre.

import requests
from collections import deque
from dataclasses import dataclass, field
from typing import Optional
import time

@dataclass
class ProxyManager:
    """Gestionnaire de proxy avec suivi des tentatives échouées"""
    
    proxies: list
    max_failures: int = 3
    cooldown_seconds: int = 300
    _failures: dict = field(default_factory=dict)
    _cooldown_until: dict = field(default_factory=dict)
    
    def get_proxy(self) -> Optional[str]:
        """Obtenir un proxy fonctionnel"""
        current_time = time.time()
        
        for proxy in self.proxies:
            # Ignorer les proxies en cooldown
            if self._cooldown_until.get(proxy, 0) > current_time:
                continue
            return proxy
        
        return None  # Tous les proxies sont en cooldown
    
    def report_failure(self, proxy: str):
        """Signaler une requête échouée"""
        self._failures[proxy] = self._failures.get(proxy, 0) + 1
        
        if self._failures[proxy] >= self.max_failures:
            # Mettre le proxy en cooldown
            self._cooldown_until[proxy] = time.time() + self.cooldown_seconds
            self._failures[proxy] = 0
            print(f"Proxy {proxy} mis en cooldown")
    
    def report_success(self, proxy: str):
        """Réinitialiser le compteur d'erreurs en cas de succès"""
        self._failures[proxy] = 0


def fetch_with_rotation(url, proxy_manager, max_attempts=5):
    """Requête avec changement automatique de proxy en cas d'erreur"""
    
    for attempt in range(max_attempts):
        proxy = proxy_manager.get_proxy()
        
        if not proxy:
            raise Exception("Aucun proxy disponible")
        
        proxies = {"http": proxy, "https": proxy}
        
        try:
            response = requests.get(url, proxies=proxies, timeout=(10, 30))
            response.raise_for_status()
            proxy_manager.report_success(proxy)
            return response
            
        except (requests.exceptions.Timeout, 
                requests.exceptions.ConnectionError):
            proxy_manager.report_failure(proxy)
            print(f"Timeout via {proxy}, essai d'un autre...")
            continue
    
    raise Exception(f"Impossible d'obtenir les données après {max_attempts} tentatives")

Lors de l'utilisation de proxies résidentiels avec rotation automatique, cette logique est simplifiée — le fournisseur bascule automatiquement l'IP à chaque requête ou selon l'intervalle spécifié.

Requêtes asynchrones avec contrôle des timeouts

Lors du scraping massif, les requêtes synchrones sont inefficaces. L'approche asynchrone permet de traiter des centaines d'URL en parallèle, mais nécessite une gestion prudente des timeouts.

import aiohttp
import asyncio
from typing import List, Tuple

async def fetch_one(
    session: aiohttp.ClientSession, 
    url: str,
    semaphore: asyncio.Semaphore
) -> Tuple[str, str | None, str | None]:
    """Chargement d'une URL avec gestion du timeout"""
    
    async with semaphore:  # Limiter la parallélité
        try:
            async with session.get(url) as response:
                content = await response.text()
                return (url, content, None)
                
        except asyncio.TimeoutError:
            return (url, None, "timeout")
        except aiohttp.ClientError as e:
            return (url, None, str(e))


async def fetch_all(
    urls: List[str],
    proxy: str,
    max_concurrent: int = 10
) -> List[Tuple[str, str | None, str | None]]:
    """Chargement massif avec contrôle des timeouts et de la parallélité"""
    
    timeout = aiohttp.ClientTimeout(total=45, connect=10, sock_read=30)
    semaphore = asyncio.Semaphore(max_concurrent)
    
    connector = aiohttp.TCPConnector(
        limit=max_concurrent,
        limit_per_host=5  # Pas plus de 5 connexions par hôte
    )
    
    async with aiohttp.ClientSession(
        timeout=timeout,
        connector=connector
    ) as session:
        # Définir le proxy pour toutes les requêtes
        tasks = [
            fetch_one(session, url, semaphore) 
            for url in urls
        ]
        results = await asyncio.gather(*tasks)
    
    # Statistiques
    success = sum(1 for _, content, _ in results if content)
    timeouts = sum(1 for _, _, error in results if error == "timeout")
    print(f"Succès : {success}, Timeouts : {timeouts}")
    
    return results


# Utilisation
async def main():
    urls = [f"https://example.com/page/{i}" for i in range(100)]
    results = await fetch_all(
        urls, 
        proxy="http://user:pass@proxy.example.com:8080",
        max_concurrent=10
    )

asyncio.run(main())

Important : Ne définissez pas une parallélité trop élevée. 50-100 requêtes simultanées via un seul proxy — c'est déjà beaucoup. Mieux vaut 10-20 avec plusieurs proxies.

Diagnostic : comment trouver la cause

Avant de modifier les paramètres, identifiez la source du problème.

Étape 1 : Vérifiez le proxy directement

# Test simple via curl avec mesure du temps
curl -x http://user:pass@proxy:8080 \
     -w "Connect: %{time_connect}s\nTotal: %{time_total}s\n" \
     -o /dev/null -s \
     https://httpbin.org/get

Si time_connect dépasse 5 secondes — le problème vient du proxy ou du réseau jusqu'à lui.

Étape 2 : Comparez avec une requête directe

import requests
import time

def measure_request(url, proxies=None):
    start = time.time()
    try:
        r = requests.get(url, proxies=proxies, timeout=30)
        elapsed = time.time() - start
        return f"OK: {elapsed:.2f}s, status: {r.status_code}"
    except Exception as e:
        elapsed = time.time() - start
        return f"FAIL: {elapsed:.2f}s, error: {type(e).__name__}"

url = "https://target-site.com"
proxy = {"http": "http://proxy:8080", "https": "http://proxy:8080"}

print("Directement:", measure_request(url))
print("Via proxy:", measure_request(url, proxy))

Étape 3 : Testez différents types de proxy

Les timeouts peuvent dépendre du type de proxy :

Type de proxy Délai typique Timeout recommandé
Datacenter 50-200 ms Connect: 5s, Read: 15s
Résidentiel 200-800 ms Connect: 10s, Read: 30s
Mobile 300-1500 ms Connect: 15s, Read: 45s

Étape 4 : Enregistrez les détails

import logging
import requests
from requests.adapters import HTTPAdapter

# Activer l'enregistrement de débogage
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("urllib3").setLevel(logging.DEBUG)

# Vous verrez maintenant toutes les étapes de la requête :
# - Résolution DNS
# - Établissement de la connexion
# - Envoi de la requête
# - Réception de la réponse

Liste de contrôle pour résoudre les erreurs de timeout

Algorithme rapide d'action en cas de timeout :

  1. Identifiez le type de timeout — connect ou read ? Ce sont des problèmes différents.
  2. Vérifiez le proxy séparément — fonctionne-t-il ? Quel est le délai ?
  3. Augmentez les timeouts — les valeurs sont peut-être trop agressives pour votre type de proxy.
  4. Ajoutez un retry avec backoff — les timeouts uniques sont normaux, l'important est la résilience.
  5. Configurez la rotation — basculez automatiquement sur un autre proxy en cas de problème.
  6. Limitez la parallélité — trop de requêtes simultanées surchargent le proxy.
  7. Vérifiez le site cible — il se peut qu'il bloque ou limite vos requêtes.

Conclusion

Les erreurs de timeout via proxy sont un problème résolvable. Dans la plupart des cas, il suffit de configurer correctement les timeouts selon le type de proxy, d'ajouter une logique de retry et de mettre en place une rotation en cas de défaillance. Pour les tâches avec des exigences élevées de stabilité, utilisez des proxies résidentiels avec rotation automatique — plus de détails sur proxycove.com.