← Volver al blog

Python requests: ProxyError, SSLError WRONG_VERSION_NUMBER y 407 — cómo solucionarlo en la práctica

Análisis detallado de errores comunes al trabajar con proxies en Python requests: ProxyError, SSLError WRONG_VERSION_NUMBER, 407 Proxy Authentication Required. Ejemplos de código funcional para cada situación.

📅11 de noviembre de 2026

Si estás raspando sitios o automatizando solicitudes a través de proxies en Python, tarde o temprano te encontrarás con tres errores clásicos: ProxyError, SSLError: WRONG_VERSION_NUMBER y HTTP 407 Proxy Authentication Required. En este artículo analizaremos la causa de cada uno de ellos y mostraremos código funcional para solucionarlos.

Cómo requests trabaja con proxies: sintaxis básica

La biblioteca requests acepta proxies a través de un diccionario proxies, donde las claves son el esquema de la solicitud (http y https), y los valores son las URL del servidor proxy con autorización opcional.

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())

Ten en cuenta: incluso para solicitudes HTTPS, el esquema del proxy generalmente se indica como http://, porque la mayoría de los servidores proxy aceptan un túnel CONNECT a través del protocolo HTTP, y no lo envuelven en SSL. Si especificas https:// para un proxy que no lo soporta, obtendrás precisamente un SSLError, cuyo análisis se presentará a continuación.

Si trabajas con proxies SOCKS5, necesitarás un paquete adicional:

pip install requests[socks]

# Y la sintaxis de proxies cambia:
proxies = {
    "http": "socks5://user:password@proxy_host:port",
    "https": "socks5://user:password@proxy_host:port",
}

ProxyError: causas y soluciones

requests.exceptions.ProxyError es un envoltorio sobre excepciones de red de bajo nivel que ocurren al intentar establecer una conexión con un servidor proxy. Los cuatro escenarios más comunes son:

1. Proxy no disponible o dirección/puerto incorrectos

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')))

Verifica que el host y el puerto estén ingresados correctamente y que el servidor proxy esté realmente activo. La forma más rápida de diagnosticar es intentar conectarse a través de curl:

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

2. El proxy bloqueó tu solicitud (baneo por IP o User-Agent)

Los proxies de centros de datos a menudo son bloqueados por los sitios objetivo debido a que sus rangos de IP son conocidos por los sistemas antifraude. Si el error aparece de manera consistente al acceder a un sitio específico (por ejemplo, Wildberries, Ozon o redes sociales), y no a todos los sitios a la vez, el problema no está en el proxy en sí, sino en su reputación. En este caso, cambiar a proxies residenciales puede ayudar, ya que utilizan IP de usuarios reales y son menos propensos a ser bloqueados por sistemas antifraude.

3. Límite de conexiones simultáneas excedido

Si estás ejecutando un raspado multihilo y abriendo cientos de conexiones a través del mismo proxy, el servidor puede comenzar a cerrar conexiones adicionales. La solución es utilizar un pool de conexiones a través de requests.adapters.HTTPAdapter con un límite de pool_maxsize y 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. Timeout en el servidor proxy

Si el proxy "se queda colgado" más allá del timeout establecido, verás un ProxyError envuelto en ReadTimeoutError. Aumenta el timeout a un valor razonable (generalmente 15-30 segundos es suficiente) y agrega el manejo de excepciones por separado:

try:
    response = requests.get(url, proxies=proxies, timeout=20)
except requests.exceptions.ProxyError as e:
    print(f"Problema con el proxy: {e}")
except requests.exceptions.Timeout:
    print("El proxy no respondió a tiempo")

SSLError WRONG_VERSION_NUMBER: análisis del problema

Este error es uno de los más confusos a primera vista, pero en realidad casi siempre tiene una sola causa.

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 significa que el cliente intentó realizar un handshake SSL/TLS donde el servidor espera tráfico HTTP normal (sin cifrado). La causa más común radica en un esquema de proxy incorrecto especificado en el diccionario proxies.

