Si vous vivez en JavaScript, vous avez déjà la plupart de ce dont vous avez besoin pour extraire des données structurées du web. Node.js embarque un runtime rapide, un vaste écosystème de packages, et un modèle d'I/O asynchrone qui gère de nombreuses requêtes sans effort. Ajoutez deux petites bibliothèques et vous avez un scraper fonctionnel en quelques dizaines de lignes.

Ce guide vous montre comment construire un scraper web avec Node.js de manière pragmatique. Nous commençons avec la stack standard, axios pour HTTP et cheerio pour le parsing HTML à la jQuery, et construisons un scraper qui récupère une page, sélectionne les champs voulus, boucle sur une liste, et écrit les résultats en JSON et CSV. Ensuite, nous reconnaissons franchement où HTTP simple atteint ses limites (pages rendues par JavaScript et blocages à grande échelle) et ce qu'il faut faire.

Ce que vous allez construire

Un petit script Node.js qui prend une URL, télécharge le HTML, le parse avec cheerio, et extrait un enregistrement propre par élément. Nous utiliserons une mise en page de liste de produits générique comme exemple fil rouge, puisque ce pattern (une carte répétitive avec un titre, un prix et un lien) couvre la plupart des vrais travaux de scraping. À la fin, vous aurez :

  • Un récupérateur de page unique construit sur axios et cheerio.
  • Un extracteur qui mappe des sélecteurs CSS à des champs.
  • Une boucle qui parcourt des pages de liste paginées et collecte chaque ligne.
  • Une sortie écrite en JSON et CSV.
  • Un chemin de mise à niveau plug-in pour les pages qui vous bloquent ou qui s'affichent côté client.

Pourquoi Node.js pour le scraping

Node.js exécute JavaScript hors du navigateur sur le moteur V8 de Chrome, qui compile en code machine et reste rapide. Son modèle non-bloquant et orienté événements est un ajustement naturel pour le scraping, où vous passez la plupart du temps à attendre des réponses réseau : vous pouvez avoir de nombreuses requêtes en vol sur un seul thread sans lancer un thread par connexion. Ajoutez l'écosystème npm, où presque tous les besoins de parsing, de mise en file d'attente ou de stockage ont déjà un package éprouvé, et vous avez un runtime conçu pour ce type de travail. Des entreprises comme Netflix et PayPal utilisent Node.js en production pour les mêmes raisons.

Les deux bibliothèques qui font le gros du travail pour le scraping statique sont axios (un client HTTP basé sur les promesses) et cheerio (un parseur léger qui vous donne des sélecteurs jQuery sur du HTML côté serveur, sans navigateur attaché). Si vous voulez revoir spécifiquement le côté des requêtes, consultez comment faire des requêtes HTTP dans Node.js avec l'API Fetch.

Prérequis

Rien d'exotique. Vous avez besoin de trois choses avant d'écrire du code.

JavaScript et Node.js de base. Vous devez être à l'aise pour écrire un script, l'exécuter depuis le terminal, et installer des packages avec npm. Async/await rendra le code lisible, donc une connaissance fonctionnelle des promesses aide.

Node.js 18 ou plus récent. Vérifiez votre version avec node --version. Si vous ne l'avez pas, installez la LTS actuelle depuis nodejs.org.

Un éditeur de code. Tout fonctionne ; VS Code est le choix courant.

Configurer le projet

Créez un dossier, initialisez un projet, et installez les deux dépendances.

bash
mkdir node-scraper && cd node-scraper
npm init -y

npm install axios cheerio

Pour utiliser la syntaxe import moderne, ajoutez "type": "module" à votre package.json. Si vous préférez rester avec require, le code ci-dessous fonctionne de la même façon avec CommonJS, remplacez simplement les lignes import par const axios = require("axios").

Étape 1 : Récupérer une page

