Aller au contenu
Retour au blog

Comment extraire les données d'un tableau HTML en JavaScript et exporter un fichier CSV propre

Mihai MaximDernière mise à jour le 15 min read
Comment extraire les données d'un tableau HTML en JavaScript et exporter un fichier CSV propre
En bref : commencez par déterminer si les lignes du tableau figurent dans la réponse HTTP, proviennent d’une requête de données ou nécessitent un rendu par le navigateur. Utilisez ensuite le script statique Axios et Cheerio ou la méthode Puppeteer spécifique décrite ci-dessous pour extraire le contenu d’un tableau HTML en JavaScript, valider les enregistrements en fonction des en-têtes, dédupliquer les pages et générer un fichier CSV en toute sécurité. Un tableau HTML organise les informations en lignes et en colonnes, généralement à l’aide de <table>, <tr>, <th>, et <td> . Extraire un tableau HTML en JavaScript consiste à récupérer ou à afficher ce balisage, à convertir chaque ligne de données en un objet JavaScript, à valider le résultat et à le sérialiser dans un format tel que le CSV.

La boucle sur les lignes est rarement la partie la plus difficile. La plupart des échecs surviennent parce que le scraper lit la réponse du serveur alors que le développeur inspecte un DOM rendu par le navigateur, cible le mauvais tableau, suppose des positions de cellules fixes ou exporte des valeurs non échappées. Un workflow fiable choisit la méthode d’extraction la plus légère capable de détecter réellement les données.

Ce tutoriel de web scraping avec Node.js commence par utiliser Axios et Cheerio pour le balisage statique, puis ajoute un parcours de scraping de tableaux ciblé avec Puppeteer pour les lignes créées après le chargement de la page. L’analyseur dérive les clés à partir des en-têtes de tableau plutôt que de partir du principe qu’il n’existe qu’un seul jeu de données. Il signale également les lignes mal formées, conserve les valeurs sous forme de chaînes de caractères jusqu’à ce qu’une conversion soit intentionnelle, et vérifie que le résultat est exploitable. Il en résulte une méthode pratique pour extraire des tableaux HTML en JavaScript sans prétendre que rowspan, colspan, les tableaux imbriqués ou toutes les grilles personnalisées puissent être aplatis automatiquement.

Choisissez la bonne approche d’extraction avant d’écrire du code

Avant d’extraire les données d’un tableau HTML en JavaScript, identifiez où se trouvent les données des lignes. Dans un balisage standard, <tr> représente une ligne, <th> désigne généralement une colonne, et <td> contient une valeur ordinaire. La référence MDN sur l’élément de tableau HTML constitue un outil de vérification sémantique utile lorsque la structure vous est inconnue.

Suivez cet ordre de décision :

  1. Lignes dans le code HTML de la réponse : récupérez la page avec Axios et analysez-la avec Cheerio.
  2. Lignes renvoyées par Fetch ou XHR : utilisez le point de terminaison de données autorisé lorsqu’il est stable et approprié.
  3. Lignes créées uniquement après l'exécution de scripts ou une interaction : utilisez l'automatisation du navigateur.

Cet ordre permet de rendre l'extraction d'un tableau via JavaScript plus simple et moins coûteuse que de démarrer un navigateur pour chaque page.

Vérifiez si les lignes sont présentes dans le code HTML de la réponse

Ouvrez « Afficher la source », et pas seulement le panneau « Éléments », puis recherchez une valeur de cellule distinctive. Vous pouvez également enregistrer response.data et y effectuer une recherche directement. Si la valeur et un sélecteur de ligne ciblé sont présents, l’automatisation du navigateur n’est pas nécessaire, même lorsque la page affiche des commandes de pagination.

Testez les sélecteurs dans DevTools avant d’envoyer des requêtes répétées, puis retestez-les par rapport au code HTML récupéré. Une fiche de référence des sélecteurs CSS est utile lorsque table tr de capturer également la navigation, les tableaux imbriqués ou les lignes de pied de page.

