Guia Completo de Puppeteer-Extra Stealth com Proxies Residenciais

Aprenda a combinar puppeteer-extra com o plugin stealth e proxies residenciais para construir crawlers Node.js indetectáveis. Cobrimos rotação de IPs, randomização de fingerprints e escalabilidade em produção.

Guia Completo de Puppeteer-Extra Stealth com Proxies Residenciais

Por Que Puppeteer e Playwright São Detectáveis

Se você já tentou fazer scraping com Puppeteer ou Playwright e recebeu um CAPTCHA logo na primeira requisição, não está sozinho. Ferramentas anti-bot como Cloudflare, Datadome e PerimeterX detectam browsers automatizados em segundos — e não é mágica, é ciência.

Os sinais mais explorados por sistemas de detecção incluem:

  • navigator.webdriver — o Puppeteer define essa propriedade como true por padrão. Qualquer script pode ler navigator.webdriver e identificar que o browser é controlado automaticamente.
  • Array de plugins inconsistente — browsers reais expõem navigator.plugins com entradas como Chrome PDF Viewer. O Puppeteer headless geralmente retorna um array vazio ou com valores inesperados.
  • Artefatos de iframe do ChromeDriver — variáveis como window.cdc_adoQpoasnfa76pfcZLmcfl_Array ou propriedades similares vazam a presença do ChromeDriver.
  • User-Agent headless — o UA headless contém "HeadlessChrome", um marcador óbvio.
  • Permissões de notificação — browsers headless falham de forma diferente em Notification.permission.

Um único sinal basta. A maioria dos WAFs combina múltiplos sinais em um score de confiança — se o score ultrapassa o limiar, você recebe um desafio ou um block direto.

Puppeteer-Extra com Stealth Plugin: O Que Realmente Corrige

O puppeteer-extra-plugin-stealth é um conjunto de patches de evasão que roda como middleware antes de cada página carregar. Ele não é um único truque — são 10+ sub-plugins que cobrem os sinais mais comuns:

Sub-pluginSinal corrigidoO que faz
stealth.webdrivernavigator.webdriverRemove ou redefine para undefined
stealth.pluginsnavigator.plugins vazioInjeta plugins realistas no array
stealth.languagesnavigator.languagesDefine idiomas consistentes com o UA
stealth.iframe.contentWindowArtefatos cdc_Remove propriedades de ChromeDriver do iframe
stealth.user-agentUA com HeadlessChromeSubstitui por UA de browser normal
stealth.permissionsNotification.permissionSimula comportamento de browser real
stealth.webglWebGL renderer headlessSubstitui string do renderer

A configuração básica é direta:

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

(async () => {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: ['--no-sandbox', '--disable-setuid-sandbox'],
  });

  const page = await browser.newPage();
  await page.goto('https://bot.sannysoft.com/');
  // Agora a maioria dos testes passa como "not detected"
  await new Promise(r => setTimeout(r, 5000));
  await browser.close();
})();

Mas aqui está o problema: stealth resolve fingerprints do browser, não do IP. Se você acessa um site protegido com o mesmo IP de datacenter 100 vezes, o WAF bloqueia pelo IP — não importa quão perfeito seja seu fingerprint.

Combinando Stealth com Proxies Residenciais: A Stack Anti-Detecção Completa

A combinação puppeteer-extra stealth proxy é o que separa scripts amadores de crawlers de produção. O stealth mascara quem é o browser; o proxy residencial mascara de onde vem a requisição.

Proxies residenciais usam IPs de dispositivos reais (ISPs residenciais), então cada requisição parece vir de um usuário legítimo em casa. Proxies de datacenter são baratos, mas os ranges de IP são conhecidos e frequentemente listados em blacklists.

Tipo de ProxyCustoDetectabilidadeMelhor Uso
Datacenter$$Alta — ranges conhecidosTarefas sem proteção anti-bot
Residencial rotativo$$$$Baixa — IPs de ISP realScraping geral, SERP, e-commerce
Mobile$$$$$Muito baixa — IPs de operadoraRedes sociais, apps mobile

Com o ProxyHat, a configuração é simples — as flags de geolocalização e sessão vão diretamente no username:

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

// Proxy residencial com geolocalização para EUA
const PROXY_URL = 'http://user-country-US:PASSWORD@gate.proxyhat.com:8080';

