Puppeteer-Extra Stealth + Proxy Residenziali: Guida Anti-Rilevamento 2025

Scopri come combinare puppeteer-extra, il plugin stealth e i proxy residenziali per costruire crawler Node.js non rilevabili. Codice, fingerprint randomization e pattern di scalabilità.

Puppeteer-Extra Stealth + Proxy Residenziali: Guida Anti-Rilevamento 2025

Perché Puppeteer Nudo Viene Bloccato Subito

Se hai mai lanciato uno script Puppeteer contro un sito protetto da Cloudflare, Datadome o PerimeterX, sai già cosa succede: una pagina di challenge, un blocco silenzioso, o un HTTP 403 immediato. Il problema non è il tuo codice — è che il browser automatizzato trasmette decine di segnali che lo identificano come non-umano.

I sistemi anti-bot moderni non si limitano a controllare un singolo header. Costruiscono un fingerprint composito del visitatore: proprietà del navigator, comportamento degli oggetti JavaScript, pattern di rete, e coerenza tra tutti questi elementi. Un browser controllato da Puppeteer fallisce su molteplici fronti simultaneamente.

I segnali di rilevamento più comuni

  • navigator.webdriver = true — La W3C WebDriver spec richiede che questa proprietà sia true quando il browser è sotto automazione. Puppeteer non la nasconde.
  • plugins array vuoto o inconsistente — I browser reali riportano plugin come Chrome PDF Viewer; Chromium headless spesso riporta un array vuoto.
  • iframe chrome.runtime — L'artefatto chrome.runtime su iframes è presente nelle estensioni Chromium ma assente o inconsistente in Puppeteer.
  • Canvas e WebGL fingerprint identiche — Ogni istanza headless produce lo stesso hash canvas/WebGL, creando cluster di fingerprint identiche che i sistemi anti-bot rilevano come bot farm.
  • User-Agent incoerente — Puppeteer usa un UA generico che spesso non corrisponde alla piattaforma reale (es. UA Windows con navigator.platform = MacIntel).
  • Missing WebRTC IPs — I browser reali espongono IP locali tramite WebRTC; Chromium headless con --disable-webrtc non lo fa.

Qualsiasi singolo segnale può bastare per un blocco. I sistemi avanzati li combinano in un confidence score: anche se superi il test navigator.webdriver, una canvas fingerprint identica a migliaia di altre sessioni ti banna comunque.

Puppeteer-Extra e il Plugin Stealth: Cosa Patcha Realmente

puppeteer-extra è un wrapper modulare per Puppeteer che supporta un sistema di plugin. Il più critico per l'anti-rilevamento è puppeteer-extra-plugin-stealth, che applica patch a 10+ superfici di rilevamento attraverso un sistema di Page.evaluateOnNewDocument hooks.

Ecco i principali sottoplugin stealth e cosa fanno:

Stealth Sub-pluginSegnale PatchatoTecnica
webdrivernavigator.webdriver = trueRidefinisce getter per restituire undefined
chrome.runtimeiframe chrome artifactsInietta mock di chrome.runtime nei frames
plugin.lengthplugins array vuotoAggiunge plugin fittizi realistici
languagesnavigator.languages inconsistenteOverride con lingue coerenti col UA
iframe.contentWindowcross-origin iframe detectionNormalizza proprietà cross-origin
webgl.vendorWebGL vendor/renderer sospettiOverride di vendor e renderer strings
media.codecssupporta codecs mancantiRiempie il mock di codecs supportati
user-agent-overrideUA non coerente con platformSincronizza UA, platform, appVersion

Il plugin stealth agisce come un middleware di inizializzazione: prima che qualsiasi JavaScript della pagina venga eseguito, inietta gli override tramite evaluateOnNewDocument. Questo è fondamentale — se le patch arrivano dopo il primo script della pagina, il rilevamento è già avvenuto.

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

// Registra il plugin stealth come middleware
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/');

  // Verifica: navigator.webdriver deve essere undefined
  const isUndetected = await page.evaluate(() => {
    return navigator.webdriver === undefined;
  });
  console.log('Stealth attivo:', isUndetected); // true

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

Questo risolve la maggior parte dei controlli JavaScript lato client. Ma c'è un problema: il plugin stealth non tocca il livello di rete. Il tuo IP datacenter, la mancanza di cookie storici, e il pattern di richiesta restano quelli di un bot.

Combinare Stealth con Proxy Residenziali: Lo Stack Anti-Rilevamento Completo

Il vero vantaggio competitivo arriva quando combini patch lato browser (stealth) con patch lato rete (proxy residenziali). Un sito anti-bot vede: un browser con fingerprint coerente e un IP residenziale con storico di traffico legittimo. Questo è il gold standard per il Puppeteer anti-detection.