Vérifiez la présence de lignes récupérées via le réseau ou rendues par le navigateur

Si la valeur est absente de la source, filtrez le panneau Réseau sur « Fetch/XHR », rechargez la page, modifiez-la ou appliquez un filtre, puis inspectez les réponses. Un point de terminaison de données autorisé et stable est généralement plus facile à valider que des cellules affichées, mais assurez-vous que son utilisation est autorisée.

Optez pour un navigateur sans interface graphique lorsque JavaScript, l’état de connexion, les clics ou le défilement sont indispensables. Une comparaison entre Cheerio et Puppeteer peut aider à documenter ce compromis. Évitez d’utiliser les expressions régulières comme analyseur HTML général ; réservez-les uniquement à des motifs précis à l’intérieur d’une cellule déjà sélectionnée.

Créez un scraper réutilisable utilisant Axios et Cheerio pour extraire le contenu d’un tableau HTML en JavaScript

Pour une page statique, le pipeline est simple : Axios récupère la réponse, Cheerio charge le balisage, et un analyseur à portée limitée transforme les lignes en objets. Le choix de conception important concerne la configuration. Acceptez l’URL, le sélecteur de tableau et le chemin de sortie en tant qu’entrées, puis déduisez les clés d’enregistrement à partir des en-têtes du tableau sélectionné.

Cette approche de scraping Web combinant Axios et Cheerio évite un mappage fragile du type « la cellule zéro correspond au nom, la cellule un au prix ». Elle fonctionne avec les tableaux conventionnels dont les en-têtes et les largeurs de lignes sont cohérentes, tout en mettant en évidence les exceptions structurelles.

Configurez le projet Node.js et récupérez la page en toute sécurité

Créez un projet et installez les trois paquets utilisés par l’exemple statique :

mkdir table-scraper
cd table-scraper
npm init -y
npm install axios cheerio objects-to-csv

Créer scrape-table.js. Le script complet utilise systématiquement CommonJS, applique un délai d’expiration pour les requêtes, n’accepte que les statuts HTTP réussis et regroupe les opérations de requête, d’analyse, de validation et d’exportation au sein d’une seule limite d’erreur. Il ne réessaie pas aveuglément les erreurs côté client, car des requêtes répétées ne permettront pas de corriger un sélecteur manquant, un accès refusé ou une URL invalide.

Les API des paquets peuvent évoluer. Fixez les versions après avoir testé le script dans votre environnement, et consultez la documentation officielle du paquet avant de considérer cet exemple comme une dépendance de production.

Sélectionnez la table et déterminez des noms de colonnes stables

Limitez le sélecteur à une seule table dans la mesure du possible, par exemple #results plutôt que table. L’analyseur retient délibérément la première correspondance, puis recherche la dernière ligne dans <thead>. S’il n’y a pas de <thead>, il considère la première ligne contenant des <th> cellules comme en-tête. Si les libellés ne sont toujours pas disponibles, il génère column_1, column_2, et ainsi de suite.

Le texte des en-têtes est normalisé en minuscules selon la convention « snake case ». Un en-tête vide reçoit une valeur de repli positionnelle, tandis que les doublons deviennent des clés telles que price et price_2. Cela empêche les cellules suivantes de remplacer les propriétés précédentes. Pour les en-têtes multilingues ou un contrat de données public, remplacez la normalisation automatique par une carte d’en-têtes explicite.

En-têtes sur plusieurs lignes, rowspanet colspan nécessitent une logique spécifique au tableau, car la grille visuelle peut ne pas correspondre au nombre de cellules du DOM. Ne faites pas de suppositions en silence. Inspectez le balisage, définissez les colonnes prévues et ajoutez une étape d’extension de la grille si ces aspects sémantiques ont de l’importance.

Mappez chaque ligne à un objet sans noms de champs codés en dur

Lisez les cellules enfants directes de chaque ligne plutôt que toutes les cellules descendantes. Cette distinction empêche un tableau imbriqué d’ajouter des valeurs inattendues à sa ligne parente. Le script construit d’abord une matrice de chaînes de cellules nettoyées, ignore les lignes ne contenant aucun texte significatif et calcule la ligne la plus large observée.

