El problema con Selenium proxy auth
Si alguna vez intentaste pasar --proxy-server=http://user:pass@host:port a ChromeDriver, ya sabes el resultado: Chrome ignora las credenciales y lanza un popup de autenticación que bloquea la ejecución. Este es uno de los dolores más persistentes para QA engineers y scraping teams que trabajan con Selenium residential proxies.
El problema no es un bug — es una decisión de diseño. Chromium delega la autenticación proxy al sistema operativo, no al proceso del navegador. Selenium, al envolver Chrome vía DevTools Protocol, no tiene forma nativa de inyectar credenciales en esa capa. El resultado: necesitas workarounds.
En esta guía cubrimos cada enfoque disponible — desde selenium-wire para Chrome hasta perfiles Firefox, pasando por Selenium stealth, rotación de IPs y despliegue en Grid con contenedores. Al final, una comparativa honesta con Playwright.
Chrome + selenium-wire: proxies autenticados sin hacks
selenium-wire extiende el WebDriver de Chrome para interceptar y modificar requests a nivel de proxy. En lugar de depender de que Chrome maneje la autenticación, selenium-wire la inyecta directamente en la conexión TCP.
Instalación y configuración base
pip install selenium-wire selenium
from seleniumwire import webdriver
from selenium.webdriver.chrome.options import Options
proxy_user = "user-country-US-session-sess01"
proxy_pass = "PASSWORD"
proxy_host = "gate.proxyhat.com"
proxy_port = 8080
options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-blink-features=AutomationControlled")
seleniumwire_options = {
"proxy": {
"https": f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}",
"http": f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}",
},
"disable_encoding": True, # evita problemas con content-encoding
}
driver = webdriver.Chrome(
options=options,
seleniumwire_options=seleniumwire_options,
)
driver.get("https://httpbin.org/ip")
print(driver.page_source)
driver.quit()
Puntos clave de selenium-wire
- Interceptación transparente: las credenciales se inyectan antes de que la request salga del proceso de Python.
- Acceso a requests: puedes inspeccionar
driver.requestsydriver.wait_for_request()— útil para verificar que el proxy responde. - Overhead: selenium-wire añade un proxy local (mitmproxy bajo el hood). Espera ~50-100ms extra por request y un consumo de memoria ~2x respecto a Selenium vanilla.
- Compatibilidad: funciona con Chrome y Edge. Firefox no está soportado — para Firefox usa el enfoque de perfil que veremos después.
Cuándo usarlo: cuando necesitas Selenium proxy auth en Chrome sin tocar el sistema operativo, y el overhead de memoria es aceptable para tu volumen de scraping.
Firefox: proxy autenticado vía perfil generado
Firefox sí permite configurar proxies autenticados directamente en el perfil — sin necesidad de librerías externas. El truco es crear un perfil programáticamente y escribir las preferencias antes de iniciar el navegador.
from selenium import webdriver
from selenium.webdriver.firefox.options import Options as FirefoxOptions
import tempfile, os, configparser
def create_firefox_proxy_profile(proxy_user, proxy_pass, proxy_host, proxy_port):
"""Crea un perfil Firefox con proxy autenticado."""
profile_dir = tempfile.mkdtemp(prefix="ff_proxy_")
# Escribir preferencias de proxy directamente en user.js
prefs = {
"network.proxy.type": 1, # manual
"network.proxy.http": proxy_host,
"network.proxy.http_port": proxy_port,
"network.proxy.ssl": proxy_host,
"network.proxy.ssl_port": proxy_port,
"network.proxy.socks": proxy_host,
"network.proxy.socks_port": 1080,
"network.proxy.socks_version": 5,
"network.proxy.no_proxies_on": "localhost, 127.0.0.1",
# Credenciales — solo funcionan con extensions o auto-auth
"signon.autologin.network": True,
}
user_js = os.path.join(profile_dir, "user.js")
with open(user_js, "w") as f:
for key, val in prefs.items():
if isinstance(val, bool):
f.write(f'user_pref("{key}", {str(val).lower()});\n')
elif isinstance(val, str):
f.write(f'user_pref("{key}", "{val}");\n')
else:
f.write(f'user_pref("{key}", {val});\n')
return profile_dir
proxy_user = "user-country-DE-city-berlin"
proxy_pass = "PASSWORD"
profile_dir = create_firefox_proxy_profile(
proxy_user, proxy_pass,
"gate.proxyhat.com", 8080
)
options = FirefoxOptions()
options.add_argument("-headless")
options.add_argument(f"-profile")
options.add_argument(profile_dir)
driver = webdriver.Firefox(options=options)
# Para credenciales: inyectar una extension que maneje el auth popup
# Alternativa: usar selenium-wire con Firefox (no soportado oficialmente)
# Mejor opción para Firefox + auth: extensiones de auto-auth
driver.get("https://httpbin.org/ip")
print(driver.page_source)
driver.quit()
Firefox no envía credenciales automáticamente desde user.js. Necesitas una extensión que capture el diálogo de autenticación y lo rellene. La extensión Close Proxy Auth (disponible en GitHub) hace exactamente esto — intercepta el 407 Proxy Auth Required y reenvía las credenciales.
Ventaja Firefox: el perfil es portable — puedes pregenerarlo una vez, cachearlo en disco y reutilizarlo en cada sesión. Ideal para flotas de contenedores donde cada instancia necesita un perfil limpio.
Selenium stealth: reduciendo fingerprints de automatización
Incluso con el proxy correcto, los sitios pueden detectarte por browser fingerprints: navigator.webdriver, window.chrome, permisos de plugins, y docenas de señales más. Aquí entra Selenium stealth.
selenium-stealth: parches rápidos
pip install selenium-stealth
from selenium import webdriver
from selenium_stealth import stealth
options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument("--disable-gpu")
driver = webdriver.Chrome(options=options)
stealth(driver,
languages=["es-ES", "es", "en-US", "en"],
vendor="Google Inc.",
platform="Win32",
webgl_vendor="Intel Inc.",
renderer="Intel Iris OpenGL Engine",
fix_hairline=True,
)
# Verificar que navigator.webdriver es undefined
driver.get("https://bot.sannysoft.com/")
print("WebDriver flag:", driver.execute_script("return navigator.webdriver"))
driver.quit()
selenium-stealth parchea ~15 propiedades del navegador en runtime. Es rápido de integrar pero tiene limitaciones:
- No evita detección de Canvas fingerprinting avanzado.
- No modifica WebGL hash — sitios sofisticados como Cloudflare Turnstile aún pueden detectarlo.
- Es un parche, no una reescritura del navegador.
selenium-driverless: la opción más agresiva
selenium-driverless toma un enfoque radical: en lugar de usar ChromeDriver, se comunica directamente con Chrome vía CDP (Chrome DevTools Protocol), eliminando la capa que Cloudflare y otros detectan.
pip install selenium-driverless
from selenium_driverless import webdriver as driverless_webdriver
from selenium_driverless.types import By
async def scrape_with_driverless():
options = driverless_webdriver.ChromeOptions()
options.add_argument("--headless=new")
# Proxy via flag de Chrome (sin auth)
options.add_argument("--proxy-server=http://gate.proxyhat.com:8080")
driver = await driverless_webdriver.Chrome(options=options)
# Navegar
await driver.get("https://nowsecure.nl/")
title = await driver.title
print(f"Page title: {title}")
await driver.quit()
import asyncio
asyncio.run(scrape_with_driverless())
Nota: selenium-driverless no soporta autenticación proxy nativamente. Combínalo con IP autenticada (whitelist de IP en tu proveedor de proxies) o con un proxy local como mitmproxy que inyecte las credenciales.
Patrón de rotación de proxies: IP fresca por sesión
Para scraping a escala, necesitas que cada WebDriver session use una IP diferente. El patrón es simple: una pool de proxies que rota por sesión, con sticky sessions para mantener la IP durante la vida de una sesión.
import random
import string
from seleniumwire import webdriver as sw_webdriver
from selenium.webdriver.chrome.options import Options
PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = 8080
PROXY_USER = "user-country-US" # base username
PROXY_PASS = "PASSWORD"
def generate_session_id(length=8):
"""Genera un ID de sesión único para sticky session."""
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
class RotatingProxyPool:
"""Pool de proxies residenciales con rotación por sesión."""
def __init__(self, base_user, password, host, port, countries=None):
self.base_user = base_user
self.password = password
self.host = host
self.port = port
self.countries = countries or ["US", "DE", "GB", "FR", "JP"]
self._session_counter = 0
def get_proxy_config(self, country=None, session_id=None):
"""Retorna la configuración de proxy para una nueva sesión."""
country = country or random.choice(self.countries)
session_id = session_id or f"sess{self._session_counter:06d}"
self._session_counter += 1
username = f"{self.base_user}-country-{country}-session-{session_id}"
return {
"proxy": {
"http": f"http://{username}:{self.password}@{self.host}:{self.port}",
"https": f"http://{username}:{self.password}@{self.host}:{self.port}",
},
"username": username,
"session_id": session_id,
"country": country,
}
def create_driver(self, country=None, headless=True):
"""Crea un WebDriver con IP fresca."""
proxy_config = self.get_proxy_config(country=country)
options = Options()
if headless:
options.add_argument("--headless=new")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--window-size=1920,1080")
driver = sw_webdriver.Chrome(
options=options,
seleniumwire_options=proxy_config,
)
# Adjuntar metadata de la sesión al driver para debugging
driver._proxy_meta = proxy_config
return driver
# Uso
pool = RotatingProxyPool(
base_user="user",
password="PASSWORD",
host=PROXY_HOST,
port=PROXY_PORT,
countries=["US", "DE", "GB"],
)
# Cada driver tiene su propia IP residencial
for i in range(3):
driver = pool.create_driver(country="US")
driver.get("https://httpbin.org/ip")
print(f"Session {i}: {driver._proxy_meta['session_id']} -> {driver.page_source[:200]}")
driver.quit()
Estrategias de rotación
- Per-request: sin flag
-session-— cada request obtiene una IP aleatoria del pool. Ideal para scraping masivo de SERPs donde cada request es independiente. - Sticky session: con
-session-XXXX— la IP se mantiene durante 10-30 minutos (depende del proveedor). Necesario para flujos de login, carritos de compra o cualquier flujo multi-step. - Per-session: nueva IP por cada
webdriver.Chrome()— el patrón de arriba. Balance entre diversidad de IPs y estabilidad dentro de una sesión.
Selenium Grid + contenedores: scraping paralelo a escala
Para procesar miles de páginas por hora, necesitas ejecutar múltiples browsers en paralelo. Selenium Grid distribuye sesiones across nodos, y Docker lo hace reproducible.
Docker Compose para Selenium Grid
# docker-compose.yml
version: '3.8'
services:
selenium-hub:
image: selenium/hub:4.18
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
environment:
- GRID_MAX_SESSION=16
- SE_SESSION_REQUEST_TIMEOUT=300
chrome-node-1:
image: selenium/node-chrome:4.18
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=4
deploy:
resources:
limits:
memory: 2G
chrome-node-2:
image: selenium/node-chrome:4.18
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=4
deploy:
resources:
limits:
memory: 2G
Cliente que conecta al Grid con proxies rotativos
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from concurrent.futures import ThreadPoolExecutor, as_completed
import requests
GRID_URL = "http://localhost:4444/wd/hub"
PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = 8080
def scrape_task(url, session_id):
"""Ejecuta una tarea de scraping en el Grid con proxy rotativo."""
proxy_user = f"user-country-US-session-{session_id}"
proxy_pass = "PASSWORD"
options = Options()
options.add_argument("--headless=new")
options.add_argument("--proxy-server=http://gate.proxyhat.com:8080")
# Nota: en Grid, selenium-wire no funciona directamente.
# Usa IP whitelisting o un proxy local mitmproxy como sidecar.
driver = webdriver.Remote(
command_executor=GRID_URL,
options=options,
)
try:
driver.get(url)
return {"url": url, "title": driver.title, "status": "ok"}
except Exception as e:
return {"url": url, "error": str(e), "status": "fail"}
finally:
driver.quit()
# Ejecutar 16 URLs en paralelo
urls = [f"https://httpbin.org/delay/{i % 3}" for i in range(16)]
with ThreadPoolExecutor(max_workers=8) as executor:
futures = {
executor.submit(scrape_task, url, f"grid{i:04d}"): url
for i, url in enumerate(urls)
}
for future in as_completed(futures):
result = future.result()
print(result)
Patrones de escalado
- Sidecar proxy: cada nodo del Grid ejecuta un contenedor
mitmproxycomo sidecar que inyecta credenciales. Chrome apunta alocalhost:8080y mitmproxy reenvía agate.proxyhat.com:8080con auth. - IP whitelisting: configura tu cuenta de ProxyHat para whitelistear las IPs de tus nodos. Así Chrome puede usar el proxy sin credenciales.
- Auto-scaling: usa Kubernetes HPA (Horizontal Pod Autoscaler) basado en la métrica de sesiones activas del Grid para escalar nodos Chrome dinámicamente.
Comparativa: Selenium vs Playwright
Playwright nació en 2020 resolviendo muchos de los problemas que Selenium arrastra desde hace años. Pero Selenium tiene ventajas que Playwright no puede replicar fácilmente. Aquí la comparativa honesta:
| Criterio | Selenium | Playwright |
|---|---|---|
| Proxy auth nativo | No — requiere selenium-wire o extensiones | Sí — proxy: {username, password} nativo |
| Stealth / anti-detección | Requiere selenium-stealth o driverless | Built-in: no expone navigator.webdriver |
| Soporte de navegadores | Chrome, Firefox, Edge, Safari (legacy) | Chromium, Firefox, WebKit (no Safari legacy) |
| Ecosistema / librerías | Maduro: Appium, IDEs, CBT, miles de SO answers | Creciendo rápido, pero menos recursos legacy |
| Grid / distribución | Selenium Grid 4 + Docker, bien documentado | Playwright no tiene Grid nativo — usa contenedores propios |
| Velocidad de ejecución | Más lento (overhead de WebDriver protocol) | 2-3x más rápido (CDP directo, auto-wait) |
| Compatibilidad legacy | Excelente — funciona con tests escritos en 2015 | No — API moderna, sin backward compat |
| Multi-tab / contexts | Posible pero verbose | Browser contexts nativos — aislamiento limpio |
Cuándo elegir Playwright
- Proyecto nuevo donde no tienes deuda técnica con Selenium.
- Necesitas Selenium proxy auth sin dependencias extra — Playwright lo resuelve en 3 líneas.
- Alto volumen con requisitos de velocidad — Playwright es significativamente más rápido.
- Stealth es prioridad — Playwright no necesita parches para ocultar
navigator.webdriver.
Cuándo quedarse con Selenium
- Tienes cientos de tests existentes que migrar sería costoso.
- Tu equipo ya domina Selenium y el coste de aprendizaje no se justifica.
- Necesitas integración con herramientas que solo soportan Selenium (Appium, BrowserStack, Sauce Labs legacy).
- Requisitos de compliance que exigen herramientas auditadas y estables.
Consejo práctico: si estás construyendo un sistema de scraping nuevo desde cero y no tienes restricciones de ecosistema, empieza con Playwright. Si mantienes una suite de QA existente o necesitas compatibilidad con herramientas de testing enterprise, Selenium sigue siendo la opción correcta.
Mejores prácticas para Selenium con proxies residenciales
- Usa sticky sessions para flujos multi-step: login, checkout, navegación por categorías — cualquier secuencia donde el servidor pueda correlacionar IPs entre requests.
- Rotación per-request para scraping masivo: SERPs, product pages, datos públicos — cada request es independiente y la diversidad de IPs maximiza el throughput.
- Geo-targeting preciso: usa
-country-XX-city-YYYYpara simular usuarios locales. Los precios de e-commerce varían por región; los SERPs cambian por ubicación. - Respeta robots.txt y ToS: verifica
robots.txtantes de scraping. Cumple con GDPR/CCPA para datos personales. El scraping ético reduce riesgo legal y técnico. - Monitorea success rate y latencia: registra cuántas requests devuelven 200 vs 403/429. Si el success rate baja del 90%, ajusta la concurrencia o rota el pool.
- Timeouts agresivos: configura
driver.set_page_load_timeout(30)ydriver.implicitly_wait(10). Un navegador colgado consume recursos del Grid. - Limpieza de sesiones: siempre llama
driver.quit()en un bloquefinally. Sesiones huérfanas agotan la memoria del Grid.
Puntos clave
- Selenium proxy auth no funciona nativamente en Chrome — usa selenium-wire para inyectar credenciales o extensiones de auto-auth en Firefox.
- Selenium stealth reduce fingerprints pero no es infalible — selenium-driverless ofrece evasión más profunda a cambio de complejidad.
- La rotación de IPs por sesión con sticky sessions es el patrón más equilibrado para Selenium residential proxies.
- Selenium Grid + Docker permite paralelización, pero la autenticación proxy requiere sidecars (mitmproxy) o IP whitelisting.
- Playwright resuelve proxy auth y stealth de forma nativa — considéralo para proyectos nuevos sin deuda técnica.
- Siempre limpia sesiones, configura timeouts, y monitorea success rate — la higiene operativa es tan importante como la técnica.
¿Listo para llevar tu scraping al siguiente nivel con proxies residenciales confiables? Explora los planes de ProxyHat o revisa las ubicaciones disponibles para geo-targeting preciso. Para más guías técnicas, consulta nuestro artículo sobre mejores prácticas de web scraping y SERP tracking con proxies.






