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
| Ves | Significa | Solución |
|---|---|---|
ProxyError … 407 Proxy Authentication Required | Credenciales incorrectas, o tu IP no está en la lista blanca | Revisa usuario/contraseña (¡cadenas de rotación largas!), codifica caracteres especiales, verifica la lista blanca |
InvalidSchema: Missing dependencies for SOCKS support | requests sin el extra de socks | pip install "requests[socks]" |
TypeError: … unexpected keyword argument 'proxies' | httpx ≥ 0.28 con el parámetro eliminado | Usa proxy= o mounts= |
ProxyError … Cannot connect to proxy / ConnectTimeout | Host/puerto incorrecto, proxy caído o firewall | Verifica con curl -x; prueba el otro protocolo |
| IP real devuelta por un endpoint de eco de IP | Proxy 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 proxy | El proxy está interceptando TLS (MITM) o esquema incorrecto | Usa esquema http:// para el proxy; pregunta al proveedor si terminan TLS |
| 403/CAPTCHA en sitios protegidos, la IP se ve bien | La huella TLS/HTTP identifica a Python | curl_cffi con impersonate, o un navegador real |
| Funciona por un tiempo, luego 429 | Límite de tasa por IP alcanzado | Rota 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.