Il crée ensuite une clé pour chaque colonne et associe les clés aux valeurs par index. Les valeurs manquantes deviennent des chaînes vides. Les cellules supplémentaires reçoivent des en-têtes générés lorsque le balayage initial les révèle. L’analyseur enregistre également un avertissement chaque fois que le nombre de cellules d’une ligne diffère de la largeur calculée, afin que les colonnes décalées ne passent pas inaperçues.

Il s’agit là du noyau réutilisable lorsque vous extraitez le contenu d’un tableau HTML en JavaScript : le tableau de destination est géré indépendamment de l’itération sur les lignes, et chaque ligne acceptée apporte un objet complet. Il reste toutefois important d’examiner les avertissements. Un décalage de largeur peut indiquer une cellule s’étendant sur plusieurs lignes, une colonne de contrôle masquée, un balisage mal formé ou un sélecteur qui a débordé sur une autre section du tableau.

Normaliser et valider les valeurs extraites

Normalisez le bruit de présentation, pas le sens. L’exemple convertit les espaces insécables en espaces normaux, tronque les extrémités et regroupe les espaces blancs répétés. Il conserve les dates, les devises, les pourcentages, les identifiants et les zéros non significatifs sous forme de chaînes de caractères, car des valeurs telles que 01/02/03 ou 1,234 sont ambigus sans les règles de localisation et de schéma.

La validation doit échouer de manière évidente lorsque le sélecteur de tableau est manquant ou qu’il ne reste aucun enregistrement non vide. Vérifiez également les colonnes obligatoires, inspectez un objet échantillon, passez en revue les avertissements de non-correspondance et confirmez que le fichier final existe avant qu’un traitement en aval ne l’utilise. Si vous avez besoin de valeurs typées, appliquez des convertisseurs explicites par colonne après l’extraction et conservez la valeur brute à des fins de débogage. Un guide d’analyse des données est un outil utile lorsque ces conversions deviennent une étape distincte du pipeline.

Sérialisez les enregistrements au format CSV en toute sécurité

Ne créez pas de fichiers CSV avec row.join(","). Les virgules, les guillemets doubles, les retours chariot et les sauts de ligne à l’intérieur d’une cellule doivent être échappés et mis entre guillemets. Utilisez un sérialiseur activement maintenu dont vous avez testé le comportement. L’exemple utilise objects-to-csv, qui attribue à chaque objet ses propriétés dans le même ordre que celui de l’en-tête, puis écrit le tableau JavaScript résultant au format CSV.

Testez la version choisie avec des données de test contenant des virgules, des guillemets, des retours à la ligne intégrés, des valeurs vides et du texte non ASCII. Vérifiez son comportement au niveau des en-têtes et de l’encodage UTF-8 dans les applications qui liront le fichier. Une sérialisation CSV correcte ne peut pas corriger une table source irrégulière ; résolvez les cellules étendues, les doublons sémantiques et les lignes non correspondantes avant l’exportation.

Exécutez le scraper de table statique complet

Enregistrez ce qui suit sous le nom scrape-table.js. Il couvre la requête, la sélection, la normalisation des en-têtes, le mappage des lignes, les vérifications et l’exportation au format CSV. Modifiez requiredColumns lorsque les données en aval nécessitent des champs garantis.

const axios = require("axios");
const cheerio = require("cheerio");
const ObjectsToCsv = require("objects-to-csv");

const [url, tableSelector = "table", outputPath = "table.csv"] =
  process.argv.slice(2);

const requiredColumns = [];