(async () => {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: [
      `--proxy-server=${PROXY_URL}`,
      '--no-sandbox',
      '--disable-setuid-sandbox',
    ],
  });

  const page = await browser.newPage();
  await page.goto('https://httpbin.org/ip');
  const content = await page.content();
  console.log(content); // IP residencial dos EUA
  await browser.close();
})();
Nota importante: Puppeteer aceita --proxy-server apenas no formato host:port. A autenticação deve ser feita via evento proxyRequiresAuthentication ou com o helper page.authenticate(). Veja o exemplo completo na seção de rotação.

Autenticação de Proxy no Puppeteer

O Chromium não suporta credenciais diretamente no argumento --proxy-server. Você precisa autenticar via handler de evento:

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

const PROXY_HOST = 'gate.proxyhat.com';
const PROXY_PORT = '8080';
const PROXY_USER = 'user-country-US';
const PROXY_PASS = 'PASSWORD';

(async () => {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: [
      `--proxy-server=http://${PROXY_HOST}:${PROXY_PORT}`,
      '--no-sandbox',
    ],
  });

  const page = await browser.newPage();

  // Autenticação do proxy
  await page.authenticate({
    username: PROXY_USER,
    password: PROXY_PASS,
  });

  await page.goto('https://httpbin.org/ip');
  const ip = await page.evaluate(() =>
    document.querySelector('pre')?.textContent
  );
  console.log('IP do proxy:', ip);
  await browser.close();
})();

Esse padrão é a base para tudo que vem a seguir — rotação de IP, sticky sessions e geolocalização são controlados alterando o username na chamada page.authenticate().

Avaliadores Customizados: Randomização de Canvas e WebGL por Sessão

O stealth plugin padroniza fingerprints, mas padronizar não é o mesmo que diversificar. Se 100 instâncias do seu crawler geram o mesmo canvas hash, um WAF avançado detecta o padrão. A solução: injetar ruído controlado por sessão.

Randomização de Canvas

Cada sessão injeta um deslocamento aleatório minúsculo nas operações de canvas — invisível para o olho humano, mas suficiente para alterar o hash:

const crypto = require('crypto');

async function injectCanvasNoise(page, sessionId) {
  // Gerar seed consistente por sessão, diferente entre sessões
  const seed = crypto
    .createHash('sha256')
    .update(sessionId)
    .digest('hex');
  const offset = parseInt(seed.slice(0, 8), 16) % 10 - 5; // -5 a +4

  await page.evaluateOnNewDocument((offset) => {
    const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
    HTMLCanvasElement.prototype.toDataURL = function (type) {
      const ctx = this.getContext('2d');
      if (ctx) {
        // Injetar ruído sub-pixel imperceptível
        const imgData = ctx.getImageData(
          0, 0,
          Math.min(this.width, 1),
          Math.min(this.height, 1)
        );
        imgData.data[0] = Math.max(0, Math.min(255, imgData.data[0] + offset));
        ctx.putImageData(imgData, 0, 0);
      }
      return origToDataURL.apply(this, [type]);
    };
  }, offset);
}

// Uso: antes de page.goto()
await injectCanvasNoise(page, 'session-abc123');

Randomização de WebGL

De forma similar, o renderer e vendor do WebGL podem ser randomizados por sessão:

const GL_RENDERERS = [
  'ANGLE (Intel, Intel(R) UHD Graphics 630, OpenGL 4.5)',
  'ANGLE (NVIDIA, NVIDIA GeForce GTX 1060, OpenGL 4.6)',
  'ANGLE (AMD, AMD Radeon RX 580, OpenGL 4.5)',
  'ANGLE (Intel, Intel(R) Iris(R) Xe Graphics, OpenGL 4.6)',
];

const GL_VENDORS = ['Google Inc. (Intel)', 'Google Inc. (NVIDIA)', 'Google Inc. (AMD)'];

async function injectWebGLFingerprint(page, sessionId) {
  const idx = crypto
    .createHash('sha256')
    .update(sessionId)
    .digest()
    .readUInt8(0) % GL_RENDERERS.length;

  await page.evaluateOnNewDocument((renderer, vendor) => {
    const getParameter = WebGLRenderingContext.prototype.getParameter;
    WebGLRenderingContext.prototype.getParameter = function (param) {
      // UNMASKED_RENDERER_WEBGL = 0x9246
      // UNMASKED_VENDOR_WEBGL = 0x9245
      if (param === 0x9246) return renderer;
      if (param === 0x9245) return vendor;
      return getParameter.call(this, param);
    };
  }, GL_RENDERERS[idx], GL_VENDORS[idx]);
}

