Playwright per Automazione e Scraping: Guida Pratica

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 firefox

Dopo 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 DOM
  • page.waitForLoadState('networkidle') — aspetta che la rete sia silenziosa per 500ms
  • page.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.txt del 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 test

Se 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.com per 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: false durante 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())) e page.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.

Condividi

Articoli Recenti

Categorie popolari