Volver al blog

Integración de proxies con Puppeteer y Playwright: guía completa con ejemplos de código

Guía detallada para configurar proxies HTTP, HTTPS y SOCKS5 en Puppeteer y Playwright con ejemplos de código en JavaScript y TypeScript.

📅13 de febrero de 2026
```html

Puppeteer y Playwright son herramientas populares para la automatización de navegadores y el web scraping. Al trabajar con grandes volúmenes de solicitudes o al hacer scraping de sitios protegidos, el uso de proxies se vuelve críticamente importante para evitar bloqueos por IP. En esta guía, analizaremos todas las formas de integrar proxies en ambas herramientas, desde la configuración básica hasta escenarios avanzados con rotación y manejo de errores.

Fundamentos del uso de proxies en navegadores sin cabeza

Puppeteer y Playwright controlan navegadores reales (Chromium, Firefox, WebKit) a través del Protocolo DevTools. Esto significa que el proxy se configura a nivel de inicio del navegador, no de solicitudes individuales. Ambas herramientas admiten proxies HTTP, HTTPS y SOCKS5, pero tienen diferentes API para su configuración.

Las principales diferencias con respecto a las bibliotecas HTTP convencionales (axios, fetch):

  • El proxy se establece al iniciar el navegador — no se puede cambiar el proxy sobre la marcha dentro de una sesión del navegador
  • Soporte para JavaScript y renderizado — el proxy se aplica a todos los recursos de la página (imágenes, scripts, XHR)
  • Manejo automático de redirecciones y cookies — el navegador se comporta como un usuario real
  • Fingerprinting — incluso con proxies, los sitios pueden detectar la automatización a través de las características del navegador

Importante: Para tareas que requieren un cambio frecuente de IP (scraping de miles de páginas, registro masivo), es más efectivo usar proxies residenciales con rotación — permiten cambiar la IP en cada solicitud sin reiniciar el navegador.

Configuración de proxies en Puppeteer

Puppeteer es una biblioteca de Google para controlar Chrome/Chromium. El proxy se configura a través de los argumentos de lanzamiento del navegador utilizando la bandera --proxy-server.

Configuración básica de proxy HTTP/HTTPS

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    headless: true,
    args: [
      '--proxy-server=http://proxy.example.com:8080'
    ]
  });

  const page = await browser.newPage();
  
  // Verificación de la dirección IP
  await page.goto('https://api.ipify.org?format=json');
  const content = await page.content();
  console.log('IP actual:', content);

  await browser.close();
})();

Este código inicia el navegador a través de un servidor proxy. Todas las solicitudes HTTP y HTTPS pasarán a través del proxy especificado. Para verificar su funcionamiento, se utiliza el servicio ipify, que devuelve la dirección IP externa.

Configuración de proxy SOCKS5

const browser = await puppeteer.launch({
  headless: true,
  args: [
    '--proxy-server=socks5://proxy.example.com:1080'
  ]
});

// El resto del código es similar

Los proxies SOCKS5 funcionan a un nivel más bajo y admiten tráfico UDP, lo que puede ser útil para algunas aplicaciones web. La sintaxis es idéntica a la de los proxies HTTP, solo cambia el protocolo en la URL.

Uso de proxies para dominios específicos

const browser = await puppeteer.launch({
  args: [
    '--proxy-server=http://proxy1.example.com:8080',
    '--proxy-bypass-list=localhost;127.0.0.1;*.internal.com'
  ]
});

// Las solicitudes locales y las solicitudes a *.internal.com irán directamente
// Todas las demás — a través del proxy

La bandera --proxy-bypass-list permite excluir ciertos dominios de la proxy. Esto es útil cuando se necesita combinar solicitudes directas y proxy.

Configuración de proxies en Playwright

Playwright es una biblioteca más moderna de Microsoft que admite Chromium, Firefox y WebKit. El proxy se configura a través de un objeto de configuración, lo que hace que la API sea más clara y tipada.

Configuración básica de proxy

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({
    proxy: {
      server: 'http://proxy.example.com:8080'
    }
  });

  const context = await browser.newContext();
  const page = await context.newPage();
  
  await page.goto('https://api.ipify.org?format=json');
  const ip = await page.textContent('body');
  console.log('IP a través del proxy:', ip);

  await browser.close();
})();

Playwright utiliza el objeto proxy en lugar de argumentos de línea de comandos. Esto proporciona una mejor tipificación en TypeScript y un código más limpio.

Configuración de proxy a nivel de contexto

const browser = await chromium.launch();

// Contexto 1 - con proxy
const context1 = await browser.newContext({
  proxy: {
    server: 'http://proxy1.example.com:8080'
  }
});

// Contexto 2 - con otro proxy
const context2 = await browser.newContext({
  proxy: {
    server: 'http://proxy2.example.com:8080'
  }
});

const page1 = await context1.newPage();
const page2 = await context2.newPage();

// ¡Cada página utiliza su propio proxy!

Una de las ventajas clave de Playwright es la capacidad de crear múltiples contextos de navegador con diferentes proxies dentro de un mismo proceso. Esto ahorra recursos al trabajar con múltiples direcciones IP.

Exclusión de dominios de la proxy

const browser = await chromium.launch({
  proxy: {
    server: 'http://proxy.example.com:8080',
    bypass: 'localhost,127.0.0.1,*.internal.com'
  }
});

// Las solicitudes a localhost y *.internal.com irán directamente

Autenticación por nombre de usuario y contraseña

La mayoría de los proxies comerciales requieren autenticación. Ambas herramientas admiten autorización, pero la implementan de manera diferente.

Autenticación en Puppeteer

const browser = await puppeteer.launch({
  args: ['--proxy-server=http://proxy.example.com:8080']
});

const page = await browser.newPage();

// Establecemos las credenciales para el proxy
await page.authenticate({
  username: 'your_username',
  password: 'your_password'
});

await page.goto('https://httpbin.org/ip');
const content = await page.content();
console.log(content);

El método page.authenticate() establece las credenciales para todas las solicitudes posteriores en esta página. Es importante llamarlo ANTES del primer page.goto().

Autenticación en Playwright

const browser = await chromium.launch({
  proxy: {
    server: 'http://proxy.example.com:8080',
    username: 'your_username',
    password: 'your_password'
  }
});

const page = await browser.newPage();
await page.goto('https://httpbin.org/ip');

Playwright permite especificar las credenciales directamente en el objeto de configuración del proxy — esto es más conveniente y seguro, ya que no requiere llamadas adicionales a métodos.

Método alternativo: credenciales en la URL

// Funciona en ambos frameworks
const proxyUrl = 'http://username:password@proxy.example.com:8080';

// Puppeteer
const browser = await puppeteer.launch({
  args: [`--proxy-server=${proxyUrl}`]
});

// Playwright
const browser = await chromium.launch({
  proxy: { server: proxyUrl }
});

Este método funciona, pero no se recomienda para producción — las credenciales pueden quedar registradas en los logs. Utiliza variables de entorno para almacenar datos sensibles.

Consejo: Al trabajar con scraping de sitios comerciales, recomendamos usar proxies residenciales — tienen IP reales de usuarios domésticos y son menos bloqueados por sistemas anti-bot.

Rotación de proxies y gestión del pool de IP

Para scraping a gran escala, es necesario cambiar regularmente las direcciones IP. Dado que el proxy se establece al iniciar el navegador, la rotación requiere reiniciar la sesión del navegador.

Rotación simple con un array de proxies (Puppeteer)

const puppeteer = require('puppeteer');

const proxyList = [
  'http://user1:pass1@proxy1.example.com:8080',
  'http://user2:pass2@proxy2.example.com:8080',
  'http://user3:pass3@proxy3.example.com:8080'
];

async function scrapeWithRotation(urls) {
  for (let i = 0; i < urls.length; i++) {
    const proxyUrl = proxyList[i % proxyList.length];
    
    const browser = await puppeteer.launch({
      args: [`--proxy-server=${proxyUrl}`]
    });

    try {
      const page = await browser.newPage();
      await page.goto(urls[i], { waitUntil: 'networkidle0' });
      
      const data = await page.evaluate(() => {
        return {
          title: document.title,
          url: window.location.href
        };
      });
      
      console.log(`URL ${i + 1}:`, data);
    } catch (error) {
      console.error(`Error en ${urls[i]}:`, error.message);
    } finally {
      await browser.close();
    }
  }
}

const urlsToScrape = [
  'https://example.com/page1',
  'https://example.com/page2',
  'https://example.com/page3',
  'https://example.com/page4'
];

scrapeWithRotation(urlsToScrape);

Este código itera cíclicamente sobre los proxies de la lista y lanza un nuevo navegador para cada URL. El método i % proxyList.length asegura la rotación cíclica.

Rotación con pool de contextos (Playwright)

const { chromium } = require('playwright');

const proxyList = [
  { server: 'http://proxy1.example.com:8080', username: 'user1', password: 'pass1' },
  { server: 'http://proxy2.example.com:8080', username: 'user2', password: 'pass2' },
  { server: 'http://proxy3.example.com:8080', username: 'user3', password: 'pass3' }
];

async function scrapeWithContextPool(urls) {
  const browser = await chromium.launch();
  
  // Creamos un pool de contextos con diferentes proxies
  const contexts = await Promise.all(
    proxyList.map(proxy => browser.newContext({ proxy }))
  );

  for (let i = 0; i < urls.length; i++) {
    const context = contexts[i % contexts.length];
    const page = await context.newPage();
    
    try {
      await page.goto(urls[i], { waitUntil: 'networkidle' });
      const title = await page.title();
      console.log(`URL ${i + 1} (proxy ${i % contexts.length}):`, title);
    } catch (error) {
      console.error(`Error en ${urls[i]}:`, error.message);
    } finally {
      await page.close();
    }
  }

  await browser.close();
}

const urls = [
  'https://example.com/page1',
  'https://example.com/page2',
  'https://example.com/page3'
];

scrapeWithContextPool(urls);

Playwright permite crear múltiples contextos en un solo navegador, cada uno con su propio proxy. Esto ahorra memoria y tiempo de inicio en comparación con el reinicio completo del navegador.

Rotación inteligente con seguimiento de errores

class ProxyRotator {
  constructor(proxyList) {
    this.proxyList = proxyList;
    this.currentIndex = 0;
    this.failedProxies = new Set();
  }

  getNext() {
    const availableProxies = this.proxyList.filter(
      (_, index) => !this.failedProxies.has(index)
    );

    if (availableProxies.length === 0) {
      throw new Error('Todos los proxies están inactivos');
    }

    const proxy = this.proxyList[this.currentIndex];
    this.currentIndex = (this.currentIndex + 1) % this.proxyList.length;
    
    return { proxy, index: this.currentIndex - 1 };
  }

  markFailed(index) {
    this.failedProxies.add(index);
    console.log(`Proxy ${index} marcado como inactivo`);
  }

  resetFailed() {
    this.failedProxies.clear();
  }
}

// Uso
const rotator = new ProxyRotator(proxyList);

async function scrapeWithSmartRotation(url, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const { proxy, index } = rotator.getNext();
    
    const browser = await chromium.launch({ proxy });
    const page = await browser.newPage();

    try {
      await page.goto(url, { timeout: 30000 });
      const data = await page.content();
      await browser.close();
      return data;
    } catch (error) {
      console.error(`Error con el proxy ${index}:`, error.message);
      rotator.markFailed(index);
      await browser.close();
      
      if (attempt === maxRetries - 1) {
        throw new Error(`No se pudo cargar ${url} después de ${maxRetries} intentos`);
      }
    }
  }
}

Esta clase rastrea proxies inactivos y los excluye de la rotación. Al ocurrir un error, se cambia automáticamente al siguiente proxy de la lista.

Manejo de errores y verificación de funcionamiento

Al trabajar con proxies, pueden surgir errores específicos: tiempos de espera de conexión, denegación de autorización, bloqueo de IP por parte del sitio objetivo. Un manejo adecuado de errores es crítico para el funcionamiento estable del scraper.

Errores comunes y su manejo

async function safePageLoad(page, url, options = {}) {
  const defaultOptions = {
    timeout: 30000,
    waitUntil: 'networkidle0',
    maxRetries: 3,
    retryDelay: 2000
  };

  const config = { ...defaultOptions, ...options };

  for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
    try {
      await page.goto(url, {
        timeout: config.timeout,
        waitUntil: config.waitUntil
      });
      
      return { success: true, attempt };
    } catch (error) {
      console.error(`Intento ${attempt} fallido:`, error.message);

      // Análisis del tipo de error
      if (error.message.includes('ERR_PROXY_CONNECTION_FAILED')) {
        throw new Error('Proxy inactivo');
      }
      
      if (error.message.includes('ERR_TUNNEL_CONNECTION_FAILED')) {
        throw new Error('Error de tunelización del proxy');
      }

      if (error.message.includes('407')) {
        throw new Error('Error de autorización del proxy (407)');
      }

      if (error.message.includes('Navigation timeout')) {
        console.log(`Tiempo de espera de carga, reintentando en ${config.retryDelay}ms`);
        await new Promise(resolve => setTimeout(resolve, config.retryDelay));
        continue;
      }

      // Si es el último intento, lanzamos el error
      if (attempt === config.maxRetries) {
        throw error;
      }
    }
  }
}

Verificación de funcionamiento del proxy

async function testProxy(proxyConfig) {
  const browser = await chromium.launch({ proxy: proxyConfig });
  const page = await browser.newPage();

  const result = {
    working: false,
    ip: null,
    responseTime: null,
    error: null
  };

  const startTime = Date.now();

  try {
    await page.goto('https://api.ipify.org?format=json', { timeout: 10000 });
    const content = await page.textContent('body');
    const data = JSON.parse(content);
    
    result.working = true;
    result.ip = data.ip;
    result.responseTime = Date.now() - startTime;
  } catch (error) {
    result.error = error.message;
  } finally {
    await browser.close();
  }

  return result;
}

// Prueba de la lista de proxies
async function validateProxyList(proxyList) {
  console.log('Verificando proxies...');
  
  const results = await Promise.all(
    proxyList.map(async (proxy, index) => {
      const result = await testProxy(proxy);
      console.log(`Proxy ${index + 1}:`, result.working ? `✓ ${result.ip} (${result.responseTime}ms)` : `✗ ${result.error}`);
      return { proxy, ...result };
    })
  );

  const workingProxies = results.filter(r => r.working);
  console.log(`\nProxies operativos: ${workingProxies.length}/${proxyList.length}`);
  
  return workingProxies.map(r => r.proxy);
}

Esta función verifica cada proxy antes de usarlo, mide el tiempo de respuesta y devuelve solo los proxies operativos. Se recomienda ejecutar la validación al iniciar la aplicación.

Monitoreo y registro

class ProxyMonitor {
  constructor() {
    this.stats = {
      totalRequests: 0,
      successfulRequests: 0,
      failedRequests: 0,
      proxyErrors: 0,
      averageResponseTime: 0,
      requestsByProxy: new Map()
    };
  }

  recordRequest(proxyIndex, success, responseTime, error = null) {
    this.stats.totalRequests++;
    
    if (success) {
      this.stats.successfulRequests++;
      this.stats.averageResponseTime = 
        (this.stats.averageResponseTime * (this.stats.successfulRequests - 1) + responseTime) / 
        this.stats.successfulRequests;
    } else {
      this.stats.failedRequests++;
      if (error && error.includes('proxy')) {
        this.stats.proxyErrors++;
      }
    }

    // Estadísticas por cada proxy
    if (!this.stats.requestsByProxy.has(proxyIndex)) {
      this.stats.requestsByProxy.set(proxyIndex, { success: 0, failed: 0 });
    }
    
    const proxyStats = this.stats.requestsByProxy.get(proxyIndex);
    success ? proxyStats.success++ : proxyStats.failed++;
  }

  getReport() {
    const successRate = (this.stats.successfulRequests / this.stats.totalRequests * 100).toFixed(2);
    
    return {
      ...this.stats,
      successRate: `${successRate}%`,
      averageResponseTime: `${this.stats.averageResponseTime.toFixed(0)}ms`
    };
  }
}

// Uso
const monitor = new ProxyMonitor();

async function monitoredScrape(url, proxyIndex) {
  const startTime = Date.now();
  try {
    // ... código de scraping ...
    const responseTime = Date.now() - startTime;
    monitor.recordRequest(proxyIndex, true, responseTime);
  } catch (error) {
    monitor.recordRequest(proxyIndex, false, 0, error.message);
    throw error;
  }
}

Escenarios avanzados: geolocalización y fingerprinting

Los sistemas anti-bot modernos no solo verifican la dirección IP, sino también la correspondencia de la geolocalización, la zona horaria, el idioma del navegador y otros parámetros. Al usar proxies de otro país, es importante configurar todos estos parámetros correctamente.

Configuración de geolocalización e idioma para el proxy

const { chromium } = require('playwright');

async function createContextWithGeo(proxy, geoData) {
  const browser = await chromium.launch({ proxy });
  
  const context = await browser.newContext({
    locale: geoData.locale,           // 'es-ES', 'de-DE', 'fr-FR'
    timezoneId: geoData.timezone,     // 'America/Madrid', 'Europe/Berlin'
    geolocation: {
      latitude: geoData.latitude,
      longitude: geoData.longitude
    },
    permissions: ['geolocation']
  });

  return { browser, context };
}

// Ejemplo: proxy de Alemania
const germanyProxy = {
  server: 'http://de-proxy.example.com:8080',
  username: 'user',
  password: 'pass'
};

const germanyGeo = {
  locale: 'de-DE',
  timezone: 'Europe/Berlin',
  latitude: 52.520008,
  longitude: 13.404954
};

const { browser, context } = await createContextWithGeo(germanyProxy, germanyGeo);
const page = await context.newPage();

await page.goto('https://www.google.com');
// Google mostrará la versión alemana con resultados para Berlín

Este código configura el navegador para que se vea como un usuario real de Alemania: idioma alemán de la interfaz, zona horaria de Berlín y coordenadas del centro de Berlín.

Configuración completa de fingerprint

async function createStealthContext(proxy, profile) {
  const context = await chromium.launch({ proxy }).then(b => 
    b.newContext({
      locale: profile.locale,
      timezoneId: profile.timezone,
      userAgent: profile.userAgent,
      viewport: profile.viewport,
      deviceScaleFactor: profile.deviceScaleFactor,
      isMobile: profile.isMobile,
      hasTouch: profile.hasTouch,
      colorScheme: profile.colorScheme,
      geolocation: profile.geolocation,
      permissions: ['geolocation']
    })
  );

  return context;
}

// Perfil para Windows 10 + Chrome de EE. UU.
const desktopUSProfile = {
  locale: 'en-US',
  timezone: 'America/New_York',
  userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
  viewport: { width: 1920, height: 1080 },
  deviceScaleFactor: 1,
  isMobile: false,
  hasTouch: false,
  colorScheme: 'light',
  geolocation: { latitude: 40.7128, longitude: -74.0060 }
};

// Perfil para iPhone de Reino Unido
const mobileUKProfile = {
  locale: 'en-GB',
  timezone: 'Europe/London',
  userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
  viewport: { width: 390, height: 844 },
  deviceScaleFactor: 3,
  isMobile: true,
  hasTouch: true,
  colorScheme: 'light',
  geolocation: { latitude: 51.5074, longitude: -0.1278 }
};

La configuración integral de fingerprint reduce la probabilidad de detección de automatización. Es importante que todos los parámetros sean coherentes entre sí: por ejemplo, un User-Agent móvil debe ir acompañado de una resolución de pantalla móvil.

Importante: Al trabajar con plataformas publicitarias (Google Ads, Facebook Ads) o servicios financieros, recomendamos utilizar proxies móviles — tienen un trust score de operadores móviles reales y prácticamente no son bloqueados.

Evitando WebRTC leak

// Puppeteer: bloqueando WebRTC
const browser = await puppeteer.launch({
  args: [
    '--proxy-server=http://proxy.example.com:8080',
    '--disable-webrtc',
    '--disable-webrtc-hw-encoding',
    '--disable-webrtc-hw-decoding'
  ]
});

// Playwright: redefiniendo la API de WebRTC
const context = await browser.newContext({ proxy: proxyConfig });

await context.addInitScript(() => {
  // Bloqueamos RTCPeerConnection
  window.RTCPeerConnection = undefined;
  window.RTCDataChannel = undefined;
  window.RTCSessionDescription = undefined;
  
  // Redefinimos getUserMedia
  navigator.mediaDevices.getUserMedia = undefined;
  navigator.getUserMedia = undefined;
});

WebRTC puede revelar tu dirección IP real incluso al usar un proxy. Este código desactiva completamente la API de WebRTC en el navegador.

Comparación de Puppeteer y Playwright para trabajar con proxies

Criterio Puppeteer Playwright
Configuración de proxy A través de args de línea de comandos A través de un objeto de configuración
Autenticación page.authenticate() después del lanzamiento En el objeto proxy al crear
Múltiples proxies en un solo navegador No, se necesita un navegador separado Sí, a través de diferentes contextos
Soporte de navegadores Solo Chromium/Chrome Chromium, Firefox, WebKit
Rendimiento Inicio rápido de un solo navegador Más eficiente con múltiples contextos
TypeScript Tipos a través de @types/puppeteer Soporte integrado para TypeScript
Documentación Buena, con muchos ejemplos Excelente, más estructurada
Ecosistema Más plugins y extensiones Se desarrolla más rápido, nuevas funciones

Recomendaciones para la elección

Elige Puppeteer si:

  • Trabajas solo con Chrome/Chromium
  • Usas un proxy a la vez
  • Necesitas la máxima compatibilidad con herramientas existentes
  • Ya tienes una gran base de código en Puppeteer

Elige Playwright si:

  • Necesitas trabajar con Firefox o Safari (WebKit)
  • Requieres el uso simultáneo de múltiples proxies
  • Es importante el rendimiento al escalar
  • Estás escribiendo en TypeScript
  • Comienzas un nuevo proyecto desde cero

Rendimiento al rotar proxies

Prueba: scraping de 100 páginas con rotación de 10 proxies en un MacBook Pro M1:

Método Tiempo de ejecución Consumo de RAM
Puppeteer (reinicio del navegador) 8 minutos 23 segundos ~1.2 GB pico
Playwright (reinicio del navegador) 7 minutos 54 segundos ~1.1 GB pico
Playwright (pool de contextos) 4 minutos 12 segundos ~800 MB estable

Playwright con pool de contextos es casi 2 veces más rápido debido a la ausencia de sobrecargas por el inicio del navegador. Esto es crítico al hacer scraping de miles de páginas.

Conclusión

La integración de proxies con Puppeteer y Playwright es una práctica estándar para el web scraping, pruebas y automatización. Puppeteer ofrece simplicidad y un amplio ecosistema, mientras que Playwright proporciona una API moderna y un mejor rendimiento al trabajar con múltiples proxies a través de contextos de navegador.

Puntos clave que hemos cubierto:

  • Configuración básica de proxies HTTP, HTTPS y SOCKS5 en ambos frameworks
  • Autenticación por nombre de usuario y contraseña
  • Rotación de proxies para scraping a gran escala
  • Manejo de errores y validación de proxies antes de su uso
  • Configuración de geolocalización y fingerprint para evadir sistemas anti-bot
  • Comparación del rendimiento de diferentes enfoques

Para soluciones de producción, recomendamos combinar proxies de calidad con una configuración adecuada de fingerprint, manejo de errores y monitoreo. Esto asegurará un funcionamiento estable del scraper incluso en sitios protegidos.

Si planeas hacer scraping de sitios comerciales, marketplaces o trabajar con plataformas publicitarias, recomendamos utilizar proxies residenciales — proporcionan la máxima anonimidad y el mínimo riesgo de bloqueos gracias a las IP reales de usuarios domésticos.

```