Extraire des données d’une page web avec R – 2 – Données structurées

Ce tutoriel a pour ambition d’expliquer comment extraire des données d’une page web à l’aide du logiciel R et de montrer quelques opérations courantes de traitement des données pour les rendre exploitables.

Le tutoriel est composé de trois parties :

  1. Données sous forme de tableau
  2. Données structurées
  3. Données sur plusieurs pages

Données structurées

Souvent, les données disponibles sur internet ne sont pas présentées sous forme de tableau, mais elles sont malgré tout représentées sous une forme structurée, ce qui en rend l’extraction automatique possible.

Jetez un oeil à la page suivante par exemple : http://www.kaggle.com/users/1482/francois-guillem

Mon profil sur le site Kaggle. On va essayer d'extraire les informations sur les compétitions auxquelles j'ai participé.

Il s’agit de mon profil sur le site Kaggle. On peut notamment y voir la liste des compétitions auxquelles j’ai participé. Les informations relatives à ces compétitions ne sont pas présentées sous forme de tableau, mais elles sont néanmoins toujours représentées de la même manière :

  • à gauche, on a une image
  • au centre, en gras, le nom de la compétition
  • en dessous, une phrase indiquant le nombre de soumissions et le nom de l’équipe
  • enfin, à droite, le classement

Supposons que l’on veuille récupérer ces données. Comme elles ne sont pas sous forme de tableau, la fonction readHTMLTable n’est d’aucun secours. L’extraction des données reste possible, mais elle est un peu plus compliquée. Pour cela, il est nécessaire de connaître la structure des pages web et de comprendre comment les données sont représentées à l’intérieur des pages.

Structure d’une page web

D’un point de vue abstrait une page web est un ensemble d’éléments imbriqués. Un élément est représenté de la manière suivante :

<nom_de_l’élément> contenu de l’élément </nom_de_l’élément>

Une page web ressemble donc à quelque chose comme ça :

<html>
    <head>
        contenu du head
    </head>
    <body>
        <h1>bla bla</h1>
        <div>....</div>
        etc.
    </body>
</html>

Cela signifie que la page contient un élément “html” qui contient un “head” et un “body”. Le body contient un élément “h1” dont la valeur est égale à “bla bla” et un élément “div”, etc.

Un élément peut avoir des attributs. Ceux-ci sont représentés de la manière suivante :

<nom_de_l’élément attribut1=”valeur1” attribut2=”valeur2>...</nom_de_l’élément>

En HTML, deux attributs sont fréquemment utilisés :

  • l’attribut “id” : il représente en quelque sorte le nom propre de l’élément et il permet de l’identifier de manière unique
  • l’élément “class” : cet attribut est utilisé pour la mise en page, il donne souvent un indice sur la fonction de l’élément.

“href” est un troisième attribut très courant et souvent utile. Généralement associé à un élément “a” (qui représente un lien hypertexte), sa valeur est égale à la cible du lien.

Repérer les données dans la structure d’une page

Pour extraire les données d’une page, il faut tout d’abord repérer où celles-ci se trouvent dans la structure de la page web. Pour cela, vous devrez faire une petite manipulation dans votre navigateur :

  • Firefox : installez le module bugzilla
  • Chrome : vous n’avez rien à faire
  • Safari : allez dans Préférences/Avancées et cochez la case “Afficher le menu développement… ”
  • Internet Explorer 9 : je ne sais pas trop.
  • Internet Explorer 8 ou antérieur : pour le bien de l’humanité, téléchargez un navigateur moderne !

Dans ce qui suit, je vais utiliser Chrome car il est rapide, efficace et disponible sur toutes les plateformes. Mais le principe est le même pour Firefox et Safari (et peut-être qui sait pour IE9 ?). Faites un clic droit sur une des compétitions et cliquez sur “Procédez à l’inspection de l’élément”.

Faites un clic droit dans le cadre d'une des compétitions et cliquez sur "procéder à l'inspection de l'élément". Cela ouvre dans le bas de la fenêtre l'inspecteur web.

