I siti moderni hanno imparato a riconoscere i browser automatizzati (Selenium, Puppeteer, Playwright) e bloccano le loro richieste. I browser headless lasciano decine di tracce digitali, che i sistemi anti-bot utilizzano per rilevare l'automazione in millisecondi. In questa guida esamineremo tutti i metodi di mascheramento dei browser headless con esempi di codice in Python e JavaScript, affinché i vostri parser funzionino in modo stabile senza blocchi.
L'articolo è destinato agli sviluppatori che si occupano di web scraping, automazione dei test o raccolta di dati da siti protetti. Esamineremo i dettagli tecnici del rilevamento e le soluzioni pratiche per bypassare la protezione.
Come i siti rilevano i browser headless: metodi principali
I sistemi anti-bot utilizzano un controllo multilivello del browser, analizzando centinaia di parametri. I browser headless si differenziano da quelli normali per molteplici segni, che non possono essere nascosti semplicemente sostituendo l'User-Agent. Comprendere i metodi di rilevamento è il primo passo verso un'efficace mascheratura.
Marker JavaScript di automazione
Il metodo più comune è il controllo delle proprietà JavaScript, che appaiono solo nei browser automatizzati:
navigator.webdriver— restituiscetruein Selenium e Puppeteerwindow.chrome— assente in Chrome headlessnavigator.plugins.length— uguale a 0 in modalità headlessnavigator.languages— spesso un array vuoto o contiene solo "en-US"navigator.permissions— l'API si comporta in modo diverso in modalità headless
Analisi del Chrome DevTools Protocol
Puppeteer e Playwright gestiscono il browser tramite il Chrome DevTools Protocol (CDP). La presenza di una connessione CDP può essere rilevata tramite controlli JavaScript speciali, che analizzano gli oggetti window.cdc_ o controllano anomalie nel comportamento degli eventi del mouse e della tastiera.
Canvas e WebGL Fingerprinting
I browser headless generano impronte di Canvas e WebGL identiche, poiché utilizzano il rendering software invece di quello hardware. I sistemi anti-bot creano un elemento Canvas invisibile, disegnano su di esso testo o forme e calcolano l'hash dell'immagine. Se migliaia di utenti hanno lo stesso hash, questo è un segno di bot.
Analisi comportamentale
I sistemi moderni (DataDome, PerimeterX, Cloudflare Bot Management) analizzano i movimenti del mouse, la velocità di scorrimento, i modelli di clic. I browser headless eseguono azioni istantaneamente e senza ritardi naturali, il che rivela l'automazione. Vengono inoltre analizzati gli eventi: in un browser normale, prima di un clic si verifica sempre un evento mousemove, mentre i bot spesso cliccano senza un movimento del mouse precedente.
Importante: I moderni sistemi anti-bot utilizzano l'apprendimento automatico per analizzare centinaia di parametri contemporaneamente. Mascherare solo un parametro (ad esempio, l'User-Agent) non proteggerà dal blocco: è necessario un approccio complessivo.
Rimozione di navigator.webdriver e altri marker JavaScript
La proprietà navigator.webdriver è il modo più semplice per rilevare Selenium e altri strumenti WebDriver. In un browser normale, questa proprietà restituisce undefined, mentre in uno automatizzato restituisce true. Può essere rimossa eseguendo codice JavaScript prima del caricamento della pagina.
Selenium (Python): rimozione del webdriver tramite CDP
Per Selenium è necessario utilizzare il Chrome DevTools Protocol per eseguire JavaScript prima del caricamento di qualsiasi pagina:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('--disable-blink-features=AutomationControlled')
driver = webdriver.Chrome(options=options)
# Rimozione di navigator.webdriver tramite CDP
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': '''
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
'''
})
driver.get('https://example.com')
L'opzione --disable-blink-features=AutomationControlled disabilita il flag che Chrome aggiunge in modalità automatizzata. Questa è una protezione di base che deve essere combinata con altri metodi.
Puppeteer (Node.js): mascheramento tramite Page.evaluateOnNewDocument
In Puppeteer si utilizza il metodo page.evaluateOnNewDocument() per eseguire codice prima del caricamento della pagina:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: ['--disable-blink-features=AutomationControlled']
});
const page = await browser.newPage();
// Rimozione del webdriver e di altri marker
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
// Aggiunta dell'oggetto chrome
window.chrome = {
runtime: {}
};
// Emulazione dei plugin
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5]
});
});
await page.goto('https://example.com');
})();
Playwright: opzioni di mascheramento integrate
Playwright ha una mascheratura più avanzata di default, ma è comunque necessaria una configurazione aggiuntiva:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({
headless: true,
args: ['--disable-blink-features=AutomationControlled']
});
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
});
const page = await context.newPage();
await page.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
});
await page.goto('https://example.com');
})();
Mascheramento del Chrome DevTools Protocol
Puppeteer e Playwright lasciano tracce di connessione CDP, che possono essere rilevate analizzando gli oggetti window. I sistemi anti-bot cercano variabili con il prefisso cdc_, $cdc_ o __webdriver_, che vengono create da Chrome quando ci si connette tramite il DevTools Protocol.
Rimozione delle variabili CDP
Il seguente script rimuove tutte le variabili relative all'automazione dall'oggetto window:
await page.evaluateOnNewDocument(() => {
// Rimozione di tutte le variabili con il prefisso cdc_
const cdcProps = Object.keys(window).filter(prop =>
prop.includes('cdc_') ||
prop.includes('$cdc_') ||
prop.includes('__webdriver_')
);
cdcProps.forEach(prop => {
delete window[prop];
});
// Ridefinizione di document.__proto__ per nascondere CDP
const originalQuery = document.querySelector;
document.querySelector = function(selector) {
if (selector.includes('cdc_')) return null;
return originalQuery.call(this, selector);
};
});
Utilizzo di versioni patchate di Chromium
Esistono build modificate di Chromium che non lasciano tracce CDP. Ad esempio, la libreria puppeteer-extra con il plugin puppeteer-extra-plugin-stealth applica automaticamente decine di patch per mascherare il CDP.
Impostazione di User-Agent e intestazioni HTTP
I browser headless utilizzano stringhe User-Agent obsolete o irrealistiche. Ad esempio, Puppeteer aggiunge di default la parola "HeadlessChrome" all'User-Agent. Inoltre, è necessario configurare intestazioni aggiuntive che sono presenti nelle richieste dei browser normali.
User-Agent aggiornati per la mascheratura
Utilizza User-Agent freschi di browser reali. Ecco alcuni esempi per il 2024:
# Chrome 120 su Windows 10
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
# Chrome 120 su macOS
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
# Firefox 121 su Windows
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Impostazione delle intestazioni in Selenium
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36')
# Intestazioni aggiuntive tramite CDP
driver.execute_cdp_cmd('Network.setUserAgentOverride', {
"userAgent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
"platform": 'Win32',
"acceptLanguage": 'en-US,en;q=0.9'
})
Impostazione delle intestazioni in Puppeteer
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
await page.setExtraHTTPHeaders({
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Sec-Fetch-Dest': 'document'
});
Le intestazioni Sec-Fetch-* sono critiche: sono apparse in Chrome 76+ e la loro assenza rivela versioni obsolete dei browser o bot.
Emulazione del Canvas e del WebGL Fingerprint
Il fingerprinting di Canvas e WebGL è un metodo potente di rilevamento, poiché i browser headless generano impronte identiche. I sistemi anti-bot creano un Canvas invisibile, disegnano su di esso testo e calcolano l'hash dei pixel. Se migliaia di richieste hanno lo stesso hash, sono bot.
Aggiunta di rumore al Canvas
Il seguente script aggiunge rumore casuale all'impronta del Canvas, rendendo ogni richiesta unica:
await page.evaluateOnNewDocument(() => {
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
const originalToBlob = HTMLCanvasElement.prototype.toBlob;
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
// Funzione per aggiungere rumore
const addNoise = (canvas, context) => {
const imageData = originalGetImageData.call(context, 0, 0, canvas.width, canvas.height);
for (let i = 0; i < imageData.data.length; i += 4) {
imageData.data[i] += Math.floor(Math.random() * 10) - 5;
imageData.data[i + 1] += Math.floor(Math.random() * 10) - 5;
imageData.data[i + 2] += Math.floor(Math.random() * 10) - 5;
}
context.putImageData(imageData, 0, 0);
};
HTMLCanvasElement.prototype.toDataURL = function() {
addNoise(this, this.getContext('2d'));
return originalToDataURL.apply(this, arguments);
};
});
Emulazione dei parametri WebGL
WebGL rivela informazioni sulla scheda video e sui driver. In modalità headless, questi parametri rivelano il rendering software:
await page.evaluateOnNewDocument(() => {
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
// Emulazione di una scheda video reale
if (parameter === 37445) {
return 'Intel Inc.';
}
if (parameter === 37446) {
return 'Intel Iris OpenGL Engine';
}
return getParameter.call(this, parameter);
};
});
Il parametro 37445 è UNMASKED_VENDOR_WEBGL, mentre il 37446 è UNMASKED_RENDERER_WEBGL. In modalità headless, restituiscono "Google SwiftShader", il che rivela l'automazione.
Selenium Stealth: soluzioni pronte per Python
La libreria selenium-stealth applica automaticamente decine di patch per mascherare Selenium. Questa è la soluzione più semplice per gli sviluppatori Python, che non richiede la configurazione manuale di ogni parametro.
Installazione e configurazione di base
pip install selenium-stealth
from selenium import webdriver
from selenium_stealth import stealth
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
driver = webdriver.Chrome(options=options)
# Applicazione delle patch stealth
stealth(driver,
languages=["en-US", "en"],
vendor="Google Inc.",
platform="Win32",
webgl_vendor="Intel Inc.",
renderer="Intel Iris OpenGL Engine",
fix_hairline=True,
)
driver.get("https://bot.sannysoft.com")
driver.save_screenshot("test.png")
La libreria rimuove automaticamente navigator.webdriver, aggiunge window.chrome, emula i plugin, maschera WebGL e applica oltre 20 patch. Questo copre l'80% dei casi di rilevamento.
Configurazione avanzata con proxy
Per una mascheratura completa, combina selenium-stealth con proxy residenziali — forniscono indirizzi IP reali di utenti domestici, il che è critico per bypassare sistemi anti-bot avanzati:
from selenium.webdriver.common.proxy import Proxy, ProxyType
proxy = Proxy()
proxy.proxy_type = ProxyType.MANUAL
proxy.http_proxy = "ip:port"
proxy.ssl_proxy = "ip:port"
capabilities = webdriver.DesiredCapabilities.CHROME
proxy.add_to_capabilities(capabilities)
driver = webdriver.Chrome(desired_capabilities=capabilities, options=options)
stealth(driver, languages=["en-US", "en"], vendor="Google Inc.", platform="Win32")
Puppeteer Extra Stealth Plugin per Node.js
Per Puppeteer esiste un plugin puppeteer-extra-plugin-stealth, che è la soluzione più avanzata per mascherare i browser headless nell'ecosistema JavaScript. Contiene 23 moduli indipendenti, ognuno dei quali maschera un aspetto specifico dell'automazione.
Installazione e utilizzo di base
npm install puppeteer-extra puppeteer-extra-plugin-stealth
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
await page.goto('https://bot.sannysoft.com');
await page.screenshot({ path: 'test.png' });
await browser.close();
})();
Cosa maschera il Stealth Plugin
Il plugin applica automaticamente le seguenti patch:
- Rimozione di
navigator.webdriver - Aggiunta dell'oggetto
window.chrome - Emulazione dell'API
navigator.permissions - Mascheramento di
navigator.pluginsenavigator.mimeTypes - Emulazione del fingerprint di Canvas e WebGL
- Mascheramento di User-Agent e lingue
- Correzione delle anomalie nel contentWindow degli iframe
- Emulazione dell'API della batteria, dei dispositivi multimediali, WebRTC
Configurazione con proxy e parametri aggiuntivi
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
const AdblockerPlugin = require('puppeteer-extra-plugin-adblocker');
puppeteer.use(StealthPlugin());
puppeteer.use(AdblockerPlugin({ blockTrackers: true }));
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: [
'--proxy-server=http://your-proxy:port',
'--disable-web-security',
'--disable-features=IsolateOrigins,site-per-process'
]
});
const page = await browser.newPage();
// Autenticazione del proxy
await page.authenticate({
username: 'your-username',
password: 'your-password'
});
await page.setViewport({ width: 1920, height: 1080 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
})();
Playwright: impostazione dell'anti-detect
Playwright ha un'architettura più sofisticata rispetto a Puppeteer e lascia meno tracce di automazione. Tuttavia, per bypassare sistemi anti-bot avanzati è necessaria una configurazione aggiuntiva. Per Playwright esiste un porting del plugin stealth — playwright-extra.
Installazione di playwright-extra
npm install playwright-extra playwright-extra-plugin-stealth playwright
const { chromium } = require('playwright-extra');
const stealth = require('playwright-extra-plugin-stealth');
chromium.use(stealth());
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
viewport: { width: 1920, height: 1080 },
locale: 'en-US',
timezoneId: 'America/New_York'
});
const page = await context.newPage();
await page.goto('https://bot.sannysoft.com');
await page.screenshot({ path: 'test.png' });
await browser.close();
})();
Impostazione del contesto del browser per la massima mascheratura
Playwright consente di creare contesti del browser isolati con impostazioni individuali. Questo è utile per il multi-accounting o il parsing parallelo:
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
viewport: { width: 1440, height: 900 },
locale: 'en-US',
timezoneId: 'America/Los_Angeles',
permissions: ['geolocation'],
geolocation: { latitude: 37.7749, longitude: -122.4194 },
colorScheme: 'light',
deviceScaleFactor: 2,
isMobile: false,
hasTouch: false,
// Impostazione del proxy per il contesto
proxy: {
server: 'http://your-proxy:port',
username: 'user',
password: 'pass'
}
});
I parametri geolocation e timezoneId devono corrispondere all'indirizzo IP del proxy, altrimenti i sistemi anti-bot rileveranno la discrepanza (ad esempio, IP dalla California, ma timezone di New York).
Rotazione dei proxy per ridurre il rischio di blocco
Anche un browser headless perfettamente mascherato può essere bloccato se utilizza un solo indirizzo IP per centinaia di richieste. I moderni sistemi anti-bot analizzano la frequenza delle richieste da un singolo IP e bloccano attività sospette. La rotazione dei proxy è un elemento obbligatorio di protezione durante il parsing.
Tipi di proxy per il parsing: confronto
| Tipo di proxy | Velocità | Trust Score | Meglio per |
|---|---|---|---|
| Datacenter | Molto alta (50-200 ms) | Basso | Siti semplici, parsing massivo |
| Residenziali | Media (300-1000 ms) | Alto | Siti protetti, social media |
| Mobile | Bassa (500-2000 ms) | Molto alto | App mobili, Instagram, TikTok |
Per il parsing di siti protetti (marketplace, social media, piattaforme pubblicitarie) si raccomandano proxy residenziali, poiché hanno indirizzi IP reali di utenti domestici e non finiscono nelle liste nere dei sistemi anti-bot.
Implementazione della rotazione dei proxy in Puppeteer
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
const proxyList = [
'http://user1:pass1@proxy1:port',
'http://user2:pass2@proxy2:port',
'http://user3:pass3@proxy3:port'
];
async function scrapeWithRotation(urls) {
for (let i = 0; i < urls.length; i++) {
const proxy = proxyList[i % proxyList.length];
const browser = await puppeteer.launch({
headless: true,
args: [`--proxy-server=${proxy}`]
});
const page = await browser.newPage();
try {
await page.goto(urls[i], { waitUntil: 'networkidle2' });
const data = await page.evaluate(() => document.body.innerText);
console.log(data);
} catch (error) {
console.error(`Errore su ${urls[i]}:`, error);
} finally {
await browser.close();
}
// Ritardo tra le richieste
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
scrapeWithRotation([
'https://example1.com',
'https://example2.com',
'https://example3.com'
]);
Rotazione tramite proxy basati su sessione
Alcuni fornitori di proxy (incluso ProxyCove) offrono rotazione basata su sessione: ogni richiesta riceve automaticamente un nuovo IP senza la necessità di riavviare il browser. Questo viene realizzato tramite un formato URL speciale per il proxy:
// Formato: username-session-RANDOM:password@gateway:port
const generateSessionProxy = () => {
const sessionId = Math.random().toString(36).substring(7);
return `http://username-session-${sessionId}:password@gateway.proxycove.com:12321`;
};
const browser = await puppeteer.launch({
args: [`--proxy-server=${generateSessionProxy()}`]
});
Come verificare la qualità del mascheramento: strumenti di test
Dopo aver configurato il mascheramento, è necessario verificare quanto bene il vostro browser headless imita un utente normale. Esistono diversi siti specializzati che analizzano decine di parametri del browser e mostrano quali tracce di automazione sono rimaste.
Principali strumenti di test
- bot.sannysoft.com — verifica oltre 15 parametri di rilevamento, inclusi webdriver, oggetto Chrome, plugin, Canvas
- arh.antoinevastel.com/bots/areyouheadless — specializzato nel rilevamento di Chrome headless
- pixelscan.net — analisi avanzata del fingerprint con visualizzazione di tutti i parametri
- abrahamjuliot.github.io/creepjs — analisi più dettagliata (oltre 200 parametri), mostra il livello di fiducia del browser
- iphey.com — verifica dell'indirizzo IP per appartenenza a proxy e VPN
Automazione del test
Crea uno script per il controllo automatico del mascheramento dopo ogni modifica delle impostazioni:
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
async function testStealth() {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
// Test 1: Sannysoft
await page.goto('https://bot.sannysoft.com');
await page.screenshot({ path: 'test-sannysoft.png', fullPage: true });
// Test 2: Are you headless
await page.goto('https://arh.antoinevastel.com/bots/areyouheadless');
const headlessDetected = await page.evaluate(() => {
return document.body.innerText.includes('You are not Chrome headless');
});
console.log('Headless rilevato:', !headlessDetected);
// Test 3: Proprietà Webdriver
const webdriverPresent = await page.evaluate(() => navigator.webdriver);
console.log('navigator.webdriver:', webdriverPresent);
// Test 4: Oggetto Chrome
const chromePresent = await page.evaluate(() => !!window.chrome);
console.log('window.chrome presente:', chromePresent);
await browser.close();
}
testStealth();
Checklist per un mascheramento riuscito
Il tuo browser headless è correttamente mascherato se:
navigator.webdriverrestituisceundefinedwindow.chromeesiste e contiene l'oggettoruntimenavigator.plugins.lengthè maggiore di 0- Il vendor e il renderer WebGL mostrano una scheda video reale, non SwiftShader
- Il fingerprint di Canvas è unico per ogni sessione
- L'User-Agent corrisponde all'ultima versione di Chrome/Firefox
- L'indirizzo IP del proxy non è nelle liste nere (verifica tramite iphey.com)
- Timezone e locale corrispondono alla geolocalizzazione dell'indirizzo IP
Conclusione
La mascheratura dei browser headless è un compito complesso che richiede attenzione a decine di parametri. I moderni sistemi anti-bot utilizzano l'apprendimento automatico e analizzano centinaia di caratteristiche del browser contemporaneamente, quindi una semplice sostituzione dell'User-Agent non funziona più. Per un parsing di successo è necessario combinare più metodi di protezione.
Gli elementi principali di una mascheratura efficace: rimozione dei marker JavaScript di automazione (navigator.webdriver, variabili CDP), emulazione del fingerprint di Canvas e WebGL, impostazione di intestazioni HTTP realistiche e utilizzo di proxy di qualità. Le soluzioni pronte (selenium-stealth per Python, puppeteer-extra-plugin-stealth per Node.js) coprono l'80% dei casi, ma per bypassare protezioni avanzate è necessaria una configurazione aggiuntiva.
Un punto critico è la scelta dei proxy. Anche un browser perfettamente mascherato sarà bloccato se utilizza indirizzi IP da liste nere o effettua troppe richieste da un singolo IP. Per il parsing di siti protetti, raccomandiamo di utilizzare proxy residenziali con rotazione automatica: forniscono un alto trust score e un rischio minimo di blocchi, poiché utilizzano indirizzi IP reali di utenti domestici invece di indirizzi server.
Testa regolarmente la qualità del mascheramento tramite servizi specializzati (bot.sannysoft.com, pixelscan.net) e adatta le impostazioni ai cambiamenti nei sistemi anti-bot. Il parsing è una corsa agli armamenti continua tra gli sviluppatori di bot e i creatori di protezioni, quindi una configurazione che funziona oggi potrebbe richiedere aggiornamenti tra qualche mese.