I proxy residenziali sono cruciali perché:

  • Gli IP datacenter sono catalogati in range AS dedicati — i sistemi anti-bot mantengono database di ASN datacenter e li bloccano proattivamente.
  • Gli IP residenziali provengono da ISP veri, con reputazione storica e pattern di traffico realistici.
  • La rotazione per-request garantisce che ogni richiesta parte da un contesto IP fresco, rendendo impossibile il rate-limiting basato su IP.

Con ProxyHat, la configurazione è diretta:

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

puppeteer.use(StealthPlugin());

// Configurazione proxy residenziale ProxyHat
const PROXY_HOST = 'gate.proxyhat.com';
const PROXY_PORT = 8080;
const PROXY_USER = 'your-username-country-US';
const PROXY_PASS = 'your-password';

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

  const page = await browser.newPage();

  // Autenticazione 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').innerText
  );
  console.log('IP residenziale:', ip);

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

Nota il pattern country-US nel username — ProxyHat supporta geo-targeting a livello di nazione e città, essenziale per lo SERP tracking localizzato.

Perché non basta un solo layer

Un errore comune: usare proxy residenziali senza stealth, o stealth senza proxy residenziali. Ecco perché entrambi sono necessari:

StackRilevamento JSRilevamento IP/ASNSuccess Rate Stimato
Puppeteer nudo❌ Fail immediato❌ IP datacenter bloccato5-15%
+ Stealth plugin✅ Patch applicate❌ IP datacenter bloccato30-50%
+ Proxy residenziali❌ Fail immediato✅ IP residenziale pulito20-40%
Stealth + Proxy residenziali✅ Patch applicate✅ IP residenziale pulito85-95%

I numeri parlano chiaro: il layer JavaScript e il layer di rete coprono superfici di attacco diverse. Entrambi sono necessari per la produzione.

Custom Evaluators: Randomizzazione Canvas e WebGL Per-Session

Il plugin stealth standardizza le fingerprint — ma se 1000 istanze usano la stessa fingerprint standardizzata, i sistemi anti-bot rilevano il cluster. La soluzione: randomizzare le fingerprint per sessione aggiungendo rumore controllato a canvas e WebGL.

Questo è il punto dove puppeteer-extra brilla davvero: puoi scrivere plugin custom che si integrano nello stesso middleware pipeline del plugin stealth.

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

// Plugin custom per canvas/WebGL fingerprint randomization
class FingerprintRandomizerPlugin extends puppeteer.Plugin {
  constructor(opts = {}) {
    super(opts);
    this.noiseLevel = opts.noiseLevel || 0.01;
  }

  get name() { return 'fingerprint-randomizer'; }

  async onPageCreated(page) {
    const seed = Math.random() * 10000;
    const noise = this.noiseLevel;

    await page.evaluateOnNewDocument((seed, noise) => {
      // Canvas noise: aggiunge micro-variazioni ai pixel
      const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
      HTMLCanvasElement.prototype.toDataURL = function(...args) {
        const ctx = this.getContext('2d');
        if (ctx) {
          const imgData = ctx.getImageData(0, 0, this.width, this.height);
          for (let i = 0; i < imgData.data.length; i += 4) {
            imgData.data[i] += Math.round(
              (Math.sin(seed + i) * noise) * 2
            );
          }
          ctx.putImageData(imgData, 0, 0);
        }
        return origToDataURL.apply(this, args);
      };

      // WebGL vendor/renderer randomization
      const getParameterOrig = WebGLRenderingContext.prototype.getParameter;
      const vendors = ['Google Inc.', 'Google Inc. (NVIDIA)', 'Google Inc. (Intel)'];
      const renderers = [
        'ANGLE (NVIDIA GeForce GTX 1060)',
        'ANGLE (Intel HD Graphics 630)',
        'ANGLE (AMD Radeon RX 580)'
      ];
      WebGLRenderingContext.prototype.getParameter = function(param) {
        if (param === 0x1F00) return vendors[Math.floor(seed) % vendors.length]; // UNMASKED_VENDOR
        if (param === 0x1F01) return renderers[Math.floor(seed) % renderers.length]; // UNMASKED_RENDERER
        return getParameterOrig.call(this, param);
      };
    }, seed, noise);
  }
}

puppeteer.use(StealthPlugin());
puppeteer.use(new FingerprintRandomizerPlugin({ noiseLevel: 0.02 }));

Il pattern Math.sin(seed + i) è importante: produce variazioni deterministiche per sessione ma apparentemente casuali tra sessioni diverse. Questo significa che una singola sessione ha una fingerprint coerente (necessario per non innescare controlli di coerenza intra-sessione), ma sessioni diverse hanno fingerprint diverse (necessario per non formare cluster rilevabili).

Rotazione Proxy Per-Browser-Context