Un cadre s’ouvre en bas de l’écran et dans ce cadre, on peut voir la structure de la page web. L’élément sur lequel vous avez cliqué est surligné. Remarquez que lorsque vous survolez un élément dans ce cadre, il est mis en valeur dans la partie supérieure de l’écran.

L'inspecteur nous permet d'apprendre que les informations des compétitions sont contenues dans des "div" de classe "profile-comp-box". Notez que lorsque vous survolez un élément dans l'explorateur, l'élément correspondant de la page est mis en surbrillance.

On remarque ici qu’il y a trois éléments qui ont la même classe (“profile-comp-box”) : chacun d’eux représente une compétition et contient les données que l’on veut récupérer. Ouvrez l’un de ces éléments en cliquant sur la flèche qui se trouve à sa gauche. On peut voir (image ci-dessous) que chaque compétition comporte :

  • un élément “a” contenant une image (élément “img”)
  • un “div” de classe “comp-details” contenant un élément “h4” avec le titre et le lien de la compétition et un texte avec le nombre de soumissions et un lien vers l”équipe.
  • un “div” de classe “comp-time” avec le classement

En cliquant sur les flèches à gauche des éléments, on peut en afficher le contenu et voir ainsi leur structure.

Notez bien toutes ces informations. Elles sont nécessaires et suffisantes pour l’extraction des données.

Extraction des données avec R

Pour extraire les données, on utilise le package XML. On commence par importer la page web dans R à l’aide de la fonction htmlParse :

library(XML)
doc <- htmlParse("http://www.kaggle.com/users/1482/francois-guillem")

Pour extraire les informations de la page, on utilise la fonction xpathSApply. Cette fonction prend trois arguments obligatoires :

  • la page web
  • une chaine de caractère qui représente le chemin vers les éléments auxquels on s’intéresse. Le chemin est décrit dans le langage “xpath”. Pour un aperçu de la syntaxe, voir ci-dessous.
  • une fonction à appliquer à chaque élément. On en utilise généralement deux : “xmlValue” qui récupère le contenu d’un élément et xmlGetAttr qui récupère un attribut de l’élément.

xpathSApply applique la fonction qui lui est passée à tous les éléments dont la position dans la page web est décrite par le second argument.

Supposons par exemple que l’on souhaite récupérer les titres des compétitions. Dans ce cas, on utilise le code suivant :

titre <- xpathSApply(doc,
                     "//div[contains(@class, 'profile-comp-box')]/div[@class='comp-details']/h4",
                     xmlValue)
titre

[1] "Stay Alert! The Ford Challenge" "RTA Freeway Travel Time Prediction"
[3] "INFORMS Data Mining Contest 2010"

Pour comprendre ce que fait cette ligne il faut la lire de droite à gauche : elle dit qu’il faut récupérer la valeur (xmlValue) des éléments “h4” contenus dans des “div” dont l’attribut “class” est exactement égal à “comp-details” et qui se trouvent à l’intérieur de “div” donc l’attribut “class” contient “profile-combo-box”. Le double slash au début indique que ces dernières doivent être cherchées n’importe où dans la page et pas seulement à la racine de la page. Notez l’usage des apostrophes à la place des guillemets dans le deuxième argument. Cela évite toute confusion avec les guillemets utilisés par R.

Si maintenant on veut obtenir les liens vers les compétitions, on utilise le code suivant :

lien <- xpathSApply(doc,
                    "//div[contains(@class, 'profile-comp-box')]/div[@class='comp-details']/h4/a",
                    xmlGetAttr,
                    name = "href")
lien

[1] "/c/stayalert" "/c/RTA" "/c/informs2010"

# Les liens sont relatifs. Rajouter www.kaggle.com devant pour avoir l’url complète.

Cette ligne de code récupère la valeur de l’attribut “href” des éléments “a” contenus dans des “h4” contenu dans des “div” dont l’attribut class est exactement égal à…

Pour récupérer le classement :

rang <- xpathSApply(doc,
                    "//div[contains(@class, 'profile-comp-box')]/div[@class='comp-time']",
                    xmlValue)
rang

[1] "\r\n 46th place\r\n "
[2] "\r\n 14th place\r\n "
[3] "\r\n 86th place\r\n "

Pour ne récupérer que le nombre, on efface tous les caractères non numériques à l’aide de la commande :

