Elise LINCKER

Boîte à outils 1

Extraction du contenu textuel

Objectif

L'objectif de cette première boîte à outils est de parcourir toute l'arborescence du fil RSS et d'extraire le contenu textuel des balises <title> et <description>.

Pour cela, nous avons utilisé deux méthodes :

Méthode 1 : expressions régulières

#/usr/bin/perl
<<DOC; 
Nom -- Elise LINCKER
Date -- MARS 2021

Traitement --	parcours d une arborescence de fichiers (fil RSS)
		extraction du contenu textuel des balises TITLE et DESCRIPTION avec les expressions régulières
		nettoyage du contenu textuel

Utilisation du programme -- perl bao1_regexp.pl REPERTOIRE_A_PARCOURIR RUBRIQUE_A_TRAITER
Entrée -- le nom du répertoire-racine contenant les fichiers à traiter
	et le nom de la rubrique à traiter parmi ces fichiers
Sortie -- un fichier au format txt
	un fichier au format xml
DOC
#-----------------------------------------------------------
use strict;
use utf8;
use Timer::Simple;
# on instancie un timer commencant à 0.0s par défaut
my $t = Timer::Simple->new();
# on lance le timer
$t->start;

#-----------------------------------------------------------
# on récupère le nom du répertoire racine de l'arborescence à traiter
my $rep="$ARGV[0]";
# on récupère le nom de la rubrique à traiter
my $rubrique ="$ARGV[1]";

# on s'assure que le nom du répertoire ne se termine pas par un "/", sinon on enlève le "/"
$rep=~ s/[\/]$//;

# déclaration-initialisation des fichiers de sortie
open my $OUT,">:encoding(utf8)","BAO1_regexp_sortie_$rubrique.txt";
open my $OUTXML,">:encoding(utf8)","BAO1_regexp_sortieXML_$rubrique.xml";

# dans le fichier XML, écritute de l'en-tête XML et ouverture de la balise racine
print $OUTXML "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
print $OUTXML "<articles rubrique=\"$rubrique\">\n";

# initialisation de compteurs et d'une table de hachage
my %dico_des_titres=();
my $numberItem=0;
my $nbFile=0;

#----------------------------------------
# on déclenche la procédure parcoursarborescencefichiers (récursivité !) en prenant 
# comme argument le nom du répertoire racine par lequel on doit commencer le parcours
&parcoursarborescencefichiers($rep);

#----------------------------------------
# dans le fichier XML, fermeture de la balise racine
print $OUTXML "</articles>\n";

# fermeture des fichiers de sortie
close $OUT;
close $OUTXML;

print "Nb item : $numberItem \n";
# affichage du temps écoulé depuis le lancement du programme
print "Time so far: ", $t->elapsed, " seconds\n";

# fin de l'exécution du programme
exit;

