Extraire les données de tables HTML avec XPath

Récemment j’avais la tâche d’extraire des données à partir de pages sans doute encodées sous Excel et sauvegardées sous forme de tableaux HTML. Comme il y avait environ 800 fichiers à traiter une solution programmatique s’imposait.

Le défi n’était pas évident mais avec un peu d’inspiration j’ai songé à utiliser des outils déjà existants. Le HTML venant aussi du SGML il n’y avait qu’un pas pour adapter des techniques utilisées avec le XML.

Voici une petite description étape par étape qui pourrait éventuellement servir pour former une couche données pour une application.

Par convention on peut placer tous les fichiers HTML à lire dans un dossier. Personnellement, j’ai utilisé des variables pour définir le path relatif et le nom du dossier comme suit:

$path = "/var/www/extractor";
   
$dataSourcePath = "html_datasource";

$file = $path. DIRECTORY_SEPARATOR . $dataSourcePath . DIRECTORY_SEPARATOR . $id . ».htm »;

On peut suppléer ce code avec un file_exists($file) pour tester la présence d’un fichier. Si les fichiers dont vous disposez ne sont pas numérotés de manière séquentielle vous pouvez générer une liste en utilisant la fonction glob:

$files = glob($dataSourcePath."/{*.html}");

Voici que vient la partie intéressante. On commence par charger un fichier HTML en DOMDocument (Document Object Model):

$doc = new DOMDocument(); // Document Object Model document
$doc->loadHTMLFile($file); // Load the HTML file

Ensuite on utilise cet objet pour initialiser une instance DOMXPath. Cela permet d’utiliser une requête de type XPath (ici sélectionner toutes les tables) qui renverra un node list.

$xpath = new DOMXPath($doc);
$table = $xpath->query("//table")->item(0); // XPath Query to extract first table element

Disposant à présent d’un élément TABLE (notez l’appel à la méthode item pour récupérer le premier élement) sous forme de node DOM on peut en extraire les rangées de la manière suivante:

$rows = $table->getElementsByTagName("tr"); // Get table rows

En récupérant le nombre de rangées extraits on peut passer d’une rangée à une autre dans une boucle:

$numRows = $rows->length;
 
for( $i = 0; $i <= $numRows; $i++ ) { // Loop through rows
  $row = $rows->item($i); // Extract row element
 
  // [...]

On fini ainsi par pouvoir accéder aux valeurs des cellules mêmes:

if( isset( $row ) ){
  $cells = $row -> getElementsByTagName('td');    
  $numCells = $cells->length;
     
  for( $j = 0; $j <= $numCells; $j++ ) {
     
    $cell = $cells->item($j); // Extract cell element
       
    if( isset( $cell ) ){
      echo $cell->nodeValue;
 
      // [...]

Si certains contenus de cellules servent de pseudo-étiquettes vous pouvez utiliser un switch pour répérer les valeurs et une astuce du genre comme ci-dessous pour sauter des cellules:

switch( $value ){
  case "First name":
 
    // Skip to next TD
    $j++;
    $cell = $cells->item($j);
 
    $nextValue = $cell->nodeValue;
 
    $firstName = $nextValue;
 
    break;
 
  // [...]
}

Ceci résume une approche qui pourrais faire gagner beaucoup de temps et permettre de profiter d’un travail déjà effectué par un encodeur. Je vous laisse avec ces fonctions utilitaires qui pourraient servir en plus d’un trim() pour nettoyer le texte. Bonne extraction :) .

  // Virer les caractères non-ASCII. Attention aux accents.
  static function stripNonASCII( $string ){
    return preg_replace("/[^(\x20-\x7F)]*/", "", $string );
  }
  // Réduire les espaces multiples  
  static function replaceMultipleSpaces( $string ){
    return preg_replace('!\s+!', ' ', $string );
  }
  // Retirer les apostrophes
  static function stripApostrophes( $string ){
    return str_replace("'", "", $string );  
  }
  // Enlever les backslash  
  static function stripBackSlashes( $string ){
    return str_replace("\\", "", $string );  
  }

– James Poulson.

19 réflexions au sujet de « Extraire les données de tables HTML avec XPath »

  1. Ping : Recap java, semaine 27, année 2012 | Blog de la rubrique java

  2. Bonjour FredGuile,

    Content d’entendre que l’article t’a rendu service. Concernant le problème évoqué cela vient probablement de la manière dont nodeValue fonctionne (extraction de texte).

    Pour contourner cela ce que tu peut essayer c’est un troisième boucle en récupérant tous les sous-éléments:

    cellElements = $cell->getElementsByTagName(« * ») // Wildcard

    La valeurs d’attributs tel que href pourrait être extrait de la manière suivante (code non testé):

    $cellElement = $cellElements->item($k);

    if ($cellElement->hasAttributes()) {
    foreach ($cellElement->attributes as $attr) {
    $name = $attr->nodeName;
    $value = $attr->nodeValue;
    echo « Attribute ‘$name’ :: ‘$value’
    « ;
    }
    }

    Il y a aussi la possibilité de changer la requête XPath en amont mais on perd la rangée en tant que repère.

    Voici quelques pages de documentation en espérant que cela a pu t’avancer :) .

    http://php.net/manual/fr/domelement.getelementsbytagname.php
    http://www.php.net/manual/fr/class.domnodelist.php
    http://php.net/manual/fr/class.domnode.php

    http://www.w3schools.com/xpath/xpath_operators.asp
    http://www.w3schools.com/xpath/xpath_syntax.asp

  3. pardon, ma ligne d’exemple est interprétée en html, j’enlève les balises

    td a href=’http://monlien’ mon texte a td

  4. Bonjour et merci pour cet article qui m’a bien aidé.

    Il me reste cependant un dernier problème que je n’arrive pas à résoudre :

    une de mes cellules contient, en plus du texte, un lien

    mon texte

    Hors, $cell->nodeValue ne me renvoi que le texte et pas le contenu complet de la cellule.

    Auriez-vous une solution ?

    Merci d’avance.

Les commentaires sont fermés.