function clean(value) {
  return value
    .replace(/\u00a0/g, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function uniqueHeaders(labels, width) {
  const seen = new Map();

  return Array.from({ length: width }, (_, index) => {
    const normalized = clean(labels[index] || "")
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, "_")
      .replace(/^_+|_+$/g, "");

    const base = normalized || `column_${index + 1}`;
    const count = (seen.get(base) || 0) + 1;
    seen.set(base, count);

    return count === 1 ? base : `${base}_${count}`;
  });
}

function parseTable(html) {
  const $ = cheerio.load(html);
  const table = $(tableSelector).first();

  if (!table.length) {
    throw new Error(`No table matched selector: ${tableSelector}`);
  }

  let headerRow = table.find("thead tr").last();

  if (!headerRow.length) {
    const firstRow = table.find("tr").first();
    if (firstRow.children("th").length) {
      headerRow = firstRow;
    }
  }

  const headerLabels = headerRow
    .children("th, td")
    .map((_, cell) => clean($(cell).text()))
    .get();

  let dataRows = table.children("tbody").children("tr");
  if (!dataRows.length) {
    dataRows = table.children("tr");
  }

  const headerNode = headerRow.get(0);
  const matrix = dataRows
    .toArray()
    .filter((row) => row !== headerNode)
    .map((row) =>
      $(row)
        .children("th, td")
        .map((_, cell) => clean($(cell).text()))
        .get()
    )
    .filter((cells) => cells.some(Boolean));

  const width = Math.max(
    0,
    headerLabels.length,
    ...matrix.map((cells) => cells.length)
  );

  if (!width) {
    throw new Error("The selected table has no readable cells.");
  }

  const headers = uniqueHeaders(headerLabels, width);
  const warnings = [];

  if (headerLabels.length && headerLabels.length !== width) {
    warnings.push(
      `Header has ${headerLabels.length} cells; widest row has ${width}.`
    );
  }

  const records = matrix.map((cells, rowIndex) => {
    if (cells.length !== width) {
      warnings.push(
        `Row ${rowIndex + 1} has ${cells.length} cells; expected ${width}.`
      );
    }

    return Object.fromEntries(
      headers.map((header, cellIndex) => [
        header,
        cells[cellIndex] ?? "",
      ])
    );
  });

  return { headers, records, warnings };
}

async function main() {
  if (!url) {
    throw new Error(
      'Usage: node scrape-table.js "<url>" "<table-selector>" "<output.csv>"'
    );
  }

  const response = await axios.get(url, {
    timeout: 15000,
    responseType: "text",
    maxRedirects: 5,
    validateStatus: (status) => status >= 200 && status < 300,
    headers: {
      "User-Agent": "TableScraper/1.0 (+contact@example.com)",
    },
  });

  const { headers, records, warnings } = parseTable(response.data);

  if (!records.length) {
    throw new Error("The table matched, but no nonempty rows were parsed.");
  }

  for (const column of requiredColumns) {
    if (!headers.includes(column)) {
      throw new Error(`Required column is missing: ${column}`);
    }
  }

  if (warnings.length) {
    console.warn(warnings.slice(0, 10).join("\n"));
  }

  await new ObjectsToCsv(records).toDisk(outputPath);

  console.log(`Wrote ${records.length} rows to ${outputPath}`);
  console.log(`Columns: ${headers.join(", ")}`);
  console.log("Sample record:", records[0]);
}

main().catch((error) => {
  console.error(`Scrape failed: ${error.message}`);
  process.exitCode = 1;
});

Exécutez-le en indiquant l’URL cible, un sélecteur de table entre guillemets et un chemin de sortie :

node scrape-table.js "https://example.com/page" "#results" "results.csv"

Vérifiez les colonnes rapportées et l’exemple d’enregistrement avant de vous fier au fichier CSV. Considérez chaque avertissement de largeur comme un problème de qualité des données à examiner.

Gérer les tableaux générés par JavaScript et paginés

Pour extraire un tableau HTML en JavaScript lorsque des lignes sont absentes de la réponse, utilisez l’automatisation du navigateur après avoir écarté tout point de terminaison de données approprié. Un navigateur sans interface utilisateur exécute les scripts de la page et peut cliquer sur les contrôles, mais il consomme plus de mémoire et génère davantage d’états d’échec qu’une requête HTTP.

La méthode présentée ci-dessous utilise Puppeteer et un contrôle Next configurable. Installez-le dans le même projet :

npm install puppeteer

Cette approche convient à un tableau rendu de manière conventionnelle. Elle ne tente pas de résoudre les grilles virtualisées qui réutilisent des nœuds DOM, les défis d’authentification ou les frameworks de composants arbitraires sans modification des sélecteurs.

Attendez l’affichage des lignes et collectez chaque page ou lot de défilement

Attendez un sélecteur de ligne plutôt que de rester inactif pendant une durée arbitraire. Sur chaque page, extrayez une matrice de cellules, ajoutez ses enregistrements, cliquez sur « Next », puis attendez que la matrice change. La documentation officielle de Puppeteer waitForSelector décrit l’attente de sélecteur utilisée pour le premier lot.

Enregistrez ceci sous le nom scrape-rendered.js. Le code réutilise la stratégie d’en-tête, échoue en cas de décalage des colonnes et refuse d’écrire un fichier lorsque la pagination semble bloquée.

const puppeteer = require("puppeteer");
const ObjectsToCsv = require("objects-to-csv");

const [
  url,
  tableSelector = "#results",
  nextSelector = 'button[aria-label="Next"]',
  outputPath = "rendered.csv",
] = process.argv.slice(2);

function clean(value) {
  return value.replace(/\u00a0/g, " ").replace(/\s+/g, " ").trim();
}

function uniqueHeaders(labels, width) {
  const seen = new Map();

  return Array.from({ length: width }, (_, index) => {
    const normalized = clean(labels[index] || "")
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, "_")
      .replace(/^_+|_+$/g, "");

    const base = normalized || `column_${index + 1}`;
    const count = (seen.get(base) || 0) + 1;
    seen.set(base, count);
    return count === 1 ? base : `${base}_${count}`;
  });
}