#----------------------------------------------
# procédure parcoursarborescencefichiers
# on accède au contenu du dossier dans lequel on se trouve
sub parcoursarborescencefichiers {

	# on récupère l'argument passé à la procédure (le nom de dossier)
	my $path = shift(@_);
	
	# ouverture du répertoire avec la fonction perl opendir :
	# elle prend comme arguments : le nom du répertoire à ouvrir 
	# + un descripteur, qui va être à partir de maintenant le moyen d'accéder au répertoire qu'on a ouvert
	opendir(my $DIRhandle, $path) or die "can't open $path: $!\n";
	
	# lecture du répertoire avec la fonction perl readdir :
	# elle prend comme argument le descripteur du répertoire qu'on vient d'ouvrir
	# et renvoie comme valeur la liste des fichiers contenus dans le répertoire
	# on stocke cette liste dans @files
	my @files = readdir($DIRhandle);

	# fermeture du répertoire
	closedir($DIRhandle);

	# pour chaque élément contenu dans le répertoire (ficher ou répertoire)
	foreach my $file (@files) {
		# on passe au fichier suivant s'il s'agit d'un répertoire caché
		next if $file =~ /^\.\.?$/;

		# reconstruction du chemin relatif de l'élément traité $file
		$file = $path."/".$file;
		# "." est un opérateur de concaténation : "2020"."/"."01" => "2020/01"

		# on teste le statut du fichier avec les opérateurs -d et - f
		
		# -d ---> true si $file est un répertoire 
		if (-d $file) {
			# on relance la procédure parcoursarborescencefichiers sur le répertoire $file
			&parcoursarborescencefichiers($file);	# recursivité !
		}
		
		# -f ---> true si $file est un fichier
		if (-f $file) {

			# on veut récupérer des fichiers XML RSS et une seule rubrique en particuler :
			# si le nom du fichier correspond au nom de la RUBRIQUE qu'on veut traiter
			# ET se termine par l'EXTENSION .xml ---> ON TRAITE LE FICHIER !
			if ($file =~/$rubrique.+xml$/) {
			
				# incrémentation du compteur de fichiers + message dans le terminal du fichier traité
				print $nbFile++," Traitement de : ",$file,"\n";

				# **************** TRAITEMENT DE LA BAO 1 : PERL REGEPX ****************
				# ****************     EXTRACTION DU CONTENU TEXTUEL    ****************

				# ouverture et lecture du fichier jusqu'à la fin en une seule fois
				open my $fic,"<:encoding(utf8)",$file;
				$/=undef;     #ou $\=""
				my $textelu=<$fic>;
				# fermeture du fichier
				close $fic;

				# boucle qui extrait les titres et descriptions à l'aide des expressions régulières
				while ($textelu=~/<item>.*?<title>(.+?)<\/title>.+?<description>(.+?)<\/description>/sg) {
					# initialisation des variables $titre et $description pour traiter les titres et descriptions séparément 
					my $titre=$1;
					my $description=$2;

					# incrémentation du compteur d'items
					$numberItem++;

					# appel de la procédure de nettoyage 
					($titre,$description)=&nettoyage($titre,$description);

					# si le titre n'existe pas dans la table de hachage $dico_des_titres, 
					if (!(exists $dico_des_titres{$titre})) { 
						# alors ce n'est pas un doublon : on ajoute le titre et sa description à la table de hachage
						$dico_des_titres{$titre}=$description ;

						# Ecriture des résultats dans les fichiers de sortie
						print $OUT "$titre\n";
						print $OUT "$description\n";
						print $OUT "--------------------\n";
						print $OUTXML "\t<item>\n";
						print $OUTXML "\t\t<titre>$titre</titre>\n";
						print $OUTXML "\t\t<description>$description</description>\n";
						print $OUTXML "\t</item>\n";
					}
					
					# si le titre est déjà dans $dico_des_titres, on ne fait rien
				}
			}
		}
	}
}

#----------------------------------------------
# procédure nettoyage
sub nettoyage {
	#my $titre=shift(@_); autre solution en vidant la liste des arguments du programmes...
	#my $description=shift(@_);
	my $titre = $_[0];
	my $description = $_[1];
	$titre=~s/^<!\[CDATA\[//;
	$titre=~s/\]\]>$//;
	$description=~s/^<!\[CDATA\[//;
	$description=~s/\]\]>$//;
	$description=~s/<.+?>//g;
	$description=~s/&#39;/'/g;
	$description=~s/&#34;/"/g;
	$titre=~s/<.+?>//g;
	$titre=~s/&#39;/'/g;
	$titre=~s/&#34;/"/g;
	$titre=~s/$/\./g;
	$titre=~s/\.+$/\./g;
	return $titre,$description;
}

Méthode 2 : bibliothèque XML::RSS

#/usr/bin/perl
<<DOC; 
Nom -- Elise LINCKER
Date -- MARS 2021

Traitement --	parcours d une arborescence de fichiers (fil RSS)
		extraction du contenu textuel des balises TITLE et DESCRIPTION avec la bibliothèque perl XML::RSS
		nettoyage du contenu textuel

Utilisation du programme -- perl bao1_xmlrss.pl REPERTOIRE_A_PARCOURIR RUBRIQUE_A_TRAITER
Entrée -- le nom du répertoire-racine contenant les fichiers à traiter
	et le nom de la rubrique à traiter parmi ces fichiers
