Playwright per Automazione e Scraping: Guida Pratica
Perché Playwright nel 2026
Nel 2026, Playwright è diventato lo standard de facto per chi vuole automatizzare il browser o fare scraping web in modo affidabile. Sviluppato da Microsoft, ha superato Puppeteer in popolarità grazie al supporto nativo per Chromium, Firefox e WebKit, all’API moderna basata su async/await e a funzionalità avanzate come il network interception, i context isolati e il codegen integrato.
Se sei un developer che ha già sentito parlare di testing frontend moderno con Jest, Vitest e Playwright, sai bene che questo strumento non si limita ai test end-to-end. La stessa potenza che permette di simulare il comportamento utente può essere sfruttata per raccogliere dati strutturati dal web, monitorare i competitor, automatizzare task ripetitivi e costruire pipeline dati robuste.
In questa guida analizziamo Playwright da un’ottica pratica e operativa: installazione, scraping responsabile, gestione della paginazione, intercettazione delle API, stealth mode e — aspetto cruciale nel 2026 — compliance GDPR per chi raccoglie dati da siti europei. Ogni sezione include esempi di codice reali e funzionanti, testati direttamente su progetti client.
Installazione e Primo Progetto
Iniziamo dall’inizio. Playwright supporta JavaScript/TypeScript, Python, Java e .NET. In questa guida useremo TypeScript per il setup principale e Python per alcuni esempi di scraping — le due combinazioni più usate in produzione.
# Inizializzazione progetto TypeScript
npm init playwright@latest
# Oppure aggiunta a progetto esistente
npm install -D @playwright/test
npx playwright install
# Solo i browser che ti servono (riduce i tempi di setup)
npx playwright install chromium
npx playwright install firefoxDopo l’installazione troverai una cartella tests/ con esempi e il file playwright.config.ts. Per il scraping non usiamo il test runner ma l’API diretta:
import { chromium, Browser, BrowserContext, Page } from '@playwright/test';
async function setupBrowser(): Promise<{ browser: Browser; context: BrowserContext; page: Page }> {
const browser = await chromium.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-dev-shm-usage',
'--disable-blink-features=AutomationControlled',
],
});
// Context isolato: ogni sessione è indipendente
const context = await browser.newContext({
userAgent:
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
'(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
viewport: { width: 1366, height: 768 },
locale: 'it-IT',
timezoneId: 'Europe/Rome',
});
const page = await context.newPage();
return { browser, context, page };
}
// Uso base
(async () => {
const { browser, page } = await setupBrowser();
await page.goto('https://example.com');
const title = await page.title();
console.log('Titolo:', title);
await browser.close();
})();Il context isolato è una delle feature più potenti: ogni context ha i propri cookie, localStorage e session storage. Puoi aprire decine di context in parallelo senza interferenze — fondamentale per scraping multi-account o test A/B.
Scraping con Selettori e Attesa Intelligente
La differenza tra uno scraper che funziona in locale e uno che regge in produzione sta nella gestione dell’attesa. Playwright offre tre livelli di wait strategy, da quello più semplice a quello più preciso:
page.waitForSelector()— aspetta che un elemento sia presente nel DOMpage.waitForLoadState('networkidle')— aspetta che la rete sia silenziosa per 500mspage.waitForResponse()— aspetta una specifica risposta HTTP (ottimo per SPA)
import { chromium } from '@playwright/test';
interface Product {
name: string;
price: string;
sku: string;
available: boolean;
}
async function scrapeEcommerce(baseUrl: string): Promise<Product[]> {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
const products: Product[] = [];
try {
await page.goto(baseUrl, { waitUntil: 'domcontentloaded' });
// Gestione cookie banner (obbligatorio per siti GDPR-compliant)
const cookieBanner = page.locator('[data-testid="cookie-accept"]');
if (await cookieBanner.isVisible({ timeout: 3000 })) {
await cookieBanner.click();
await page.waitForTimeout(500);
}
// Paginazione automatica
let hasNextPage = true;
while (hasNextPage) {
await page.waitForSelector('.product-card', { timeout: 10000 });
const cards = page.locator('.product-card');
const count = await cards.count();
for (let i = 0; i < count; i++) {
const card = cards.nth(i);
const name = await card.locator('.product-name').textContent() ?? '';
const price = await card.locator('.product-price').textContent() ?? '';
const sku = (await card.getAttribute('data-sku')) ?? '';
const available = !(await card.locator('.out-of-stock').isVisible());
products.push({ name: name.trim(), price: price.trim(), sku, available });
}
// Vai alla pagina successiva se esiste
const nextBtn = page.locator('a[rel="next"]');
if (await nextBtn.isVisible()) {
await Promise.all([page.waitForNavigation(), nextBtn.click()]);
} else {
hasNextPage = false;
}
}
} finally {
await browser.close();
}
return products;
}Nota l’uso di page.locator() invece dei vecchi page.$(): i locator sono lazy e si rivalutano automaticamente al momento dell’interazione, eliminando la maggior parte degli errori staleElement tipici di Selenium.
Intercettazione API e Network Mocking
Uno dei casi d’uso più potenti — e meno documentati — di Playwright è l’intercettazione delle chiamate di rete. Invece di parsare l’HTML renderizzato, intercetti direttamente le risposte JSON delle API interne del sito. Il risultato è dati più puliti, meno fragili e spesso 10x più veloci.
Questa tecnica si integra perfettamente con workflow di automazione basati su n8n e Claude AI dove hai bisogno di dati strutturati freschi da fonti web.
import { chromium, Route, Request } from '@playwright/test';
interface ApiResponse {
items: Array<{ id: string; title: string; price: number }>;
total: number;
page: number;
}
async function interceptApiData(targetUrl: string): Promise<ApiResponse[]> {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
const results: ApiResponse[] = [];
// Intercetta le chiamate all'API interna
await page.route('**/api/v*/products**', async (route: Route, request: Request) => {
// Lascia passare la richiesta originale
const response = await route.fetch();
const json = await response.json() as ApiResponse;
results.push(json);
// Risponde con i dati originali (trasparente al sito)
await route.fulfill({ response });
});
// Blocca risorse inutili per velocizzare lo scraping
await page.route('**/*.{png,jpg,jpeg,gif,svg,ico,woff,woff2}', (route) => {
route.abort();
});
await page.goto(targetUrl);
// Simula scroll per caricare tutti i dati lazy
for (let i = 0; i < 10; i++) {
await page.evaluate(() => window.scrollBy(0, window.innerHeight));
await page.waitForTimeout(300);
}
await browser.close();
return results;
}
// Mocking per test: sostituisci la risposta API con dati controllati
async function testWithMockedApi() {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
await page.route('**/api/products', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: [{ id: '1', title: 'Mock Product', price: 99 }], total: 1, page: 1 }),
});
});
await page.goto('https://myapp.com/products');
// La pagina riceve i dati mock e li renderizza normalmente
await browser.close();
}🔧 Pro tip: Prima di fare HTML scraping, apri DevTools — Network — XHR/Fetch nel browser. Nella maggior parte delle SPA moderne trovi l’API JSON usata dal frontend. Intercettarla con Playwright è sempre più affidabile del parsing HTML.
Stealth Mode e Anti-Detection
Molti siti rilevano i browser automatizzati tramite proprietà JavaScript come navigator.webdriver, fingerprint del canvas o pattern di timing anomali. Playwright offre strumenti per rendere lo scraper meno rilevabile, utili soprattutto per monitoraggio prezzi e ricerche di mercato legittime.
Prima di procedere, una nota importante: usa queste tecniche solo su siti dove hai il diritto di raccogliere dati, in conformità con i ToS del sito e il GDPR. Ne parliamo in dettaglio nella prossima sezione.
import { chromium, BrowserContext } from '@playwright/test';
async function createStealthContext(): Promise<BrowserContext> {
const browser = await chromium.launch({
headless: true,
args: [
'--disable-blink-features=AutomationControlled',
'--disable-features=IsolateOrigins',
'--disable-site-isolation-trials',
],
});
const context = await browser.newContext({
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' +
'(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 2,
hasTouch: false,
isMobile: false,
locale: 'it-IT',
geolocation: { longitude: 9.19, latitude: 45.46 }, // Milano
permissions: ['geolocation'],
extraHTTPHeaders: {
'Accept-Language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7',
'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124"',
'sec-ch-ua-platform': '"macOS"',
},
});
// Rimuovi navigator.webdriver
await context.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
});
// Simula plugin realistici
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5],
});
});
return context;
}
// Rate limiting rispettoso: delay casuale tra richieste
function randomDelay(min = 1000, max = 3000): Promise<void> {
const ms = Math.floor(Math.random() * (max - min) + min);
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function scrapeWithStealth(urls: string[]) {
const context = await createStealthContext();
const page = await context.newPage();
for (const url of urls) {
await randomDelay(1500, 4000); // Rispetta i server altrui
await page.goto(url, { waitUntil: 'networkidle' });
// ... elabora la pagina
}
await context.browser()?.close();
}GDPR Compliance nello Scraping
Il GDPR non riguarda solo le aziende che raccolgono dati degli utenti — impatta anche chi fa scraping di dati personali da siti web europei. Questo è l’aspetto che distingue un developer maturo da chi scopre il problema dopo una diffida. Se lavori con clienti enterprise, questa sezione è fondamentale — così come conoscere le clausole essenziali nei contratti freelance quando il cliente ti chiede di raccogliere dati da terze parti.
- Dati pubblici ≠ dati liberamente utilizzabili: anche un nome su una pagina pubblica è un dato personale se identifica una persona fisica.
- Finalità legittima: devi avere una base giuridica (art. 6 GDPR) per ogni dato raccolto. Il “mi serve per analisi” non basta.
- Minimizzazione: raccogli solo i dati strettamente necessari. Se ti serve il prezzo di un prodotto, non salvare anche il nome del venditore se non ti serve.
- Robots.txt e ToS: rispetta sempre il
robots.txtdel sito. Ignorarlo non è illegale di per sé, ma viola i ToS e può costituire accesso abusivo (art. 615-ter c.p. in Italia). - Diritto all’oblio: se memorizzi dati personali, implementa un meccanismo per eliminarli su richiesta. Un semplice script Python con chiamata DELETE all’API è sufficiente come punto di partenza.
# GDPR-aware scraper: controlla robots.txt prima di ogni richiesta
# e registra ogni raccolta dati con timestamp e finalità
import asyncio
import json
from datetime import datetime
from urllib.robotparser import RobotFileParser
from playwright.async_api import async_playwright
class GdprCompliantScraper:
def __init__(self, user_agent: str, purpose: str):
self.user_agent = user_agent
self.purpose = purpose # Finalità del trattamento (art. 6 GDPR)
self.audit_log: list[dict] = []
self._robots_cache: dict[str, RobotFileParser] = {}
def can_fetch(self, url: str) -> bool:
# Controlla robots.txt prima di ogni fetch
from urllib.parse import urlparse
parsed = urlparse(url)
base = f"{parsed.scheme}://{parsed.netloc}"
if base not in self._robots_cache:
rp = RobotFileParser()
rp.set_url(f"{base}/robots.txt")
try:
rp.read()
except Exception:
return True # Se robots.txt non esiste, si può procedere
self._robots_cache[base] = rp
allowed = self._robots_cache[base].can_fetch(self.user_agent, url)
if not allowed:
print(f"[GDPR] Bloccato da robots.txt: {url}")
return allowed
def log_collection(self, url: str, fields_collected: list[str]):
# Audit trail: registra cosa hai raccolto, quando e perche
self.audit_log.append({
"timestamp": datetime.utcnow().isoformat(),
"url": url,
"fields": fields_collected,
"purpose": self.purpose,
"legal_basis": "legitimate_interest", # o "consent", "contract"
})
async def scrape_prices(self, urls: list[str]) -> list[dict]:
results = []
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page(user_agent=self.user_agent)
for url in urls:
if not self.can_fetch(url):
continue
await page.goto(url, wait_until="domcontentloaded")
await asyncio.sleep(2) # Rispetta il server
# Raccoglie SOLO il dato necessario (principio minimizzazione)
price = await page.locator('[data-price]').get_attribute('data-price')
product_id = await page.locator('[data-product-id]').get_attribute('data-product-id')
if price and product_id:
# NON salviamo nomi, email o altri dati personali
results.append({"product_id": product_id, "price": price, "scraped_at": datetime.utcnow().isoformat()})
self.log_collection(url, ["product_id", "price"])
await browser.close()
# Salva audit log
with open("/tmp/scraping_audit.json", "w") as f:
json.dump(self.audit_log, f, indent=2)
return results
# Utilizzo
scraper = GdprCompliantScraper(
user_agent="PriceMonitorBot/1.0 (+https://miazienda.it/bot-policy)",
purpose="Monitoraggio prezzi competitor per analisi di mercato interno"
)
# asyncio.run(scraper.scrape_prices(["https://example.com/product/123"]))Il pattern sopra è quello che usiamo nei progetti client: ogni sessione di scraping genera un audit trail JSON con timestamp, URL, campi raccolti e base giuridica. È il minimo indispensabile per dimostrare compliance in caso di audit. Per progetti che gestiscono volumi significativi di dati, il passo successivo è integrare questo con un sistema come quello descritto nella guida su come l’AI elimina il caos dei documenti in azienda.
Playwright in Produzione: Docker e CI/CD
Uno scraper che funziona solo sul tuo laptop non vale nulla in produzione. Playwright richiede browser installati, il che complica il deploy. La soluzione standard nel 2026 è usare l’immagine Docker ufficiale di Playwright, che include tutti i browser e le dipendenze di sistema necessarie.
# Dockerfile per scraper Playwright in produzione
# Usa l'immagine ufficiale con browser preinstallati
FROM mcr.microsoft.com/playwright:v1.44.0-jammy
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npx tsc
# Variabili d'ambiente per production
ENV NODE_ENV=production
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
# Utente non-root per sicurezza
RUN groupadd -r pwuser && useradd -r -g pwuser -G audio,video pwuser
USER pwuser
CMD ["node", "dist/scraper.js"]
# Build e run:
# docker build -t my-scraper .
# docker run --rm -e TARGET_URL=https://example.com my-scraper
# Per GitHub Actions (integra con pipeline CI/CD):
# - name: Run Playwright Scraper
# uses: docker://mcr.microsoft.com/playwright:v1.44.0-jammy
# with:
# run: npx playwright testSe vuoi integrare lo scraping in una pipeline di automazione più ampia — per esempio estrarre dati, processarli con AI e inviarli via email — Playwright si combina perfettamente con workflow n8n. Puoi chiamare uno script Playwright tramite un nodo “Execute Command” in n8n, esattamente come mostrato nella guida su n8n avanzato con subflow e error handling.
💡 Consiglio: Per scraping schedulato in cloud, considera Playwright Cloud (il servizio managed di Microsoft) o Browserless.io. Eliminano il problema di mantenere browser aggiornati e gestiscono automaticamente i proxy rotativi.
🎯 Strategia: Prima di scrivere codice, apri il sito target in Chrome DevTools e osserva le chiamate di rete. Se il sito usa un’API REST o GraphQL, intercettare quella è sempre più performante e stabile del selettore CSS. I siti cambiano il markup spesso; le API interne raramente.
FAQ e Domande Frequenti
Playwright o Puppeteer: quale scegliere nel 2026?
Playwright ha di fatto rimpiazzato Puppeteer per la maggior parte dei casi d’uso. I motivi principali sono il supporto multi-browser nativo (Firefox e WebKit oltre a Chromium), l’API dei locator più robusta, il network interception integrato e il supporto ufficiale per Python, Java e .NET. Puppeteer resta valido se hai un codebase esistente o hai bisogno solo di Chrome e vuoi una dipendenza più leggera. Per i nuovi progetti, scegli Playwright senza esitazioni.
Playwright funziona con siti che richiedono login?
Sì, e gestisce l’autenticazione meglio di qualsiasi altra alternativa. Il pattern consigliato è usare page.context().storageState() per salvare la sessione dopo il primo login e ricaricarla nelle esecuzioni successive. In questo modo eviti di fare login a ogni scraping run. Puoi anche usare HTTP credentials per basic auth o intercettare le richieste per iniettare token Bearer. Per OAuth2 e SSO aziendali, il flusso richiede qualche accortezza in più, ma è gestibile.
Come gestire i CAPTCHA con Playwright?
I CAPTCHA sono il segnale che il sito non vuole essere automatizzato — e devi rispettarlo. Per casi legittimi (es. test della tua applicazione che usa CAPTCHA), puoi usare servizi di risoluzione come 2Captcha o Anti-Captcha che espongono API. Per hCaptcha e reCAPTCHA v3, esistono estensioni browser che puoi caricare in Playwright. Detto questo: se ti trovi a combattere continuamente con CAPTCHA su siti di terze parti, fermati e valuta se esiste un’API ufficiale o un accordo commerciale con il sito. Risolvere CAPTCHA in automatico viola quasi sempre i ToS del servizio target.
Playwright su server headless senza X11: funziona?
Sì, senza problemi. Playwright in modalità headless: true (default) non richiede un display. Su server Linux minimal (es. Ubuntu 22.04 minimal su VPS), installa le dipendenze di sistema con npx playwright install-deps e sei pronto. L’immagine Docker ufficiale le include già tutte. Se usi la modalità headless: false per debug su server remoto, hai bisogno di Xvfb o di SSH X forwarding, ma in produzione non ti serve mai.
Conclusione
Playwright nel 2026 è uno strumento maturo, potente e ben documentato che copre un range enorme di casi d’uso: da test end-to-end a scraping strutturato, da automazione di task ripetitivi a monitoraggio continuo. La vera competenza non sta nel conoscere l’API — quella si impara in un pomeriggio — ma nel costruire pipeline robuste, rispettose dei termini di servizio e conformi al GDPR.
I developer che nel 2026 sanno combinare Playwright con AI (per classificare e arricchire i dati raccolti) e strumenti di automazione come n8n stanno costruendo vantaggi competitivi concreti per i loro clienti. Non si tratta di scraping selvaggio, ma di intelligenza di mercato automatizzata — un servizio ad alto valore che giustifica tariffe premium. Se vuoi alzare il livello della tua offerta come developer, questa è una skill che vale investire.
Suggerimenti e Risorse
🔧 Strumento: Usa
npx playwright codegen https://example.comper generare automaticamente il codice Playwright mentre navighi manualmente. È il modo più veloce per costruire il primo scraper di un sito nuovo.
💡 Consiglio: Testa sempre lo scraper con
headless: falsedurante lo sviluppo. Vedere il browser in azione ti permette di capire immediatamente perché un selettore non trova l’elemento o perché la pagina non carica come ti aspetti.
🎯 Strategia: Prima di deployare in produzione, aggiungi
page.on('console', msg => logger.debug(msg.text()))epage.on('pageerror', err => logger.error(err)). Catturare gli errori JavaScript del sito target ti aiuta a diagnosticare perché lo scraper si comporta in modo inatteso su certi URL.

