Tutoriels

Web scraping avec Node.js : Cheerio vs Puppeteer

Mark Toledo

Mark Toledo

1 avril 2026

Web scraping avec Node.js : Cheerio vs Puppeteer

Le web scraping fait partie de ma boîte à outils depuis des années. Surveillance des prix concurrentiels, agrégation de données publiques, tests de régression visuelle — les cas d'usage légitimes sont nombreux. Le choix entre Cheerio et Puppeteer dépend d'un facteur central : le site cible utilise-t-il du rendu côté client ?

Avant de rentrer dans le code, une précision importante : scrape uniquement ce que tu as le droit de scraper. Respecte le robots.txt, lis les conditions d'utilisation, et évite de surcharger les serveurs. Les exemples de ce guide s'appliquent à des sites publics dont le scraping est autorisé.

Cheerio : le HTML statique

Cheerio est une implémentation de jQuery côté serveur. Il parse le HTML retourné par le serveur et te donne une API jQuery-like pour naviguer dans le DOM. Rapide, léger, sans overhead.

npm install cheerio node-fetch
import * as cheerio from 'cheerio';

async function scrapeArticles(url) {
  const response = await fetch(url, {
    headers: {
      'User-Agent': 'Mozilla/5.0 (compatible; MonBot/1.0)'
    }
  });
  
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  
  const html = await response.text();
  const $ = cheerio.load(html);
  
  const articles = [];
  
  $('article.post').each((index, element) => {
    articles.push({
      titre: $(element).find('h2').text().trim(),
      lien: $(element).find('a').attr('href'),
      excerpt: $(element).find('p.excerpt').text().trim(),
      date: $(element).find('time').attr('datetime')
    });
  });
  
  return articles;
}

La vitesse de Cheerio est son atout principal. Une requête HTTP + parsing HTML = quelques dizaines de millisecondes. Pour scraper des centaines de pages, c'est imbattable.

Scraper plusieurs pages avec une file d'attente

Pour scraper un site avec de la pagination, une file d'attente évite de saturer le serveur.

async function scrapeAvecPagination(urlBase, nbPages) {
  const resultats = [];
  
  for (let page = 1; page <= nbPages; page++) {
    const url = `${urlBase}?page=${page}`;
    
    try {
      const donnees = await scrapeArticles(url);
      resultats.push(...donnees);
      
      console.log(`Page ${page}/${nbPages} : ${donnees.length} articles`);
      
      // Pause entre les requêtes — ne pas hammerer le serveur
      await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 500));
    } catch (erreur) {
      console.warn(`Erreur page ${page}:`, erreur.message);
    }
  }
  
  return resultats;
}

Le délai aléatoire entre les requêtes est une bonne pratique : il rend le trafic moins mécanique et réduit le risque de déclencher des rate limits.

Puppeteer : quand le JavaScript côté client est indispensable

Cheerio est aveugle au JavaScript. Si les données que tu veux sont chargées dynamiquement (appels API, rendu React/Vue, infinite scroll), Cheerio ne verra que le HTML initial vide.

Puppeteer pilote un vrai navigateur Chromium. Il exécute le JavaScript, attend les chargements, peut interagir avec la page (cliquer, remplir des formulaires, scroller).

npm install puppeteer
import puppeteer from 'puppeteer';

async function scrapeSPA(url) {
  const browser = await puppeteer.launch({
    headless: 'new', // Mode headless moderne
    args: ['--no-sandbox', '--disable-setuid-sandbox'] // Nécessaire sur Linux serveur
  });
  
  const page = await browser.newPage();
  
  try {
    // Bloquer les ressources inutiles pour accélérer
    await page.setRequestInterception(true);
    page.on('request', (req) => {
      if (['image', 'font', 'media'].includes(req.resourceType())) {
        req.abort();
      } else {
        req.continue();
      }
    });

    await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
    
    // Attendre un élément spécifique plutôt qu'un délai fixe
    await page.waitForSelector('.liste-produits', { timeout: 10000 });
    
    const produits = await page.evaluate(() => {
      return Array.from(document.querySelectorAll('.produit')).map(el => ({
        nom: el.querySelector('.nom')?.textContent?.trim(),
        prix: el.querySelector('.prix')?.textContent?.trim(),
        disponible: el.querySelector('.stock')?.textContent?.includes('En stock')
      }));
    });
    
    return produits;
  } finally {
    await browser.close(); // Toujours fermer le navigateur
  }
}

