Si su script en Python recibe un error 403, CAPTCHA o un baneo por IP, significa que el sitio web objetivo ya lo ha notado. Conectar un proxy a la biblioteca requests resuelve este problema: cambia su dirección IP, elude las restricciones geográficas y distribuye la carga entre varias direcciones. En esta guía, encontrará todo, desde la conexión básica hasta la rotación avanzada con ejemplos de código reales.
¿Por qué se necesitan proxies en scripts de Python?
La mayoría de los sitios web y API rastrean las direcciones IP de las solicitudes entrantes. Si una dirección realiza más de 100 solicitudes por minuto, se bloquea. Esta es una protección estándar contra bots utilizada por Wildberries, Ozon, Avito, Google, Instagram y cientos de otras plataformas. Un proxy permite dirigir la solicitud a través de un servidor intermedio con otra dirección IP, haciéndolo invisible para los sistemas de protección.
Aquí están las principales tareas donde los proxies en Python son críticamente necesarios:
- Scraping de marketplaces — recopilación de precios de Wildberries, Ozon, Yandex.Market sin bloqueos por IP
- Monitoreo de competidores — solicitudes regulares a sitios de competidores cada 5–15 minutos
- Trabajo con API con límites — distribución de solicitudes entre varias IP para no exceder el límite de tasa
- Pruebas de geolocalización — ver cómo se ve el sitio desde diferentes países y regiones
- Automatización de formularios y registros — creación de cuentas o llenado de formularios desde diferentes IP
- Monitoreo SEO — obtención de posiciones desde diferentes regiones de Rusia y otros países
Sin proxies, incluso un scraper bien escrito se encontrará con un bloqueo después de unas pocas horas de trabajo. Con una rotación de IP correctamente configurada, el mismo script puede funcionar durante semanas sin interrupciones.
Configuración básica de proxies en requests
La biblioteca requests admite proxies de forma nativa, no se necesitan paquetes adicionales. El proxy se pasa a través de un diccionario proxies en los parámetros de la solicitud.
El ejemplo más simple es un proxy HTTP para una sola solicitud:
import requests
proxies = {
"http": "http://123.45.67.89:8080",
"https": "http://123.45.67.89:8080",
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
print(response.json())
# {'origin': '123.45.67.89'}
Tenga en cuenta: en el diccionario proxies debe especificar ambas claves — http y https. Si solo se especifica una, las solicitudes del segundo protocolo irán directamente sin proxy. Este es un error común de los principiantes, que hace que la IP real se filtre de todos modos.
Para asegurarse de que el proxy funciona, utilice el servicio httpbin.org/ip — devuelve la dirección IP desde la que llegó la solicitud. Si en la respuesta ve la IP del servidor proxy y no la suya, todo está configurado correctamente.
Proxies HTTP, HTTPS y SOCKS5: diferencias y ejemplos de código
Los proxies vienen en diferentes tipos, y cada uno es adecuado para sus tareas. En el contexto de Python requests, es importante entender la diferencia entre los tres protocolos principales:
| Tipo | Protocolo en URL | Velocidad | Soporte UDP | Mejor escenario |
|---|---|---|---|---|
| HTTP | http:// |
Alta | No | Scraping de sitios HTTP |
| HTTPS | https:// |
Alta | No | Scraping de sitios seguros |
| SOCKS5 | socks5:// |
Media | Sí | Anonimato completo, cualquier protocolo |
Para trabajar con SOCKS5 en Python, necesita instalar un paquete adicional:
pip install requests[socks] # o por separado: pip install PySocks
Después de la instalación, la conexión de un proxy SOCKS5 se ve así:
import requests
# Proxy SOCKS5
proxies = {
"http": "socks5://123.45.67.89:1080",
"https": "socks5://123.45.67.89:1080",
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
print(response.json())
SOCKS5 es el protocolo preferido para tareas donde la anonimidad es importante. A diferencia de los proxies HTTP, SOCKS5 no añade encabezados X-Forwarded-For, que pueden revelar su IP real.
Proxies con autenticación por nombre de usuario y contraseña
La mayoría de los servicios de proxy de pago utilizan autenticación por nombre de usuario y contraseña. Esta es una práctica estándar: sin autenticación, el proxy simplemente no permitirá su solicitud. En la biblioteca requests, los datos de autenticación se pasan directamente en la URL del proxy.
import requests # Formato: protocolo://usuario:contraseña@host:puerto proxy_url = "http://myuser:[email protected]:8080" proxies = { "http": proxy_url, "https": proxy_url, } response = requests.get("https://httpbin.org/ip", proxies=proxies) print(response.status_code) print(response.json())
Si la contraseña o el nombre de usuario contienen caracteres especiales (por ejemplo, @, #, %), deben ser codificados en URL. Para esto, use el módulo urllib.parse:
import requests
from urllib.parse import quote
username = "myuser"
password = "p@ss#word!" # Caracteres especiales
# Codificamos en URL el nombre de usuario y la contraseña
encoded_user = quote(username, safe="")
encoded_pass = quote(password, safe="")
proxy_url = f"http://{encoded_user}:{encoded_pass}@123.45.67.89:8080"
proxies = {
"http": proxy_url,
"https": proxy_url,
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
print(response.json())
💡 Consejo de seguridad
Nunca codifique el nombre de usuario y la contraseña directamente en el código del script. Utilice variables de entorno o un archivo .env con la biblioteca python-dotenv. De esta manera, evitará la filtración accidental de credenciales al publicar el código en GitHub.
Rotación de proxies: cambio automático de IP para scraping
Un proxy sigue siendo una dirección IP que puede ser bloqueada. La verdadera protección contra baneos es la rotación: cada solicitud (o cada N solicitudes) se envía con una nueva IP. A continuación, se presentan algunos enfoques para implementar la rotación.
Método 1: Selección aleatoria de una lista
import requests
import random
# Lista de proxies
proxy_list = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
def get_random_proxy():
proxy = random.choice(proxy_list)
return {"http": proxy, "https": proxy}
# Scraping de 10 páginas con rotación de IP
urls = [f"https://example.com/page/{i}" for i in range(1, 11)]
for url in urls:
proxies = get_random_proxy()
try:
response = requests.get(url, proxies=proxies, timeout=10)
print(f"URL: {url} | IP: {proxies['http'].split('@')[1]} | Estado: {response.status_code}")
except requests.RequestException as e:
print(f"Error: {e}")
Método 2: Rotación cíclica a través de itertools
import requests
import itertools
proxy_list = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
# Creamos un ciclo infinito sobre la lista de proxies
proxy_cycle = itertools.cycle(proxy_list)
def get_next_proxy():
proxy = next(proxy_cycle)
return {"http": proxy, "https": proxy}
# Cada solicitud utiliza el siguiente proxy en círculo
for i in range(9):
proxies = get_next_proxy()
response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(f"Solicitud {i+1}: {response.json()['origin']}")
Para tareas industriales con miles de solicitudes por hora, se recomienda utilizar proxies residenciales con rotación automática incorporada: el proveedor cambia la IP para cada solicitud a través de un único endpoint, y no necesita gestionar la lista de direcciones manualmente.
Proxies a través de Session: conexiones persistentes y cookies
Cuando necesita hacer varias solicitudes dentro de una misma sesión (por ejemplo, iniciar sesión y luego hacer solicitudes), utilice el objeto requests.Session(). Este guarda cookies, encabezados y configuraciones de proxy entre solicitudes — no es necesario pasar el proxy en cada llamada por separado.
import requests
# Creamos una sesión con proxy
session = requests.Session()
session.proxies = {
"http": "http://user:[email protected]:8080",
"https": "http://user:[email protected]:8080",
}
# Agregamos encabezados para imitar un navegador
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept-Language": "es-ES,es;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
})
# Paso 1: Autenticación
login_data = {"username": "myuser", "password": "mypass"}
session.post("https://example.com/login", data=login_data)
# Paso 2: Solicitudes ya con cookies y a través de proxy
response = session.get("https://example.com/dashboard")
print(response.status_code)
# Paso 3: Cerramos la sesión
session.close()
Utilizar Session también es más eficiente en términos de rendimiento: la conexión TCP se reutiliza, en lugar de abrirse de nuevo para cada solicitud. Al hacer scraping de más de 1000 páginas, esto proporciona un aumento de velocidad notable.
Manejo de errores, tiempos de espera y reintentos automáticos
Los servidores proxy pueden no estar disponibles, responder lentamente o devolver errores de conexión. Un script confiable para scraping debe ser capaz de manejar estas situaciones y cambiar automáticamente a otro proxy en caso de fallo.
import requests
import random
import time
proxy_list = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
def fetch_with_retry(url, max_retries=3, timeout=10):
"""
Realiza una solicitud con cambio automático de proxy en caso de error.
Devuelve un objeto Response o None al agotar los intentos.
"""
available_proxies = proxy_list.copy()
random.shuffle(available_proxies)
for attempt, proxy_url in enumerate(available_proxies[:max_retries], 1):
proxies = {"http": proxy_url, "https": proxy_url}
try:
response = requests.get(
url,
proxies=proxies,
timeout=timeout,
headers={"User-Agent": "Mozilla/5.0"}
)
response.raise_for_status() # Lanza una excepción en 4xx/5xx
print(f"✓ Éxito en el intento {attempt}")
return response
except requests.exceptions.ProxyError:
print(f"✗ Intento {attempt}: proxy no disponible — {proxy_url}")
except requests.exceptions.Timeout:
print(f"✗ Intento {attempt}: tiempo de espera — {proxy_url}")
except requests.exceptions.HTTPError as e:
print(f"✗ Intento {attempt}: error HTTP {e.response.status_code}")
if e.response.status_code == 403:
print(" → Baneo recibido, probando el siguiente proxy...")
except requests.exceptions.RequestException as e:
print(f"✗ Intento {attempt}: error general — {e}")
time.sleep(1) # Pausa entre intentos
print(f"✗ Todos los {max_retries} intentos agotados para {url}")
return None
# Uso
result = fetch_with_retry("https://httpbin.org/ip")
if result:
print(result.json())
Preste atención a raise_for_status() — este método lanza automáticamente una excepción para los estados HTTP 4xx y 5xx. Sin él, el script considerará exitoso incluso una respuesta con código 403 (baneo) o 429 (límite de solicitudes excedido).
Proxies a través de variables de entorno: almacenamiento seguro de datos
La biblioteca requests lee automáticamente las variables de entorno HTTP_PROXY y HTTPS_PROXY. Esto permite no almacenar credenciales en el código y cambiar fácilmente entre proxies sin modificar el script.
Estableciendo variables en la terminal (Linux/macOS):
export HTTP_PROXY="http://user:[email protected]:8080" export HTTPS_PROXY="http://user:[email protected]:8080" export NO_PROXY="localhost,127.0.0.1"
O a través de un archivo .env con la biblioteca python-dotenv:
# Archivo .env (¡agregue a .gitignore!) HTTP_PROXY=http://user:[email protected]:8080 HTTPS_PROXY=http://user:[email protected]:8080
# Script de Python
from dotenv import load_dotenv
import requests
import os
load_dotenv() # Cargamos las variables desde .env
# requests utiliza automáticamente HTTP_PROXY y HTTPS_PROXY
response = requests.get("https://httpbin.org/ip")
print(response.json())
# O explícitamente desde las variables de entorno:
proxies = {
"http": os.getenv("HTTP_PROXY"),
"https": os.getenv("HTTPS_PROXY"),
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
print(response.json())
⚠️ Importante: variable NO_PROXY
La variable NO_PROXY permite excluir ciertas direcciones de la proxy. Asegúrese de agregar localhost y 127.0.0.1 para que las solicitudes locales no pasen por el proxy.
Escenarios reales: scraping de marketplaces, trabajo con API y automatización
Consideremos tres escenarios prácticos con los que los desarrolladores se enfrentan con más frecuencia.
Escenario 1: Scraping de precios de un marketplace
Al monitorear precios en Wildberries o Ozon, es importante imitar el comportamiento de un usuario real: enviar los encabezados correctos del navegador, agregar retrasos entre solicitudes y rotar IP. Para esta tarea, los proxies de centros de datos son muy adecuados: son rápidos y económicos al trabajar con grandes volúmenes de datos.
import requests
import time
import random
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "application/json, text/plain, */*",
"Accept-Language": "es-ES,es;q=0.9",
"Referer": "https://www.wildberries.ru/",
}
PROXIES = [
{"http": "http://user:[email protected]:8080",
"https": "http://user:[email protected]:8080"},
{"http": "http://user:[email protected]:8080",
"https": "http://user:[email protected]:8080"},
]
def get_product_price(article_id: int) -> dict:
"""Obtiene el precio del producto por artículo de Wildberries."""
url = f"https://card.wb.ru/cards/v1/detail?appType=1&curr=rub&nm={article_id}"
proxies = random.choice(PROXIES)
try:
resp = requests.get(url, headers=HEADERS, proxies=proxies, timeout=15)
resp.raise_for_status()
data = resp.json()
product = data["data"]["products"][0]
return {
"id": product["id"],
"name": product["name"],
"price": product["salePriceU"] / 100, # precio en kopecks
}
except (requests.RequestException, KeyError, IndexError) as e:
return {"error": str(e)}
# Scrapeamos varios artículos con retraso
articles = [12345678, 87654321, 11223344]
for article in articles:
result = get_product_price(article)
print(result)
time.sleep(random.uniform(1.5, 3.0)) # Retraso aleatorio de 1.5-3 seg
Escenario 2: Trabajo con API a través de proxies
Algunas API limitan la cantidad de solicitudes desde una sola IP (limitación de tasa). Distribuir las solicitudes entre varios proxies permite eludir esta restricción:
import requests
import itertools
from typing import Optional
class ProxyAPIClient:
"""Cliente para trabajar con API a través de rotación de proxies."""
def __init__(self, api_key: str, proxy_list: list):
self.api_key = api_key
self.proxy_cycle = itertools.cycle(proxy_list)
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
})
def _get_proxy(self) -> dict:
proxy = next(self.proxy_cycle)
return {"http": proxy, "https": proxy}
def get(self, url: str, **kwargs) -> Optional[dict]:
proxies = self._get_proxy()
try:
resp = self.session.get(url, proxies=proxies, timeout=10, **kwargs)
resp.raise_for_status()
return resp.json()
except requests.RequestException as e:
print(f"La solicitud a la API falló: {e}")
return None
# Uso
proxy_list = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
client = ProxyAPIClient(api_key="your_api_key", proxy_list=proxy_list)
data = client.get("https://api.example.com/products")
Escenario 3: Pruebas de geolocalización
Los especialistas en marketing y SEO a menudo verifican cómo se ve un sitio desde diferentes regiones. Con proxies de las ubicaciones necesarias, se puede automatizar este proceso:
import requests
# Proxies de diferentes regiones
regional_proxies = {
"Moscú": "http://user:[email protected]:8080",
"San Petersburgo": "http://user:[email protected]:8080",
"Novosibirsk": "http://user:[email protected]:8080",
"EE.UU.": "http://user:[email protected]:8080",
}
url = "https://example.com/prices"
for region, proxy_url in regional_proxies.items():
proxies = {"http": proxy_url, "https": proxy_url}
try:
resp = requests.get(url, proxies=proxies, timeout=15)
print(f"[{region}] Estado: {resp.status_code} | "
f"Tamaño: {len(resp.content)} bytes")
except requests.RequestException as e:
print(f"[{region}] Error: {e}")
Qué tipo de proxy elegir para su tarea
La elección del tipo de proxy influye directamente en el éxito de su proyecto. Un proxy de centro de datos barato puede funcionar muy bien para ciertas tareas y fracasar completamente para otras. Aquí hay una guía práctica para elegir:
| Tarea | Tipo de proxy | Por qué |
|---|---|---|
| Scraping de marketplaces (Wildberries, Ozon) | Residenciales | Parecen usuarios normales, se banean menos |
| Scraping de datos abiertos, noticias | Centros de datos | Rápidos, baratos, suficientemente anónimos |
| Trabajo con Facebook API, Instagram | Móviles | Las redes sociales confían más en IP móviles |
| Pruebas de geolocalización | Residenciales con geotargeting | Geolocalización precisa, IP reales de la región deseada |
| Scraping de alta carga (10k+ solicitudes/hora) | Centros de datos (pool) | Velocidad y costo en grandes volúmenes |
| Autenticación y trabajo con cuentas | Residenciales o móviles | Menos disparadores de sistemas antifraude |
Para tareas donde la máxima confiabilidad y el mínimo riesgo de bloqueo son importantes al trabajar con sitios protegidos, los desarrolladores a menudo eligen proxies móviles — utilizan direcciones IP de operadores móviles reales (MTS, Beeline, Megafon), que rara vez caen en listas de bloqueo.
Lista de verificación para verificar proxies antes de usar
- ✅ Verifique la IP a través de
httpbin.org/ip— ¿se ve su dirección real? - ✅ Verifique la velocidad — el tiempo de respuesta no debe exceder 2-3 segundos
- ✅ Asegúrese de que el proxy no esté en listas de bloqueo a través de
blocklist.deoipqualityscore.com - ✅ Verifique la geolocalización a través de
ipinfo.io— ¿coincide con la región esperada? - ✅ Pruebe en el sitio objetivo con una sola solicitud antes de ejecutar el script completo
- ✅ Asegúrese de que el tráfico HTTPS también pase a través del proxy (ambas claves en el diccionario)
Conclusión
Configurar proxies en Python requests no es complicado, pero requiere atención a los detalles. Los principios clave que debe recordar son: siempre especifique ambas claves (http y https) en el diccionario de proxies, use Session para escenarios de múltiples pasos, asegúrese de manejar errores y tiempos de espera, y almacene las credenciales en variables de entorno, no en el código.
Para scraping industrial con miles de solicitudes al día, una lista manual de proxies no es suficiente — se necesita rotación. Si está haciendo scraping de marketplaces protegidos como Wildberries o Ozon, trabajando con redes sociales o probando geolocalización, le recomendamos probar proxies residenciales — ofrecen un alto nivel de confianza por parte de los sistemas antibot y admiten rotación automática de IP a través de un único endpoint, lo que simplifica significativamente el código de su script.