async function main() {
  if (!url) {
    throw new Error(
      'Usage: node scrape-rendered.js "<url>" "<table>" "<next>" "<output.csv>"'
    );
  }

  const rowSelector = `${tableSelector} tbody tr`;
  const headerSelector =
    `${tableSelector} thead tr:last-child th, ` +
    `${tableSelector} thead tr:last-child td`;

  const browser = await puppeteer.launch({ headless: true });

  try {
    const page = await browser.newPage();
    await page.goto(url, {
      waitUntil: "domcontentloaded",
      timeout: 30000,
    });
    await page.waitForSelector(rowSelector, { timeout: 15000 });

    const labels = await page.$$eval(headerSelector, (cells) =>
      cells.map((cell) => cell.textContent.replace(/\s+/g, " ").trim())
    );

    const recordsByValue = new Map();
    let headers;
    let pageNumber = 0;

    while (true) {
      pageNumber += 1;

      if (pageNumber > 1000) {
        throw new Error("Safety limit reached before pagination completed.");
      }

      const matrix = await page.$$eval(rowSelector, (rows) =>
        rows.map((row) =>
          Array.from(row.children)
            .filter((cell) => cell.matches("th, td"))
            .map((cell) => cell.textContent.replace(/\s+/g, " ").trim())
        )
      );

      if (!matrix.length) {
        throw new Error(`No rows found on rendered page ${pageNumber}.`);
      }

      if (!headers) {
        const width = Math.max(
          labels.length,
          ...matrix.map((cells) => cells.length)
        );
        headers = uniqueHeaders(labels, width);
      }

      for (const cells of matrix) {
        if (cells.length !== headers.length) {
          throw new Error(
            `Column mismatch on page ${pageNumber}: ` +
              `${cells.length} cells, expected ${headers.length}.`
          );
        }

        const record = Object.fromEntries(
          headers.map((header, index) => [header, cells[index]])
        );

        recordsByValue.set(JSON.stringify(record), record);
      }

      const next = await page.$(nextSelector);
      if (!next) break;

      const disabled = await next.evaluate(
        (element) =>
          element.disabled ||
          element.getAttribute("aria-disabled") === "true" ||
          element.classList.contains("disabled")
      );

      if (disabled) break;

      const previous = JSON.stringify(matrix);
      await next.click();

      try {
        await page.waitForFunction(
          ({ rowSelector, previous }) => {
            const current = Array.from(
              document.querySelectorAll(rowSelector)
            ).map((row) =>
              Array.from(row.children)
                .filter((cell) => cell.matches("th, td"))
                .map((cell) =>
                  cell.textContent.replace(/\s+/g, " ").trim()
                )
            );

            return current.length > 0 && JSON.stringify(current) !== previous;
          },
          { timeout: 10000 },
          { rowSelector, previous }
        );
      } catch {
        throw new Error(
          `Pagination did not advance after page ${pageNumber}; ` +
            "refusing to save a possibly partial file."
        );
      }
    }

    const records = [...recordsByValue.values()];

    if (!records.length) {
      throw new Error("No rendered records were collected.");
    }

    await new ObjectsToCsv(records).toDisk(outputPath);
    console.log(`Wrote ${records.length} unique rows to ${outputPath}`);
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error(`Rendered scrape failed: ${error.message}`);
  process.exitCode = 1;
});

