Si estás ejecutando scripts de parsing o multi-cuentas en Playwright y levantas una instancia separada del navegador para cada proxy, estás desperdiciando memoria y CPU innecesariamente. Playwright puede asignar proxies a nivel de BrowserContext, y no a todo el navegador, lo que cambia drásticamente la arquitectura de los scripts: un proceso de navegador, decenas de contextos aislados con diferentes IP, cookies y huellas digitales. En este artículo, veremos cómo funciona, el código en Python y Node.js, y por qué es especialmente importante al trabajar con proxies residenciales y móviles.
Problema: proxies en browser.launch() — caro y poco práctico
El enfoque clásico de muchos scripts en Playwright es el siguiente: para cada proxy se llama a chromium.launch({ proxy: {...} }), se crea un proceso separado del navegador, funciona, luego se cierra, y se inicia el siguiente con un nuevo proxy. Si necesitas procesar 50 direcciones IP en paralelo para hacer scraping de Wildberries o Ozon, esto significa 50 procesos completos de Chromium al mismo tiempo — cada uno consume entre 150 y 300 MB de RAM solo al inicio, sin contar las pestañas y el renderizado.
En la práctica, esto se encuentra con un límite ya en 15-20 navegadores paralelos en un VPS normal con 8 GB de memoria. El servidor comienza a intercambiar memoria, los scripts fallan con tiempos de espera, y parte de los proxies no tienen tiempo de funcionar. El problema no está en los proxies ni en Playwright — el problema está en la arquitectura: cada lanzamiento del navegador arrastra consigo todo el motor de renderizado, el proceso de GPU, la red y decenas de hilos auxiliares, aunque en realidad solo necesitas una nueva IP y una sesión limpia.
Playwright resuelve este problema de manera elegante: los proxies se pueden especificar no al iniciar el navegador, sino al crear un BrowserContext — un "sandbox" aislado y ligero dentro del mismo proceso del navegador. Un proceso de Chromium puede mantener decenas de contextos, cada uno con su propio proxy, sus propias cookies y localStorage, sin cruzarse entre sí.
Browser vs BrowserContext: ¿cuál es la diferencia?
Browser — es el propio proceso del motor (Chromium, Firefox o WebKit), que se inicia una vez y vive mucho tiempo. BrowserContext — es un entorno aislado dentro de este proceso, similar a una ventana privada (incógnito), con sus propias cookies, caché, permisos y, lo que es importante para nosotros, sus propias configuraciones de red, incluidos los proxies.
Si comparamos con navegadores anti-detección como Dolphin Anty o AdsPower — allí cada "perfil" también es, en esencia, un contenedor aislado separado con cookies y huellas digitales. Playwright permite construir una lógica similar programáticamente: un navegador — múltiples contextos, cada uno como un "perfil" separado con su propia IP.
| Parámetro | Proxy en Browser | Proxy en BrowserContext |
|---|---|---|
| Memoria por IP | 150-300 MB (nuevo proceso) | 15-40 MB (nuevo contexto) |
| Velocidad de cambio de IP | 2-5 seg (reinicio del navegador) | 0.1-0.3 seg (nuevo contexto) |
| Aislamiento de cookies | Completo (procesos diferentes) | Completo (los contextos no comparten cookies) |
| Máx. sesiones paralelas en 8 GB RAM | ~20-25 | ~150-200 |
Proxy por contexto: código en Python
A continuación se presenta un patrón básico: un navegador se inicia una vez, y luego se crea un contexto separado para cada proxy. Este es el API sincrónico de Playwright, pero la lógica es idéntica para la versión asíncrona.
from playwright.sync_api import sync_playwright
proxies = [
{"server": "http://proxy1.example.com:8000", "username": "user1", "password": "pass1"},
{"server": "http://proxy2.example.com:8000", "username": "user2", "password": "pass2"},
{"server": "http://proxy3.example.com:8000", "username": "user3", "password": "pass3"},
]
with sync_playwright() as p:
# El navegador se inicia UNA vez, sin proxy
browser = p.chromium.launch(headless=True)
for proxy in proxies:
# El proxy se asigna al contexto, no al navegador
context = browser.new_context(
proxy={
"server": proxy["server"],
"username": proxy["username"],
"password": proxy["password"],
},
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
)
page = context.new_page()
page.goto("https://httpbin.org/ip", timeout=30000)
print(page.content())
page.close()
context.close() # cerramos solo el contexto, el navegador sigue vivo
browser.close()
El punto clave es que browser.launch() se llama una vez fuera del ciclo, mientras que browser.new_context() se llama dentro del ciclo por proxy. Para proxies con autenticación por nombre de usuario y contraseña, Playwright inserta automáticamente los encabezados de Basic Auth a nivel de red, no es necesario configurar nada adicional.
Proxy por contexto: código en Node.js
La misma lógica en JavaScript utilizando async/await — a menudo se aplica en conjunto con frameworks de parsing en Node.js:
const { chromium } = require('playwright');
const proxies = [
{ server: 'http://proxy1.example.com:8000', username: 'user1', password: 'pass1' },
{ server: 'http://proxy2.example.com:8000', username: 'user2', password: 'pass2' },
];
(async () => {
const browser = await chromium.launch({ headless: true });
for (const proxy of proxies) {
const context = await browser.newContext({
proxy: {
server: proxy.server,
username: proxy.username,
password: proxy.password,
},
});
const page = await context.newPage();
await page.goto('https://httpbin.org/ip', { timeout: 30000 });
console.log(await page.content());
await page.close();
await context.close();
}
await browser.close();
})();
Para el procesamiento paralelo de decenas de proxies, los contextos se pueden crear a través de Promise.all, sin esperar a que se cierren los anteriores — esta es la principal ventaja del enfoque "un navegador, muchos contextos" frente al lanzamiento de procesos separados.
Rotación limpia de proxies sin reiniciar el navegador
"Rotación limpia" significa que al cambiar de IP no quedan residuos de la sesión anterior — ni cookies, ni caché, ni service workers. Al reiniciar todo el navegador, esto se garantiza automáticamente, pero es costoso en recursos. Al trabajar con contextos, lo mismo se logra cerrando el contexto antiguo y creando uno nuevo — la operación toma fracciones de segundo.
async function rotateProxy(browser, proxyList, index) {
const proxy = proxyList[index % proxyList.length];
const context = await browser.newContext({ proxy });
const page = await context.newPage();
return { context, page };
}
// Usamos así: en cada N-ésima iteración cambiamos IP
for (let i = 0; i < 100; i++) {
if (i % 10 === 0) {
const { context, page } = await rotateProxy(browser, proxies, i);
// trabajamos con una nueva sesión durante 10 solicitudes
}
}
Este patrón es especialmente útil al hacer scraping de marketplaces: por ejemplo, al recopilar precios de Wildberries o Ozon, donde la protección contra bots rastrea la cantidad de solicitudes desde una sola IP en un período de tiempo. La rotación a nivel de contexto permite cambiar la IP cada 5-10 solicitudes, sin perder rendimiento por el reinicio del navegador.
Para tales escenarios, los proxies residenciales son muy adecuados — proporcionan IP de usuarios reales, que los marketplaces marcan con menos frecuencia como sospechosas, a diferencia de los rangos claramente de servidores.
Comparación de memoria y velocidad
En un VPS de prueba con 8 GB de RAM y 4 vCPU se realizó una verificación de dos enfoques en la tarea de recorrer 100 páginas con diferentes IP. En el primer caso, para cada IP se lanzaba un chromium.launch(), en el segundo — un solo navegador con la creación de 100 contextos de manera secuencial.
| Métrica | 100 lanzamientos de browser.launch() | 100 contextos en 1 navegador |
|---|---|---|
| Memoria máxima | ~6.8 GB | ~1.9 GB |
| Tiempo total de ejecución | 182 seg | 97 seg |
| Número de sesiones caídas por tiempo de espera | 7 | 0 |
El ahorro de memoria en esta prueba fue de aproximadamente el 70%, y el tiempo total de ejecución se redujo casi a la mitad gracias a la ausencia de sobrecargas por el inicio y finalización del proceso de Chromium cada vez. En grandes volúmenes — miles de IP al día — la diferencia se vuelve aún más notable, y esto afecta directamente a cuántos proxies puedes usar en un solo servidor sin actualizar el hardware.
Casos prácticos: parsing y multi-cuentas
Scraping de marketplaces. Al recopilar precios de Wildberries, Ozon o Avito, un mismo navegador mantiene abiertos decenas de contextos, cada uno con una IP única y su propio almacenamiento de cookies. Esto reduce el riesgo de bloqueo por patrones de solicitudes y permite recopilar datos más rápido, sin esperar el reinicio del proceso entre IP.
Multi-cuentas y creación de cuentas. Para probar campañas publicitarias en Facebook Ads o TikTok Ads a través de scripts de Playwright, tiene sentido mantener cada cuenta en un contexto separado con su propio proxy y user-agent — esto imita el comportamiento de navegadores anti-detección como Multilogin o GoLogin, solo que sin licencia para el propio anti-detección, completamente programático.
Pruebas geográficas de publicidad. A los mercadólogos que necesitan verificar cómo se muestra un creativo en diferentes países, los contextos con diferentes proxies les permiten "estar" secuencialmente en las regiones necesarias en una sola sesión del navegador, sin abrir decenas de ventanas separadas manualmente.
Para escenarios donde es importante simular tráfico móvil — por ejemplo, verificar la versión móvil de un sitio o la publicidad en TikTok Ads — vale la pena considerar proxies móviles, que proporcionan IP de operadores de telefonía real y generan menos sospechas en los sistemas anti-fraude.
Si la tarea es un scraping rápido y estable sin necesidad de alta anonimidad (por ejemplo, recopilación de datos públicos de sitios sin estrictas protecciones anti-bots), proxies de centros de datos ofrecen la mejor relación entre velocidad y costo en grandes volúmenes de solicitudes.
Errores comunes y cómo evitarlos
1. Se asignan proxies en launch(), no en new_context(). Este es el error más común de los principiantes — el proxy especificado al iniciar el navegador se aplica a todos los contextos a la vez y no puede ser sobrescrito individualmente. Siempre pasa el proxy en new_context(), si necesitas flexibilidad con la IP.
2. No se cierran contextos antiguos. Si creas cientos de contextos y no llamas a context.close(), la memoria del navegador crecerá linealmente, hasta que el proceso se caiga por OOM. Siempre cierra el contexto después de completar la tarea para esa IP.
3. Un solo user-agent para todos los contextos. Si el proxy cambia, pero la huella digital del navegador permanece igual (user-agent, idioma, zona horaria), los sistemas anti-bots de los marketplaces y redes sociales aún pueden vincular las sesiones entre sí. Cambia el user-agent y el viewport junto con el proxy para cada contexto.
4. Ignorar los tiempos de espera de los proxies. Los proxies residenciales y móviles a veces responden más lentamente que los de centros de datos debido a la infraestructura de red real. Establece un timeout razonable (20-40 segundos) al llamar a page.goto(), de lo contrario, el script considerará que el proxy funcional está "muerto".
5. Demasiados contextos en un solo navegador al mismo tiempo. A pesar del ahorro de memoria, la creación incontrolada de 500+ contextos activos al mismo tiempo aún se encontrará con los límites del proceso. Un equilibrio razonable es de 30-80 contextos activos simultáneamente por proceso de navegador, dependiendo de la cantidad de RAM del servidor.
Conclusión
Asignar proxies a nivel de BrowserContext, en lugar de a todo el navegador, es una solución arquitectónica que ahorra hasta un 70% de memoria, acelera la rotación de IP decenas de veces y proporciona un aislamiento limpio de cookies entre sesiones sin reinicios constantes de Chromium. Para el scraping de marketplaces, pruebas publicitarias en diferentes regiones y multi-cuentas, esta es una forma directa de aumentar la cantidad de sesiones paralelas en el mismo servidor sin actualizar el hardware.
Si estás construyendo un script en Playwright para recopilar datos de Wildberries, Ozon o para trabajar con las plataformas publicitarias de Facebook Ads y TikTok Ads, la calidad del pool de proxies es tan importante como la arquitectura del código. Para tareas donde la autenticidad de la IP y la resistencia a los sistemas anti-bots son críticas, es recomendable comenzar con proxies residenciales — se combinan bien con el enfoque "un navegador, muchos contextos" descrito anteriormente.