← Retour au blog

Python requests : ProxyError, SSLError WRONG_VERSION_NUMBER et 407 - comment les corriger en pratique

Analyse détaillée des erreurs fréquentes lors de l'utilisation de proxies avec Python requests : ProxyError, SSLError WRONG_VERSION_NUMBER, 407 Proxy Authentication Required. Exemples de code fonctionnels pour chaque situation.

📅11 novembre 2026

Si vous parsez des sites ou automatisez des requêtes via un proxy en Python, tôt ou tard, vous serez confronté à trois erreurs classiques : ProxyError, SSLError: WRONG_VERSION_NUMBER et HTTP 407 Proxy Authentication Required. Dans cet article, nous allons examiner la cause de chacune d'elles et montrer un code fonctionnel pour les corriger.

Comment requests fonctionne avec des proxies : syntaxe de base

La bibliothèque requests accepte les proxies via un dictionnaire proxies, où les clés sont le schéma de la requête (http et https), et les valeurs sont les URL du serveur proxy avec une autorisation optionnelle.

import requests

proxies = {
    "http": "http://user:password@proxy_host:port",
    "https": "http://user:password@proxy_host:port",
}

response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(response.json())

Notez que même pour les requêtes HTTPS, le schéma du proxy est généralement spécifié comme http://, car la plupart des serveurs proxy acceptent un tunnel CONNECT via le protocole HTTP, et non pas en l'enveloppant dans SSL. Si vous spécifiez https:// pour un proxy qui ne le supporte pas, vous obtiendrez exactement une SSLError, dont l'analyse sera faite ci-dessous.

Si vous travaillez avec des proxies SOCKS5, un paquet supplémentaire sera nécessaire :

pip install requests[socks]

# Et la syntaxe des proxies change :
proxies = {
    "http": "socks5://user:password@proxy_host:port",
    "https": "socks5://user:password@proxy_host:port",
}

ProxyError : causes et solutions

requests.exceptions.ProxyError est un wrapper autour des exceptions réseau de bas niveau qui se produisent lors de la tentative d'établissement d'une connexion avec le serveur proxy. On rencontre le plus souvent quatre scénarios.

1. Le proxy est inaccessible ou l'adresse/le port sont incorrects

requests.exceptions.ProxyError: HTTPSConnectionPool(host='example.com', port=443):
Max retries exceeded with url: / (Caused by ProxyError('Cannot connect to proxy.',
ConnectionResetError(104, 'Connection reset by peer')))

Vérifiez que l'hôte et le port sont correctement saisis, et que le serveur proxy est réellement en ligne. Le moyen le plus rapide de diagnostiquer est d'essayer de se connecter via curl :

curl -x http://user:password@proxy_host:port https://httpbin.org/ip -v

2. Le proxy a bloqué votre requête (ban par IP ou User-Agent)

Les proxies de centres de données sont souvent bloqués par les sites cibles car leurs plages d'IP sont connues des systèmes anti-fraude. Si l'erreur apparaît de manière constante lors de l'accès à un site spécifique (par exemple, Wildberries, Ozon ou les réseaux sociaux), et non pas sur tous les sites en même temps, le problème ne vient pas du proxy lui-même, mais de sa réputation. Dans ce cas, passer à des proxies résidentiels peut aider — ils utilisent des IP de vrais utilisateurs et sont moins souvent bloqués par les systèmes anti-fraude.

3. Limite de connexions simultanées dépassée

Si vous exécutez un parsing multithread et ouvrez des centaines de connexions via le même proxy, le serveur peut commencer à réinitialiser les connexions supplémentaires. La solution consiste à utiliser un pool de connexions via requests.adapters.HTTPAdapter avec une limite pool_maxsize et max_retries :

from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

session = requests.Session()
retries = Retry(total=3, backoff_factor=1, status_forcelist=[502, 503, 504])
adapter = HTTPAdapter(max_retries=retries, pool_connections=20, pool_maxsize=20)

