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.
- Vérifiez le schéma dans le dictionnaire
proxies— il doit presque toujours êtrehttp://, même pour les requêtes HTTPS. - Testez le proxy directement via
curl -x, pour exclure un problème du côté du code. - Assurez-vous que le login et le mot de passe ne contiennent pas de caractères spéciaux non échappés dans l'URL.
- Vérifiez si le proxy est lié à une IP spécifique (whitelist) au lieu d'une autorisation par login/mot de passe.
- Augmentez le
timeoutà 15-30 secondes avant de considérer le proxy comme non fonctionnel. - 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.
- Ajoutez une logique de réessai avec un délai exponentiel au lieu de tentatives uniques.
- 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.