Puppeteer e Playwright sono strumenti popolari per l'automazione del browser e lo scraping web. Quando si lavora con grandi volumi di richieste o si effettuano scraping di siti protetti, l'uso di proxy diventa critico per evitare blocchi IP. In questa guida esamineremo tutti i modi per integrare i proxy in entrambi gli strumenti, dalla configurazione di base a scenari avanzati con rotazione e gestione degli errori.
Fondamenti del funzionamento dei proxy nei browser headless
Puppeteer e Playwright controllano browser reali (Chromium, Firefox, WebKit) tramite il DevTools Protocol. Ciò significa che il proxy viene configurato a livello di avvio del browser, non per singole richieste. Entrambi gli strumenti supportano proxy HTTP, HTTPS e SOCKS5, ma hanno API diverse per la loro configurazione.
Le principali differenze rispetto alle normali librerie HTTP (axios, fetch):
- Il proxy viene impostato all'avvio del browser — non è possibile cambiare il proxy al volo all'interno di una singola sessione del browser
- Supporto per JavaScript e rendering — il proxy si applica a tutte le risorse della pagina (immagini, script, XHR)
- Gestione automatica dei reindirizzamenti e dei cookie — il browser si comporta come un vero utente
- Fingerprinting — anche con il proxy, i siti possono determinare l'automazione in base alle caratteristiche del browser
Importante: Per compiti che richiedono frequenti cambi di IP (scraping di migliaia di pagine, registrazione di massa), è più efficace utilizzare proxy residenziali con rotazione — consentono di cambiare IP per ogni richiesta senza riavviare il browser.
Configurazione del proxy in Puppeteer
Puppeteer è una libreria di Google per controllare Chrome/Chromium. Il proxy viene configurato tramite argomenti di avvio del browser utilizzando il flag --proxy-server.
Configurazione di base del 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();
// Verifica dell'indirizzo IP
await page.goto('https://api.ipify.org?format=json');
const content = await page.content();
console.log('IP attuale:', content);
await browser.close();
})();
Questo codice avvia il browser tramite un server proxy. Tutte le richieste HTTP e HTTPS passeranno attraverso il proxy specificato. Per verificare la funzionalità, viene utilizzato il servizio ipify, che restituisce l'indirizzo IP esterno.
Configurazione del proxy SOCKS5
const browser = await puppeteer.launch({
headless: true,
args: [
'--proxy-server=socks5://proxy.example.com:1080'
]
});
// Il resto del codice è simile
I proxy SOCKS5 operano a un livello più basso e supportano il traffico UDP, il che può essere utile per alcune applicazioni web. La sintassi è identica a quella dei proxy HTTP, cambia solo il protocollo nell'URL.
Utilizzo del proxy per domini specifici
const browser = await puppeteer.launch({
args: [
'--proxy-server=http://proxy1.example.com:8080',
'--proxy-bypass-list=localhost;127.0.0.1;*.internal.com'
]
});
// Le richieste locali e quelle a *.internal.com andranno direttamente
// Tutte le altre — tramite proxy
Il flag --proxy-bypass-list consente di escludere determinati domini dalla proxy. Questo è utile quando è necessario combinare richieste dirette e proxy.
Configurazione del proxy in Playwright
Playwright è una libreria più moderna di Microsoft che supporta Chromium, Firefox e WebKit. Il proxy viene configurato tramite un oggetto di configurazione, il che rende l'API più chiara e tipizzata.
Configurazione di base del 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 tramite proxy:', ip);
await browser.close();
})();
Playwright utilizza l'oggetto proxy invece degli argomenti della riga di comando. Questo garantisce una migliore tipizzazione in TypeScript e un codice più pulito.
Configurazione del proxy a livello di contesto
const browser = await chromium.launch();
// Contesto 1 - con proxy
const context1 = await browser.newContext({
proxy: {
server: 'http://proxy1.example.com:8080'
}
});
// Contesto 2 - con un altro proxy
const context2 = await browser.newContext({
proxy: {
server: 'http://proxy2.example.com:8080'
}
});
const page1 = await context1.newPage();
const page2 = await context2.newPage();
// Ogni pagina utilizza il proprio proxy!
Uno dei principali vantaggi di Playwright è la possibilità di creare più contesti del browser con proxy diversi all'interno di un unico processo. Questo risparmia risorse quando si lavora con molti indirizzi IP.
Esclusione di domini dalla proxy
const browser = await chromium.launch({
proxy: {
server: 'http://proxy.example.com:8080',
bypass: 'localhost,127.0.0.1,*.internal.com'
}
});
// Le richieste a localhost e *.internal.com andranno direttamente
Autenticazione con nome utente e password
La maggior parte dei proxy commerciali richiede autenticazione. Entrambi gli strumenti supportano l'autenticazione, ma la implementano in modo diverso.
Autenticazione in Puppeteer
const browser = await puppeteer.launch({
args: ['--proxy-server=http://proxy.example.com:8080']
});
const page = await browser.newPage();
// Impostiamo le credenziali per il 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);
Il metodo page.authenticate() imposta le credenziali per tutte le richieste successive su questa pagina. È importante chiamarlo PRIMA del primo page.goto().
Autenticazione in 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 consente di specificare le credenziali direttamente nell'oggetto di configurazione del proxy — questo è più conveniente e sicuro, poiché non richiede chiamate a metodi aggiuntivi.
Metodo alternativo: credenziali nell'URL
// Funziona in entrambi i framework
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 }
});
Questo metodo funziona, ma non è raccomandato per la produzione — le credenziali potrebbero finire nei log. Utilizzare variabili d'ambiente per memorizzare dati sensibili.
Consiglio: Quando si lavora con lo scraping di siti commerciali, si consiglia di utilizzare proxy residenziali — hanno IP reali di utenti domestici e vengono bloccati meno frequentemente dai sistemi anti-bot.
Rotazione dei proxy e gestione del pool IP
Per uno scraping su larga scala è necessario cambiare regolarmente gli indirizzi IP. Poiché il proxy viene impostato all'avvio del browser, la rotazione richiede il riavvio della sessione del browser.
Rotazione semplice con un array di proxy (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(`Errore su ${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);
Questo codice itera ciclicamente sui proxy dell'elenco e avvia un nuovo browser per ogni URL. Il metodo i % proxyList.length garantisce una rotazione ciclica.
Rotazione con pool di contesti (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();
// Creiamo un pool di contesti con proxy diversi
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(`Errore su ${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 consente di creare più contesti in un unico browser, ognuno con il proprio proxy. Questo risparmia memoria e tempo di avvio rispetto a un riavvio completo del browser.
Rotazione intelligente con monitoraggio degli errori
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('Tutti i proxy sono non disponibili');
}
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} contrassegnato come non disponibile`);
}
resetFailed() {
this.failedProxies.clear();
}
}
// Utilizzo
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(`Errore con il proxy ${index}:`, error.message);
rotator.markFailed(index);
await browser.close();
if (attempt === maxRetries - 1) {
throw new Error(`Impossibile caricare ${url} dopo ${maxRetries} tentativi`);
}
}
}
}
Questa classe tiene traccia dei proxy non funzionanti ed esclude questi dalla rotazione. In caso di errore, passa automaticamente al proxy successivo dell'elenco.
Gestione degli errori e verifica della funzionalità
Quando si lavora con i proxy, si verificano errori specifici: timeout di connessione, rifiuto di autorizzazione, blocco IP da parte del sito di destinazione. Una corretta gestione degli errori è critica per il funzionamento stabile dello scraper.
Errori comuni e loro gestione
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(`Tentativo ${attempt} fallito:`, error.message);
// Analisi del tipo di errore
if (error.message.includes('ERR_PROXY_CONNECTION_FAILED')) {
throw new Error('Proxy non disponibile');
}
if (error.message.includes('ERR_TUNNEL_CONNECTION_FAILED')) {
throw new Error('Errore di tunneling del proxy');
}
if (error.message.includes('407')) {
throw new Error('Errore di autorizzazione del proxy (407)');
}
if (error.message.includes('Navigation timeout')) {
console.log(`Timeout di caricamento, riprovare tra ${config.retryDelay}ms`);
await new Promise(resolve => setTimeout(resolve, config.retryDelay));
continue;
}
// Se è l'ultimo tentativo, rilanciare l'errore
if (attempt === config.maxRetries) {
throw error;
}
}
}
}
Verifica della funzionalità 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;
}
// Testare l'elenco dei proxy
async function validateProxyList(proxyList) {
console.log('Verifica dei proxy...');
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(`\nProxy funzionanti: ${workingProxies.length}/${proxyList.length}`);
return workingProxies.map(r => r.proxy);
}
Questa funzione verifica ogni proxy prima dell'uso, misura il tempo di risposta e restituisce solo i proxy funzionanti. Si consiglia di eseguire la validazione all'avvio dell'applicazione.
Monitoraggio e logging
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++;
}
}
// Statistiche per ogni 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`
};
}
}
// Utilizzo
const monitor = new ProxyMonitor();
async function monitoredScrape(url, proxyIndex) {
const startTime = Date.now();
try {
// ... codice di scraping ...
const responseTime = Date.now() - startTime;
monitor.recordRequest(proxyIndex, true, responseTime);
} catch (error) {
monitor.recordRequest(proxyIndex, false, 0, error.message);
throw error;
}
}
Scenari avanzati: geolocalizzazione e fingerprinting
I moderni sistemi anti-bot controllano non solo l'indirizzo IP, ma anche la corrispondenza della geolocalizzazione, del fuso orario, della lingua del browser e di altri parametri. Quando si utilizza un proxy da un altro paese, è importante configurare correttamente tutti questi parametri.
Configurazione della geolocalizzazione e della lingua per il proxy
const { chromium } = require('playwright');
async function createContextWithGeo(proxy, geoData) {
const browser = await chromium.launch({ proxy });
const context = await browser.newContext({
locale: geoData.locale, // 'en-US', 'de-DE', 'fr-FR'
timezoneId: geoData.timezone, // 'America/New_York', 'Europe/Berlin'
geolocation: {
latitude: geoData.latitude,
longitude: geoData.longitude
},
permissions: ['geolocation']
});
return { browser, context };
}
// Esempio: proxy dalla Germania
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 mostrerà la versione tedesca con risultati per Berlino
Questo codice configura il browser in modo che appaia come un vero utente dalla Germania: lingua dell'interfaccia tedesca, fuso orario di Berlino e coordinate del centro di Berlino.
Configurazione completa del 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;
}
// Profilo per Windows 10 + Chrome dagli Stati Uniti
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 }
};
// Profilo per iPhone dal Regno Unito
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 }
};
Una configurazione completa del fingerprint riduce la probabilità di rilevamento dell'automazione. È importante che tutti i parametri corrispondano tra loro: ad esempio, un User-Agent mobile deve essere abbinato a una risoluzione dello schermo mobile.
Importante: Quando si lavora con piattaforme pubblicitarie (Google Ads, Facebook Ads) o servizi finanziari, si consiglia di utilizzare proxy mobili — hanno un trust score di veri operatori mobili e vengono praticamente mai bloccati.
Evitare le perdite WebRTC
// Puppeteer: blocco 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: ridefinizione dell'API WebRTC
const context = await browser.newContext({ proxy: proxyConfig });
await context.addInitScript(() => {
// Blocchiamo RTCPeerConnection
window.RTCPeerConnection = undefined;
window.RTCDataChannel = undefined;
window.RTCSessionDescription = undefined;
// Ridefiniamo getUserMedia
navigator.mediaDevices.getUserMedia = undefined;
navigator.getUserMedia = undefined;
});
WebRTC può rivelare il tuo vero indirizzo IP anche quando si utilizza un proxy. Questo codice disabilita completamente l'API WebRTC nel browser.
Confronto tra Puppeteer e Playwright per l'uso dei proxy
| Criterio | Puppeteer | Playwright |
|---|---|---|
| Configurazione del proxy | Attraverso args della riga di comando | Attraverso oggetto di configurazione |
| Autenticazione | page.authenticate() dopo l'avvio | Nell'oggetto proxy durante la creazione |
| Più proxy in un unico browser | No, è necessario un browser separato | Sì, tramite contesti diversi |
| Supporto per i browser | Solo Chromium/Chrome | Chromium, Firefox, WebKit |
| Prestazioni | Avvio rapido di un singolo browser | Più efficiente con molti contesti |
| TypeScript | Tipi tramite @types/puppeteer | Supporto TypeScript integrato |
| Documentazione | Buona, molti esempi | Ottima, più strutturata |
| Ecosistema | Più plugin ed estensioni | Si sviluppa più rapidamente, nuove funzionalità |
Raccomandazioni per la scelta
Scegli Puppeteer se:
- Stai lavorando solo con Chrome/Chromium
- Utilizzi un proxy alla volta
- Hai bisogno della massima compatibilità con gli strumenti esistenti
- Hai già una grande base di codice su Puppeteer
Scegli Playwright se:
- Hai bisogno di lavorare con Firefox o Safari (WebKit)
- È necessario utilizzare contemporaneamente più proxy
- Le prestazioni sono importanti per la scalabilità
- Stai scrivendo in TypeScript
- Inizi un nuovo progetto da zero
Prestazioni nella rotazione dei proxy
Test: scraping di 100 pagine con rotazione di 10 proxy su MacBook Pro M1:
| Metodo | Tempo di esecuzione | Consumo di RAM |
|---|---|---|
| Puppeteer (riavvio del browser) | 8 minuti 23 secondi | ~1.2 GB picco |
| Playwright (riavvio del browser) | 7 minuti 54 secondi | ~1.1 GB picco |
| Playwright (pool di contesti) | 4 minuti 12 secondi | ~800 MB stabile |
Playwright con pool di contesti è quasi due volte più veloce grazie all'assenza di overhead per l'avvio del browser. Questo è critico quando si effettuano scraping di migliaia di pagine.
Conclusione
L'integrazione dei proxy con Puppeteer e Playwright è una pratica standard per lo scraping web, il testing e l'automazione. Puppeteer offre semplicità e un'ampia ecosistema, mentre Playwright offre un'API moderna e migliori prestazioni quando si lavora con più proxy tramite contesti del browser.
Punti chiave che abbiamo esaminato:
- Configurazione di base dei proxy HTTP, HTTPS e SOCKS5 in entrambi i framework
- Autenticazione con nome utente e password
- Rotazione dei proxy per scraping su larga scala
- Gestione degli errori e validazione dei proxy prima dell'uso
- Configurazione della geolocalizzazione e del fingerprint per eludere i sistemi anti-bot
- Confronto delle prestazioni di diversi approcci
Per soluzioni di produzione, si consiglia di combinare proxy di qualità con una corretta configurazione del fingerprint, gestione degli errori e monitoraggio. Questo garantirà un funzionamento stabile dello scraper anche su siti protetti.
Se prevedi di effettuare scraping di siti commerciali, marketplace o di lavorare con piattaforme pubblicitarie, ti consigliamo di utilizzare proxy residenziali — offrono la massima anonimato e il minimo rischio di blocchi grazie a indirizzi IP reali di utenti domestici.