rang <- gsub("\\D",  "",  rang)
rang <- as.numeric(rang)
rang

[1] 46 14 86

Enfin, pour récupérer le nombre de soumissions, on utilise la fonction suivante :

n <- xpathSApply(doc,
                 "/div[contains(@class, 'profile-comp-box')]/div[@class='comp-details']",
                 xmlValue)
n

[1] "\r\n Stay Alert! The Ford Challenge \r\n 7 entries as a member of fguillem.\r\n "
[2] "\r\n RTA Freeway Travel Time Prediction \r\n 37 entries as a member of fguillem.\r\n "
[3] "\r\n INFORMS Data Mining Contest 2010 \r\n 3 entries as a member of fguillem.\r\n"

Le problème est qu’on récupère bien le nombre de soumissions, mais on récupère aussi le titre des compétitions, car celui-ci se trouve dans un “h4” qui se trouve dans l’élément décrit par le chemin « //div[contains(@class, 'profile-comp-box')]/div[@class='comp-details']« . Pour éviter cela, on rajoute l’argument “recursive = FALSE” :

n <- xpathSApply(doc,
                 "//div[contains(@class, 'profile-comp-box')]/div[@class='comp-details']",
                 xmlValue,
                 recursive = FALSE)
n

[1] "\r\n \r\n 7 entries as a member of .\r\n "
[2] "\r\n \r\n 37 entries as a member of .\r\n "
[3] "\r\n \r\n 3 entries as a member of .\r\n "

Pour isoler les nombres, on utilise le même procédé que pour le classement :

n <- gsub("\\D", "", n)
n <- as.numeric(n)
n

[1] 7 37 3

Complément sur Xpath

J’espère que les exemples précédents vous auront donné l’intuition du fonctionnement de Xpath. Pour une documentation exhaustive, vous pouvez vous reporter à cette page (http://www.w3.org/TR/xpath/#section-Location-Steps). Voici les structures syntaxiques les plus utiles à retenir :

  • //parent : éléments “parent” qui se situent n’importe où dans la page
  • //parent/enfant : éléments “enfant” contenus dans des éléments “parent”
  • //parent/* : tous les éléments contenus dans des éléments “parent”
  • //parent/enfant[1] : premier élément “enfant” de chaque élément “parent”
  • //parent/enfant[last()] : dernier élément “enfant” de chaque élément “parent”
  • //parent/enfant[last() - 1] : avant-dernier élément “enfant” de chaque élément “parent”
  • //parent[@attr] : éléments “parent” possédant un attribut “attr”
  • //parent[@attr=’valeur’] : éléments “parent” dont l’attribut “attr” vaut exactement “valeur”

Enfin, si vous voulez pointer un élément précis et que cet élément a un attribut id, vous pouvez utiliser la syntaxe suivante :

id(‘valeur_de_l’id’)

Notez qu’il n’y a pas besoin de slash.

Conseils pratiques

Dans l’exemple utilisé dans cet article, chaque compétition est représentée par une “div” ayant une classe spécifique (“profile-comp-box”). Parfois les données sont contenues dans des éléments qui n’ont pas d’attribut particulier permettant de les distinguer des autres éléments. Dans ce cas, il faut regarder si l’élément qui les contient (leur “parent”) a un signe distinctif. Si ce n’est pas le cas, on regarde alors le “grand parent ” et ainsi de suite.

Dans l’explorateur de Chrome, on peut voir directement les parents d’un élément et leurs attributs dans la barre du bas (quelque chose de similaire existe dans Firefox et Safari et qui sait peut-être dans IE).

La barre en bas de l'inspecteur décrit le chemin complet vers l'élément surligné. On apprend ici qu'il est contenu dans une div dont l'id est "profile-content" elle-même contenue dans une div dont l'id est "content", etc.

Un dièse désigne l’attribut “id” et un point désigne l’attribut “class”. Sur l’image ci-dessus, on apprend que l’élément surligné est une div de classes “profile-comp-box » et “even”. Il a pour parent une “div” dont l’id est “profile-content”, etc.

Cette entrée a été publiée dans R. Vous pouvez la mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>