Sortie -- un fichier au format txt
	un fichier au format xml
DOC
#-----------------------------------------------------------
use strict;
use utf8;
# appel de la bibliothèque XML::RSS
use XML::RSS;
use Timer::Simple;
# on instancie un timer commencant à 0.0s par défaut
my $t = Timer::Simple->new();
# on lance le timer
$t->start;

#-----------------------------------------------------------
# utilisation de la bibliothèque : création de l'objet XML::RSS
my $rss=new XML::RSS;

# on récupère dans la variable $rep le nom du répertoire racine de l'arborescence à traiter
my $rep="$ARGV[0]";
# on récupère dans la variable $rubrique le nom de la rubrique à traiter
my $rubrique ="$ARGV[1]";

# on s'assure que le nom du répertoire ne se termine pas par un "/", sinon on enlève le "/"
$rep=~ s/[\/]$//;

# déclaration-initialisation des fichiers de sortie
open my $OUT,">:encoding(utf8)","BAO1_xmlrss_sortie_$rubrique.txt";
open my $OUTXML,">:encoding(utf8)","BAO1_xmlrss_sortieXML_$rubrique.xml";

# dans le fichier XML, écritute de l'en-tête XML et ouverture de la balise racine
print $OUTXML "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
print $OUTXML "<articles rubrique=\"$rubrique\">\n";

# initialisation de compteurs et d'une table de hachage
my %dico_des_titres=();
my $numberItem=0;
my $nbFile=0;

#----------------------------------------
# on déclenche la procédure parcoursarborescencefichiers (récursivité !) en prenant 
# comme argument le nom du répertoire racine par lequel on doit commencer le parcours
&parcoursarborescencefichiers($rep);

#----------------------------------------
# dans le fichier XML, fermeture de la balise racine
print $OUTXML "</articles>\n";

# fermeture des fichiers de sortie
close $OUT;
close $OUTXML;

print "Nb item : $numberItem \n";
# affichage du temps écoulé depuis le lancement du programme
print "Time so far: ", $t->elapsed, " seconds\n";

exit;

#----------------------------------------------
# procédure parcoursarborescencefichiers
# on accède au contenu du dossier dans lequel on se trouve
sub parcoursarborescencefichiers {

	# on récupère l'argument passé à la procédure (le nom de dossier)
	my $path = shift(@_);

	# ouverture du répertoire avec la fonction perl opendir :
	# elle prend comme arguments : le nom du répertoire à ouvrir 
	# + un descripteur, qui va être à partir de maintenant le moyen d'accéder au répertoire qu'on a ouvert
	opendir(my $DIRhandle, $path) or die "can't open $path: $!\n";

	# lecture du répertoire avec la fonction perl readdir :
	# elle prend comme argument le descripteur du répertoire qu'on vient d'ouvrir
	# et renvoie comme valeur la liste des fichiers contenus dans le répertoire
	# on stocke cette liste dans @files
	my @files = readdir($DIRhandle);

	# fermeture du répertoire
	closedir($DIRhandle);

	# pour chaque élément contenu dans le répertoire (ficher ou répertoire)
	foreach my $file (@files) {
		# on passe au fichier suivant s'il s'agit d'un répertoire caché
		next if $file =~ /^\.\.?$/;

		# reconstruction du chemin relatif de l'élément traité $file
		$file = $path."/".$file;
		# "." est un opérateur de concaténation : "2020"."/"."01" => "2020/01"

		# on teste le statut du fichier avec les opérateurs -d et - f
		
		# -d ---> true si $file est un répertoire 
		if (-d $file) {
			# on relance la procédure parcoursarborescencefichiers sur le répertoire $file
			&parcoursarborescencefichiers($file);	# recursivité !
		}
		
		# -f ---> true si $file est un fichier
		if (-f $file) {

			# on veut récupérer des fichiers XML RSS et une seule rubrique en particuler :
			# si le nom du fichier correspond au nom de la RUBRIQUE qu'on veut traiter
			# ET se termine par l'EXTENSION .xml ---> ON TRAITE LE FICHIER !
			if ($file =~/$rubrique.+xml$/) {
			
				# incrémentation du compteur + message dans le terminal du fichier traité
				print $nbFile++," Traitement de : ",$file,"\n";

				# **************** TRAITEMENT DE LA BAO 1 : PERL XML::RSS ****************
				# ****************      EXTRACTION DU CONTENU TEXTUEL     ****************

				# on parse le fichier
				eval {$rss->parsefile($file);};

				# si il y a eu une erreur de parsing, on affiche un message d'erreur
				if( $@ ){
					$@ =~ s/at \/.*?$//s;	# remove module line number
					print STDERR "\nERROR in '$file':\n$@\n";
				}

				# sinon ---> extraction
				else{
					foreach my $item (@{$rss->{'items'}}){
						# extraction du titre dans la variable $titre
						my $titre=$item->{'title'};
						# extraction de la description dans la variable $description
						my $description=$item->{'description'};
						
						# incrémentation du compteur d'items
						$numberItem++;
					
						# appel de la procédure de nettoyage 
						($titre, $description)=&nettoyage($titre, $description);

						# si le titre n'existe pas dans la table de hachage $dico_des_titres, 
						if (!(exists $dico_des_titres{$titre})) { 
							# alors ce n'est pas un doublon : on ajoute le titre et sa description à la table de hachage
							$dico_des_titres{$titre}=$description ;
				
							# Ecriture des résultats en sorties
							print $OUT "$titre\n";
							print $OUT "$description\n";
							print $OUT "--------------------\n";
							print $OUTXML "\t<item>\n";
							print $OUTXML "\t\t<titre>$titre</titre>\n";
							print $OUTXML "\t\t<description>$description</description>\n";
							print $OUTXML "\t</item>\n";
						}

						# si le titre est déjà dans $dico_des_titres, on ne fait rien
					}
				}
			}
		}
	}
}

