Comment scraper les sites web riches en JavaScript

Gratter le contenu rendu par JavaScript avec des navigateurs et des proxies sans tête. Guides de configuration Puppeteer, Playwright et chromedp avec optimisation des performances et stratégies d'interception API.

Comment scraper les sites web riches en JavaScript

Le défi du contenu rendu par JavaScript

Les sites Web modernes comptent de plus en plus sur JavaScript pour rendre le contenu. Les applications à une page (SPA) construites avec React, Vue ou Angular chargent un shell HTML minimal, puis récupèrent et rendent les données côté client. Lorsque vous faites une simple requête HTTP à ces sites, vous obtenez une page vide ou incomplète parce que le contenu n'existe qu'après l'exécution de JavaScript.

Scraping JavaScript-heavy sites Web nécessite navigateurs sans tête — de véritables moteurs de navigateur fonctionnant sans fenêtre visible pouvant exécuter JavaScript, rendre DOM et interagir avec des éléments de page. Combiné avec des proxies, navigateurs sans tête déverrouillent les données des sites Web les plus dynamiques.

Ce guide fait partie de notre Guide complet des produits de scraping Web. Pour éviter la détection en utilisant des navigateurs sans tête, voir Comment les systèmes anti-bot détectent les proxies.

Quand avez-vous besoin d'un navigateur sans tête?

Quand avez-vous besoin d'un navigateur sans tête?
ScénarioSimple HTTPNavigateur sans tête
Pages HTML statiquesFonctionne parfaitementSurcapacité
Pages rendues par le serveur avec APIFonctionne (toucher l'API directement)Pas nécessaire
SPA (Réaction, Vue, Angulaire)Obtient un shell videRequis
défilement infini / chargement paresseuxImpossible de déclencherRequis
Contenu derrière connexion + JSDifficultéRecommandation
Pages avec contrôles JS antibotDétection des défaillancesRequis
Vérifiez toujours si le site a une API ou un rendu côté serveur avant d'atteindre un navigateur sans tête. Beaucoup de sites "JavaScript-heavy" ont en fait des paramètres d'API qui retournent JSON propre - beaucoup plus rapide et moins cher pour racler.

Puppeteer + Proxies (Node.js)

Puppeteer contrôle Chrome / Chrome programmatiquement. C'est l'outil de navigateur sans tête le plus mature pour Node.js.

Configuration de base avec 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`);

Tirage optimisé multi-pages

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();

Dramaturge + Proxies (Python)

Playwright est une alternative plus récente qui prend en charge Chrome, Firefox et WebKit. Son API Python est propre et bien adapté pour le grattage.

Configuration de 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 pour scraping parallèle

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))

Go: Utilisation de chromedp avec Proxies

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))
}

Stratégies d'optimisation des performances

Les navigateurs sans tête sont 10-50x plus lents que les simples requêtes HTTP. Voici des stratégies pour minimiser l'écart de rendement :

1. Bloc des ressources inutiles

Les images, CSS, polices et fichiers multimédias ne sont pas nécessaires pour l'extraction des données. Les bloquer accélère considérablement les charges de pages :

# 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. Utiliser la bonne stratégie d'attente

2. Utiliser la bonne stratégie d'attente
StratégieVitesseFiabilitéCas d'utilisation
domcontentloadedRapidePeut manquer les données d'asyncPages avec données en ligne
loadMoyenneBonneLa plupart des pages
networkidleLentementPlus hautSpas lourds, défilement infini
Sélecteur spécifiqueVariablePlus hautQuand vous connaissez l'élément cible

3. Réutiliser les instances du navigateur

Lancer un navigateur prend 1-3 secondes. Pour le grattage par lots, lancez une fois et créez de nouvelles pages/contextes pour chaque 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. Intercepter les appels API au lieu d'analyser DOM

De nombreux SPA récupèrent les données des API. Interceptez ces appels d'API directement — vous obtenez JSON propre sans analyse 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`);

