# Proxies residenciales rotativos en Python: requests, httpx y aiohttp

> Proxies residenciales rotativos en Python: requests con reintentos, httpx síncrono y asíncrono, aiohttp, sesiones persistentes y conteo de bytes. Código con sintaxis verificada.

- URL: https://crawlrelay.com/es/docs/python/
- Última actualización: 2026-10-13
- Publisher: CrawlRelay (https://crawlrelay.com)

Patrones de producción, no un solo comando: reintentos que respetan Retry-After, una IP nueva por petición o una IP por flujo, concurrencia por host en asyncio y los bytes que realmente consume cada página.

**Respuesta rápida — ¿Cómo uso proxies residenciales rotativos en Python?**

Pasa `http://USERNAME:PASSWORD@gw.crawlrelay.com:7777` como proxy tanto para `http` como para `https` en requests, como `proxy=` en httpx 0.26+, o como `proxy=` por petición en aiohttp. **CrawlRelay** proxies residenciales rotan en cada conexión nueva y permanecen persistentes con `-session-<id>` en el nombre de usuario. Cuestan 1,50 US$ por 1 GB y 0,50 US$/GB desde 50 GB. Pagados en cripto, sin KYC.

## Configuración de Python de un vistazo

| Atributo | Valor |
| --- | --- |
| requests | `proxies={"http": url, "https": url}`; reintentos vía urllib3 `Retry` |
| httpx | `httpx.Client(proxy=url)`; el antiguo argumento `proxies=` fue eliminado en httpx 0.28 |
| aiohttp | `session.get(url, proxy="http://USER:PASS@host:port")`, credenciales en la URL del proxy |
| SOCKS5 | `pip install "requests[socks]"` o `httpx[socks]`, luego `socks5h://` en el puerto 7778 |
| Nueva IP por solicitud | Desactivar keep-alive: `Connection: close`, `max_keepalive_connections=0` o `force_close=True` |
| Misma IP para un flujo | `-session-<id>-ttl-<minutes>` en el nombre de usuario, hasta 30 minutos |
| Esquema de URL del proxy | Siempre `http://` para el puerto HTTP, incluso en sitios HTTPS |

## ¿Cómo uso un proxy con requests y reintentos que respeten Retry-After?

Monta un `HTTPAdapter` con el `Retry` de urllib3 sobre un `Session`. Reintenta errores de conexión y los estados que indiques, espera con retroceso exponencial y respeta la cabecera `Retry-After` que envían los sitios con 429 y 503. Configura `trust_env = False` para que una variable `HTTPS_PROXY` residual no sobrescriba tu proxy.

*requests + reintentos urllib3*

```python
import os

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

USER = os.environ.get("CRAWLRELAY_USER", "USERNAME-country-us")
PASSWORD = os.environ.get("CRAWLRELAY_PASSWORD", "PASSWORD")
PROXY = "http://%s:%s@gw.crawlrelay.com:7777" % (USER, PASSWORD)

retry = Retry(
    total=4,
    connect=2,
    backoff_factor=1.0,  # waits of 0 s, 2 s, 4 s, 8 s (Retry-After wins on 429/503)
    status_forcelist=(429, 502, 503, 504),
    allowed_methods=frozenset({"GET", "HEAD"}),
    respect_retry_after_header=True,
)

session = requests.Session()
session.trust_env = False  # ignore HTTP(S)_PROXY from the environment
session.mount("http://", HTTPAdapter(max_retries=retry))
session.mount("https://", HTTPAdapter(max_retries=retry))
session.proxies = {"http": PROXY, "https": PROXY}
session.headers["User-Agent"] = "ExampleCrawler/1.0 (+https://example.com/crawler-info)"
session.headers["Connection"] = "close"  # new connection per request, so usually a new exit IP

r = session.get("https://api.ipify.org", timeout=(10, 30))
print(r.status_code, r.text)
```

## ¿Cómo construyo URLs de proxy dirigidas de forma segura?

Genera el nombre de usuario en una función y valídalo. Un error tipográfico en el código de país o un carácter no codificado en la contraseña produce un error [407](https://crawlrelay.com/es/docs/errors/407-proxy-authentication-required/) que parece un problema de credenciales. El ayudante abajo mantiene el orden documentado (`USERNAME-country-{cc}-state-{state}-city-{city}-zip-{zip}-asn-{asn}-session-{id}-ttl-{minutes}`), rechaza claves desconocidas y codifica en URL ambas credenciales.

*Generador de usuario y URL de proxy*

```python
import re
from urllib.parse import quote

ORDER = ("country", "state", "city", "zip", "asn", "session", "ttl")
VALUE = re.compile(r"^[a-z0-9_]+$")


def build_username(base, **params):
    """build_username("USERNAME", country="us", city="new_york", session="job1", ttl=30)"""
    parts = [base]
    for key in ORDER:
        value = params.pop(key, None)
        if value is None:
            continue
        value = str(value).lower()
        if not VALUE.match(value):
            raise ValueError("bad value for %s: %r" % (key, value))
        if key == "ttl" and not 1 <= int(value) <= 30:
            raise ValueError("ttl must be 1 to 30 minutes")
        parts.append("%s-%s" % (key, value))
    if params:
        raise ValueError("unknown parameters: %s" % ", ".join(sorted(params)))
    return "-".join(parts)


def proxy_url(username, password, socks=False):
    scheme, port = ("socks5h", 7778) if socks else ("http", 7777)
    return "%s://%s:%s@gw.crawlrelay.com:%d" % (
        scheme, quote(username, safe=""), quote(password, safe=""), port)


print(proxy_url(build_username("USERNAME", country="us", state="ny", city="new_york", session="job1", ttl=20), "p@ss:word"))
```

## ¿Cómo obtengo una IP nueva en cada petición con httpx?

Desactiva keep-alive. httpx agrupa conexiones por defecto, y una conexión reutilizada mantiene su IP de salida. Con `max_keepalive_connections=0`, cada petición abre una conexión nueva hacia el gateway y suele salir por un IP residencial diferente. `num_bytes_downloaded` muestra los bytes recibidos en la transmisión, que es lo más cercano a la facturación por tráfico.

*httpx: cliente rotativo*

```python
import httpx

PROXY = "http://USERNAME-country-gb:PASSWORD@gw.crawlrelay.com:7777"

# No keep-alive: every request opens a new connection, so it usually gets a new exit IP
with httpx.Client(
    proxy=PROXY,
    timeout=httpx.Timeout(30.0, connect=10.0),
    limits=httpx.Limits(max_keepalive_connections=0),
    follow_redirects=True,
) as client:
    for _ in range(3):
        r = client.get("https://api.ipify.org")
        print(r.text, r.num_bytes_downloaded, "bytes on the wire")
```

## ¿Cómo hago scraping concurrente con asyncio sin saturar un sitio?

Limita la concurrencia **por host**, no solo a nivel global. Un límite global de 50 permite que las 50 peticiones toquen el mismo sitio cuando tu lista está ordenada por dominio. Un semáforo por hostname mantiene cada sitio a un nivel respetuoso mientras el crawl sigue rápido. Empieza de 2 a 4 por host y lee la [guía de concurrencia](https://crawlrelay.com/es/docs/concurrency/) antes de subirlo.

*httpx.AsyncClient con semáforo por host*

```python
import asyncio
from collections import defaultdict
from urllib.parse import urlsplit

import httpx

PROXY = "http://USERNAME-country-us:PASSWORD@gw.crawlrelay.com:7777"
PER_HOST = 4  # parallel requests per target host
host_slots = defaultdict(lambda: asyncio.Semaphore(PER_HOST))


async def fetch(client, url):
    async with host_slots[urlsplit(url).hostname]:
        try:
            r = await client.get(url)
            return url, r.status_code, r.num_bytes_downloaded
        except httpx.HTTPError as exc:
            return url, type(exc).__name__, 0


async def main(urls):
    limits = httpx.Limits(max_connections=50, max_keepalive_connections=0)
    timeout = httpx.Timeout(30.0, connect=10.0)
    async with httpx.AsyncClient(proxy=PROXY, limits=limits, timeout=timeout) as client:
        for result in await asyncio.gather(*(fetch(client, u) for u in urls)):
            print(*result)


asyncio.run(main(["https://example.com/", "https://example.org/", "https://example.net/"]))
```

## ¿Cómo uso un proxy con aiohttp?

Pasa el gateway como `proxy=` en cada petición, con las credenciales en la URL del proxy. Funciona en cualquier versión de aiohttp 3.x sin avisos; `BasicAuth` y el parámetro `proxy_auth=` están obsoletos desde aiohttp 3.14 y se eliminarán en 4.0. Un `TCPConnector(force_close=True)` cierra cada conexión tras la respuesta, así que cada petición suele tener una IP nueva. Sin él, aiohttp reutiliza conexiones y mantienes la misma IP un tiempo.

*aiohttp con credenciales en la URL del proxy*

```python
import asyncio

import aiohttp

# Credentials go in the proxy URL; percent-encode a password that contains @ or :
PROXY = "http://USERNAME-country-fr:PASSWORD@gw.crawlrelay.com:7777"


async def main():
    # force_close=True: one connection per request, so each request usually gets a new exit IP
    connector = aiohttp.TCPConnector(limit=20, force_close=True)
    timeout = aiohttp.ClientTimeout(total=60, sock_connect=10)
    async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
        for _ in range(3):
            async with session.get("https://api.ipify.org", proxy=PROXY) as resp:
                print(resp.status, await resp.text())


asyncio.run(main())
```

## ¿Cómo mantengo una IP para un flujo de varios pasos?

Genera un ID de sesión por flujo y ponlo en el nombre de usuario con un TTL más largo que el flujo. Keep-alive va bien aquí: quieres la misma IP de salida. Si el dispositivo residencial detrás de la sesión se desconecta, la IP puede cambiar antes de que acabe el TTL, así que haz que el flujo pueda restartarse desde su último punto.

*Sesión persistente con requests*

```python
import uuid

import requests

session_id = uuid.uuid4().hex[:10]  # one ID per flow, lowercase letters and digits
user = "USERNAME-country-us-state-ca-session-%s-ttl-20" % session_id
proxy = "http://%s:PASSWORD@gw.crawlrelay.com:7777" % user

with requests.Session() as s:
    s.proxies = {"http": proxy, "https": proxy}
    first = s.get("https://api.ipify.org", timeout=(10, 30)).text
    second = s.get("https://api.ipify.org", timeout=(10, 30)).text
    print(first, second, "same exit" if first == second else "exit changed")
```

## ¿Cuánto tráfico usa un scraper Python por página?

Mídelo en 100 páginas reales antes de comprar volumen. Registra `num_bytes_downloaded` en httpx, o la longitud del cuerpo comprimido en tu middleware, y multiplica por el número de páginas. Las peticiones solo HTML suelen costar decenas de kilobytes por página; la [guía de ancho de banda](https://crawlrelay.com/es/guides/bandwidth-calculator/) da los números y el coste por 1.000 páginas a precios CrawlRelay.

Tres hábitos reducen el tráfico sin cambiar resultados:

- Envía `Accept-Encoding: gzip, br` (httpx y requests envían gzip por defecto) para que las páginas viajen comprimidas.
- Obtén el JSON que carga la página, cuando el sitio lo documente o permita, en vez del HTML completo.
- Omite páginas sin cambios con `If-None-Match` o `If-Modified-Since`: un `304 Not Modified` cuesta unos cientos de bytes.

## Python y proxies: preguntas

### requests, httpx o aiohttp: ¿cuál debo usar para scraping?

Usa requests para scripts simples y crawls pequeños. Usa httpx cuando quieras la misma API en código síncrono y asíncrono, y HTTP/2 vía `httpx[http2]`. Usa aiohttp para una concurrencia muy alta en asyncio puro. Para un crawl con programación, deduplicación y pipelines, usa [Scrapy](https://crawlrelay.com/es/docs/scrapy/) en lugar de escribir esas partes tú mismo.

### ¿Por qué recibo SSL: WRONG_VERSION_NUMBER con mi proxy en Python?

Probablemente escribiste `https://` delante de la dirección del proxy. El puerto de la puerta de enlace 7777 usa HTTP simple y abre un túnel CONNECT para sitios HTTPS, así que la URL del proxy debe empezar con `http://` en ambas claves `http` y `https`. Mira la [página de error](https://crawlrelay.com/es/docs/errors/ssl-wrong-version-number/).

### ¿Cómo usar proxies SOCKS5 en Python?

Instala el extra (`pip install "requests[socks]"` o `pip install "httpx[socks]"`), luego usa `socks5h://USERNAME:PASSWORD@gw.crawlrelay.com:7778`. El `h` envía nombres de host al proxy, así el DNS se resuelve en la salida y ves las mismas respuestas regionales que visitantes locales.

### ¿Por qué mi proxy rotativo devuelve la misma IP dos veces?

Tu cliente reutilizó una conexión keep-alive, y la rotación ocurre por conexión nueva. Envía `Connection: close` en requests, configura `max_keepalive_connections=0` en httpx, o usa `force_close=True` en aiohttp. También verifica que el nombre de usuario no tenga parte `session-`, que mantiene una IP a propósito.

### ¿Cómo configuro los timeouts para solicitudes con proxy?

Ponlos explícitamente: requests no tiene timeout por defecto. Un timeout de conexión de unos 10 segundos y lectura de 30 segundos va bien para páginas HTML; salidas móviles y destinos lentos necesitan 45 a 60 segundos. Reintenta un timeout una vez con conexión nueva en lugar de esperar más. Mira [read timeouts](https://crawlrelay.com/es/docs/errors/read-timeout/).

### ¿Puedo usar estos proxies en Jupyter o con pandas?

Sí. Todo lo que use requests o httpx debajo toma las mismas configuraciones de proxy. Para `pandas.read_html` o `read_csv` en una URL, descarga el contenido con una llamada proxied requests primero, después pásale el texto o bytes a pandas.

**Ejecuta tu scraper Python con IPs residenciales** 1,50 US$ por 1 GB, 0,50 US$/GB desde 50 GB. [Ver precios](https://crawlrelay.com/es/pricing/) · [Guía de Scrapy](https://crawlrelay.com/es/docs/scrapy/)

---

CrawlRelay: CrawlRelay es un servicio de proxies residenciales rotativos y proxies móviles 4G/5G para desarrolladores, ideal para scrapers y pipelines de datos de IA: tráfico prepago sin vencimiento desde 0,50 US$/GB, pagado con criptomonedas, sin subir documentos de identidad. Sin KYC — sin ID, sin selfie, sin documentos. Paga con criptomonedas.
