Cada librería HTTP de Python toma un proxy como una URL de la forma scheme://user:pass@host:port; lo que difiere es el nombre del parámetro, si se establece por cliente o por petición, y cómo se manejan SOCKS5 y DNS. Los ejemplos siguientes son el código exacto que funciona con las versiones actuales de requests, httpx y aiohttp — cada uno se ejecutó a través de un proxy autenticado antes de publicar este artículo — seguido de las tres cosas que explican la mayoría de las preguntas de “mi proxy no funciona en Python”: nombres de parámetros incorrectos copiados de tutoriales antiguos, extras de SOCKS faltantes y una huella TLS que delata al script independientemente de la IP.

La URL del proxy

http://user:pass@proxy.example.com:8080     # Proxy HTTP, usado tanto para destinos http:// como https://
socks5://user:pass@proxy.example.com:1080   # SOCKS5, DNS resuelto localmente
socks5h://user:pass@proxy.example.com:1080  # SOCKS5, DNS resuelto por el proxy (usa este)

Dos puntos que confunden a la gente. Primero, un proxy HTTP también maneja destinos HTTPS — el cliente envía CONNECT y tuneliza TLS a través de él — por lo que casi nunca necesitas un esquema de proxy https://; eso significaría TLS hacia el propio proxy, que pocos proveedores ofrecen. Segundo, si tu nombre de usuario contiene parámetros de rotación (user-country-de-session-abc123) o tu contraseña contiene @, : o /, codifícalos en porcentaje (urllib.parse.quote(pw, safe='')) o la URL se analizará incorrectamente.

requests

import requests

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

# por petición
r = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=20)
print(r.json())

# por sesión (reutiliza conexiones; la forma normal)
s = requests.Session()
s.proxies.update(proxies)
r = s.get("https://api.ipify.org?format=json", timeout=20)

El diccionario está indexado por el esquema del destino: las peticiones a URLs http:// usan la entrada "http", las peticiones a URLs https:// usan "https". Establece ambas al mismo proxy a menos que tengas una razón para no hacerlo. Siempre pasa un timeout; un proxy colgado cuelga tu script para siempre.

SOCKS5 necesita el extra: pip install "requests[socks]". Luego proxies = {"http": "socks5h://…", "https": "socks5h://…"}. Sin el extra obtienes InvalidSchema: Missing dependencies for SOCKS support.

Variables de entorno: requests respeta HTTP_PROXY / HTTPS_PROXY / NO_PROXY por defecto (trust_env=True). Si un proxy está misteriosamente en uso cuando no lo configuraste, o no en uso cuando lo hiciste, revisa el entorno.

httpx

import httpx

PROXY = "http://user:pass@proxy.example.com:8080"

# síncrono
with httpx.Client(proxy=PROXY, timeout=20) as client:
    r = client.get("https://api.ipify.org?format=json")
    print(r.json())

# asíncrono
import asyncio
async def main():
    async with httpx.AsyncClient(proxy=PROXY, timeout=20) as client:
        r = await client.get("https://api.ipify.org?format=json")
        print(r.json())
asyncio.run(main())

El parámetro es proxy= (singular). Los tutoriales antiguos muestran proxies={"http://": …, "https://": …}; esa palabra clave fue deprecada en httpx 0.26 y eliminada en 0.28, y pasarla ahora lanza TypeError: Client.__init__() got an unexpected keyword argument 'proxies'. Si necesitas diferentes proxies para diferentes destinos, usa mounts:

mounts = {
    "https://api.example.com": httpx.HTTPTransport(proxy="http://user:pass@dc-proxy:8080"),
    "all://": httpx.HTTPTransport(proxy="http://user:pass@resi-proxy:8080"),
}
client = httpx.Client(mounts=mounts)

SOCKS5: pip install "httpx[socks]", luego proxy="socks5://…". httpx resuelve los nombres de host a través del proxy SOCKS por defecto. HTTP/2: pip install "httpx[http2]" y httpx.Client(http2=True, …) — relevante porque un navegador a través de proxy usa HTTP/2 de todos modos (ver HTTP/2 y HTTP/3 a través de proxies).

aiohttp

import aiohttp, asyncio

PROXY = "http://user:pass@proxy.example.com:8080"

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.ipify.org?format=json",
                               proxy=PROXY,
                               timeout=aiohttp.ClientTimeout(total=20)) as r:
            print(await r.json())

asyncio.run(main())

El proxy se pasa por petición (o se establece una vez mediante un connector personalizado). Las credenciales van en la URL; la forma separada proxy_auth=aiohttp.BasicAuth(...) todavía funciona en 3.x pero está deprecada desde 3.14 y programada para eliminación en 4.0, así que prefiere la forma de URL. aiohttp no lee las variables de entorno HTTP_PROXY a menos que crees la sesión con trust_env=True. aiohttp nativo solo habla proxies HTTP; para SOCKS usa el paquete aiohttp-socks y su ProxyConnector.

Rotación de proxies: el patrón de ID de sesión

Con una puerta de enlace de backconnect (ver cómo funcionan las puertas de enlace de rotación) rara vez rotas en Python en absoluto — rotas cambiando el nombre de usuario. Un nuevo ID de sesión significa una nueva IP de salida; uno reutilizado la mantiene:

import secrets, requests

def proxy_for(session_id: str, country: str = "de") -> dict:
    user = f"customer-country-{country}-session-{session_id}"   # formato específico del proveedor
    url  = f"http://{user}:PASSWORD@gate.provider.com:7777"
    return {"http": url, "https": url}