Comparaison entre navigateur sans tête et HTTP

Comparaison entre navigateur sans tête et HTTP
métriqueSimple HTTP + ProxyNavigateur sans tête + Proxy
Vitesse par page0,5-2 secondes3-15 secondes
Mémoire par instance~50 Mo200-500 Mo
Utilisation du processeurMinimaleImportant
Bande passante par page50-200 KB2-10 MB (avec ressources)
rendu JavaScriptNuméroTotal
Voie de contournement antibotsLimitéeMieux (navigateur réel)
Pages simultanées100 ans et plus3-10 par machine

Meilleures pratiques

  • Essayez toujours HTTP en premier. Vérifiez les paramètres d'API, le contenu rendu par le serveur ou JSON intégré dans le HTML avant d'utiliser un navigateur sans tête.
  • Bloquer les ressources inutiles. Images, CSS et polices ajoutent du temps de chargement sans fournir de données.
  • Utilisez des sélecteurs spécifiques pour attendre. networkidle est sûr mais lent. Attendez l'élément spécifique dont vous avez besoin.
  • Réutiliser les instances du navigateur. Lancez une fois, créez de nouveaux contextes par page.
  • Interceptez les appels d'API. De nombreux SPA chargent les données via les API — interceptez directement le JSON.
  • Limitez la concordance. Les navigateurs sans tête sont à forte intensité de mémoire. 3-5 pages simultanées par Go de RAM est une bonne règle.
  • Utilisez des procurations résidentielles. ProxyHat proxies résidentielles fournir les scores de confiance les plus élevés, réduisant la détection lors de l'exécution de navigateurs sans tête.

Pour manipuler les CAPTCHA que les navigateurs sans tête rencontrent, voir Traitement des CAPTCHA Lors du scraping. Pour le grattage du navigateur sans tête, lire Comment évaluer l'infrastructure de scraping.

Commencez par Python SDK, Numéro SDKou Allez au SDK pour l'intégration par procuration, et explorer ProxyHat pour le scraping Web.

Foire aux questions

Ai-je toujours besoin d'un navigateur sans tête pour les sites JavaScript ?

C'est pas vrai. De nombreux sites JavaScript-lourds chargent les données à partir des paramètres de l'API. Vérifiez l'onglet Réseau du navigateur pour les requêtes XHR/fetch — si les données proviennent d'une API, vous pouvez appeler cette API directement avec des requêtes HTTP simples via un proxy, ce qui est beaucoup plus rapide.

Puppeteer ou Playwright — qui est mieux pour le grattage?

Le dramaturge est généralement recommandé pour de nouveaux projets. Il prend en charge plusieurs moteurs de navigateur (Chromium, Firefox, WebKit), a mieux auto-attente, native async support en Python, et la configuration proxy intégrée. Puppeteer est plus mature et a un écosystème plus grand si vous êtes dans le monde Node.js.

Combien de pages de navigateur sans tête puis-je exécuter simultanément?

Chaque page consomme 200-500 Mo de RAM. Sur une machine avec 8 Go de RAM, 3-10 pages simultanées est réaliste. Utilisez le blocage des ressources (images, CSS) pour réduire la mémoire. Pour une plus grande cohérence, distribuez sur plusieurs machines en utilisant une architecture basée sur la file d'attente.

Pourquoi utiliser des proxies avec des navigateurs sans tête ?

Même avec un vrai navigateur, les requêtes répétées de la même IP sont bloquées. Proxies tourne votre IP de sorte que chaque charge de page semble venir d'un utilisateur différent. Les procurations résidentielles par l'intermédiaire de ProxyHat fournissent les scores de confiance les plus élevés, minimisant les blocs et les CAPTCHA.

Prêt à commencer ?

Accédez à plus de 50M d'IPs résidentielles dans plus de 148 pays avec filtrage IA.

Voir les tarifsProxies résidentiels
← Retour au Blog