DEV Community

Cover image for Comment je Gère les Rowspans et Colspans dans l'Extraction de Tableaux
circobit
circobit

Posted on

Comment je Gère les Rowspans et Colspans dans l'Extraction de Tableaux

Les tableaux avec cellules fusionnées sont partout. Wikipédia les utilise. Les sites de statistiques sportives les utilisent. Les rapports financiers les utilisent.

Et ils cassent presque tous les scrapers de tableaux que j'ai vus.

Voici le problème : quand vous itérez sur les éléments <tr> et <td>, vous obtenez ce qui est dans le HTML — pas ce qui est rendu visuellement. Une cellule avec rowspan="3" apparaît une seule fois dans le HTML mais occupe trois lignes dans le résultat.

L'approche naïve échoue silencieusement. Vous obtenez des colonnes désalignées, des données manquantes et des exports qui ne ressemblent en rien au tableau original.

Voici comment corriger cela.

Le Problème : Le HTML ne Correspond Pas à la Grille

Considérez ce tableau simple avec une cellule fusionnée :

<table>
  <tr>
    <td rowspan="2">A</td>
    <td>B</td>
  </tr>
  <tr>
    <td>C</td>
  </tr>
</table>
Enter fullscreen mode Exit fullscreen mode

Représentation visuelle :

| A | B |
|   | C |
Enter fullscreen mode Exit fullscreen mode

Mais si vous extrayez naïvement les cellules en itérant les lignes :

// Mauvaise approche
table.querySelectorAll('tr').forEach(row => {
  const cells = row.querySelectorAll('td');
  console.log(cells.map(c => c.textContent));
});
// Résultat : ["A", "B"], ["C"]
// Attendu : ["A", "B"], ["A", "C"]
Enter fullscreen mode Exit fullscreen mode

La ligne 2 manque le « A » qui s'étend visuellement dedans.

La Solution : Construire une Matrice

L'approche correcte est de pré-allouer une grille 2D et de la remplir selon les attributs span.

function extractTableMatrix(table) {
  const rows = Array.from(table.rows);
  const grid = [];

  rows.forEach((rowEl, rowIndex) => {
    if (!grid[rowIndex]) grid[rowIndex] = [];
    const gridRow = grid[rowIndex];

    let colIndex = 0;

    // Sauter les colonnes déjà occupées par les rowspans des lignes précédentes
    while (gridRow[colIndex] !== undefined) {
      colIndex++;
    }

    const cells = Array.from(rowEl.cells);

    cells.forEach((cell) => {
      // Avancer au-delà des cellules occupées
      while (gridRow[colIndex] !== undefined) {
        colIndex++;
      }

      const text = cell.textContent.trim();
      const rowSpan = parseInt(cell.rowSpan, 10) || 1;
      const colSpan = parseInt(cell.colSpan, 10) || 1;

      // Remplir le bloc couvert par cette cellule
      for (let r = 0; r < rowSpan; r++) {
        const targetRowIndex = rowIndex + r;
        if (!grid[targetRowIndex]) grid[targetRowIndex] = [];

        for (let c = 0; c < colSpan; c++) {
          const targetColIndex = colIndex + c;
          if (grid[targetRowIndex][targetColIndex] === undefined) {
            grid[targetRowIndex][targetColIndex] = text;
          }
        }
      }

      colIndex += colSpan;
    });
  });

  return grid;
}
Enter fullscreen mode Exit fullscreen mode

Maintenant le résultat correspond au tableau visuel :

const matrix = extractTableMatrix(table);
// [["A", "B"], ["A", "C"]]
Enter fullscreen mode Exit fullscreen mode

Pourquoi C'est Important : Exemples Concrets

Tableaux de Pays sur Wikipédia

Wikipédia utilise les cellules fusionnées massivement. Un tableau listant les pays par région pourrait avoir :

<tr>
  <td rowspan="5">Europe</td>
  <td>France</td>
</tr>
<tr>
  <td>Allemagne</td>
</tr>
<!-- ... -->
Enter fullscreen mode Exit fullscreen mode

Sans traitement approprié, vous perdez l'association « Europe » pour les lignes 2 à 5.

Sites de Statistiques Sportives

Des sites comme FBREF utilisent à la fois rowspan ET colspan. Les tableaux de stats de joueurs ont :

  • Des en-têtes groupés avec colspan (Temps de Jeu → MJ | Titulaire | Min)
  • Des lignes récapitulatives avec rowspan couvrant les détails du joueur

Un scraper naïf exporte du charabia. Une approche matricielle préserve la structure.

Rapports Financiers

Les rapports trimestriels ont souvent :

<td colspan="4">2024</td>
<!-- Puis : T1, T2, T3, T4 -->
Enter fullscreen mode Exit fullscreen mode