Commencez par télécharger le HTML brut. axios retourne le corps de la réponse sur response.data. Définir un en-tête User-Agent réaliste fait paraître la requête comme un navigateur plutôt qu'un client Node par défaut, ce que de nombreux sites traitent avec suspicion.

javascript
import axios from "axios";

const fetchPage = async (url) => {
  const { data } = await axios.get(url, {
    headers: {
      "User-Agent":
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    },
    timeout: 15000,
  });
  return data;
};

Le timeout empêche un hôte lent ou mort de bloquer votre exécution indéfiniment. axios rejette la promesse sur tout statut non-2xx, donc envelopper les appels dans try/catch (nous le faisons plus tard) rend les échecs visibles plutôt que silencieux.

Étape 2 : Parser et extraire avec cheerio

cheerio charge une chaîne HTML et vous donne une fonction $ qui se comporte comme jQuery. Vous sélectionnez des éléments par sélecteur CSS et lisez leur texte ou leurs attributs. Le pattern est : trouvez le conteneur répétitif, puis pour chacun extrayez les champs qui vous intéressent.

javascript
import * as cheerio from "cheerio";

const parseProducts = (html) => {
  const $ = cheerio.load(html);
  const products = [];

  $(".product-card").each((_, el) => {
    const card = $(el);
    products.push({
      title: card.find("h2.title").text().trim(),
      price: card.find(".price").text().trim(),
      url: card.find("a").attr("href"),
    });
  });

  return products;
};

Deux détails importants. .text() retourne le texte combiné d'un élément, donc .trim() supprime les espaces que le balisage laisse autour. Lire un lien utilise .attr("href") plutôt que .text(), car la valeur que vous voulez se trouve dans l'attribut, pas dans le texte visible. Ajustez les sélecteurs (.product-card, h2.title, .price) pour correspondre à la page que vous ciblez réellement ; inspectez-la dans les outils de développement de votre navigateur pour trouver les bons.

Les sélecteurs ne sont pas permanents

Les noms de classes changent quand un site publie un redesign, et un sélecteur qui fonctionnait le mois dernier peut silencieusement retourner des chaînes vides. Traitez les sélecteurs comme quelque chose que vous maintenez, pas que vous définissez une fois. Quand un champ revient vide, réinspectez la page en direct et mettez-le à jour. La maintenance périodique des sélecteurs est normale pour tout scraper de production.

Étape 3 : Boucler sur les pages de liste

Une page est une démo. Un vrai travail parcourt la pagination. La plupart des listes paginées exposent un numéro de page dans l'URL (?page=2) ou un lien "suivant". L'approche robuste la plus simple est d'itérer sur une plage de pages connue, récupérer chacune, la parser, et s'arrêter quand une page ne retourne aucun élément.

javascript
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

const scrapeAll = async (baseUrl, maxPages = 10) => {
  const all = [];

  for (let page = 1; page <= maxPages; page++) {
    try {
      const html = await fetchPage(`${baseUrl}?page=${page}`);
      const rows = parseProducts(html);
      if (rows.length === 0) break;
      all.push(...rows);
      console.log(`Page ${page}: ${rows.length} items`);
    } catch (err) {
      console.error(`Page ${page} failed: ${err.message}`);
    }
    await sleep(1000);
  }

  return all;
};

Le sleep entre les requêtes n'est pas de la décoration. Une pause d'une seconde vous empêche de marteler le serveur dans une boucle serrée, ce qui est à la fois poli et le moyen le plus rapide d'éviter d'être throttlé. Le try/catch signifie qu'une mauvaise page journalise une erreur et l'exécution continue plutôt que de planter à l'élément 4 sur 200.

Étape 4 : Écrire en JSON et CSV

Les données collectées ne sont utiles qu'une fois qu'elles quittent la mémoire. JSON est la valeur par défaut sans dépendance ; le module fs intégré de Node l'écrit directement. Un CSV plat est tout aussi facile à la main et s'ouvre directement dans une feuille de calcul.

