Oracle Text et Data Mining (Partie 1)

Vous le savez, Oracle Data Mining est un de mes sujets de prédilection ! C’est notamment parce que le type d’informations qu’on arrive à extraire des données à partir des algorithmes spécialisés sont d’un intérêt extrême… Le décisionnel n’a alors plus rien à voir avec les explorations, même adhoc des données réalisées par les utilisateurs.
Dans cet article en 2 parties, vous allez découvrir comment l’algorithme de clustering d’ODM appliqué à des données non structurées arrive à faire des miracles. Une fois n’est pas coutume, j’ai décider de travailler sur des vraies données cette fois pour vous montrer, sans tricher, l’intérêt du décisionnel. Je vais donc vous montrer comment utiliser ODM avec des documents non structurés et, pour cela, je vais utiliser le contenu de ce blog. Les 2 parties consistent donc à :

  • Extraire les données de ce blog depuis Internet et les préparer (a.k.a les charger dans une base 11.2)
  • Ensuite, appliquer l’algorithme de clustering (Oracle Data Mining) et s’étonner.

Mais commençons par un peu de plomberie…

Etape 1: descendre tous les articles du blog

Pour commencer, j’ai téléchargé JSpider que j’ai installé et exécuté à l’aide de la commande :

./jspider.sh http://arkzoyd.wpengine.com/ download

Après quelque minutes, le résultat ne se fait pas attendre, la phase 1 de mon expérience est réussie :

SPIDERING SUMMARY : 
known urls ............. : 3994

visited urls ........... : 1338
parsed urls ............ : 718
parse ignored urls ..... : 620
parse error urls ....... : 0

not visited urls ....... : 2656
fetching ignored urls .. : 2575
forbidden urls ......... : 75
fetch error urls ....... : 6

not yet visited urls .. : 0
[Plugin] Spidering Stopped
INFO [core.Spider] Spidering done!
INFO [core.Spider] Elapsed time : 1414432

Etape 2 : Un outil XPath pour les fichiers HTML

Pour aller un peu plus loin, j’ai extrait des données et des métadonnées des articles téléchargés avec JSpider. Pour cela, j’ai utilisé des expressions XPath sur les fichiers HTML. Ces fichiers n’étant pas bien formé (au sens XML), il n’est malheureusement pas impossible d’utiliser les moteurs XPath classiques ! J’ai donc chargé l’extension Perl HTML::TreeBuilder::XPath sur mon laptop Ubuntu :

sudo apt-get install libhtml-treebuilder-xpath-perl

J’ai ensuite créé le programme xpath4html qui permet d’utiliser des syntaxes XPATH avec des fichiers HTML et dont vous découvrez le code ci-dessous :

cat > /usr/local/bin/xpath4html <<EOF
#!/usr/bin/perl
use strict;
use HTML::TreeBuilder::XPath;

if ($#ARGV != 1) {
print "usage: xpath4html xpath_expression filenamen";
exit 1;
}

my $xpath = $ARGV[0];
my $filename = $ARGV[1];

my $tree= HTML::TreeBuilder::XPath->new;
$tree->parse_file($filename);
my @result=$tree->findvalues($xpath);

foreach (@result) {
print $_ . "n";
}

EOF
chmod 755 /usr/local/bin/xpath4html

Quelques notes intéressantes même si, après réflexion, je n’ai pas utilisé ces outils cette fois-ci :

  • Le package Ubuntu/Perl libxml-xpath-perl contient une commande xpath pour utiliser avec des fichiers XML; la base de données contient quant à elle une syntaxe xquery complète.
  • Le package tidy permet de nettoyer les fichiers HTML et les rendre plus conformes aux différents outils de manipulation. Inutile mais fun
  • La commande perl -i -pe 'BEGIN{undef $/;} s/<script[^>]*>.*?</script[^>]*>//smg' permet d’enlever la balise script contenu sur plusieurs lignes d’un fichier passé via un pipe

Etape 3 : Extraire les métadonnées et le contenu des articles

Pour créer les métadonnées, j’ai listé, dans un premier temps, l’ensemble des articles dans un fichier content.dat qui me servira également à charger les données plus tard :

