Come Scrape JavaScript-Heavy Siti web

Scrape JavaScript-rendered content con browser senza testa e proxy. Puppeteer, Playwright e Chromedp guide di configurazione con l'ottimizzazione delle prestazioni e le strategie di intercettazione API.

Come Scrape JavaScript-Heavy Siti web

La sfida del contenuto di JavaScript-rendered

I siti web moderni si affidano sempre più a JavaScript per rendere contenuti. Applicazioni di singola pagina (SPA) costruite con React, Vue, o Angular caricare una shell HTML minimale, quindi catturare e rendere i dati client-side. Quando si effettua una semplice richiesta HTTP a questi siti, si ottiene una pagina vuota o incompleta perché il contenuto esiste solo dopo l'esecuzione di JavaScript.

Scraping JavaScript-heavy siti richiede browser senza testa — veri motori del browser in esecuzione senza una finestra visibile che può eseguire JavaScript, rendere DOM e interagire con elementi di pagina. Combinato con i proxy, i browser senza testa sbloccano i dati anche dai siti più dinamici.

Questa guida fa parte della nostra Guida completa ai proxy Web Scraping. Per evitare il rilevamento durante l'utilizzo di browser senza testa, vedere Come i sistemi anti-bot rilevano i proxy.

Quando ti serve un browser senza testa?

ScenarioSemplice HTTPBrowser senza testa
Pagine HTML staticheFunziona perfettamenteOverkill
Pagine erogate dal server con APIFunziona (hit the API direttamente)Non è necessario
SPA (React, Vue, Angolare)Ottiene guscio vuotoObbligo
Carico infinito / carico pigroNon è possibile attivareObbligo
Contenuto dietro login + JSDifficileConsigliato
Pagine con controlli anti-bot JSRilevamento di erroriObbligo
Controllare sempre se il sito ha un'API o un rendering lato server prima di raggiungere un browser senza testa. Molti siti "JavaScript-heavy" in realtà hanno endpoint API che restituiscono JSON pulito — molto più veloce e più economico per raschiare.

Puppeteer + Proxies (Node.js)

Puppeteer controlla Chrome/Chromium programmaticamente. È lo strumento del browser senza testa più maturo per Node.js.

Configurazione di base con ProxyHat

const puppeteer = require('puppeteer');
async function scrapeWithPuppeteer(url) {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: [
      '--proxy-server=http://gate.proxyhat.com:8080',
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--disable-dev-shm-usage',
    ],
  });
  const page = await browser.newPage();
  // Authenticate with proxy
  await page.authenticate({
    username: 'USERNAME',
    password: 'PASSWORD',
  });
  // Set realistic viewport and user agent
  await page.setViewport({ width: 1920, height: 1080 });
  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'
  );
  try {
    await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
    // Wait for specific content to render
    await page.waitForSelector('.product-list', { timeout: 10000 });
    const content = await page.content();
    const data = await page.evaluate(() => {
      return Array.from(document.querySelectorAll('.product-item')).map(el => ({
        name: el.querySelector('.product-name')?.textContent?.trim(),
        price: el.querySelector('.product-price')?.textContent?.trim(),
        url: el.querySelector('a')?.href,
      }));
    });
    return { html: content, data };
  } finally {
    await browser.close();
  }
}
// Usage
const result = await scrapeWithPuppeteer('https://example.com/products');
console.log(`Found ${result.data.length} products`);

Scraping Multi-Page ottimizzato