Pour le défilement infini, remplacez le clic sur « Suivant » par une action de défilement et comparez le nombre de lignes ou la signature des lignes après chaque lot. N’arrêtez-vous qu’une fois que le nombre de lignes et la hauteur de défilement restent inchangés après plusieurs vérifications, et fixez une limite de sécurité pour le nombre maximal de passages.

Arrêtez-vous en toute sécurité et dédupliquez les enregistrements

Arrêtez-vous lorsque le bouton « Suivant » est absent ou désactivé, et respectez une limite de pages. Si le contrôle reste activé mais que la signature des lignes ne change pas, signalez une pagination partielle plutôt que d’enregistrer en silence.

L’exemple supprime les doublons exacts à l’aide d’un Map. Privilégiez un identifiant stable ou une URL canonique lorsqu’ils sont disponibles, car les clés portant sur l’intégralité de l’enregistrement peuvent fusionner des lignes répétées légitimes. Un guide sur le scraping Web avec JavaScript et Node.js peut aider à séparer la navigation, l’extraction et le stockage.

Dépannage des échecs courants lors du scraping de tableaux

Lorsque vous effectuez un scraping de tableaux HTML en JavaScript, classez les échecs par catégorie : récupération, sélection, pagination ou exportation.

Symptôme

Solution pratique

Axios analyse zéro ligne

Recherchez une valeur connue dans la réponse enregistrée ; si elle est absente, inspectez les requêtes réseau ou utilisez un navigateur

Aucun tableau ne correspond

Définissez un ID ou un conteneur stable, et incluez le sélecteur ayant échoué dans l'erreur

Décalage des colonnes

Inspectez les cellules directement et ajoutez une gestion personnalisée pour les structures étendues ou imbriquées

Seule la première page est enregistrée

Attendez une modification de la signature de la ligne, consignez la progression de la page et signalez une erreur en cas de blocage d’un contrôle

Le texte de la cellule est vide

Lire le lien, le champ de saisie ou l’attribut de données correspondant au lieu du texte seul

Les requêtes renvoient un code d'erreur 403 ou 429

Arrêtez, vérifiez les droits d'accès, réduisez la concurrence et utilisez un backoff plafonné si nécessaire

Les colonnes CSV sont incohérentes

Utilisez un sérialiseur testé et des fixtures contenant des guillemets, des virgules, des sauts de ligne et du texte UTF-8

Ajoutez des mesures de sécurité responsables en production