// Uso
await injectWebGLFingerprint(page, 'session-abc123');

Combinados, esses dois evaluators garantem que cada sessão do seu crawler tenha um fingerprint único — mas ainda realista e consistente dentro da própria sessão.

Rotação de Proxy por Browser Context

Uma técnica avançada de Puppeteer anti-detection é usar browser.createIncognitoBrowserContext() para isolar sessões. Cada contexto pode ter seu próprio proxy e fingerprint, enquanto compartilha a mesma instância do Chromium — economizando RAM significativamente em escala.

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

const PROXY_HOST = 'gate.proxyhat.com';
const PROXY_PORT = '8080';
const PROXY_PASS = 'PASSWORD';

// Fila de países para rotacionar
const COUNTRIES = ['US', 'DE', 'BR', 'JP', 'GB'];

async function createContextWithProxy(browser, country) {
  const context = await browser.createIncognitoBrowserContext();
  const page = await context.newPage();

  // Cada contexto usa um proxy de país diferente
  const username = `user-country-${country}`;
  await page.authenticate({
    username,
    password: PROXY_PASS,
  });

  // Injetar fingerprint único por contexto
  const sessionId = `ctx-${country}-${Date.now()}`;
  await injectCanvasNoise(page, sessionId);
  await injectWebGLFingerprint(page, sessionId);

  return { context, page, country };
}

(async () => {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: [
      `--proxy-server=http://${PROXY_HOST}:${PROXY_PORT}`,
      '--no-sandbox',
    ],
  });

  // Criar 5 contextos isolados com proxies de países diferentes
  const sessions = await Promise.all(
    COUNTRIES.map(c => createContextWithProxy(browser, c))
  );

  // Executar tarefas em paralelo
  const results = await Promise.allSettled(
    sessions.map(async ({ page, country }) => {
      await page.goto('https://httpbin.org/ip');
      const ip = await page.evaluate(() =>
        document.querySelector('pre')?.textContent
      );
      console.log(`${country}: ${ip}`);
    })
  );

  // Limpeza
  for (const { context } of sessions) {
    await context.close();
  }
  await browser.close();
})();
Importante: o argumento --proxy-server é global para o browser. Para usar proxies diferentes por contexto, você precisa de um proxy rotativo no lado do servidor (como o ProxyHat) que alterna IPs com base no username. O page.authenticate() com usernames diferentes garante que cada contexto obtenha um IP diferente do pool.

Escalando para Produção: Fleet Containerizada e Browser Pools

Um crawler de produção não é um script — é um sistema. Aqui estão os padrões que funcionam em escala.

1. Pool de Browsers com Reutilização

Criar e destruir instâncias do Chromium a cada requisição é caro. Um pool com aquecimento prévio reduz latência e consumo de CPU:

class BrowserPool {
  constructor(maxBrowsers = 5) {
    this.maxBrowsers = maxBrowsers;
    this.pool = [];
    this.waitQueue = [];
  }

  async init() {
    for (let i = 0; i < this.maxBrowsers; i++) {
      const browser = await puppeteer.launch({
        headless: 'new',
        args: [
          '--proxy-server=http://gate.proxyhat.com:8080',
          '--no-sandbox',
          '--disable-dev-shm-usage',
          '--disable-gpu',
        ],
      });
      this.pool.push({ browser, inUse: false });
    }
  }

  async acquire() {
    const available = this.pool.find(b => !b.inUse);
    if (available) {
      available.inUse = true;
      return available;
    }
    if (this.pool.length < this.maxBrowsers) {
      const browser = await puppeteer.launch({
        headless: 'new',
        args: ['--proxy-server=http://gate.proxyhat.com:8080', '--no-sandbox'],
      });
      const entry = { browser, inUse: true };
      this.pool.push(entry);
      return entry;
    }
    // Esperar até um slot ficar livre
    return new Promise(resolve => this.waitQueue.push(resolve));
  }

  release(entry) {
    entry.inUse = false;
    if (this.waitQueue.length > 0) {
      const next = this.waitQueue.shift();
      entry.inUse = true;
      next(entry);
    }
  }

  async destroy() {
    await Promise.all(this.pool.map(b => b.browser.close()));
    this.pool = [];
  }
}