const puppeteer = require('puppeteer');
class PuppeteerScraper {
  constructor(concurrency = 3) {
    this.concurrency = concurrency;
    this.browser = null;
  }
  async init() {
    this.browser = await puppeteer.launch({
      headless: 'new',
      args: [
        '--proxy-server=http://gate.proxyhat.com:8080',
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
        '--disable-gpu',
        '--disable-extensions',
      ],
    });
  }
  async scrapePage(url) {
    const page = await this.browser.newPage();
    await page.authenticate({ username: 'USERNAME', password: 'PASSWORD' });
    await page.setViewport({ width: 1920, height: 1080 });
    // Block unnecessary resources to speed up loading
    await page.setRequestInterception(true);
    page.on('request', (req) => {
      const type = req.resourceType();
      if (['image', 'stylesheet', 'font', 'media'].includes(type)) {
        req.abort();
      } else {
        req.continue();
      }
    });
    try {
      await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
      const content = await page.content();
      return { url, status: 'success', html: content };
    } catch (err) {
      return { url, status: 'error', error: err.message };
    } finally {
      await page.close();
    }
  }
  async scrapeMany(urls) {
    const results = [];
    for (let i = 0; i < urls.length; i += this.concurrency) {
      const batch = urls.slice(i, i + this.concurrency);
      const batchResults = await Promise.all(
        batch.map(url => this.scrapePage(url))
      );
      results.push(...batchResults);
      console.log(`Progress: ${results.length}/${urls.length}`);
    }
    return results;
  }
  async close() {
    if (this.browser) await this.browser.close();
  }
}
// Usage
const scraper = new PuppeteerScraper(3);
await scraper.init();
const results = await scraper.scrapeMany(urls);
await scraper.close();

Playwright + Proxies (Python)

Playwright è una nuova alternativa che supporta Chromium, Firefox e WebKit. La sua API Python è pulita e adatta per la raschiatura.

Set di base

from playwright.sync_api import sync_playwright
def scrape_with_playwright(url: str) -> dict:
    """Scrape a JavaScript-heavy page using Playwright with ProxyHat proxy."""
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=True,
            proxy={
                "server": "http://gate.proxyhat.com:8080",
                "username": "USERNAME",
                "password": "PASSWORD",
            }
        )
        context = browser.new_context(
            viewport={"width": 1920, "height": 1080},
            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",
        )
        page = context.new_page()
        try:
            page.goto(url, wait_until="networkidle", timeout=60000)
            # Wait for dynamic content
            page.wait_for_selector(".product-list", timeout=10000)
            # Extract data using page.evaluate
            products = page.evaluate("""() => {
                return Array.from(document.querySelectorAll('.product-item')).map(el => ({
                    name: el.querySelector('.product-name')?.textContent?.trim(),
                    price: el.querySelector('.product-price')?.textContent?.trim(),
                    url: el.querySelector('a')?.href,
                }));
            }""")
            return {"url": url, "products": products, "html": page.content()}
        finally:
            browser.close()

Async Playwright per Scraping parallelo

import asyncio
from playwright.async_api import async_playwright
async def scrape_batch(urls: list[str], concurrency: int = 3) -> list[dict]:
    """Scrape multiple JS-heavy pages in parallel using Playwright."""
    results = []
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            headless=True,
            proxy={
                "server": "http://gate.proxyhat.com:8080",
                "username": "USERNAME",
                "password": "PASSWORD",
            }
        )
        semaphore = asyncio.Semaphore(concurrency)
        async def scrape_one(url: str) -> dict:
            async with semaphore:
                context = await browser.new_context(
                    viewport={"width": 1920, "height": 1080},
                )
                page = await context.new_page()
                # Block heavy resources
                await page.route("**/*.{png,jpg,jpeg,gif,svg,css,woff,woff2}",
                                 lambda route: route.abort())
                try:
                    await page.goto(url, wait_until="networkidle", timeout=30000)
                    html = await page.content()
                    return {"url": url, "status": "success", "html": html}
                except Exception as e:
                    return {"url": url, "status": "error", "error": str(e)}
                finally:
                    await context.close()
        tasks = [scrape_one(url) for url in urls]
        results = await asyncio.gather(*tasks)
        await browser.close()
    return results
# Usage
urls = [f"https://example.com/product/{i}" for i in range(50)]
results = asyncio.run(scrape_batch(urls, concurrency=5))

Vai: Utilizzo di cromo con proxy