Error común: especificar https:// en lugar de http:// en la dirección del servidor proxy. La mayoría de los proxies HTTP(S) aceptan conexiones a través de HTTP normal, incluso cuando están proxyando tráfico HTTPS a través del mecanismo CONNECT.

# INCORRECTO — causará WRONG_VERSION_NUMBER
proxies = {
    "http": "https://user:password@proxy_host:port",
    "https": "https://user:password@proxy_host:port",
}

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

La segunda causa común es un puerto que físicamente no soporta TLS. Por ejemplo, si el proveedor de proxy asignó el puerto 8080 solo para tráfico HTTP, y estás intentando establecer una conexión TLS directamente (saltando CONNECT), el servidor responderá con texto normal en lugar de un handshake TLS, y la biblioteca cliente OpenSSL generará este error. Verifica en la documentación de tu proveedor de proxy qué puertos y protocolos (HTTP/HTTPS/SOCKS5) están disponibles en cada puerto específico.

La tercera opción es que estés utilizando socket o bibliotecas externas para una conexión TCP directa con el proxy, saltando el mecanismo estándar de requests. En este caso, asegúrate de que la envoltura TLS se aplique solo al sitio objetivo real, y no a la propia conexión con el proxy.

407 Proxy Authentication Required: autorización

El código de respuesta 407 es análogo al estándar 401, pero para servidores proxy. Significa que el servidor requiere autorización, y tú no la has proporcionado o la has proporcionado en un formato incorrecto.

Método 1: nombre de usuario y contraseña directamente en la 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)

Este método es el más simple, pero si el nombre de usuario o la contraseña contienen caracteres especiales (por ejemplo, @, :, /), deben ser escapados a través de 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étodo 2: encabezado Proxy-Authorization manualmente

Si el proveedor de proxy utiliza un esquema de autorización no estándar o necesitas más control, puedes construir el encabezado manualmente a través de codificación 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
)

Importante: algunos proveedores de proxy vinculan la autorización no al nombre de usuario/contraseña, sino a una IP estática específica del cliente (whitelist). Si cambias de IP (por ejemplo, trabajas a través de VPN o IP dinámica del proveedor), el error 407 puede aparecer incluso con credenciales correctas, porque tu IP externa actual no está en la lista blanca del proxy. La solución es acceder a la cuenta del proveedor y actualizar la lista de IP permitidas, o cambiar a autorización por nombre de usuario/contraseña en lugar de IP-whitelist.

Lógica de reintentos y manejo de excepciones

En la práctica, los errores de proxy rara vez son únicos, especialmente durante el raspado masivo. La arquitectura correcta del código debe prever reintentos con retraso y registro de cada error por separado.

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"[Intento {attempt}] ProxyError: {e}")
        except requests.exceptions.SSLError as e:
            logger.warning(f"[Intento {attempt}] SSLError: {e}")
        except requests.exceptions.HTTPError as e:
            if response.status_code == 407:
                logger.error("407: verifica el nombre de usuario/contraseña o la whitelist de IP del proxy")
                break
            logger.warning(f"[Intento {attempt}] HTTPError: {e}")
        except requests.exceptions.Timeout:
            logger.warning(f"[Intento {attempt}] Timeout de conexión")

        time.sleep(backoff * attempt)

    raise RuntimeError(f"No se pudo obtener {url} después de {max_retries} intentos")

Este enfoque ahorra horas de depuración: en lugar de que el script falle en el primer problema de red, obtienes un registro claro que indica el tipo específico de error en cada intento.

Rotación de proxies y lucha contra bloqueos

Si ProxyError y 407 están más relacionados con la configuración del código, los bloqueos masivos y las negativas estables de conexión en dominios específicos casi siempre indican un problema con la calidad del propio pool de IP. Para tareas como el raspado de marketplaces (Wildberries, Ozon, Avito) o trabajo con redes sociales, se recomienda rotar IP en cada solicitud o a través de un intervalo establecido.

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)