find $JSPIDER_HOME/output/www.easyteam.fr/2*/*/ -name *.html 
| cut -d '.' -f 1-3 | awk '{ print NR","$1".html,"$1".txt"}' > content.dat

Ensuite, j’ai lancé le script ci-dessous qui collecte les métadonnées et les balises de mes articles à l’aide de l’utilitaire créé à l’étape précédente :

for i in `cat content.dat`; do 
j=`echo $i | cut -d ',' -f 2`;
k=`echo $i | cut -d ',' -f 1`;
echo $k|`xpath4html "//h3[@class='post-title entry-title']/a" $j`|
`xpath4html "//h3[@class='post-title entry-title']/a/@href" $j`|
`xpath4html "//abbr[@class='published']/@title" $j` >> metacontent.dat
for l in `xpath4html "//a[@rel='tag']" $j`; do
echo $k|$l >>tags.dat
done
done

Enfin, j’ai extrait le contenu des articles dans des fichiers texte :

for i in `cat content.dat`; do 
j=`echo $i | cut -d ',' -f 2`;
z=`echo $j | cut -d '.' -f 1-3`;
xpath4html "//div[@class='post-body entry-content']" ${z}.html > ${z}.txt
done

Etape 4 : Charger les données et les métadonnées en base

Nous voilà prêt à charger les articles dans les différents formats ainsi que les metadonnées associées dans ma base de données. Pour cela, j’utilise SQL*Plus et SQL*Loader comme ci-dessous :

sqlplus dmuser/dmuser
drop table article purge;
drop table metadata purge;
drop table tag purge;
create table article(id number, html_content clob, txt_content clob);
create table metadata(id number, title varchar2(500), url varchar2(500), pdate varchar2(30));
create table tag(id number, tag varchar2(50));
exit

Je crée ensuite les définitions des fichiers de contrôle SQL*Loader et les charge en base :

cat >metacontent.ctl <<EOF
LOAD DATA
INFILE 'metacontent.dat'
INTO TABLE metadata
FIELDS TERMINATED BY '|'
(id CHAR(10),
title CHAR(500),
url CHAR(500),
pdate CHAR(30))
EOF

sqlldr userid=dmuser/dmuser control=metacontent.ctl log=output.log bad=output.bad

cat >tag.ctl <<EOF
LOAD DATA
INFILE 'tags.dat'
INTO TABLE tag
FIELDS TERMINATED BY '|'
(id CHAR(10),
tag CHAR(50))
EOF

sqlldr userid=dmuser/dmuser control=tag.ctl log=output.log bad=output.bad

Enfin j’effectue la même opération pour les documents récoltés dans une table avec SQL*Loader en format HTML et en format TXT :

cat >content.ctl <<EOF
LOAD DATA
INFILE 'content.dat'
INTO TABLE article
FIELDS TERMINATED BY ','
(id CHAR(10),
html_filename FILLER CHAR(256),
html_content LOBFILE(html_filename) TERMINATED BY EOF,
txt_filename FILLER CHAR(256),
txt_content LOBFILE(txt_filename) TERMINATED BY EOF)
EOF

sqlldr userid=dmuser/dmuser control=content.ctl log=output.log bad=output.bad

Nous voilà prêt à utiliser Oracle Data Mining ! Pour partager ces données, j’ai fait un export datapump :

sqlplus / as sysdba
create directory dp as '/u01/app/oracle/oradata/BLACK';
exit
expdp "/ as sysdba" tables=dmuser.metadata,dmuser.tag,dmuser.article
directory=dp dumpfile=blog.dmpdp logfile=blog.log;
zip blog.zip blog.dmpdp blog.log
rm blog.dmpdp blog.log
sqlplus / as sysdba
drop directory dp;
exit;

Nous en avons fini pour la plomberie. Avouez que ce n’est pas sorcier; en 2 heures le travail était fait. Si tous les systèmes décisionnels pouvaient né
cessiter autant de travail ;-). Je ne vous en dis pas plus sinon que le prochain article sera bientôt disponible et, qu’une fois encore, vous verrez que vous pouvez faire des trucs étonnant avec Oracle DataMining … Soyez patient, mais pas trop.