Puppeteer supporta BrowserContext (simile ai profili di Firefox): contesti isolati con cookie, storage e cache separati. Questo è il livello giusto per la rotazione dei proxy — ogni context riceve il proprio IP residenziale e la propria fingerprint.

Il modo idiomatico per implementare la rotazione è attraverso un middleware proxy manager che assegna un proxy diverso ad ogni context:

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

puppeteer.use(StealthPlugin());

const GATE = 'gate.proxyhat.com';
const PORT = 8080;
const USER = 'your-username';
const PASS = 'your-password';

// Genera credenziali proxy con geo-targeting e sessione sticky
class ProxyRotator {
  constructor(countries = ['US', 'DE', 'GB', 'FR', 'JP']) {
    this.countries = countries;
    this.counter = 0;
  }

  next() {
    const country = this.countries[
      this.counter % this.countries.length
    ];
    const sessionId = `sess-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
    this.counter++;
    return {
      server: `http://${GATE}:${PORT}`,
      username: `${USER}-country-${country}-session-${sessionId}`,
      password: PASS,
      country
    };
  }
}

// Crea un BrowserContext isolato con proxy dedicato
async function createContextWithProxy(browser, proxyRotator) {
  const proxy = proxyRotator.next();

  const context = await browser.createBrowserContext({
    proxyServer: proxy.server
  });

  const page = await context.newPage();
  await page.authenticate({
    username: proxy.username,
    password: proxy.password
  });

  return { context, page, proxy };
}