Naviguer dans un site protégé par une session

Parfois, il faut se connecter avant de scraper. Puppeteer gère ça naturellement.

async function scrapeApresConnexion(urlLogin, urlCible, identifiants) {
  const browser = await puppeteer.launch({ headless: 'new' });
  const page = await browser.newPage();
  
  try {
    // Se connecter
    await page.goto(urlLogin);
    await page.type('#email', identifiants.email);
    await page.type('#password', identifiants.password);
    
    await Promise.all([
      page.waitForNavigation(),
      page.click('button[type=submit]')
    ]);
    
    // Vérifier la connexion
    const connecte = await page.$('.dashboard');
    if (!connecte) {
      throw new Error('Connexion échouée');
    }
    
    // Scraper la page protégée
    await page.goto(urlCible, { waitUntil: 'networkidle2' });
    
    return await page.evaluate(() => {
      // Extraire les données
    });
  } finally {
    await browser.close();
  }
}

Intercepter les requêtes API

Une technique souvent plus efficace que de parser le HTML : intercepter les appels API XHR/fetch que la page fait elle-même. Les données sont déjà structurées en JSON.

async function intercepterAPI(url) {
  const browser = await puppeteer.launch({ headless: 'new' });
  const page = await browser.newPage();
  const donneesAPI = [];

  page.on('response', async (response) => {
    const url = response.url();
    if (url.includes('/api/produits') && response.status() === 200) {
      try {
        const json = await response.json();
        donneesAPI.push(...json.data);
      } catch {}
    }
  });

  await page.goto('https://exemple.com/catalogue', { waitUntil: 'networkidle2' });
  
  await browser.close();
  return donneesAPI;
}

Cette approche est souvent plus robuste que le scraping HTML — les APIs changent moins souvent que la structure du DOM.

Puppeteer vs Playwright

Playwright est un concurrent de Puppeteer, maintenu par Microsoft. Il supporte Chromium, Firefox et WebKit (Safari), là où Puppeteer se concentre sur Chrome/Chromium.

Pour la plupart des projets de scraping, les deux conviennent. Playwright a une API légèrement plus ergonomique et des fonctionnalités d'attente plus sophistiquées. Puppeteer a une communauté plus établie et une documentation plus fournie.

Mon choix par défaut reste Puppeteer pour sa maturité, mais Playwright vaut le coup d'œil si tu as besoin de tester sur plusieurs navigateurs.

Optimisation et performance

Instancier un navigateur Puppeteer pour chaque requête est lent. Pour traiter de nombreuses URLs, réutilise le navigateur.

async function scrapeMultiplePages(urls) {
  const browser = await puppeteer.launch({ headless: 'new' });
  const resultats = [];
  
  // Traitement par lots pour limiter les pages simultanées
  const TAILLE_LOT = 3;
  
  for (let i = 0; i < urls.length; i += TAILLE_LOT) {
    const lot = urls.slice(i, i + TAILLE_LOT);
    
    const lotResultats = await Promise.allSettled(
      lot.map(url => scrapeUnePageAvecBrowser(browser, url))
    );
    
    lotResultats.forEach(resultat => {
      if (resultat.status === 'fulfilled') {
        resultats.push(resultat.value);
      }
    });
  }
  
  await browser.close();
  return resultats;
}

Cheerio ou Puppeteer : la décision rapide

Le site charge ses données côté serveur ?
  ↓ OUI → Cheerio (rapide, léger)
  
Le site utilise React/Vue/Angular ou charge via des appels AJAX ?
  ↓ OUI → Puppeteer

Tu peux identifier les appels API dans les DevTools ?
  ↓ OUI → Intercepter l'API directement (plus robuste que le scraping DOM)

Ce qu'on retient

Cheerio pour le HTML statique — c'est 10 fois plus rapide et ne nécessite pas de navigateur complet. Puppeteer quand le JavaScript est indispensable.

Dans les deux cas : respecte les délais entre les requêtes, gère les erreurs proprement, et ferme toujours tes ressources (browser, streams) dans un bloc finally. Un scraper qui plante et laisse des processus Chromium orphelins en arrière-plan — on a tous eu ce problème au moins une fois.