2. Containerização com Docker

Cada worker roda em seu próprio container com recursos limitados. Isso garante isolamento de falhas e escalabilidade horizontal:

# Dockerfile
FROM node:20-slim

# Dependências do Chromium
RUN apt-get update && apt-get install -y \
    chromium \
    fonts-liberation \
    libappindicator3-1 \
    libasound2 \
    libatk-bridge2.0-0 \
    libdrm2 \
    libgbm1 \
    libgtk-3-0 \
    libnspr4 \
    libnss3 \
    libxss1 \
    xdg-utils \
    --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .

ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
ENV NODE_ENV=production

# Limitar recursos: 1 CPU, 1GB RAM
CMD ["node", "--max-old-space-size=768", "worker.js"]

3. Orquestração com Docker Compose

# docker-compose.yml
version: '3.8'
services:
  scraper-worker:
    build: .
    deploy:
      replicas: 10
      resources:
        limits:
          cpus: '1.0'
          memory: 1024M
        reservations:
          cpus: '0.5'
          memory: 512M
    environment:
      - PROXY_USER_BASE=user-country-US
      - PROXY_PASSWORD=PASSWORD
      - PROXY_HOST=gate.proxyhat.com
      - PROXY_PORT=8080
    restart: unless-stopped

4. Métricas Essenciais

Monitore estas métricas para manter a saúde do fleet:

  • Taxa de sucesso — % de requisições que retornam 200 sem CAPTCHA. Target: >95%.
  • Latência P50/P95 — tempo de carregamento incluindo proxy. Se P95 > 15s, investigue.
  • Uso de memória por browser — Chromium pode consumir 300-500MB por aba. Limite abas por instância.
  • Taxa de rotação de IP — se um IP é reutilizado mais de 5x no mesmo domínio, aumente a rotação.

5. Sticky Sessions vs Rotação por Requisição

Para login e fluxos multi-step, use sticky sessions que mantêm o mesmo IP por 10-30 minutos:

// Sticky session: o IP permanece o mesmo enquanto o sessionId não mudar
const STICKY_USER = 'user-country-US-session-orderFlow432';
await page.authenticate({
  username: STICKY_USER,
  password: PROXY_PASS,
});

Para scraping de SERP ou monitoramento de preços onde cada requisição é independente, use rotação por requisição — basta omitir a flag session- ou usar um ID diferente a cada chamada.

Considerações Éticas e Legais

Stealth e proxies são ferramentas poderosas — e com grande poder vem grande responsabilidade. Use essa stack para:

  • Scraping legítimo — coletar dados públicos para pesquisa, monitoramento de preços, verificação de SEO.
  • QA e testes — simular usuários de diferentes localizações para testar sua própria aplicação.
  • Coleta de dados para IA — reunir datasets de treinamento a partir de fontes públicas.

Não use para:

  • Violar termos de serviço de plataformas de forma fraudulenta.
  • Burlar limites de taxa para ataques de credential stuffing ou carding.
  • Enganar sistemas de verificação de identidade.

Respeite robots.txt, implemente rate limiting razoável, e esteja em conformidade com GDPR e CCPA quando processar dados pessoais. Se um site oferece uma API pública, use-a em vez de scraping — é mais confiável e mais rápido.

Key Takeaways

  • Puppeteer puro é detectável por sinais como navigator.webdriver, plugins vazios e artefatos do ChromeDriver — o stealth plugin corrige a maioria desses sinais automaticamente.
  • Stealth resolve o fingerprint do browser, mas não o IP — proxies residenciais são essenciais para evitar bloqueios baseados em IP.
  • Combine puppeteer-extra-plugin-stealth com proxies residenciais do ProxyHat para a stack anti-detecção mais robusta.
  • Randomize canvas e WebGL fingerprints por sessão para evitar detecção por correlação de hash.
  • Use browser.createIncognitoBrowserContext() para isolar sessões com proxies diferentes na mesma instância do Chromium.
  • Em produção, use browser pools, containerização e métricas para escalar com confiabilidade.
  • Stealth é para scraping legítimo — não para fraude.

Pronto para construir crawlers que realmente funcionam em produção? Confira os planos de proxy do ProxyHat e as localizações disponíveis para começar hoje.

Pronto para começar?

Acesse mais de 50M de IPs residenciais em mais de 148 países com filtragem por IA.

Ver preçosProxies residenciais
← Voltar ao Blog