L'en-tête de l'année doit se propager aux quatre colonnes trimestrielles.

Cas Limites à Gérer

Cellules Vides au Milieu

Parfois les cellules sont légitimement vides. Ne confondez pas cela avec « pas encore rempli par un span ».

// Normaliser au même nombre de colonnes
const maxCols = grid.reduce((max, row) => 
  Math.max(max, row ? row.length : 0), 0
);

const normalized = grid.map(row => {
  const r = row || [];
  const out = new Array(maxCols);
  for (let i = 0; i < maxCols; i++) {
    out[i] = r[i] != null ? r[i] : "";
  }
  return out;
});
Enter fullscreen mode Exit fullscreen mode

Tableaux Imbriqués

Les tableaux à l'intérieur de cellules créent du bruit. La plupart du temps, vous voulez le contenu textuel du tableau imbriqué, pas une extraction récursive.

function extractCellText(cell) {
  const clone = cell.cloneNode(true);

  // Supprimer les tableaux imbriqués, scripts, styles
  clone.querySelectorAll("table, script, style, noscript")
    .forEach(el => el.remove());

  return clone.textContent.replace(/\s+/g, " ").trim();
}
Enter fullscreen mode Exit fullscreen mode

Colspan + Rowspan Combinés

L'algorithme gère cela naturellement — vous remplissez simplement un rectangle plus grand dans la grille.

<td rowspan="2" colspan="3">Grande Cellule</td>
Enter fullscreen mode Exit fullscreen mode

Remplit un bloc 2×3 avec la même valeur.

Quand Utiliser un Outil

Écrire cela de zéro est éducatif mais chronophage. Pour un usage en production, considérez :

  • HTML Table Exporter — Extension de navigateur qui gère ces cas limites automatiquement
  • Pandas read_html() — Fonctionne mais peine avec les tableaux imbriqués complexes
  • Beautiful Soup — Nécessite un traitement manuel des spans

L'Algorithme Complet

Voici la version production avec tous les cas limites gérés :

function extractCellText(cell) {
  if (!cell) return "";
  const clone = cell.cloneNode(true);

  // Supprimer les éléments contenant du contenu non visible
  const invisibleSelectors = "style, script, noscript, template, link";
  clone.querySelectorAll(invisibleSelectors).forEach(el => el.remove());

  const text = clone.textContent || "";
  return text.replace(/\s+/g, " ").trim();
}

function extractTableMatrix(table) {
  const rows = Array.from(table.rows);
  const grid = [];

  rows.forEach((rowEl, rowIndex) => {
    if (!grid[rowIndex]) grid[rowIndex] = [];
    const gridRow = grid[rowIndex];

    let colIndex = 0;

    while (gridRow[colIndex] !== undefined) {
      colIndex++;
    }

    const cells = Array.from(rowEl.cells);

    cells.forEach((cell) => {
      while (gridRow[colIndex] !== undefined) {
        colIndex++;
      }

      const text = extractCellText(cell);
      const rowSpan = parseInt(cell.rowSpan, 10) || 1;
      const colSpan = parseInt(cell.colSpan, 10) || 1;

      for (let r = 0; r < rowSpan; r++) {
        const targetRowIndex = rowIndex + r;
        if (!grid[targetRowIndex]) grid[targetRowIndex] = [];
        const targetRow = grid[targetRowIndex];

        for (let c = 0; c < colSpan; c++) {
          const targetColIndex = colIndex + c;
          if (targetRow[targetColIndex] === undefined) {
            targetRow[targetColIndex] = text;
          }
        }
      }

      colIndex += colSpan;
    });
  });

  // Normaliser toutes les lignes à la même longueur
  const maxCols = grid.reduce((max, row) => 
    Math.max(max, row ? row.length : 0), 0
  );

  return grid.map(row => {
    const r = row || [];
    const out = new Array(maxCols);
    for (let i = 0; i < maxCols; i++) {
      out[i] = r[i] != null ? r[i] : "";
    }
    return out;
  });
}
Enter fullscreen mode Exit fullscreen mode

Résumé

Approche Gère les Spans Prêt pour la Production
Itération naïve
Matrice avec spans
Pandas read_html Partiel Ça dépend
Extension navigateur

L'idée clé : pensez aux tableaux comme des grilles, pas comme des éléments HTML imbriqués. Construisez d'abord la grille, puis exportez.

Pour en savoir plus sur l'export de tableaux particulièrement complexes, consultez notre guide sur les meilleures extensions Chrome pour exporter des tableaux.


Besoin d'extraire des tableaux sans écrire de code ? En savoir plus sur gauchogrid.com/fr/html-table-exporter ou essayez-le gratuitement sur le Chrome Web Store.

Top comments (0)