BàO #1 [ Extraction de texte ]

Objectif

Le but de cette première étape est de réaliser un traitement permettant :

  • d’extraire le titre et le résumé de chaque article, contenus dans les balises <title> et <description> de chaque balise <item> des fils RSS du corpus Le Monde 2009 ;
  • de nettoyer les doublons ;
  • de remplacer les entités HTML et les caractères spéciaux ;
  • et enfin créer plusieurs sorties : le texte brut des fils RSS, une version structurée en XML de ce contenu textuel, des versions destinées à être étiquetées par TreeTagger et Cordial, etc.
Exemple d'un fil RSS du Monde Exemple d'un fil RSS du Monde

Corpus

Le corpus à traiter est constitué de 17 fils RSS quotidiens au format XML tirés du journal LeMonde.fr, accompagnés de leur version "profonde" au format Lexico3, qui ont été archivés tous les jours à 19h durant l’année 2009. Chacun des fils journalier correspond à une des rubriques du journal.

L’arborescence présente une structuration (corpus/)mois/jour/heure/filRSS.xml, comme l’illustre la capture d’écran ci-contre.

Pour notre analyse, nous avons choisi de traiter la rubrique A la une, dont le fil RSS porte le n° 3208 dans l’arborescence, car il s’agit d’une rubrique qui traite des actualités les plus importantes, de la politique aux actualités internationales en passant par les faits divers, et dont la quantité d’information est relativement dense.

Exemple de l'arborescence des fichiers Exemple de l'arborescence des fichiers

Réalisation

A partir des scripts de base fournis en cours (à télécharger ici), nous avons réalisé deux versions de script PERL, qui diffèrent par leur mode de lecture du fichier :

  • l’une dite "Grep" (Parcours-fils-Grep.pl), où la lecture des fichiers et l’extraction du contenu des balises se fait ligne à ligne ; sa syntaxe est la suivante :
Marjorie&Axel@pluriTAL ~
$ perl Parcours-fils-Grep.pl <chemin-du-répertoire-à-parcourir>
  • quant à la deuxième version (Parcours-fils-XMLRSS.pl), elle utilise la bibliothèque XML::RSS, ce qui permet à Perl de considérer la structure arborescente et d’effectuer une lecture "balise par balise" ; sa syntaxe est identique à la première version :
Marjorie&Axel@pluriTAL ~
$ perl Parcours-fils-XMLRSS.pl <chemin-du-répertoire-à-parcourir>

Choix de la rubrique

Un dialogue initial permet à l’utilisateur de choisir la rubrique qui l’intéresse en choisissant l’identifiant correspondant dans la liste proposée. Chaque identifiant est relié au numéro porté par le fil associé, via un simple test d’égalité :

1if ($id eq 0) {$num="3208";}
2elsif ($id eq 1) {$num="3210";}
3etc.

Ensuite, ce numéro est stocké dans la variable $num qui servira à sélectionner le nom des fichiers correspondant à la rubrique choisie, lorsque le programme aura parcouru l’arborescence jusqu’à la liste des fichiers XML :

1if ((-f $file) && ($file =~ /0,2-$num,1-0,0\.xml$/))

Ce numéro servira également à remplir les métadonnées correspondant à la rubrique dans le fichier XML et à l’indiquer dans la description et le nom du fichier en sortie.

Vous remarquerez très certainement en observant la capture d'écran qu’il y manque quelques rubriques ! En effet, seulement les 12 rubriques ci-dessus étaient exploitables, bien que certaines d’entre elles présentent quelques problèmes :

  • les fils RSS Opinions et Planète présentent un doublement de la balise fermante </FICHIER> lors de la structuration XML, qui est dû à leur structuration initiale et qu’il est nécessaire de corriger manuellement.
  • Les rubriques Voyages et Examens 2009 sont peu fournies en contenu textuel et leurs fils RSS sont quelque peu "chaotiques" : pour la rubrique Voyages, le dump textuel reste exploitable mais on y retrouve un gros problème de balisage ; tandis que la rubrique Examens 2009 présente un gros problème d’encodage et que son contenu est donc difficilement exploitable.
  • Quant aux autres rubriques (Société, Économie et Culture), le programme ne peut en extraire de contenu, car leurs fils RSS contiennent uniquement des liens vers les articles.
Dialogue avec l'utilisateur pour déterminer le fil RSS à traiter Dialogue avec l'utilisateur