#----------------------------------------------
# procédure nettoyage
sub nettoyage {
	#my $titre=shift(@_); autre solution en vidant la liste des arguments du programmes...
	#my $description=shift(@_);
	my $titre = $_[0];
	my $description = $_[1];
	$titre=~s/^<!\[CDATA\[//;
	$titre=~s/\]\]>$//;
	$description=~s/^<!\[CDATA\[//;
	$description=~s/\]\]>$//;
	$description=~s/<.+?>//g;
	$description=~s/&#39;/'/g;
	$description=~s/&#34;/"/g;
	$description=~s/’/'/g;
	$description=~s/\s\s+/ /g;
	$titre=~s/<.+?>//g;
	$titre=~s/&#39;/'/g;
	$titre=~s/&#34;/"/g;
	$titre=~s/’/'/g;
	$titre=~s/\s\s+/ /g;
	$titre=~s/$/\./g;
	$titre=~s/\.+$/\./g;
	return $titre,$description;
}

Résultats

Les sorties obtenues par ces deux programmes sont idendiques. Les deux méthodes sont performantes et produisent des résultats très satisfaisants. Toutefois, nous remarquons une différence au niveau de la vitesse d'exécution des programmes : le programme utilisant la méthode des expressions régulières est en moyenne plus de 10 fois plus rapide que celui faisant appel à la bibliothèque XML::RSS. Pour les boîtes à outils suivantes, nous privilégierons donc la première méthode.

Temps d'exécution des programmes
RUBRIQUE METHODE
REGEXP XML::RSS
Europe - 3214 0.314611 s 4.034765 s
Planète - 3244 0.387406 s 3.86934 s
Culture - 3246 0.295774 s 4.002958 s
Cinéma - 3476 0.216899 s 3.872754 s

Vous pouvez visualiser et télécharger les fichiers de sortie depuis le tableau ci-dessous :

Sorties
RUBRIQUE METHODE REGEXP METHODE XML::RSS
SORTIE XML SORTIE TXT SORTIE XML SORTIE TXT
Europe - 3214 icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement
Planète - 3244 icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement
Culture - 3246 icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement
Cinéma - 3476 icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement
Tout icone-de-telechargement