# sticky: misma IP para un flujo de varios pasos
sid = secrets.token_hex(4)
s = requests.Session(); s.proxies.update(proxy_for(sid))
s.get("https://shop.example/login"); s.post("https://shop.example/cart", data=...)

# rotación: nueva IP por elemento
for url in urls:
    requests.get(url, proxies=proxy_for(secrets.token_hex(4)), timeout=20)

Rota también en caso de fallo: captura 403/429 y un marcador de CAPTCHA, y reintenta con un nuevo ID de sesión y un nuevo almacén de cookies — la misma IP con las mismas cookies es el mismo visitante.

El error que ningún proxy corrige: huella TLS

Las tres librerías hacen conexiones TLS a través del módulo ssl de Python (OpenSSL). Los sistemas anti-bot identifican ese handshake y saben que no es un navegador, sin importar cómo establezcas el User-Agent. Detrás de una IP residencial perfecta, el script sigue siendo identificado como script — el mecanismo está en explicación de la huella TLS. Para destinos protegidos, cambia a un cliente que imite a un navegador; curl_cffi mantiene una API similar a requests:

# pip install curl_cffi
from curl_cffi import requests as cffi_requests

r = cffi_requests.get("https://protected.example/",
                      impersonate="chrome",                 # reproduce la huella TLS + HTTP/2 de Chrome
                      proxies={"https": "http://user:pass@proxy.example.com:8080"},
                      timeout=20)

Luego mantén el resto de la historia coherente — un User-Agent de Chrome, pistas de cliente sec-ch-ua coincidentes, Accept-Language plausible para el país del proxy. Para sitios que además requieren desafíos de JavaScript, ningún cliente HTTP es suficiente; usa un navegador real como en Proxies en Playwright, Puppeteer y Selenium.

Errores y su significado

VesSignificaSolución
ProxyError … 407 Proxy Authentication RequiredCredenciales incorrectas, o tu IP no está en la lista blancaRevisa usuario/contraseña (¡cadenas de rotación largas!), codifica caracteres especiales, verifica la lista blanca
InvalidSchema: Missing dependencies for SOCKS supportrequests sin el extra de sockspip install "requests[socks]"
TypeError: … unexpected keyword argument 'proxies'httpx ≥ 0.28 con el parámetro eliminadoUsa proxy= o mounts=
ProxyError … Cannot connect to proxy / ConnectTimeoutHost/puerto incorrecto, proxy caído o firewallVerifica con curl -x; prueba el otro protocolo
IP real devuelta por un endpoint de eco de IPProxy no aplicado (clave de diccionario incorrecta, anulación por variable de entorno, parámetro por petición faltante en aiohttp)Imprime session.proxies; revisa trust_env; pasa proxy= en la petición
SSLError solo a través del proxyEl proxy está interceptando TLS (MITM) o esquema incorrectoUsa esquema http:// para el proxy; pregunta al proveedor si terminan TLS
403/CAPTCHA en sitios protegidos, la IP se ve bienLa huella TLS/HTTP identifica a Pythoncurl_cffi con impersonate, o un navegador real
Funciona por un tiempo, luego 429Límite de tasa por IP alcanzadoRota el ID de sesión; reduce la velocidad; ver la guía de rotación

Una plantilla mínima y correcta

import requests, secrets
from urllib.parse import quote

GATE, PORT = "gate.provider.com", 7777
USER_BASE, PASS = "customer-country-de", quote("p@ss:word", safe="")

def make_session() -> requests.Session:
    sid = secrets.token_hex(4)
    url = f"http://{USER_BASE}-session-{sid}:{PASS}@{GATE}:{PORT}"
    s = requests.Session()
    s.proxies.update({"http": url, "https": url})
    s.headers.update({"User-Agent": "python-requests"})   # UA honesto para destinos tolerantes
    return s

s = make_session()
r = s.get("https://api.ipify.org?format=json", timeout=20)
r.raise_for_status()
print(r.json())

Verifica que la IP cambie, luego construye sobre ello. Para una verificación previa completa (fugas, cabeceras, listas negras) ver Cómo probar un proxy; para elegir entre datacenter y residencial para un destino dado, la comparación de tipos de proxy.

Preguntas frecuentes

¿Cómo uso un proxy con Python requests?

Pasa proxies={"http": url, "https": url} a la petición o establécela en una Session, donde url es http://user:pass@host:port. Añade timeout=. Para SOCKS5 instala requests[socks] y usa socks5h://.

¿Por qué httpx dice ‘unexpected keyword argument proxies’?

Porque proxies= fue eliminado en httpx 0.28. Usa proxy="http://…" para un solo proxy, o mounts= para proxies por destino.

¿Cómo configuro un proxy en aiohttp?

Pasa proxy="http://user:pass@host:port" a cada petición (session.get(url, proxy=…)). El parámetro proxy_auth= está deprecado; pon las credenciales en la URL. Usa aiohttp-socks para SOCKS.

¿Cómo roto proxies en Python?

Con un proveedor de backconnect, cambia el ID de sesión en el nombre de usuario del proxy por petición o por flujo — no necesitas lista de IPs. Rota en 403/429 y empareja cada nueva IP con un almacén de cookies fresco.

¿Por qué mi script es bloqueado cuando mi navegador en el mismo proxy no lo es?

Huella TLS y HTTP/2: el módulo ssl de Python no parece un navegador. Usa curl_cffi con impersonate= o automatiza un navegador real.

¿Debo usar socks5 o socks5h?

socks5h. La h hace que el proxy resuelva los nombres de host, previniendo fugas de DNS y permitiéndote alcanzar hosts que solo el proxy puede resolver.