Nous voulions également permettre le traitement de l’ensemble des fils, mais cette tâche s’est révélée ardue au vu des problèmes mentionnés ci-dessus, et le résultat aurait été peu intéressant ; de plus, les fichiers générés auraient été trop volumineux pour être exploités correctement, lors de l’étiquetage ou même lors de leur affichage.

Parcours de l’arborescence du corpus

Ce traitement est effectué par la procédure &parcoursarborescencefichiers($rep), prenant en paramètre le chemin fourni en entrée lors de l’exécution du programme.

Afin de traiter l’ensemble des fichiers de chaque dossier quotidien, la procédure est récursive : si le répertoire ne contient pas de fichiers, alors la procédure est relancée avec l’ensemble des dossiers qu’il contient (stockés dans @files) jusqu’à atteindre le fichier .xml de la rubrique à traiter.

1foreach my $file (@files) {
......
7# si $file contient en réalité un répertoire (-d, directory)
8# on relance la procédure
9if (-d $file) { &parcoursarborescencefichiers($file); }

Afin d’extraire et d’imprimer le contenu de chaque fil RSS dans l’ordre chronologique, une procédure de tri est également lancée (les dossiers correspondant aux mois ont été préalablement numérotés) :

1@files = sort par_num @files;
......
200# Procédure de tri alphabétique
201sub par_num { return $a <=> $b }

Filtrage du texte

Les deux scripts récupèrent le contenu des balises <titre> et <description>, elles-mêmes contenues dans la balise <item>, délimitant les différents articles du fil RSS.

En plus d’extraire le titre et le texte, nous avons récupéré (de la même manière dans les deux versions) d’autres informations dans les balises de chaque fil RSS pour remplir les métadonnées de nos sorties structurées et ainsi identifier les différents résumés.

La structuration étant identique dans chaque fil, de simples expressions régulières ont été utilisées :

  • Le nom du fichier traité, sans le chemin de l’arborescence :
1$file =~ /.+(\d.+0,2-$num,1-0,0\.xml)$/)
  • le nom de la rubrique (balise <title> collée à la balise <channel>) :
1$ligne =~ /<channel><title>(.*?Le Monde\.fr.*?)<\/title><link>/
  • la date de publication de l’article (jour/mois /année) :