Si vous effectuez un scraping de tableaux HTML en JavaScript au-delà d’un simple test ponctuel, utilisez une liste de contrôle concise pour la production :

  • Vérifiez les conditions d’utilisation, les règles d’accès et les directives relatives aux robots avant toute collecte. Obtenez l’avis d’un expert pour les cas d’utilisation sensibles ou réglementés.
  • Limitez la concurrence, mettez en cache les pages inchangées, respectez Retry-After le cas échéant, et limitez les tentatives de connexion avec un délai d'attente. Aucun délai n'est universellement sûr.
  • Collectez uniquement les champs nécessaires, restreignez l’accès, protégez les données conservées et supprimez-les à l’expiration de la période de conservation.
  • Enregistrez l’état, le sélecteur, le numéro de page, le nombre de lignes et les avertissements, tout en excluant les identifiants et les valeurs sensibles.

Les conséquences juridiques peuvent varier selon le site, les données, la méthode et la juridiction. Considérez ces informations comme des recommandations techniques générales et non comme des conseils juridiques. Un guide pratique pour le web scraping sans se faire bloquer peut inclure des contrôles opérationnels une fois que l’autorisation et le périmètre d’accès ont été définis.

Points clés à retenir

  • La manière la plus rapide de scraper un tableau HTML en JavaScript consiste à inspecter d’abord la réponse, puis à n’utiliser un navigateur que lorsque des scripts ou une interaction sont réellement nécessaires.
  • Déduisez des clés uniques à partir des en-têtes, générez des solutions de repli explicites et signalez les divergences dans le nombre de cellules au lieu de coder en dur les colonnes d’un site.
  • Conservez les chaînes brutes jusqu’à ce que les règles de conversion soient connues, puis validez les colonnes requises, les sorties non vides, les avertissements et les cas limites au format CSV.
  • Pour la pagination affichée, attendez un changement mesurable de ligne, arrêtez-vous sur des signaux explicites, limitez la boucle et dédupliquez à l’aide d’une clé stable et significative.

FAQ

Pourquoi DevTools peut-il afficher des lignes de tableau qu’Axios et Cheerio ne parviennent pas à trouver ?

DevTools affiche le DOM en temps réel après que le navigateur a exécuté les scripts et normalisé le balisage, tandis qu’Axios reçoit les octets de la réponse du serveur. Le code côté client peut récupérer des données, insérer des lignes ou remplacer un espace réservé après le chargement. Les navigateurs peuvent également insérer des éléments structurels tels que <tbody>. Comparez donc une valeur représentative et tenez compte de la normalisation du DOM lorsque vous testez des sélecteurs.

Ai-je besoin de Puppeteer lorsqu’un tableau affiche des commandes de pagination ?

Non. Les commandes de pagination peuvent masquer des lignes déjà présentes dans le DOM, basculer entre des tranches d’un tableau en mémoire ou demander une autre page à un point de terminaison de données. N’utilisez un navigateur que lorsque le changement de page nécessite une interaction JavaScript et qu’aucun point de terminaison approprié n’est disponible. Inspectez le code source, le nombre de lignes du DOM et les réponses réseau avant d’ajouter l’automatisation du navigateur.

Comment puis-je extraire des liens ou des attributs de données à partir des cellules d’un tableau plutôt que du texte visible ?

Sélectionnez l’élément à l’intérieur de la cellule et lisez la propriété correspondante. Avec Cheerio, utilisez .attr("href") pour les liens, .attr("data-id") pour les attributs de données, et .val() pour les contrôles de formulaire. Résolvez les liens relatifs avec new URL(relativeHref, pageUrl).href. Stockez le texte visible et l'attribut dans des champs distincts lorsque les deux contiennent des informations utiles.

Puis-je enregistrer les enregistrements analysés au format JSON ou les envoyer vers une base de données plutôt qu’au format CSV ?

Oui. Générez du JSON avec JSON.stringify(records, null, 2), envoyez les enregistrements vers une API ou insérez-les dans une base de données. Validez d’abord le schéma correspondant. Pour les bases de données, utilisez des opérations paramétrées, des traitements par lots ou des transactions le cas échéant, ainsi qu’une clé unique et stable afin que les exécutions ultérieures puissent mettre à jour ou insérer des enregistrements (upsert) au lieu de créer des doublons accidentels.

Conclusion