javascript
import { writeFileSync } from "fs";

const saveJson = (rows, file) =>
  writeFileSync(file, JSON.stringify(rows, null, 2));

const saveCsv = (rows, file) => {
  const headers = Object.keys(rows[0]);
  const escape = (v) => `"${String(v ?? "").replace(/"/g, '""')}"`;
  const lines = [
    headers.join(","),
    ...rows.map((r) => headers.map((h) => escape(r[h])).join(",")),
  ];
  writeFileSync(file, lines.join("\n"));
};

Le helper escape enveloppe chaque valeur entre guillemets et double les guillemets internes, ce qui est la règle CSV qui évite qu'une virgule ou un guillemet dans un titre de produit ne décale vos colonnes. Pour quelque chose de plus complexe (données imbriquées, grands volumes), utilisez une bibliothèque comme csv-stringify, mais pour un ensemble d'enregistrements plats c'est suffisant.

Tout assembler

Câblez les quatre pièces en un seul script exécutable.

javascript
const main = async () => {
  const rows = await scrapeAll("https://example.com/products");
  if (rows.length === 0) {
    console.log("No data collected.");
    return;
  }
  saveJson(rows, "products.json");
  saveCsv(rows, "products.csv");
  console.log(`Saved ${rows.length} items.`);
};

main();

Exécutez-le avec node scraper.js. Vous obtenez une ligne de progression par page et deux fichiers sur le disque. C'est un scraper statique complet en bien moins d'une centaine de lignes.

Où HTTP statique atteint ses limites

La stack axios plus cheerio est rapide et propre, et pour les pages rendues côté serveur c'est tout ce qu'il vous faut. Mais deux murs apparaissent rapidement sur les cibles réelles.

Contenu rendu par JavaScript. De nombreux sites modernes envoient un shell HTML quasi-vide et construisent la page dans le navigateur avec JavaScript. axios ne récupère que ce shell initial ; il n'exécute pas les scripts, donc cheerio ne trouve rien là où les données devraient être. Si vos sélecteurs retournent vide sur une page qui montre clairement du contenu dans un navigateur, c'est presque toujours pourquoi.

Blocage à grande échelle. Quelques requêtes depuis votre IP, c'est bien. Quelques centaines depuis la même adresse de datacenter, dans des patterns reconnaissables, vous vaut un rate-limiting, un mur CAPTCHA, ou un blocage pur et simple. Un User-Agent personnalisé vous donne un peu de marge ; cela ne résout pas le problème d'IP.

Vous avez deux voies. La première est un navigateur headless : Puppeteer ou Playwright pilote un vrai Chrome ou Firefox, exécute le JavaScript de la page, et vous laisse scraper le DOM rendu. Cela résout le rendu, mais c'est lourd : chaque instance est un navigateur complet, consomme de la mémoire et du CPU, et à grande échelle vous devez quand même gérer vous-même un pool de proxys pour rester non-bloqué. Si c'est la voie que vous voulez, consultez notre guide sur le scraping web avec Playwright.

La seconde est de déléguer les deux problèmes à une API.

Utiliser la Crawling API pour des pages rendues et non-bloquées

La Crawling API intègre le rendu et la rotation d'IP dans une seule requête. Vous lui envoyez une URL, elle récupère la page derrière une IP résidentielle tournante de confiance (en rendant optionnellement JavaScript en premier), et elle retourne le HTML terminé. Vous gardez votre parseur cheerio existant inchangé ; seule l'étape de récupération change.

Installez le client Node officiel.

bash
npm install crawlbase

Puis remplacez fetchPage par une version qui passe par l'API. Tout ce qui est en aval (parse, boucle, sauvegarde) reste exactement tel que vous l'avez écrit.

javascript
import { CrawlingAPI } from "crawlbase";
import * as cheerio from "cheerio";

const api = new CrawlingAPI({ token: "YOUR_CRAWLBASE_TOKEN" });