1if ($ligne =~ /<lastBuildDate>(.+200.).+\ ?<\/lastBuildDate>/) {
2$date = $1;
3# récupération de l'année pour la balise <DESC> de la sortie xml
4if ($date =~ /.*(\d+)\s(\w+)\s(20\d\d).*/) {
5$jour=$1;
6$mois=$2;
7$annee=$3;
8}

Nous avons également mis en place deux compteurs, l’un permettant de numéroter les articles, et le deuxième permettant de numéroter les fichiers.

Version "Grep"

Comme nous l’avons mentionné plus haut, cette version lit le fichier ligne à ligne : afin d’éviter toute ambiguïté dans l’extraction des contenus, nous avons mis en place un test pour repérer les balises collées et les séparer par un saut de ligne. De cette façon, l’expression régulière utilisée pour récupérer le contenu des balises <title> et <description> ne pourra pas chercher à récupérer de contenu au-delà de la balise fermante.

Une boucle while se charge de parcourir chaque ligne et une expression régulière premet de récupérer pour chaque balise <item> le contenu de la balise <title> et <description>. Pour ne pas concaténer les contenus à l’infini, la variable $texte est purgée avant de passer au traitement du fichier suivant.

Version "XML::RSS"

Un nouvel objet RSS est créé afin de reconstituer, grâce à la méthode $rss->parsefile ($file);, la structuration du contenu du fichier RSS qui vient d’être ouvert et permettre à Perl d’en tenir compte.

Il est alors possible de stocker dans un tableau toutes les balises <item> et d’en récupérer le contenu des balises <title> et <description> pour chacune d’elles sans ambiguïté :

1foreach my $item (@{$rss->{'items'}}) {
2$titre = $item->{'title'};
3$contenu = $item->{'description'};

Note : la récupération du nom de la rubrique et de la date de publication de l’article se fait par une boucle while et une lecture ligne à ligne.

Nettoyage et normalisation du texte filtré

  • Suppression des doublons :
    Afin de ne pas biaiser nos résultats, les doublons d’articles, qui apparaissent parfois d’une journée à l’autre, sont supprimés. Pour cela, nous avons préalablement placé chaque titre des résumés récupérés en sortie dans une table de hachage de façon à tester (avant l’écriture en sortie), avec les fonctions unless et exists, si le traitement de l’article a déjà été effectué. De cette façon, on ne sélectionne qu’une seule fois l’article, en l’ajoutant au dump final si jamais il n’a pas encore été traité.
  • Suppression des contenus vides :
    Les balises <description> renferment parfois du texte qui ne renvoie qu’à un lien : "<description> retrouvez... </description>". Le tout est alors remplacé par une balise auto fermante <RESUME/> dans la sortie XML pour avertir l’utilisateur qu’il n’y a pas de résumé pour le titre du fil. Pour la sortie texte brut, rien n’est concaténé. Le traitement est identique pour les lignes vides.
  • Suppression des balises images présentes dans le résumé :
    Contrairement au corpus des fils du journal Le Monde de l’année 2008, qui nous a préalablement servi de test, le contenu des balises <description> des fils RSS de 2009 contiennent des liens vers des images. Ces liens sont supprimés grâce à une simple expression régulière.
  • Remplacement des entités HTML et des caractères spéciaux :
    La fonction HTML::Entities::decode du module HTML::Entities nous permet de remplacer automatiquement les entités HTML présentes dans les fils RSS. Pour éviter les erreurs d’interprétation dans la sortie XML, nous avons également remplacé les caractères "&" par leur équivalent "et". Nous avons cependant remarqué que les entités représentant ‘ et «  n’étaient pas remplacées par ce module et nous avons donc rajouté deux substitutions :
1$contenu =~ s/&#39;/\'/g;
2$contenu =~ s/&#34;/\"/g;
  • Ajout de la ponctuation :
    Afin d’éviter des erreurs d’étiquetage lié à l’absence de ponctuation et donc à un environnement contextuel faussé, un point est automatiquement ajouté à la fin des titres et des résumés qui n’en possèdent pas. Remarque : dans l’optique d’afficher les résultats de l’étiquetage dans un tableau, l’avantage se révèle ici double : il nous sera possible de séparer facilement chaque phrase par ligne.

Création des sorties et résultats

Pour les différentes utilisations qui en seront faites, cinq sorties sont créées :

  • Une sortie texte brut, où les titres et descriptions restants sont concaténés les uns à la suite des autres.
  • Une sortie XML, présentant de manière plus structurée les résultats de l'extraction
1<PARCOURS>
2<DESC>du fichier</DESC>
3<FILTRAGE rubrique="">
4<FICHIER num="" date="">
5<ARTICLE num="">
6<TITRE>titre de l'article</TITRE>
7<RESUME>résumé de l'article</RESUME>
8</ARTICLE>
9</FICHIER>
10</FILTRAGE>
11</PARCOURS>
  • Une première sortie "temporaire" destinée à TreeTagger, en vue de la construction d’un tableau regroupant les résultats des deux logiciels d’étiquetage, où le contenu est balisé par fichier et par "bloc" (séparant chaque titre et chaque résumé) :
1$DUMPTMP .= "<$file>\n";
2$DUMPTMP.="<BLOC> $titre <BLOC> $resumetxt\n";
  • Une deuxième sortie "temporaire" pour Cordial, où les indications de fichiers et blocs ne sont plus indiquées par des balises mais par des mots clés, Cordial décomposant les balises pour les tagger :
1$DUMPTMP2 .= "DEBUTDEFICHIER \n";
2$DUMPTMP2.="BLOCDEPHRASE $titre BLOCDEPHRASE $resumetxt \n";
  • Un fichier structuré au format Lexico3 afin de constituer un corpus analysable grâce à une segmentation par fichier, par date (jour, mois et année), par article, par titre et par résumé :
1$DUMPLEXICO .= "<num_fichier=$i nom_fichier=$file jour=$jour mois=$mois annee=$annee>\n";
2$DUMPLEXICO .= "<num_article= >"
3$DUMPLEXICO .= "<titre_num_article= >"
4$DUMPLEXICO .= "<resume_num_article= >"

Télécharger les fichiers

Retrouvez ici l'intégralité des fichiers de la BàO #1

Scripts originaux

Cliquez ici pour les télécharger

Script "Parcours-fils-Grep.pl"

Cliquez ici pour le visualiser
Cliquez ici pour le télécharger

Script "Parcours-fils-XMLRSS.pl"

Cliquez ici pour le visualiser
Cliquez ici pour le télécharger

Fichiers de résultat (rubrique "A La Une")

Dump textuel de la rubrique
Dump au format XML
Dump balisé pour "Lexico 3"
Version destinée à TreeTagger
Version destinée à Cordial