Respuesta rápida
¿Cómo uso proxies residenciales rotativos en Python?
Pasa http://USERNAME:[email protected]: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
| 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.
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:%[email protected]: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 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.
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:%[email protected]:%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.
import httpx
PROXY = "http://USERNAME-country-gb:[email protected]: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 antes de subirlo.
import asyncio
from collections import defaultdict
from urllib.parse import urlsplit
import httpx
PROXY = "http://USERNAME-country-us:[email protected]: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.
import asyncio
import aiohttp
# Credentials go in the proxy URL; percent-encode a password that contains @ or :
PROXY = "http://USERNAME-country-fr:[email protected]: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.
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:[email protected]: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 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-MatchoIf-Modified-Since: un304 Not Modifiedcuesta 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 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.
¿Cómo usar proxies SOCKS5 en Python?
Instala el extra (pip install "requests[socks]" o pip install "httpx[socks]"), luego usa socks5h://USERNAME:[email protected]: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.
¿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.