Une méthode fiable pour extraire le contenu d’un tableau HTML en JavaScript commence par un diagnostic, et non par le choix d’une bibliothèque. Vérifiez si les lignes existent dans la réponse, proviennent d’une requête réseau ou nécessitent un rendu par navigateur. Pour le balisage statique, Axios et Cheerio offrent une solution légère, tandis qu’un analyseur basé sur les en-têtes rend le résultat réutilisable sur des tableaux conventionnels plutôt que sur une mise en page à colonnes fixes.

Avant l’exportation, normalisez uniquement les espaces de présentation, conservez les valeurs ambiguës sous forme de chaînes de caractères, vérifiez les champs obligatoires, examinez les avertissements de non-correspondance et testez les guillemets CSV avec des données de test hostiles. Pour les tableaux dynamiques, attendez les changements de sélecteurs et de lignes, utilisez des conditions d’arrêt explicites, enregistrez la progression de la pagination et dédupliquez à l’aide d’une clé qui reflète les données. Ce sont ces vérifications qui distinguent une simple démo d’un scraper que vous pouvez déboguer.

Commencez par le script statique et n’ajoutez de la complexité que lorsque la page s’avère nécessiter davantage. Si les blocages au niveau de la couche de requête, les défis CAPTCHA ou la rotation des proxys deviennent un goulot d’étranglement, l’API Scraper de WebScrapingAPI peut renvoyer du HTML brut tout en gérant ces problèmes, ce qui vous permet de conserver le même code de parsing et de validation Cheerio.

À propos de l'auteur

Mihai Maxim, Développeur Full Stack @ WebScrapingAPI

Mihai Maxim

Développeur Full Stack

Mihai Maxim est développeur Full Stack chez WebScrapingAPI ; il participe à l'ensemble du produit et contribue à la création d'outils et de fonctionnalités fiables pour la plateforme.

Web scraping avec AWS Lambda : guide pour Python et Java 2026
Guides

Web scraping avec AWS Lambda : guide pour Python et Java 2026

En bref : le web scraping avec AWS Lambda fonctionne mieux lorsque chaque invocation est courte, bien délimitée et peut faire l'objet d'une nouvelle tentative de manière indépendante. Commencez par utiliser les requêtes HTTP directes, AWS SAM et S3, puis ajoutez SQS, des conteneurs, le rendu dans un navigateur, des proxys ou une couche de récupération gérée uniquement lorsque la charge de travail démontre qu'elle en a besoin.

Suciu Dan33 min read
Lire l'article
Comment utiliser GoSpider : exploration, nettoyage des URL et extraction de données
Guides

Comment utiliser GoSpider : exploration, nettoyage des URL et extraction de données

En bref : GoSpider est un robot d'exploration en ligne de commande destiné à découvrir des URL, et non un outil complet de collecte de données structurées. Ce guide d'utilisation de GoSpider présente comment effectuer une exploration limitée, gérer correctement les résultats, transférer les données de Colly vers un fichier CSV, ainsi qu'une procédure de diagnostic pour les réponses 403 ou les pages nécessitant un rendu JavaScript.

Suciu Dan24 min read
Lire l'article
Comment récupérer les données de Redfin : Guide Python des données immobilières
Guides

Comment récupérer les données de Redfin : Guide Python des données immobilières

TL;DR : Redfin expose des points d'extrémité d'API cachés qui renvoient du JSON structuré pour les listes de propriétés, ce qui permet d'ignorer complètement l'analyse HTML fragile. Ce guide vous accompagne dans la construction d'un scraper Python qui extrait les données de location et de vente, effectue des recherches par emplacement, surveille les nouvelles inscriptions via des sitemaps XML et exporte des résultats propres au format CSV ou JSON.

Suciu Dan15 min read
Lire l'article

Commencez à créer

Prêt à faire évoluer votre système de collecte de données ?

Rejoignez plus de 2 000 entreprises qui utilisent WebScrapingAPI pour extraire des données Web à l'échelle de l'entreprise, sans aucun coût d'infrastructure.