(async () => {
  const browser = await puppeteer.launch({ headless: 'new' });
  const rotator = new ProxyRotator(['US', 'DE', 'GB']);

  // Esegui 3 task con IP e contesti diversi
  const tasks = [
    'https://httpbin.org/ip',
    'https://httpbin.org/headers',
    'https://httpbin.org/ip'
  ];

  for (const url of tasks) {
    const { context, page, proxy } = await createContextWithProxy(browser, rotator);
    console.log(`Context con IP ${proxy.country}:`);

    await page.goto(url, { waitUntil: 'networkidle2' });
    const content = await page.evaluate(() => document.body.innerText);
    console.log(content);

    await context.close(); // Chiude context + libera IP
  }

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

Il flag session-{id} nel username ProxyHat garantisce una sticky session: tutte le richieste nello stesso context usano lo stesso IP residenziale. Quando chiudi il context e ne crei uno nuovo con un nuovo session ID, ottieni un IP fresco. Questo è essenziale per task multi-step come login, navigazione di categorie, e checkout.

Pattern di Scalabilità: Fleet Containerizzate e Browser Pool

Un singolo browser Puppeteer può gestire 5-10 tab concorrenti prima che il consumo di RAM diventi proibitivo (ogni tab ~80-150MB). Per lo scraping di produzione, hai bisogno di un browser pool con gestione del ciclo di vita.

Architettura di riferimento

Il pattern più efficace per lo web scraping a larga scala combina tre componenti:

  • Job Queue (Redis/BullMQ) — distribuisce i task e gestisce i retry.
  • Browser Pool Manager — mantiene un pool di browser contexts pronti, ciascuno con proxy e fingerprint dedicati.
  • Worker Processes — consumano dalla coda, acquiscono un context, eseguono lo scrape, rilasciano il context.
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
const { EventEmitter } = require('events');

puppeteer.use(StealthPlugin());

class BrowserPool extends EventEmitter {
  constructor({ maxBrowsers = 5, maxContextsPerBrowser = 3, proxyRotator }) {
    super();
    this.maxBrowsers = maxBrowsers;
    this.maxContextsPerBrowser = maxContextsPerBrowser;
    this.proxyRotator = proxyRotator;
    this.browsers = [];
    this.availableContexts = [];
  }

  async init() {
    for (let i = 0; i < this.maxBrowsers; i++) {
      const browser = await puppeteer.launch({
        headless: 'new',
        args: [
          '--no-sandbox',
          '--disable-setuid-sandbox',
          '--disable-dev-shm-usage',
          '--disable-gpu'
        ]
      });
      this.browsers.push({ browser, contexts: 0 });
    }
  }

  async acquire() {
    // Trova un browser con slot disponibili
    const slot = this.browsers.find(
      b => b.contexts < this.maxContextsPerBrowser
    );
    if (!slot) throw new Error('Pool esaurito');

    const proxy = this.proxyRotator.next();
    const context = await slot.browser.createBrowserContext({
      proxyServer: proxy.server
    });
    const page = await context.newPage();
    await page.authenticate({
      username: proxy.username,
      password: proxy.password
    });

    slot.contexts++;
    this.emit('acquired', { proxy });

    return {
      context,
      page,
      proxy,
      release: async () => {
        await context.close();
        slot.contexts--;
        this.emit('released', { proxy });
      }
    };
  }

  async drain() {
    await Promise.all(
      this.browsers.map(({ browser }) => browser.close())
    );
    this.browsers = [];
  }
}

// Utilizzo
const rotator = new ProxyRotator(['US', 'DE', 'GB', 'FR']);
const pool = new BrowserPool({
  maxBrowsers: 4,
  maxContextsPerBrowser: 3,
  proxyRotator: rotator
});

(async () => {
  await pool.init();

  const urls = Array.from({ length: 12 }, (_, i) =>
    `https://httpbin.org/ip?task=${i}`
  );

  const results = await Promise.all(
    urls.map(async (url) => {
      const handle = await pool.acquire();
      try {
        await handle.page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
        const body = await handle.page.evaluate(() => document.body.innerText);
        return { url, body };
      } finally {
        await handle.release();
      }
    })
  );

  console.log(`Completate ${results.length} richieste`);
  await pool.drain();
})();

Containerizzazione con Docker

Per il deployment in produzione, ogni worker gira in un container Docker con Chromium dedicato. Il pattern consigliato:

  • 1 container = 1 worker = 1 browser instance — isolamento completo, crash di un container non influenza gli altri.
  • Limita RAM a --memory=2g e CPU a --cpus=1.5 per prevenire memory leak di Chromium.
  • Usa --disable-dev-shm-usage per evitare problemi con /dev/shm nei container.
  • Mount /dev/shm come tmpfs con dimensione adeguata (almeno 512MB).
# Dockerfile per Puppeteer worker
FROM node:20-slim

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

ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium

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

# Health check: verifica che il worker risponda
HEALTHCHECK --interval=30s --timeout=10s \
  CMD node healthcheck.js || exit 1

CMD ["node", "worker.js"]

Per l'orchestrazione, usa Kubernetes con Horizontal Pod Autoscaler: scala il numero di worker in base alla profondità della coda (metrica BullMQ). Imposta maxReplicas in base al tuo piano ProxyHat — controlla i limiti del tuo piano prima di scalare.

Gestione delle risorse e resilience

Alcune regole operative per la produzione:

  • Restart browser ogni N richieste — Chromium ha memory leak noti. Dopo 50-100 richieste, chiudi il browser e lanciane uno nuovo.
  • Timeout aggressivi — Imposta timeout: 30000 su goto e aspetta al massimo 15 secondi per selettori specifici.
  • Circuit breaker — Se 3 richieste consecutive falliscono con lo stesso IP, scarta il proxy e richiedine uno nuovo.
  • Retry con backoff — Al primo fallimento, retry dopo 2s; al secondo, 4s; al terzo, scarta il task in una dead letter queue.

Nota Etica: Stealth per Scraping Legittimo, Non per Frode

Le tecniche descritte in questa guida sono potenti — e con la potenza arriva la responsabilità. Il Puppeteer anti-detection con proxy residenziali è legittimo quando:

  • Raccogli dati pubblici per ricerca di mercato, monitoraggio prezzi, o verifica di contenuti.
  • Esegui QA e testing dei tuoi stessi siti da diverse geolocazioni.
  • Addestri modelli AI su dati pubblicamente accessibili rispettando robots.txt.

Non è accettabile per:

  • Creare account falsi, scalare ticket, o manipolare aste.
  • Aggirare controlli di sicurezza per frode o phishing.
  • Violare i Termini di Servizio di un sito per estrarre dati esplicitamente protetti.

Rispetta sempre robots.txt, i ToS del sito, e le normative applicabili (GDPR, CCPA). Lo scraping etico è un diritto — l'abuso non lo è.

Key Takeaways

  • Il layer browser e il layer rete sono indipendenti — il plugin stealth patcha JavaScript, i proxy residenziali patchano l'identità di rete. Entrambi sono necessari.
  • puppeteer-extra è architetturato come middleware — i plugin si compongono. Stealth + fingerprint randomizer + proxy rotation sono layers complementari, non alternativi.
  • Randomizza le fingerprint per sessione — fingerprint identiche su migliaia di sessioni sono rilevabili quanto navigator.webdriver = true.
  • Usa BrowserContext per isolamento — ogni context ha il proprio IP, cookie, e storage. È l'unità di lavoro corretta per la rotazione.
  • Scala con container, non con tab — un browser per container, max 3-5 context per browser, restart periodico per memory leak.
  • Lo stealth è uno strumento, non una licenza — usalo per scraping legittimo, rispetta robots.txt e ToS.

Per iniziare con proxy residenziali ottimizzati per Puppeteer, visita la pagina pricing di ProxyHat o esplora le locazioni disponibili per il geo-targeting globale.

Pronto per iniziare?

Accedi a oltre 50M di IP residenziali in oltre 148 paesi con filtraggio AI.

Vedi i prezziProxy residenziali
← Torna al Blog