package main
import (
    "context"
    "fmt"
    "log"
    "time"
    "github.com/chromedp/chromedp"
)
func scrapeJSPage(targetURL string) (string, error) {
    // Configure proxy
    opts := append(chromedp.DefaultExecAllocatorOptions[:],
        chromedp.ProxyServer("http://gate.proxyhat.com:8080"),
        chromedp.Flag("headless", true),
        chromedp.Flag("disable-gpu", true),
        chromedp.Flag("no-sandbox", true),
        chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) "+
            "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"),
    )
    allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
    defer cancel()
    ctx, cancel := chromedp.NewContext(allocCtx)
    defer cancel()
    ctx, cancel = context.WithTimeout(ctx, 60*time.Second)
    defer cancel()
    var htmlContent string
    err := chromedp.Run(ctx,
        chromedp.Navigate(targetURL),
        chromedp.WaitVisible(".product-list", chromedp.ByQuery),
        chromedp.OuterHTML("html", &htmlContent),
    )
    if err != nil {
        return "", fmt.Errorf("scrape failed: %w", err)
    }
    return htmlContent, nil
}
func main() {
    html, err := scrapeJSPage("https://example.com/products")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Got %d bytes of rendered HTML\n", len(html))
}

Strategie di ottimizzazione delle prestazioni

I browser senza testa sono 10-50x più lenti delle semplici richieste HTTP. Ecco le strategie per ridurre al minimo il divario di prestazioni:

1. Block Risorse non necessarie

Immagini, CSS, font e file multimediali non sono necessari per l'estrazione dei dati. Bloccarli velocizza notevolmente i carichi delle pagine:

# Playwright resource blocking
async def fast_scrape(page, url):
    # Block images, CSS, fonts, media
    await page.route("**/*.{png,jpg,jpeg,gif,svg,css,woff,woff2,mp4,webm}",
                     lambda route: route.abort())
    # Also block tracking scripts
    await page.route("**/*google-analytics*", lambda route: route.abort())
    await page.route("**/*facebook*", lambda route: route.abort())
    await page.goto(url, wait_until="domcontentloaded")  # Faster than networkidle
    return await page.content()

2. Utilizzare la strategia di attesa giusta

StrategiaVelocitàAffidabilitàUtilizzare il caso
domcontentloadedVelocePotrebbe mancare dati asincroniPagine con dati in linea
loadMediaBene.La maggior parte delle pagine
networkidlePiano.Più altoSPA pesanti, scorrimento infinito
Selettore specificoVariabilePiù altoQuando conosci l'elemento di destinazione

3. Riutilizzare le istanze del browser

Avviare un browser richiede 1-3 secondi. Per la demolizione di lotti, lanciare una volta e creare nuove pagine/contesti per ogni URL:

from playwright.sync_api import sync_playwright
class BrowserPool:
    """Reusable browser pool for efficient headless scraping."""
    def __init__(self, pool_size: int = 3):
        self.pool_size = pool_size
        self.playwright = None
        self.browsers = []
    def start(self):
        self.playwright = sync_playwright().start()
        for _ in range(self.pool_size):
            browser = self.playwright.chromium.launch(
                headless=True,
                proxy={
                    "server": "http://gate.proxyhat.com:8080",
                    "username": "USERNAME",
                    "password": "PASSWORD",
                }
            )
            self.browsers.append(browser)
    def get_browser(self, index: int):
        return self.browsers[index % self.pool_size]
    def stop(self):
        for browser in self.browsers:
            browser.close()
        self.playwright.stop()
# Usage
pool = BrowserPool(pool_size=3)
pool.start()
for i, url in enumerate(urls):
    browser = pool.get_browser(i)
    context = browser.new_context()
    page = context.new_page()
    page.goto(url, wait_until="networkidle")
    html = page.content()
    context.close()
pool.stop()

4. Intercetta le chiamate API invece di Parsing DOM

Molti SPA recuperano i dati dalle API. Intercetta queste chiamate API direttamente — si ottiene pulito JSON senza parsing HTML:

const puppeteer = require('puppeteer');
async function interceptAPIData(url) {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: ['--proxy-server=http://gate.proxyhat.com:8080'],
  });
  const page = await browser.newPage();
  await page.authenticate({ username: 'USERNAME', password: 'PASSWORD' });
  const apiResponses = [];
  // Intercept XHR/fetch responses
  page.on('response', async (response) => {
    const url = response.url();
    if (url.includes('/api/') || url.includes('/graphql')) {
      try {
        const json = await response.json();
        apiResponses.push({ url, data: json });
      } catch {
        // Not JSON, skip
      }
    }
  });
  await page.goto(url, { waitUntil: 'networkidle2' });
  await browser.close();
  return apiResponses;
}
// Get clean API data instead of scraping DOM
const data = await interceptAPIData('https://example.com/products');
console.log(`Intercepted ${data.length} API calls`);

Browser senza testa vs Confronto HTTP

MetricoSemplice HTTP + ProxyBrowser senza testa + Proxy
Velocità per pagina0,5-2 secondi3-15 secondi
Memoria per esempio~ 50 MB200-500 MB
Utilizzo della CPUMinimalSignificato
Larghezza di banda per pagina50-200 KB2-10 MB (con risorse)
rendering JavaScriptNo.Pieno
bypass anti-botLimitazioniMeglio (vero browser)
Pagine correnti100+3-10 per macchina

Migliori Pratiche

  • Prova sempre HTTP prima. Controllare i endpoint API, i contenuti server-rendered, o JSON incorporato nel HTML prima di utilizzare un browser senza testa.
  • Bloccare risorse inutili. Immagini, CSS e font aggiungono tempo di carico senza fornire dati.
  • Utilizzare selettori specifici per l'attesa. networkidle è sicuro ma lento. Attendere l'elemento specifico di cui avete bisogno.
  • Riutilizzare le istanze del browser. Avviare una volta, creare nuovi contesti per pagina.
  • Intercetta le chiamate API. Molte SPA caricano i dati tramite API — intercettano il JSON direttamente.
  • Limitare la convalutazione. I browser senza testa sono intensivi di memoria. 3-5 pagine contemporaneamente per GB di RAM è una buona regola.
  • Utilizzare i proxy residenziali. Prossi residenziali ProxyHat fornire i più alti punteggi di fiducia, riducendo il rilevamento durante l'esecuzione di browser senza testa.

Per gestire CAPTCHAs che i browser senza testa incontrano, vedere Gestione dei CAPTCHA Quando scorrere. Per scaling browser senza testa scraping, leggere Come Scalare le Infrastrutture di Scraping.

Inizia con il Python SDKNode SDKo Vai SDK per l'integrazione proxy, ed esplorare ProxyHat per Scraping Web.

Domande frequenti

Ho sempre bisogno di un browser senza testa per siti JavaScript?

No. Molti siti JavaScript-heavy caricano i dati da endpoint API. Controllare la scheda di rete del browser per le richieste XHR/fetch — se i dati provengono da un'API, è possibile chiamare l'API direttamente con semplici richieste HTTP attraverso un proxy, che è molto più veloce.

Puppeteer o Playwright — che è meglio per raschiare?

Playwright è generalmente consigliato per i nuovi progetti. Supporta più motori del browser (Chromium, Firefox, WebKit), ha un migliore supporto asincastro automatico, nativo in Python e configurazione proxy integrata. Puppeteer è più maturo e ha un ecosistema più grande se siete nel mondo Node.js.

Quante pagine del browser senza testa posso eseguire contemporaneamente?

Ogni pagina consuma 200-500 MB di RAM. Su una macchina con 8 GB di RAM, 3-10 pagine contemporaneamente è realistico. Utilizzare il blocco delle risorse (immagini, CSS) per ridurre la memoria. Per una maggiore convalutazione, distribuire su più macchine utilizzando un'architettura basata sulla coda.

Perché usare i proxy con i browser senza testa?

Anche con un browser reale, le richieste ripetute dallo stesso IP vengono bloccate. I proxy ruotano il tuo IP in modo che ogni carico di pagina venga da un utente diverso. I proxy residenziali tramite ProxyHat forniscono i più alti punteggi di fiducia, riducendo al minimo i blocchi e CAPTCHA.

Pronto per iniziare?

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

Vedi i prezziProxy residenziali
← Torna al Blog