session.mount("http://", adapter)
session.mount("https://", adapter)

response = session.get("https://httpbin.org/ip", proxies=proxies, timeout=10)

4. Délai d'attente côté serveur proxy

Si le proxy "pend" plus longtemps que le délai d'attente défini, vous verrez ProxyError, enveloppé dans ReadTimeoutError. Augmentez le délai d'attente à une valeur raisonnable (généralement 15-30 secondes suffisent) et ajoutez un traitement des exceptions séparément :

try:
    response = requests.get(url, proxies=proxies, timeout=20)
except requests.exceptions.ProxyError as e:
    print(f"Problème avec le proxy : {e}")
except requests.exceptions.Timeout:
    print("Le proxy n'a pas répondu dans le temps imparti")

SSLError WRONG_VERSION_NUMBER : analyse du problème

Cette erreur est l'une des plus incompréhensibles à première vue, mais en réalité, elle a presque toujours une seule cause.

requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443):
Max retries exceeded with url: / (Caused by SSLError(SSLError(1, '[SSL:
WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123)')))

WRONG_VERSION_NUMBER signifie que le client a tenté d'effectuer un handshake SSL/TLS là où le serveur attend un trafic HTTP normal (sans cryptage). La plupart du temps, la cause réside dans un schéma de proxy incorrectement spécifié dans le dictionnaire proxies.

Erreur fréquente : spécification de https:// au lieu de http:// dans l'adresse du serveur proxy. La plupart des proxies HTTP(S) acceptent la connexion via HTTP normal, même lorsqu'ils proxient le trafic HTTPS via le mécanisme CONNECT.

# INCORRECT — provoquera WRONG_VERSION_NUMBER
proxies = {
    "http": "https://user:password@proxy_host:port",
    "https": "https://user:password@proxy_host:port",
}

# CORRECT
proxies = {
    "http": "http://user:password@proxy_host:port",
    "https": "http://user:password@proxy_host:port",
}

La deuxième cause fréquente est un port qui ne supporte physiquement pas TLS. Par exemple, si le fournisseur de proxy a attribué le port 8080 uniquement pour le trafic HTTP, et que vous essayez d'établir une connexion TLS directement (en contournant CONNECT), le serveur répondra avec du texte normal au lieu d'un handshake TLS, et la bibliothèque cliente OpenSSL renverra exactement cette erreur. Vérifiez dans la documentation de votre fournisseur de proxy quels ports et protocoles (HTTP/HTTPS/SOCKS5) sont disponibles sur chaque port spécifique.

La troisième option est que vous utilisez socket ou des bibliothèques tierces pour établir une connexion TCP directe avec le proxy, en contournant le mécanisme standard de requests. Dans ce cas, assurez-vous que l'enveloppe TLS est appliquée uniquement pour le véritable site cible, et non pour la connexion elle-même avec le proxy.

407 Proxy Authentication Required : autorisation

Le code de réponse 407 est l'analogue du 401 standard, mais pour les serveurs proxy. Il signifie que le serveur exige une autorisation, et que vous ne l'avez soit pas fournie, soit fournie dans un format incorrect.

Méthode 1 : login et mot de passe directement dans l'URL

proxies = {
    "http": "http://login:password@proxy_host:port",
    "https": "http://login:password@proxy_host:port",
}

response = requests.get("https://httpbin.org/ip", proxies=proxies)

Cette méthode est la plus simple, mais si le login ou le mot de passe contient des caractères spéciaux (par exemple, @, :, /), ils doivent être échappés via urllib.parse.quote :

from urllib.parse import quote

login = quote("user@domain")
password = quote("p@ssw:ord!")

proxy_url = f"http://{login}:{password}@proxy_host:port"
proxies = {"http": proxy_url, "https": proxy_url}

Méthode 2 : en-tête Proxy-Authorization manuellement

Si le fournisseur de proxy utilise un schéma d'autorisation non standard ou si vous avez besoin de plus de contrôle, vous pouvez construire l'en-tête manuellement via l'encodage base64 :

import base64
import requests

login = "user"
password = "password"
credentials = base64.b64encode(f"{login}:{password}".encode()).decode()

headers = {
    "Proxy-Authorization": f"Basic {credentials}"
}

proxies = {
    "http": "http://proxy_host:port",
    "https": "http://proxy_host:port",
}

response = requests.get(
    "https://httpbin.org/ip",
    proxies=proxies,
    headers=headers,
    timeout=10
)

Important : certains fournisseurs de proxy lient l'autorisation non pas au login/mot de passe, mais à une IP client statique spécifique (whitelist). Si vous changez d'IP (par exemple, en utilisant un VPN ou une IP dynamique du fournisseur), l'erreur 407 peut apparaître même avec des identifiants corrects — car votre IP externe actuelle n'est pas sur la liste blanche du côté du proxy. La solution consiste à se connecter au compte personnel du fournisseur et à mettre à jour la liste des IP autorisées, ou à passer à l'autorisation par login/mot de passe au lieu de la whitelist IP.

Logique de réessai et gestion des exceptions

En pratique, les erreurs de proxy sont rarement ponctuelles — surtout lors de parsing en masse. Une architecture de code correcte doit prévoir des tentatives de réessai avec un délai et un journal de chaque erreur séparément.

import time
import requests
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def fetch_with_retry(url, proxies, max_retries=3, backoff=2):
    for attempt in range(1, max_retries + 1):
        try:
            response = requests.get(url, proxies=proxies, timeout=15)
            response.raise_for_status()
            return response
        except requests.exceptions.ProxyError as e:
            logger.warning(f"[Tentative {attempt}] ProxyError: {e}")
        except requests.exceptions.SSLError as e:
            logger.warning(f"[Tentative {attempt}] SSLError: {e}")
        except requests.exceptions.HTTPError as e:
            if response.status_code == 407:
                logger.error("407 : vérifiez le login/mot de passe ou la whitelist IP du proxy")
                break
            logger.warning(f"[Tentative {attempt}] HTTPError: {e}")
        except requests.exceptions.Timeout:
            logger.warning(f"[Tentative {attempt}] Délai d'attente de la connexion")

        time.sleep(backoff * attempt)

    raise RuntimeError(f"Impossible d'obtenir {url} après {max_retries} tentatives")

Cette approche permet d'économiser des heures de débogage : au lieu de faire planter le script à la première difficulté réseau, vous obtenez un journal clair indiquant le type d'erreur spécifique à chaque tentative.

Rotation des proxies et lutte contre les bans

Si ProxyError et 407 sont souvent liés aux paramètres du code, les bans massifs et les refus de connexion stables sur des domaines spécifiques indiquent presque toujours un problème avec la qualité même du pool d'IP. Pour des tâches telles que le parsing de marketplaces (Wildberries, Ozon, Avito) ou le travail avec des réseaux sociaux, il est recommandé de faire tourner les IP à chaque requête ou à intervalles réguliers.

import random

proxy_pool = [
    "http://user:[email protected]:8000",
    "http://user:[email protected]:8000",
    "http://user:[email protected]:8000",
]

def get_random_proxy():
    proxy = random.choice(proxy_pool)
    return {"http": proxy, "https": proxy}

response = requests.get(url, proxies=get_random_proxy(), timeout=15)

Pour les parseurs en production, il est généralement plus pratique de prendre des proxies avec rotation automatique du côté du fournisseur — ainsi, il n'est pas nécessaire de conserver une liste d'IP soi-même, la rotation se fait de manière transparente via un seul endpoint. Dans ce cas, les proxies résidentiels avec rotation conviennent particulièrement bien pour contourner la protection anti-bot sur les sites avec des systèmes de détection stricts, tandis que les proxies de centres de données sont un bon choix, si la vitesse des requêtes est importante et que le site cible ne bloque pas agressivement les plages de centres de données.

Type de proxy Vitesse Risque de ban Tâches
Centres de données Élevée Moyenne-élevée Parsing rapide des API ouvertes
Résidentiels Moyenne Faible Parsing de marketplaces, réseaux sociaux
Mobiles Moyenne-faible Minimale Travail avec des systèmes anti-bot stricts

Session, délais d'attente et vérification SSL

Pour les scripts de longue durée (par exemple, les parseurs cycliques), il est préférable d'utiliser requests.Session() au lieu d'appels séparés requests.get(). La session réutilise les connexions TCP, ce qui réduit la charge sur le serveur proxy et diminue le nombre de ProxyError lors de volumes élevés de requêtes.

session = requests.Session()
session.proxies.update({
    "http": "http://user:password@proxy_host:port",
    "https": "http://user:password@proxy_host:port",
})

for url in urls_list:
    try:
        resp = session.get(url, timeout=15)
        print(resp.status_code, url)
    except requests.exceptions.RequestException as e:
        print(f"Erreur sur {url} : {e}")

Il convient de mentionner séparément le paramètre verify. Si vous travaillez avec un proxy qui remplace lui-même le certificat SSL (proxy MITM pour le débogage ou certains proxies d'entreprise), requests peut se plaindre d'un certificat non correspondant. Désactiver complètement la vérification (verify=False) ne doit être fait que dans un environnement de test — en production, cela crée un risque d'attaques "homme du milieu" :

# Seulement pour le débogage local !
response = requests.get(url, proxies=proxies, verify=False, timeout=15)

# La bonne méthode consiste à spécifier le chemin vers le certificat de proxy de confiance
response = requests.get(url, proxies=proxies, verify="/path/to/proxy_cert.pem")

Liste de vérification pour le diagnostic des erreurs

Si le script échoue avec une erreur de proxy, passez en revue cette liste de haut en bas — dans 90 % des cas, la cause se trouve dans les trois premiers points.

  1. Vérifiez le schéma dans le dictionnaire proxies — il doit presque toujours être http://, même pour les requêtes HTTPS.
  2. Testez le proxy directement via curl -x, pour exclure un problème du côté du code.
  3. Assurez-vous que le login et le mot de passe ne contiennent pas de caractères spéciaux non échappés dans l'URL.
  4. Vérifiez si le proxy est lié à une IP spécifique (whitelist) au lieu d'une autorisation par login/mot de passe.
  5. Augmentez le timeout à 15-30 secondes avant de considérer le proxy comme non fonctionnel.
  6. Si l'erreur n'apparaît que sur un site spécifique, le problème réside dans la réputation de l'IP, et non dans le code.
  7. Ajoutez une logique de réessai avec un délai exponentiel au lieu de tentatives uniques.
  8. Lors du parsing en masse, utilisez la rotation des IP, et non un seul proxy statique pour toutes les requêtes.

Conclusion

La plupart des erreurs de proxy dans Python requests se résument à trois choses : un schéma incorrect dans l'URL du proxy (http au lieu de https), l'absence ou un format incorrect d'autorisation, et la qualité même du pool d'IP. ProxyError indique généralement un problème de connexion ou un ban d'IP, SSLError WRONG_VERSION_NUMBER indique presque toujours un schéma de proxy incorrect, et 407 — un problème avec le login/mot de passe ou la whitelist.

Si vous êtes régulièrement confronté à des bans et des erreurs 407 lors du parsing de marketplaces ou de réseaux sociaux, il est temps de revoir la source même des proxies, et pas seulement le code. Pour un parsing stable avec un faible pourcentage de blocages, nous vous recommandons d'essayer des proxies résidentiels — ils utilisent de véritables adresses IP et provoquent beaucoup moins souvent des ProxyError sur des sites protégés. Si la vitesse et le volume des requêtes sont prioritaires, plutôt que de contourner des systèmes anti-bot complexes, envisagez des proxies de centres de données — ils sont moins chers et plus rapides pour des tâches de parsing simples.