const fetchPage = async (url) => {
  const response = await api.get(url, { ajax_wait: true, page_wait: 3000 });
  if (response.statusCode === 200 && response.pcStatus === 200) {
    return response.body;
  }
  throw new Error(`Crawl failed: ${response.statusCode} / ${response.pcStatus}`);
};

Deux choses à noter. Le client retourne à la fois statusCode (la réponse du site cible) et pcStatus (si le crawl lui-même a réussi) ; vérifier les deux évite qu'un échec doux ne passe pour du bon HTML. Les options ajax_wait et page_wait gèrent les cibles rendues par JavaScript : ajax_wait dit à l'API d'attendre le contenu asynchrone, et page_wait attend quelques secondes après le chargement pour que les éléments tardifs apparaissent avant la capture. Supprimez les deux options pour les pages statiques simples et vous obtenez le même avantage de rotation sans la surcharge de rendu.

Token normal vs token JS

Les tokens Crawlbase existent en deux variantes. Le token normal récupère le HTML statique et est le bon choix pour les pages rendues côté serveur. Le token JavaScript (JS) rend la page dans un vrai navigateur d'abord, ce dont vous avez besoin pour les cibles rendues côté client. Si une page revient comme un shell vide avec le token normal, passez au token JS.

Crawlbase Crawling API

Évitez de faire tourner une flotte de navigateurs headless et de gérer votre propre pool de proxys. La Crawling API rend JavaScript quand vous en avez besoin, fait tourner les IPs résidentielles côté serveur, et retourne du HTML terminé en un seul appel, de sorte que votre parseur cheerio continue de fonctionner inchangé. Commencez avec le niveau gratuit et pointez-la sur les pages qui vous bloquaient.

Conseils pour un scraper en bonne santé

Quelques habitudes maintiennent un scraper Node.js en marche sans problème, que vous restiez sur axios ou que vous passiez à l'API.

  • Lisez d'abord les conditions du site et son robots.txt. Sachez ce que vous êtes autorisé à collecter et à quel débit avant de pointer une boucle dessus.
  • Espacez vos requêtes. Un délai entre les appels et une concurrence raisonnable vous évitent de submerger un serveur et de paraître comme une attaque.
  • Envoyez des en-têtes réalistes. Un User-Agent ressemblant à un navigateur et des en-têtes accept standard réduisent le risque d'être signalé comme bot.
  • Gérez les erreurs par élément. Enveloppez chaque récupération pour qu'un échec journalise et continue plutôt que de tuer toute l'exécution.
  • Mettez en cache pendant le développement. Sauvegardez le HTML récupéré sur disque pendant que vous itérez sur les sélecteurs pour ne pas refrapper le site à chaque changement de code.
  • Surveillez les codes de statut. Un taux croissant de défis ou de réponses 4xx est un signal pour ralentir ou faire tourner les IPs, pas du bruit à ignorer.

Pour le guide complet anti-blocage, notamment la rotation d'IP et les empreintes digitales, consultez comment scraper des sites web sans être bloqué. Si vous préférez acheminer votre propre trafic via un pool tournant plutôt que d'utiliser l'API gérée, le Smart AI Proxy (aussi appelé AI Proxy) vous donne la rotation d'IP résidentielle comme endpoint proxy plug-in.

Récapitulatif

Points clés

  • axios plus cheerio est la stack statique. Récupérez le HTML avec axios, parsez-le avec les sélecteurs à la jQuery de cheerio, et vous avez un scraper fonctionnel en moins d'une centaine de lignes.
  • Le pattern est récupérer, sélectionner, boucler, sauvegarder. Trouvez le conteneur répétitif, mappez les sélecteurs aux champs, parcourez la pagination avec un délai, et écrivez en JSON et CSV.
  • HTTP statique a deux limites. Il ne peut pas exécuter JavaScript, donc il rate le contenu rendu côté client, et une seule IP se fait bloquer à grande échelle.
  • Puppeteer et Playwright résolvent le rendu mais sont lourds. Un vrai navigateur par instance coûte de la mémoire et du CPU, et vous gérez quand même les proxys vous-même.
  • La Crawling API intègre les deux. Un seul appel retourne du HTML rendu derrière une IP résidentielle tournante, et votre parseur cheerio reste inchangé.