Para parsers en producción, generalmente es más conveniente utilizar proxies con rotación automática en el lado del proveedor, así no es necesario mantener una lista de IP por tu cuenta, la rotación ocurre de manera transparente a través de un solo endpoint. En este caso, los proxies residenciales son especialmente adecuados para eludir la protección anti-bot en sitios con sistemas de detección estrictos, mientras que los proxies de centros de datos son una buena opción si la velocidad de las solicitudes es importante y el sitio objetivo no bloquea agresivamente los rangos de centros de datos.

Tipo de proxy Velocidad Riesgo de baneo Tareas
Centros de datos Alta Medio-alto Raspado rápido de APIs abiertas
Residenciales Media Bajo Raspado de marketplaces, redes sociales
Móviles Media-baja Mínimo Trabajo con sistemas anti-bot estrictos

Session, timeouts y verificación SSL

Para scripts de larga duración (por ejemplo, parsers cíclicos), es recomendable utilizar requests.Session() en lugar de llamadas individuales requests.get(). Session reutiliza conexiones TCP, lo que reduce la carga en el servidor proxy y disminuye la cantidad de ProxyError en grandes volúmenes de solicitudes.

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"Error en {url}: {e}")

También es importante mencionar el parámetro verify. Si trabajas con un proxy que cambia el certificado SSL (proxy MITM para depuración o algunos proxies corporativos), requests puede quejarse de la discrepancia del certificado. Desactivar la verificación completamente (verify=False) solo debe hacerse en un entorno de prueba, ya que en producción esto crea un riesgo de ataques "hombre en el medio":

# Solo para depuración local!
response = requests.get(url, proxies=proxies, verify=False, timeout=15)

# La forma correcta es especificar la ruta al certificado de proxy de confianza
response = requests.get(url, proxies=proxies, verify="/path/to/proxy_cert.pem")

Lista de verificación para diagnóstico de errores

Si el script falla con un error de proxy, revisa esta lista de arriba a abajo: en el 90% de los casos, la causa se encontrará en los primeros tres puntos.

  1. Verifica el esquema en el diccionario proxies — casi siempre debe ser http://, incluso para solicitudes HTTPS.
  2. Prueba el proxy directamente a través de curl -x, para descartar problemas en el lado del código.
  3. Asegúrate de que el nombre de usuario y la contraseña no contengan caracteres especiales no escapados en la URL.
  4. Verifica si el proxy está vinculado a una IP específica (whitelist) en lugar de autorización por nombre de usuario/contraseña.
  5. Aumenta el timeout a 15-30 segundos antes de considerar que el proxy no está funcionando.
  6. Si el error aparece solo en un sitio específico, el problema está en la reputación de la IP, no en el código.
  7. Agrega lógica de reintentos con retraso exponencial en lugar de intentos únicos.
  8. Para raspado masivo, utiliza rotación de IP, no un solo proxy estático para todas las solicitudes.

Conclusión

La mayoría de los errores de proxy en Python requests se reducen a tres cosas: un esquema incorrecto en la URL del proxy (http en lugar de https), falta o formato incorrecto de autorización, y la calidad del propio pool de IP. ProxyError generalmente indica un problema de conexión o baneo de IP, SSLError WRONG_VERSION_NUMBER casi siempre señala un esquema de proxy incorrecto, y 407 — un problema con el nombre de usuario/contraseña o la whitelist.

Si te enfrentas regularmente a bloqueos y errores 407 al raspar marketplaces o redes sociales, es recomendable revisar la fuente del proxy en sí, no solo el código. Para un raspado estable con un bajo porcentaje de bloqueos, recomendamos probar proxies residenciales — utilizan direcciones IP reales y generan significativamente menos ProxyError en sitios protegidos. Si la velocidad y el volumen de solicitudes son la prioridad, considera proxies de centros de datos — son más baratos y más rápidos para tareas de raspado simples.