Foire aux questions

Node.js est-il bon pour le scraping web ?

Oui. Node.js exécute JavaScript sur le moteur V8 rapide, et son modèle d'I/O non-bloquant vous permet de garder de nombreuses requêtes réseau en vol sur un seul thread, ce dont le scraping a exactement besoin. L'écosystème npm vous donne également des bibliothèques matures pour chaque étape, des requêtes HTTP au parsing HTML en passant par les navigateurs headless, de sorte que la plupart des travaux se mettent en place rapidement.

Quelle est la différence entre axios et cheerio ?

Ils font les deux moitiés du travail. axios est un client HTTP : il récupère le HTML brut d'une page sur le réseau. cheerio est un parseur : il charge cette chaîne HTML et vous donne des sélecteurs CSS à la jQuery pour extraire les champs que vous voulez. Vous les utilisez presque toujours ensemble, axios pour télécharger et cheerio pour extraire.

Pourquoi cheerio retourne-t-il des résultats vides sur certaines pages ?

Généralement parce que la page rend son contenu côté client avec JavaScript. axios ne récupère que le shell HTML initial, et cheerio parse ce qu'on lui donne, donc si les données sont injectées par des scripts après le chargement, il n'y a rien à trouver. La solution est de rendre la page d'abord, soit avec un navigateur headless comme Puppeteer ou Playwright, soit avec la Crawling API en utilisant ses options de rendu JavaScript.

Comment éviter d'être bloqué lors du scraping avec Node.js ?

Espacez vos requêtes avec un délai, envoyez des en-têtes de navigateur réalistes, gardez la concurrence raisonnable, et faites tourner les adresses IP pour qu'aucune ne déclenche un rate limit. Un User-Agent personnalisé aide mais ne résout pas le problème d'IP seul. Les proxys résidentiels tournants ou un service géré comme la Crawling API gèrent la rotation pour vous afin que vous n'ayez pas à maintenir un pool de proxys.

Dois-je utiliser Puppeteer ou la Crawling API ?

Utilisez Puppeteer (ou Playwright) quand vous avez besoin d'un contrôle précis sur un vrai navigateur, comme cliquer dans des flux multi-étapes ou capturer des screenshots, et que vous êtes prêt à exécuter et mettre à l'échelle les navigateurs vous-même. Utilisez la Crawling API quand vous avez principalement besoin de HTML rendu et non-bloqué à grande échelle sans gérer une flotte headless et un pool de proxys. De nombreuses équipes prototypent avec un navigateur headless et passent à l'API quand le volume et les taux de blocage augmentent.

Puis-je écrire les données scrapées dans une base de données plutôt que dans des fichiers ?

Oui. Les enregistrements collectés sont de simples objets JavaScript, donc une fois que vous avez le tableau, vous pouvez l'insérer n'importe où : un fichier JSON, un CSV, ou une base de données comme PostgreSQL, MongoDB, ou SQLite en utilisant leurs drivers Node.js. L'étape de sauvegarde est indépendante de la logique de scraping, donc remplacez saveJson par un appel d'insertion sans toucher au code de récupération ou de parsing.

Commencer à construire

Crawlez n'importe quel site à grande échelle, sans combattre l'infrastructure.

Crawlbase gère les proxies, les empreintes et les CAPTCHA afin que votre équipe livre des pipelines de données au lieu de maintenir la plomberie de crawl. 1 000 requêtes gratuites, sans carte requise.

En libre-service · Sans appel commercial requis · Volumes de crawl entreprise disponibles