« Web scraping avec R » : différence entre les versions
Aucun résumé des modifications |
|||
(46 versions intermédiaires par 3 utilisateurs non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
{{ | {{tutoriel | ||
|fait_partie_du_cours=Analytique et exploration de données | |||
|fait_partie_du_module=Text mining avec R | |||
|pas_afficher_sous-page=Non | |||
|page_precedente=Tutoriel tm text mining package | |||
|page_parente=Text mining avec R | |||
|page_suivante=Tutoriel koRpus | |||
|statut=brouillon | |||
|dernière_modif=2014/11/18 | |||
|difficulté=intermédiaire | |||
|pages_prérequises=Text mining avec R | |||
|voir_aussi=Tutoriel tm text mining package,Tutoriel koRpus | |||
|cat tutoriels=R | |||
}} | |||
==Introduction== | ==Introduction== | ||
[[R]] permet d'[[Importer des données dans R|importer différents types de fichiers]] et par conséquent il est possible d'importer aussi des pages web. Cependant, avec ce type d'importation, tout le contenu de la page (i.e. tout le code [[HTML]]) est importé, ce qui n'est pas souvent le comportement souhaité car on s'intéresse seulement à une partie (ou des parties) spécifique(s) du document. À ce point, il faudrait donc faire recours à des [[Traitement de données alphanumériques avec R|fonctions de traitement alphanumériques]] pour aspirer/nettoyer les parties d'intérêt du document. Cette opération peut se reveler plutôt compliquée, surtout si elle nécessite l'utilisation des expressions régulières. À ce propos, ce tutoriel illustre le fonctions | [[R]] permet d'[[Importer des données dans R|importer différents types de fichiers]] et par conséquent il est possible d'importer aussi des pages web. Cependant, avec ce type d'importation, tout le contenu de la page (i.e. tout le code [[HTML]]) est importé, ce qui n'est pas souvent le comportement souhaité car on s'intéresse seulement à une partie (ou des parties) spécifique(s) du document. À ce point, il faudrait donc faire recours à des [[Traitement de données alphanumériques avec R|fonctions de traitement alphanumériques]] pour aspirer/nettoyer les parties d'intérêt du document. Cette opération peut se reveler plutôt compliquée, surtout si elle nécessite l'utilisation des expressions régulières. À ce propos, ce tutoriel illustre le fonctions du '''paquet rvest''' afin de faciliter le [[web scraping]] avec R, c'est-à-dire mettre à disposition des puissantes fonctionnalités de R des données tirées des pages web. '''Une bonne connaissance de [[HTML]]/[[CSS]] facilite l'utilisation de rvest et permet d'exploiter au maximum ses fonctions'''. | ||
* Pour un aperçu général sur la technique d'extraction de données depuis des pages web, consulter la page [[web scraping]] disponible dans ce wiki. | |||
* La structure hiérarchique du DOM en termes d'emboitement des balises. Par exemple, un paragraphe qui se trouve directement à l'intérieur d'une balise de type <code>div</code> peut être identifié à l'aide de la notation <code>div > p</code> | ==Identifier les éléments d'intérêt dans une page== | ||
* Des identificateurs trans-hiérarchiques comme les classes, les identificateurs uniques, et les attributs. Par exemple un paragraphe auquel on a attribué la class "important" peut être identifié à l'aide de la notation <code>p.important</code> | Avant de présenter le paquet rvest, il peut être utile d'illustrer brièvement quelques pratiques pour identifier des éléments dans une page dont on veut extraire les données. À ce propos, la plupart des navigateurs web propose non seulement la possibilité de voir le code source d'une page, mais aussi d'analyser les éléments. | ||
Cette fonction est normalement disponible directement dans le menu contextuel du navigateur (i.e. click droit). Il suffit par conséquent de pointer sur l'élément qui nous intéresse, cliquer le bouton droit du mouse, et choisir l'item du menu contextuel qui permet d'analyser l'élément. Une console du navigateur s'ouvrira et selon le type de navigateur elle proposera des informations sur l'élément. Voici une capture d'écran de FireFox qui résulte de l'analyse du tableau des contributeurs dans la page d'accueil de EduTech Wiki en français. | |||
[[Fichier:Web scraping avec R web element analyse.png|1000px|vignette|néant|Capture d'écran de l'analyse du tableau des contributeurs dans la page d'accueil de EduTech Wiki (français) en FireFox (v34.0)]] | |||
À travers ces informations on peut déterminer les meilleurs critères pour identifier l'élément. Voici une liste de ces critères présentés selon leur "facilité" d'identification : | |||
* '''Attribut id''' : si l'élément présente un attribut id (caractérisé dans l'identificateur avec le caractère #), cet attribut est censé être unique à l'élément. Par conséquent, ce type d'identificateur est très utile pour extraire un élément spécifique de la page, mais ce n'est pas indiqué pour extraire des éléments récursifs (e.g. plusieurs paragraphes), car chacun devrait avoir un id unique. L'intérêt de l'identificateur id dans une extraction récursive est souvent d'identifier l'élément "conteneur" des éléments récursifs à extraire (e.g. un div de id="content" permet souvent d'isoler le contenu de la page de l'entête et du menu de navigation généraux du site) | |||
* '''Classes''' : les classes (caractérisées par le caractère .) représente un bon moyen d'identifier des éléments récursifs présents au même niveau hiérarchique. Par exemple les commentaires d'un blog partage souvent la même classe (e.g. class="comment"). Pour maximiser la probabilité d'identifier le bon élément, dans le cas que plusieurs éléments à différents endroits dans la page présente la même classe, ce critère peut être associé au nom de la balise (e.g. div.comment) et/ou à sa position hiérarchique dans la page (voir point suivant) | |||
* '''Position hiérarchique''' : l'analyse des éléments propose souvent la position relative à la racine du DOM occupé par l'élément sélectionné. Cette manière d'identifier les éléments est celle qui présente le plus de risque de ce tromper ou de trouver plusieurs éléments qui partages le même type de hiérarchie relative. Elle devrait être utilisé "en solitaire" seulement si les balises ne présentent jamais des attributs de type id ou class. | |||
Voir une [http://www.w3schools.com/cssref/css_selectors.asp liste exhaustive des sélecteurs CSS] | |||
==Le paquet rvest== | |||
Le paquet rvest permet d'extraire du contenu des pages web à l'aide de la syntaxe [[Tutoriel XPath|XPath]] ou des sélecteurs [[CSS]]. Surtout les sélecteurs [[CSS]] représentent un outil accessible car ils sont utilisés fréquemment dans le développement des pages web. Cette notation combine deux critères d'identification : | |||
* '''La structure hiérarchique du DOM''' en termes d'emboitement des balises. Par exemple, un paragraphe qui se trouve directement à l'intérieur d'une balise de type <code>div</code> peut être identifié à l'aide de la notation <code>div > p</code> | |||
* '''Des identificateurs trans-hiérarchiques''' comme les classes, les identificateurs uniques, et les attributs. Par exemple un paragraphe auquel on a attribué la class "important" peut être identifié à l'aide de la notation <code>p.important</code> | |||
Les deux critères peuvent s'appliquer de manière combinée, c'est-à-dire qu'il est possible d'identifier des éléments, caractérisée par des indicateurs trans-hiérarchiques, selon leur positionnement hiérarchique relatifs à la structure du document. La combinaison des deux exemples illustrés plus donnerait ceci : <code>div > p.important</code>. Cette notation se traduit par l'identification de paragraphes avec une classe de type "important" qui se trouvent directement à l'intérieur d'une balise <code>div</code>. | Les deux critères peuvent s'appliquer de manière combinée, c'est-à-dire qu'il est possible d'identifier des éléments, caractérisée par des indicateurs trans-hiérarchiques, selon leur positionnement hiérarchique relatifs à la structure du document. La combinaison des deux exemples illustrés plus donnerait ceci : <code>div > p.important</code>. Cette notation se traduit par l'identification de paragraphes avec une classe de type "important" qui se trouvent directement à l'intérieur d'une balise <code>div</code>. | ||
Ligne 15 : | Ligne 45 : | ||
* Voir une [http://www.w3schools.com/cssref/css_selectors.asp liste exhaustive des sélecteurs CSS] | * Voir une [http://www.w3schools.com/cssref/css_selectors.asp liste exhaustive des sélecteurs CSS] | ||
===Installation | ===Installation du paquet=== | ||
Pour installer | Pour installer le paquet rvest il suffit de lancer la commande R suivante : | ||
install.packages("rvest") | install.packages("rvest") | ||
===Utilisation | ===Utilisation du paquet=== | ||
En considération du fait que | En considération du fait que le paquet ne fait pas partie des paquets standard disponibles en tout moment dans R, il devra être "chargé" à chaque nouvelle instance d'utilisation de R avec la commande : | ||
library(rvest) | library(rvest) | ||
===Fonctions disponibles=== | ===Fonctions disponibles=== | ||
rvest est | rvest est un paquet assez simple, qui ne présente pas beaucoup de fonctions, mais qui met à disposition les fonctionnalités principales nécessaires à l'identification et extraction des données dans une page, ainsi que quelques fonctions supplémentaires qui permettent de naviguer les pages en émulant un navigateur web. Voici la liste de fonctions d'extraction qui seront approfondies dans cette page : | ||
* html() | * html() | ||
* html_nodes() | * html_nodes() | ||
* html_text(), html_attrs(), html_tag() | * html_text(), html_attrs(), html_tag() | ||
* html_table | * html_table | ||
Le paquet met également à disposition des fonction pour émuler un navigateur web. Seulement les fonctions relatives à la navigation web seront approfondies (au moment de l'écriture de ce tutoriel, les fonction pour interagir avec des formulaires étaient encore "work in progress"). Voici la liste de ces fonctions: | |||
* html_session() | |||
* jump_to() et follow_link() | |||
* session_history() | |||
====Fonction html()==== | ====Fonction html()==== | ||
Ligne 46 : | Ligne 80 : | ||
# '''HTML "brut"''' : la ressource se compose directement du code HTML passé comme une suite de caractères. Notez à ce propos que la fonction s'attende à un document HTML complet. Si votre HTML ne présente pas la structure minimal d'un document HTML, rvest va la générer automatiquement. | # '''HTML "brut"''' : la ressource se compose directement du code HTML passé comme une suite de caractères. Notez à ce propos que la fonction s'attende à un document HTML complet. Si votre HTML ne présente pas la structure minimal d'un document HTML, rvest va la générer automatiquement. | ||
En ce qui concerne le deuxième paramètre, | En ce qui concerne le deuxième paramètre, il permet d'encoder la page selon un codage de caractères spécifiques (e.g. UTF-8). Pour régler des problèmes de codage le paquet met aussi à disposition les fonctions guess_encoding() et repair_encoding(), mais l'utilisation de l'argument encoding directement dans la fonction <code>html(x, encoding = "UTF-8")</code> résout normalement le problème à la base. | ||
Voici un example d'utilisation qui associe à la variable page le contenu de la homepage EduTechWiki en français : | Voici un example d'utilisation qui associe à la variable page le contenu de la homepage EduTechWiki en français : | ||
Ligne 57 : | Ligne 91 : | ||
Vous devrez obtenir un résultat similaire à celui ci : | Vous devrez obtenir un résultat similaire à celui ci : | ||
[[Fichier:Web scraping avec R html fonction output.png|vignette|néant|1000px|Output de la variable "page" associée au résultat de la fonction html("http://edutechwiki.unige.ch/fr/Accueil")]] | |||
====Fonction html_nodes()==== | |||
==== | |||
La fonction html_nodes() représente la "vraie" fonction de web scraping, car elle permet d'extraire des morceaux de code HTML contenant les informations d'intérêt à partir d'une page importée par la fonction html(). Pour extraire les données, html_nodes() met à disposition deux moyens : | La fonction html_nodes() représente la "vraie" fonction de web scraping, car elle permet d'extraire des morceaux de code HTML contenant les informations d'intérêt à partir d'une page importée par la fonction html(). Pour extraire les données, html_nodes() met à disposition deux moyens : | ||
* XPath : standard W3C pour identifier des informations à l'intérieur d'un document XML | * '''[[Tutoriel XPath|XPath]]''' : standard W3C pour identifier des informations à l'intérieur d'un document XML | ||
* Les sélecteurs [[CSS]] : patterns pour identifier des éléments dans une page HTML. | * '''Les sélecteurs [[CSS]]''' : patterns pour identifier des éléments dans une page HTML. | ||
En réalité, | En réalité, le paquet utilise concrètement XPath pour extraire le contenu. En effet les sélecteurs CSS sont automatiquement transformés par rvest en syntaxe XPath. | ||
La fonction html_nodes() accepte deux arguments, les deux obligatoires : | La fonction html_nodes() accepte deux arguments, les deux obligatoires : | ||
Ligne 78 : | Ligne 111 : | ||
html_nodes(page, "p") | html_nodes(page, "p") | ||
rvest support la majorité des sélecteurs de type | rvest support la majorité des sélecteurs de type [http://www.w3schools.com/cssref/css_selectors.asp CSS3], les exception sont spécificées dans la [http://cran.r-project.org/web/packages/rvest/rvest.pdf documentation officiell]e de le paquet. | ||
Si vous préférez utiliser la syntaxe XPath, il faut le déclarer explicitement dans le deuxième argument. Voici le même exemple avec XPath: | Si vous préférez utiliser la syntaxe XPath, il faut le déclarer explicitement dans le deuxième argument. Voici le même exemple avec XPath: | ||
Ligne 104 : | Ligne 137 : | ||
Le paramètre x fait référence à le code HTML du tableau. Header détermine si la première ligne du tableau doit être utilisée en tant que label des données, et donc ne pas faire partie des analyses sur les données. Si cette option n'est pas spécifiée (avec une valeur TRUE ou FALSE), la fonction va créer une entête seulement si le tableau présente un ligne de type <code>th</code>. Trim détermine si des éventuels espaces avant/après les données doivent être supprimés. Fill est une option utile dans le cas que où les tableaux ne présentent pas toujours le même nombre de colonnes par ligne (ce qui peut s'avérer avec l'attribut HTML <code>colspan</code>). Si cette option est spécifiée TRUE, les lignes avec moins de colonnes que prévu seront "filled" automatiquement. L'option dec, enfin, détermine quel caractère utiliser pour séparer les chiffres décimales. | Le paramètre x fait référence à le code HTML du tableau. Header détermine si la première ligne du tableau doit être utilisée en tant que label des données, et donc ne pas faire partie des analyses sur les données. Si cette option n'est pas spécifiée (avec une valeur TRUE ou FALSE), la fonction va créer une entête seulement si le tableau présente un ligne de type <code>th</code>. Trim détermine si des éventuels espaces avant/après les données doivent être supprimés. Fill est une option utile dans le cas que où les tableaux ne présentent pas toujours le même nombre de colonnes par ligne (ce qui peut s'avérer avec l'attribut HTML <code>colspan</code>). Si cette option est spécifiée TRUE, les lignes avec moins de colonnes que prévu seront "filled" automatiquement. L'option dec, enfin, détermine quel caractère utiliser pour séparer les chiffres décimales. | ||
== | ====Fonction html_session()==== | ||
La fonction html_session() a comme seul objectif la création d'un point d'entrée d'une chronologie de navigation. Cela s'obtient tout simplement en associant à une variable la fonction avec comme argument une adresse web : | |||
s <- html_session("http://edutechwiki.unige.ch/fr/Accueil") | |||
La fonction s contient maintenant des informations sur la page "visitée". Vous pouvez les afficher à l'écran tout simplement avec le nom de la variable comme commande. Le output sera similaire à celui-ci: | |||
<session> http://edutechwiki.unige.ch/fr/Accueil | |||
Status: 200 | |||
Type: text/html; charset=UTF-8 | |||
Size: 63787 | |||
Par la suite, la variable associé à la session de navigation sera passée en tant que paramètre aux fonctions jump_to() ou follow_link() afin qu'on puisse garder trace des liens hypertexte suivis. | |||
====Fonctions jump_to() et follow_link()==== | |||
Ces deux fonctions ont le même objectif, c'est-à-dire suivre un lien hypertexte pour passer d'une page web à une autre. Les deux fonctions acceptent deux paramètres: (i) un lien à suivre, et (ii) la session de navigation courante initialisée avec la fonction html_session(). La différence entre les deux fonctions concerne la forme de ce lien: | |||
* jump_to() accepte un '''URL''' (absolu ou relatif) | |||
* follow_link() accepte '''la référence à un lien''' dans une page sous trois formes : | |||
** Le nombre cardinal du lien dans la page (e.g. "5" pour la cinquième balise <code>a</code> présente dans le HTML de la page) | |||
** Un sélecteur XPath ou CSS (voir la fonction html_nodes() pour plus d'information) | |||
** Un mot ou séquence de mots qui représentent le "label" textuel du lien (e.g. "Page au hasard" dans le menu principal de ce wiki) | |||
Tout déplacement avec ces fonctions sera enregistré dans la session de navigation, ainsi qu'on puisse garder trace de la chronologie de navigation. Voici un example d'utilisation qui prévoit la création d'une variable s avec la session html_session() qui pointe sur la page d'accueil de ce wiki : | |||
follow_link(s, "Page au hasard") | |||
Le output de cette fonction sera similaire à celui-ci (la page effective sera clairement choisie au hasard) : | |||
Navigating to /fr/Sp%C3%A9cial:Page_au_hasard | |||
<session> http://edutechwiki.unige.ch/fr/CompendiumLD | |||
Status: 200 | |||
Type: text/html; charset=UTF-8 | |||
Size: 36012 | |||
Cette fonction | ====Fonction session_history()==== | ||
Cette fonction permet d'afficher la chronologie complète des pages visitées dans la même session initialisée avec la fonction html_session(). Voici un exemple de session qui démarre sur la page d'accueil de ce wiki et suit d'abord le lien "Aide" et ensuite le lien "Page au hasard": | |||
( | s <- html_session("http://edutechwiki.unige.ch/fr/Accueil") %>% follow_link("Aide") %>% follow_link("Page au hasard") | ||
À | À ce moment, la fonction <code>session_history(s)</code> donnera un output similaire à celui-ci: | ||
http://edutechwiki.unige.ch/fr/Aide:Aide | |||
http://edutechwiki.unige.ch/fr/Sp%C3%A9cial:Page_au_hasard | |||
- http://edutechwiki.unige.ch/fr/Tutoriel_tm_text_mining_package | |||
==Séquence des commandes dans la Console R== | ==Séquence des commandes dans la Console R== | ||
Ligne 122 : | Ligne 188 : | ||
#Importer la page en R avec la fonction html() | #Importer la page en R avec la fonction html() | ||
#Identifier la partie de la page qui contient les données avec la fonction html_nodes() | #Identifier la partie de la page qui contient les données avec la fonction html_nodes() | ||
#Aspirer/Nettoyer le contenu avec l'une de ces fonctions html_text(), html_attrs(), html_tag(), ou html_table() | #Aspirer/Nettoyer le contenu avec l'une de ces fonctions : html_text(), html_attrs(), html_tag(), ou html_table() | ||
Ce processus itérative peut être enchaîné dans la Console R en trois manières différentes : | Ce processus itérative peut être enchaîné dans la Console R en trois manières différentes : | ||
Ligne 130 : | Ligne 196 : | ||
* '''Enchaînement des fonctions''' : Condenser les trois actions en une seule commande à l'aide de la notation %>% qui permet d'enchaîner les commandes | * '''Enchaînement des fonctions''' : Condenser les trois actions en une seule commande à l'aide de la notation %>% qui permet d'enchaîner les commandes | ||
De suite, chaque manière sera proposé avec la même opération qui consiste à extraire le texte du titre de la page d'accueil de EduTech Wiki en français. Le code affiché présuppose que | De suite, chaque manière sera proposé avec la même opération qui consiste à extraire le texte du titre de la page d'accueil de EduTech Wiki en français. Le code affiché présuppose que le paquet rvest ait déjà été installé et chargée dans la session de travail de R(voir plus haut dans la page). | ||
===Déclaration de variables=== | ===Déclaration de variables=== | ||
Ligne 156 : | Ligne 222 : | ||
html_text(html_nodes(html("http://edutechwiki.unige.ch/fr/Accueil"), "title")) | html_text(html_nodes(html("http://edutechwiki.unige.ch/fr/Accueil"), "title")) | ||
Cette notation a l'avantage de tenir sur une seule ligne, mais elle augmente la possibilité de se tromper avec le nombre des parenthèses ou la bonne répartition des | Cette notation a l'avantage de tenir sur une seule ligne, mais elle augmente la possibilité de se tromper avec le nombre des parenthèses ou la bonne répartition des paramètres. L'enchaînement des fonctions présenté de suite permet de surmonter ce problème. | ||
===Enchaînement des fonctions=== | ===Enchaînement des fonctions=== | ||
Ligne 164 : | Ligne 230 : | ||
==Exemples d'extraction== | ==Exemples d'extraction== | ||
'''NB''': les exemples | De suite quelques simples exemples d'extraction de données avec le paquet rvest seront présentés. La plupart de ces exemples concerne des pages disponibles dans ce wiki, ainsi que les exemples puissent être testés facilement. Deux aspects sont cependant important à ce sujet : | ||
# la structure des pages wiki peut changer facilement dans le temps, donc '''certains exemples pourraient donner des outputs différents ou même ne plus marcher du tout''' à cause des ces changements (voir la section sur les limites techniques du [[web scraping]] pour plus de détails); | |||
# pour une "vraie" extraction de données de ce wiki, il faudrait considérer la possibilité d'utiliser l'[http://edutechwiki.unige.ch/fmediawiki/api.php API] mise à disposition. | |||
'''NB''': les exemples illustrés de suite présupposent que le '''paquet rvest''' ait déjà été '''installée et chargée''' dans la session de travail de R (voir plus haut dans la page). | |||
===Extraire les URL des liens d'une page web=== | |||
Dans ce premier exemple, nous allons extraire les liens d'une page de l'article "Web scraping" de EduTech Wiki en français. Nous allons d'abord extraire tous les liens, ensuite seulement les liens externes à la page (donc sans par exemple les raccourcis du sommaire), et enfin tous les liens externe au Wiki (donc sans les liens à d'autres pages du Wiki). | |||
Commençons d'abord pour importer le code de la page et l'associer à la variable "page". Cette variable sera utilisée dans les trois extractions, il est donc bien de l'associer à une variable ainsi que chaque extraction ne requiert pas une nouvelle requête au serveur. | |||
page <- html("http://edutechwiki.unige.ch/fr/Web_scraping") | |||
====Extraire tous les liens==== | |||
Même si on est intéressés à tous les liens, en réalité on est intéressés seulement aux liens qui font partie du corpus de l'article. Donc il faudra identifier dans la page wiki seulement le contenu de l'article. Une analyse de la structure de la page Wiki nous suggère que le contenu d'un article est facilement identifiable à travers l'attribut id="content". Nous sommes donc intéressés à tous les balises <code>a</code> présent à l'intérieur de cet élément. | |||
html_nodes(page, "#content a") %>% html_attr("href") | |||
La variable all_urls contient maintenant une liste tous les attributs "href" des balises <code>a</code> présents dans le texte de l'article. Vous pouvez notez que la liste comprend aussi des liens qui n'ont pas d'attribut "href" (e.g. NA). Pour les enlever, on peut spécifier dans la selection l'attribut href : | |||
html_nodes(page, "#content a[href]") %>% html_attr("href") | |||
====Extraire les liens vers d'autres pages==== | |||
Pour extraire seulement les liens à une page externe, il faut spécifier un nouveau critère de sélection. Les ancrages internes se qualifient par le # au début du lien. On veut donc des liens qui ne commence pas avec ce caractère. Pour ce faire, on utilise le sélecteur CSS :not : | |||
html_nodes(page, "#content a[href]:not([href^='#'])") %>% html_attr("href") | |||
La variable page_urls ne contient plus les ancrages internes, mais seulement des liens à des pages externes. | |||
Selon logique, si on voulait par contre retenir seulement les ancrages internes, il suffirait d'enlever le sélecteur :not : | |||
html_nodes(page, "#content a[href][href^='#']") %>% html_attr("href") | |||
====Extraire des liens externes à EduTech Wiki==== | |||
Enfin, nous voulons trouver seulement des références à des pages qui ne font pas partie de EduTech Wiki (ni anglais, ni français). Une possibilité serait d'exploiter le caractère la selection "a[href^='http']" parce que tout lien externe nécessite de commencer avec le protocol. Cependant, il y a la possibilité que des liens qui commencent avec http amènent quand même à l'intérieur du EduTech Wiki, soit à la version anglaise, soit parce que quelqu'un a copié/collé l'URL complet d'une page plutôt che utiliser le lien interne. On pourrait donc essayer d'exclure tout les liens qui contiennent le domaine "edutechwiki.unige.ch", mais ceci n'exclurait pas tous les liens internes à la page qui sont sous forme relative. Il faudrait donc combiner les deux critères. Cependant, en analysant mieux la structure de la page, on s'aperçoit que les liens externes reçoivent automatiquement un attribut class="external". À ce point on peut combiner cette classe avec le critère du domaine pour obtenir effectivement les liens externes : | |||
html_nodes(page, "#content a.external:not([href*='edutechwiki.unige.ch'])") %>% html_attr("href") | |||
Ce critère obtient le même résultat du plus complexe critère : | |||
html_nodes(page, "#content a[href^='http']:not([href*='edutechwiki.unige.ch'])") %>% html_attr("href") | |||
Cet exemple met en évidence l'importance de bien analyser la structure du code avant de choisir comment le sélectionner. Une vue d'ensemble plus méticuleuse avant de commencer le "scraping" peut faire épargner du temps et simplifier les critères d'extraction. | |||
===Extraire des éléments d'une liste "imbriquée"=== | |||
[[Fichier:Web scraping avec R nested categories image.png|vignette|droite|300px|L'objectif est l'exctraction seulement des catégories principales]]Dans cet exemple, nous allons extraire les noms des catégories principales de ce wiki depuis la page d'accueil. L'intérêt de cet exemple consiste à extraire seulement les catégorie "de premier niveau" et pas toutes les sous-catégories. | |||
En premier, il faut importer la page principale en R : | |||
page <- html("http://edutechwiki.unige.ch/fr/Accueil") | |||
L'analyse de la structure des éléments qui contiennent le nom de la catégorie permet de savoir que chaque nom de catégorie est un lien avec un attribut class ="CategoryTreeLabel". Nous allons donc utiliser cet indicateur pour extraire le texte des liens a avec cette classe. | |||
html_nodes(page, "a.CategoryTreeLabel") %>% html_text() | |||
Nous nous apercevons tout de suite qu'avec ce critère d'extraction nous avons effectivement obtenu les noms des catégories, mais non seulement celle de premier niveau. En effet, même les sous-catégories sont présentées comme des liens avec l'attribut class="CategoryTreeLabel" et correspondent donc au critère d'extraction. De plus, on s'aperçoit que les catégories de premier niveau ne présente pas des attributs différents par rapport aux sous-catégories. Pour les distinguer, il faudra donc trouver un critère de type hiérarchique. | |||
On remonte donc dans la structure du DOM jusqu'à trouver le premier élément parent non partagé entre catégories de premier niveau et sous-catégories. Voici les deux parcours "en arrière" dans le DOM : | |||
{|border="1" cellpadding="6" style="border-collapse:collapse;" | |||
|- | |||
! Catégorie !! Lien avec nom !! Niveau -1 !! Niveau -2 !! Niveau -3 | |||
|- | |||
| Catégorie principale || a.CategoryTreeLabel || div.CategoryTreeItem || div.CategoryTreeSection || '''div.CategoryTreeTag''' | |||
|- | |||
| Sous-catégorie || a.CategoryTreeLabel || div.CategoryTreeItem || div.CategoryTreeSection || '''div.CategoryTreeChildren''' | |||
|} | |||
On constate donc que la première différentiation entre les deux types d'éléments se trouve au "3ème niveau supérieur". | |||
[[Fichier:Web scraping avec R nested list.png|cadre|néant|Une catégorie de premier niveau et une sous-catégorie se différencient seulement au 3ème niveau hiérarchique supérieur.]] | |||
Pour identifier seulement les catégories de premier niveau, il est donc suffisant de spécifier ces trois niveaux dans l'ordre hiérarchique correcte avant le lien de class="CategoryTreeLabel" : | |||
html_nodes(page, ".CategoryTreeTag > .CategoryTreeSection > .CategoryTreeItem > .CategoryTreeLabel") %>% html_text() | |||
Le output (au 10/12/2014) est le suivant : | |||
[1] "Cours et travaux" "Education et pédagogie" | |||
[3] "Méthodes de design et de recherche" "Psychologie et apprentissage" | |||
[5] "Socio-économique et organisation" "Technologies" | |||
[7] "Technologies éducatives" "Variées" | |||
===Extraire des données d'un tableau HTML en tant que list (data.frame)=== | |||
Dans cet example nous allons extraire les données concernant les pointages des contributions dans EduTech Wiki (français) - disponibles à la page http://edutechwiki.unige.ch/fr/Sp%C3%A9cial:ContributionScores - pour faire des statistiques simples : moyenne et écart-type. | |||
Commençons d'abord pour importer cette page dans R : | |||
page <- html("http://edutechwiki.unige.ch/fr/Sp%C3%A9cial:ContributionScores"); | |||
Nous voulons bases nos statistiques exclusivement sur les contributions des derniers 30 jours. Une analyse du code source de la page nous permet de déterminer que : | |||
# Les tableaux avec les données sont facilement identifiables par un attribut class="contributionscores" | |||
# Mais tous els tableaux ont exactement les même caractéristiques, donc il n'y a pas un attribut de type id ou class pour les discriminer | |||
Pour extraire les données relatives au 30 derniers jours, nous allons donc identifier le tableau d'intérêt par son ordre d'apparition par rapport aux autres tableaux. Dans le cas spécifique, le tableau d'intérêt figure en 2ème position. Nous allons donc exploiter le fait que la fonction html_nodes() retourne un élément R de type <code>list</code>. De ce fait, la notation .<nowiki>[[n]]</nowiki> placée après la fonction html_nodes() dans le flux d'extraction limite l'opération suivante à l'élément n dans al liste. Dans notre cas, n = 2 et donc : | |||
table <- page %>% html_nodes("table.contributionscores") %>% .<nowiki>[[2]]</nowiki> %>% html_table() | |||
La commande <code>table</code> nous permet à ce point de voir le résultat de notre extraction. Les données sont déjà importé en tant que liste (essayez la commande <code>typeof(table)</code>). Ceci signifie qu'on peut utiliser les notations suivantes pour accéder aux données : | |||
#table[n-row, n-col] pour obtenir les coordonnées d'une case spécifique dans la liste, e.g. | |||
table[3,4] | |||
# | |||
#table[n-row,] (rien après la virgule) pour obtenir les données de la ligne n, e.g. | |||
table[5,] | |||
# | |||
#table[, n-col] (rien avant la virgule) pour obtenir les données de la colonne n, e.g. | |||
table[, 4] | |||
De plus, en considération du fait que le tableau HTML présentait la première ligne en tant que <code>th</code>, la fonction html_table() a automatiquement importé la première ligne en tant que variable qui identifier les colonnes. (On aurait pu en tout cas obtenir le même résultat en spécifiant l'option header="TRUE" dans la fonction html_table().) Nous pouvons observer la structure de notre élément "table" avec la commande <code>str(table)</code> dont l'output sera similaire au suivant : | |||
'data.frame': 39 obs. of 5 variables: | |||
$ Rang : int 1 2 3 4 5 6 7 8 9 10 ... | |||
$ Pointage : int 160 49 42 37 35 35 28 28 27 26 ... | |||
$ Pages : int 112 35 28 17 20 22 15 22 12 10 ... | |||
$ Changements : int 679 83 79 117 80 64 56 30 69 77 ... | |||
$ Nom d’utilisateur: chr "... ... .... | |||
Cela signifie que nos colonnes peuvent être identifiées aussi avec les noms des entêtes : | |||
table$Rang | |||
table$Pointages | |||
table$Pages | |||
table$Changements | |||
Par contre, l'entête "Nom d'utilisateur" ne respecte pas la notation des noms des variables. On peut par contre changer les noms de variables, dans notre cas des colonnes : | |||
colnames(table) <- c("Rang", "Pointages", "Pages", "Changements", "Utilisateurs") | |||
Maintenant on peut accéder à la colonne avec la commande <code>table$Utilisateurs</code> | |||
Cela dit, nous pouvons à ce point faire des statistiques sur les colonnes extraites de manière très simple. Par exemple si on veut calculer la moyenne et l'écart-type de la contribution en termes de pages, il suffit des commandes : | |||
#Moyenne colonne | |||
mean(table$Pages) | |||
#Écart-type colonne | |||
var(table$Pages) | |||
==Exemples d'intégration avec fonctionnalités de R== | ==Exemples d'intégration avec fonctionnalités de R== | ||
===Exportation dans un fichier de texte=== | |||
Une des action qui peut se révéler utile est l'exportation du contenu extrait dans un fichier de texte ainsi qu'il puisse être analysé à l'aide d'un autre logiciel. | |||
====Simple exportation avec la fonction write()==== | |||
[[Fichier:Webscraping with R simple text file output.png|500px|vignette|droite|Aperçu du fichier de texte généré avec R]]La manière la plus simple pour créer un fichier de texte avec le contenu extrait d'une page est l'utilisation de la fonction de R write() - qui est un "wrapper" de la fonction cat() qu'on verra également dans l'exemple. | |||
Dans cette exemple nous alons exporter dans un ficheir de type .txt le contenu textuel de la page [[Web scraping]]. Commençons d'abord pour importer la page et attribuer le contenu d'intérêt à une variable nommée "texte" : | |||
library(rvest) | |||
page <- html("http://edutechwiki.unige.ch/fr/Web_scraping") | |||
texte <- html_nodes(page, "div#content") %>% html_text() | |||
Nous pouvons obtenir un aperçu de ce que notre fichier de texte contiendra avec la fonction cat() avec comme seul argument la variable qui identifie notre contenu. | |||
cat(texte) | |||
Comme vous pouvez le noter, le formatage de la page n'est pas optimal. Si on analyse le contenu de la variable texte sans utiliser la fonction cat() - c'est-à-dire juste en saisissant le nom de la variable - nous pouvons noter qu'en effet notre contenu présente les caractères de tabulation (\t) et de retourne à la ligne (\n) : | |||
"\n\t\t\t\n\n\t\t\t\t\t\tWeb scraping\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tDe EduTech Wiki\n\t\t\t\t\t\t\t\t\.... | |||
Nous pouvons utiliser les expressions régulières pour enlever ces caractères : | |||
* Pour enlever seulement les tabulations : <code>texte <- gsub("\\t","",texte)</code> | |||
* Pour remplacer les retours à la ligne avec un simple espace : <code>texte <- gsub("\\n"," ",texte)</code> | |||
Une fois modifié le contenu de notre variable "texte", nous pouvons s'assurer de sa mise en page encore une fois avec la fonction cat(). Si le résultat est tel que nous le souhaitons, on peut enfin le transférer dans un fichier de texte avec la fonction <code>write()</code>. Cette fonction accepte au moins deux arguments : | |||
# Le contenu du fichier | |||
# Un nom pour identifier le fichier | |||
Il est bien, avant d'utiliser cette fonction, de changer le répertoire de travail de R (à travers le menu File) afin que le fichier créé sera placé directement dans ce dossier. Une fois déterminé le nouveau dossier de travail, nous pouvons exporter le contenu d'intérêt de notre page avec la fonction <code>write()</code> : | |||
write(texte, "web-scraping.txt") | |||
Veuillez noter que quelques problèmes de formatage du texte peuvent rester, comme le montre une capture d'écran du fichier créé en suivant la procédure décrite dans cet exemple. | |||
==Liens== | ==Liens== | ||
* [http://cran.r-project.org/web/packages/rvest/rvest.pdf Documentation de | * [http://cran.r-project.org/web/packages/rvest/rvest.pdf Documentation de rvest] (PDF) | ||
* [http://www.r-bloggers.com/rvest-easy-web-scraping-with-r/ rvest: easy web scraping with R] (Tutoriel base) | * [http://www.r-bloggers.com/rvest-easy-web-scraping-with-r/ rvest: easy web scraping with R] (Tutoriel base) | ||
* [https://github.com/hadley/rvest Le projet | * [https://github.com/hadley/rvest Le projet du paquet sur GitHub] (utile en cas de changements/mis à jour) |
Dernière version du 22 mars 2020 à 22:15
Analytique et exploration de données | |
---|---|
Module: Text mining avec R | |
▲ | |
◀▬▬▶ | |
⚐ brouillon | ☸ intermédiaire |
⚒ 2020/03/22 | ⚒⚒ 2014/11/18 |
Prérequis | |
Voir aussi | |
Catégorie: R |
Introduction
R permet d'importer différents types de fichiers et par conséquent il est possible d'importer aussi des pages web. Cependant, avec ce type d'importation, tout le contenu de la page (i.e. tout le code HTML) est importé, ce qui n'est pas souvent le comportement souhaité car on s'intéresse seulement à une partie (ou des parties) spécifique(s) du document. À ce point, il faudrait donc faire recours à des fonctions de traitement alphanumériques pour aspirer/nettoyer les parties d'intérêt du document. Cette opération peut se reveler plutôt compliquée, surtout si elle nécessite l'utilisation des expressions régulières. À ce propos, ce tutoriel illustre le fonctions du paquet rvest afin de faciliter le web scraping avec R, c'est-à-dire mettre à disposition des puissantes fonctionnalités de R des données tirées des pages web. Une bonne connaissance de HTML/CSS facilite l'utilisation de rvest et permet d'exploiter au maximum ses fonctions.
- Pour un aperçu général sur la technique d'extraction de données depuis des pages web, consulter la page web scraping disponible dans ce wiki.
Identifier les éléments d'intérêt dans une page
Avant de présenter le paquet rvest, il peut être utile d'illustrer brièvement quelques pratiques pour identifier des éléments dans une page dont on veut extraire les données. À ce propos, la plupart des navigateurs web propose non seulement la possibilité de voir le code source d'une page, mais aussi d'analyser les éléments.
Cette fonction est normalement disponible directement dans le menu contextuel du navigateur (i.e. click droit). Il suffit par conséquent de pointer sur l'élément qui nous intéresse, cliquer le bouton droit du mouse, et choisir l'item du menu contextuel qui permet d'analyser l'élément. Une console du navigateur s'ouvrira et selon le type de navigateur elle proposera des informations sur l'élément. Voici une capture d'écran de FireFox qui résulte de l'analyse du tableau des contributeurs dans la page d'accueil de EduTech Wiki en français.
À travers ces informations on peut déterminer les meilleurs critères pour identifier l'élément. Voici une liste de ces critères présentés selon leur "facilité" d'identification :
- Attribut id : si l'élément présente un attribut id (caractérisé dans l'identificateur avec le caractère #), cet attribut est censé être unique à l'élément. Par conséquent, ce type d'identificateur est très utile pour extraire un élément spécifique de la page, mais ce n'est pas indiqué pour extraire des éléments récursifs (e.g. plusieurs paragraphes), car chacun devrait avoir un id unique. L'intérêt de l'identificateur id dans une extraction récursive est souvent d'identifier l'élément "conteneur" des éléments récursifs à extraire (e.g. un div de id="content" permet souvent d'isoler le contenu de la page de l'entête et du menu de navigation généraux du site)
- Classes : les classes (caractérisées par le caractère .) représente un bon moyen d'identifier des éléments récursifs présents au même niveau hiérarchique. Par exemple les commentaires d'un blog partage souvent la même classe (e.g. class="comment"). Pour maximiser la probabilité d'identifier le bon élément, dans le cas que plusieurs éléments à différents endroits dans la page présente la même classe, ce critère peut être associé au nom de la balise (e.g. div.comment) et/ou à sa position hiérarchique dans la page (voir point suivant)
- Position hiérarchique : l'analyse des éléments propose souvent la position relative à la racine du DOM occupé par l'élément sélectionné. Cette manière d'identifier les éléments est celle qui présente le plus de risque de ce tromper ou de trouver plusieurs éléments qui partages le même type de hiérarchie relative. Elle devrait être utilisé "en solitaire" seulement si les balises ne présentent jamais des attributs de type id ou class.
Voir une liste exhaustive des sélecteurs CSS
Le paquet rvest
Le paquet rvest permet d'extraire du contenu des pages web à l'aide de la syntaxe XPath ou des sélecteurs CSS. Surtout les sélecteurs CSS représentent un outil accessible car ils sont utilisés fréquemment dans le développement des pages web. Cette notation combine deux critères d'identification :
- La structure hiérarchique du DOM en termes d'emboitement des balises. Par exemple, un paragraphe qui se trouve directement à l'intérieur d'une balise de type
div
peut être identifié à l'aide de la notationdiv > p
- Des identificateurs trans-hiérarchiques comme les classes, les identificateurs uniques, et les attributs. Par exemple un paragraphe auquel on a attribué la class "important" peut être identifié à l'aide de la notation
p.important
Les deux critères peuvent s'appliquer de manière combinée, c'est-à-dire qu'il est possible d'identifier des éléments, caractérisée par des indicateurs trans-hiérarchiques, selon leur positionnement hiérarchique relatifs à la structure du document. La combinaison des deux exemples illustrés plus donnerait ceci : div > p.important
. Cette notation se traduit par l'identification de paragraphes avec une classe de type "important" qui se trouvent directement à l'intérieur d'une balise div
.
Cette notation permet de créer des critères de manière très flexible, car elle met à disposition un large éventail d'ancrages dans une page web pour repérer les informations d'intérêt. De plus, cette notation très simple à l'apparence, peut se complexifier et créer des critères de selection très précis, même si les expressions régulières restent la méthode la plus puissante.
- Voir une liste exhaustive des sélecteurs CSS
Installation du paquet
Pour installer le paquet rvest il suffit de lancer la commande R suivante :
install.packages("rvest")
Utilisation du paquet
En considération du fait que le paquet ne fait pas partie des paquets standard disponibles en tout moment dans R, il devra être "chargé" à chaque nouvelle instance d'utilisation de R avec la commande :
library(rvest)
Fonctions disponibles
rvest est un paquet assez simple, qui ne présente pas beaucoup de fonctions, mais qui met à disposition les fonctionnalités principales nécessaires à l'identification et extraction des données dans une page, ainsi que quelques fonctions supplémentaires qui permettent de naviguer les pages en émulant un navigateur web. Voici la liste de fonctions d'extraction qui seront approfondies dans cette page :
- html()
- html_nodes()
- html_text(), html_attrs(), html_tag()
- html_table
Le paquet met également à disposition des fonction pour émuler un navigateur web. Seulement les fonctions relatives à la navigation web seront approfondies (au moment de l'écriture de ce tutoriel, les fonction pour interagir avec des formulaires étaient encore "work in progress"). Voici la liste de ces fonctions:
- html_session()
- jump_to() et follow_link()
- session_history()
Fonction html()
La fonction html() est généralement la première à être utilisé dans un flux d'extraction car elle permet d'importer en R le contenu d'une page web. La fonction accepte donc deux paramètres, dont le deuxième (encoding) est optionnel. :
html(x, encoding=NULL)
Le paramètre x sert à identifier la ressource contenant du code HTML. Cette ressource peut être déclarée de trois manières :
- URL : la ressource est identifiée par son adresse web, par exemple html(http://edutechwiki.unige.ch/fr/Accueil)
- Fichier local : la ressource est identifié par le nom d'un fichier local. Cette deuxième modalité est utile dans le cas que vous ne puissiez par accéder à votre ressource sans un mécanisme d'authentification. Dans ce cas vous pouvez sauvegarder la page depuis votre navigateur. La manière plus simple pour accéder à un fichier local est de changer le répertoire de travail de R (à travers le menu File >) et de le faire pointer au dossier qui contient votre page (ou vos pages) HTML. À ce point, il vous suffit de déclarer le nom du fichier. Par exemple html("index.html").
- HTML "brut" : la ressource se compose directement du code HTML passé comme une suite de caractères. Notez à ce propos que la fonction s'attende à un document HTML complet. Si votre HTML ne présente pas la structure minimal d'un document HTML, rvest va la générer automatiquement.
En ce qui concerne le deuxième paramètre, il permet d'encoder la page selon un codage de caractères spécifiques (e.g. UTF-8). Pour régler des problèmes de codage le paquet met aussi à disposition les fonctions guess_encoding() et repair_encoding(), mais l'utilisation de l'argument encoding directement dans la fonction html(x, encoding = "UTF-8")
résout normalement le problème à la base.
Voici un example d'utilisation qui associe à la variable page le contenu de la homepage EduTechWiki en français :
page <- html("http://edutechwiki.unige.ch/fr/Accueil")
Pour contrôler que tout a bien fonctionné, vous pouvez voir le contenu de la variable simplement en saisissant son nom.
page
Vous devrez obtenir un résultat similaire à celui ci :
Fonction html_nodes()
La fonction html_nodes() représente la "vraie" fonction de web scraping, car elle permet d'extraire des morceaux de code HTML contenant les informations d'intérêt à partir d'une page importée par la fonction html(). Pour extraire les données, html_nodes() met à disposition deux moyens :
- XPath : standard W3C pour identifier des informations à l'intérieur d'un document XML
- Les sélecteurs CSS : patterns pour identifier des éléments dans une page HTML.
En réalité, le paquet utilise concrètement XPath pour extraire le contenu. En effet les sélecteurs CSS sont automatiquement transformés par rvest en syntaxe XPath.
La fonction html_nodes() accepte deux arguments, les deux obligatoires :
html_nodes(x, [css, xpath])
L'argument x représente du code HTML, notamment une page importée avec la fonction html().
Le deuxième argument est un critère de sélection à l'intérieur du code passé en référence dans l'argument x. Voici un exemple pour extraire les paragraphes avec les sélecteurs CSS à partir du code HTML préalablement associé à une variable "page":
html_nodes(page, "p")
rvest support la majorité des sélecteurs de type CSS3, les exception sont spécificées dans la documentation officielle de le paquet.
Si vous préférez utiliser la syntaxe XPath, il faut le déclarer explicitement dans le deuxième argument. Voici le même exemple avec XPath:
html_nodes(page, xpath="//p")
La fonction html_nodes() renvois une liste (tableau) des occurrences trouvées dans le code analysés en fonction des critères de sélection spécifiée. Le résultat de cette fonction est généralement traité ultérieurement avec les fonctions html_text().
La fonction html_node() (sans s finale) est également disponible. Son fonctionnement est le même, sauf pour le fait que seulement la première occurrence sera retenue.
Fonctions html_text(), html_attrs(), et html_tag()
Toutes ces fonctions servent pour aspirer/nettoyer "définitivement" les éléments d'intérêt que nous avons isolés à travers la fonction html_nodes() :
- html_text(x, ...) : enlève toutes les balises du code isolé et affiche seulement le contenu textuel. La fonction accepte un argument obligatoire, c'est-à-dire le code qui doit être "nettoyé" ainsi que des arguments facultatifs dont le plus utile est trim = TRUE pour enlever des espaces avant et après le texte, e.g. html_text(code, trim = TRUE)
- html_attrs(x) : identifie les attributs des balises présents dans le code x (disponible aussi html_attr() pour un seule attribut dont il faut spécifier le nom)
- html_tag(x) : identifie le type de balises présent dans le code x
Fonction html_table()
La fonction html_table(), comme le nom le suggère, est spécialement conçue pour extraire facilement le contenu d'un tableau HTML, en gardant la structure en lignes et colonnes, ce qui permet entre autres d'exécuter successivement des analyses statistiques avec R.
La fonction accepte 5 paramètres dont seulement le premier est obligatoire :
html_table(x, header = NA, trim = TRUE, fill = FALSE, dec = ".")
Le paramètre x fait référence à le code HTML du tableau. Header détermine si la première ligne du tableau doit être utilisée en tant que label des données, et donc ne pas faire partie des analyses sur les données. Si cette option n'est pas spécifiée (avec une valeur TRUE ou FALSE), la fonction va créer une entête seulement si le tableau présente un ligne de type th
. Trim détermine si des éventuels espaces avant/après les données doivent être supprimés. Fill est une option utile dans le cas que où les tableaux ne présentent pas toujours le même nombre de colonnes par ligne (ce qui peut s'avérer avec l'attribut HTML colspan
). Si cette option est spécifiée TRUE, les lignes avec moins de colonnes que prévu seront "filled" automatiquement. L'option dec, enfin, détermine quel caractère utiliser pour séparer les chiffres décimales.
Fonction html_session()
La fonction html_session() a comme seul objectif la création d'un point d'entrée d'une chronologie de navigation. Cela s'obtient tout simplement en associant à une variable la fonction avec comme argument une adresse web :
s <- html_session("http://edutechwiki.unige.ch/fr/Accueil")
La fonction s contient maintenant des informations sur la page "visitée". Vous pouvez les afficher à l'écran tout simplement avec le nom de la variable comme commande. Le output sera similaire à celui-ci:
<session> http://edutechwiki.unige.ch/fr/Accueil Status: 200 Type: text/html; charset=UTF-8 Size: 63787
Par la suite, la variable associé à la session de navigation sera passée en tant que paramètre aux fonctions jump_to() ou follow_link() afin qu'on puisse garder trace des liens hypertexte suivis.
Fonctions jump_to() et follow_link()
Ces deux fonctions ont le même objectif, c'est-à-dire suivre un lien hypertexte pour passer d'une page web à une autre. Les deux fonctions acceptent deux paramètres: (i) un lien à suivre, et (ii) la session de navigation courante initialisée avec la fonction html_session(). La différence entre les deux fonctions concerne la forme de ce lien:
- jump_to() accepte un URL (absolu ou relatif)
- follow_link() accepte la référence à un lien dans une page sous trois formes :
- Le nombre cardinal du lien dans la page (e.g. "5" pour la cinquième balise
a
présente dans le HTML de la page) - Un sélecteur XPath ou CSS (voir la fonction html_nodes() pour plus d'information)
- Un mot ou séquence de mots qui représentent le "label" textuel du lien (e.g. "Page au hasard" dans le menu principal de ce wiki)
- Le nombre cardinal du lien dans la page (e.g. "5" pour la cinquième balise
Tout déplacement avec ces fonctions sera enregistré dans la session de navigation, ainsi qu'on puisse garder trace de la chronologie de navigation. Voici un example d'utilisation qui prévoit la création d'une variable s avec la session html_session() qui pointe sur la page d'accueil de ce wiki :
follow_link(s, "Page au hasard")
Le output de cette fonction sera similaire à celui-ci (la page effective sera clairement choisie au hasard) :
Navigating to /fr/Sp%C3%A9cial:Page_au_hasard <session> http://edutechwiki.unige.ch/fr/CompendiumLD Status: 200 Type: text/html; charset=UTF-8 Size: 36012
Fonction session_history()
Cette fonction permet d'afficher la chronologie complète des pages visitées dans la même session initialisée avec la fonction html_session(). Voici un exemple de session qui démarre sur la page d'accueil de ce wiki et suit d'abord le lien "Aide" et ensuite le lien "Page au hasard":
s <- html_session("http://edutechwiki.unige.ch/fr/Accueil") %>% follow_link("Aide") %>% follow_link("Page au hasard")
À ce moment, la fonction session_history(s)
donnera un output similaire à celui-ci:
http://edutechwiki.unige.ch/fr/Aide:Aide http://edutechwiki.unige.ch/fr/Sp%C3%A9cial:Page_au_hasard - http://edutechwiki.unige.ch/fr/Tutoriel_tm_text_mining_package
Séquence des commandes dans la Console R
L'extraction de contenu avec rvest se fait généralement en trois étapes :
- Importer la page en R avec la fonction html()
- Identifier la partie de la page qui contient les données avec la fonction html_nodes()
- Aspirer/Nettoyer le contenu avec l'une de ces fonctions : html_text(), html_attrs(), html_tag(), ou html_table()
Ce processus itérative peut être enchaîné dans la Console R en trois manières différentes :
- Déclaration de variables : déclarer à chaque passage une nouvelle variable et la passer en argument dans l'étape successive
- Emboitement des fonctions : condenser les trois actions en une seule commande avec emboîtement des fonctions
- Enchaînement des fonctions : Condenser les trois actions en une seule commande à l'aide de la notation %>% qui permet d'enchaîner les commandes
De suite, chaque manière sera proposé avec la même opération qui consiste à extraire le texte du titre de la page d'accueil de EduTech Wiki en français. Le code affiché présuppose que le paquet rvest ait déjà été installé et chargée dans la session de travail de R(voir plus haut dans la page).
Déclaration de variables
Ce processus implique trois passages.
1. Créer une variable "page" avec le contenu HTML du document.
page <- html("http://edutechwiki.unige.ch/fr/Accueil")
2. Créer une variable "titre" qui identifie la balise title
à l'intérieur de la variable "page" passé en référence. Il n'y a qu'une balise de ce type dans une page, donc on utilisera la fonction html_node() plutôt que html_nodes()
titre <- html_node(page, "title")
3. Créer une variable "texte" qui enlève, à travers la fonction html_text() avec la variable "titre" passé en référence, les balises <title>...</title>
.
texte <- html_text(titre)
À ce point, si on demande d'imprimer à l'écran le contenu de la variable texte, on obtiendra :
[1] "EduTech Wiki"
Emboitement des fonctions
Le même résultat peut être obtenu avec une seule ligne de commande qui "imbrique" les trois fonctions.
html_text(html_nodes(html("http://edutechwiki.unige.ch/fr/Accueil"), "title"))
Cette notation a l'avantage de tenir sur une seule ligne, mais elle augmente la possibilité de se tromper avec le nombre des parenthèses ou la bonne répartition des paramètres. L'enchaînement des fonctions présenté de suite permet de surmonter ce problème.
Enchaînement des fonctions
Grâce à la notation %>% on peut étaler les trois passages en succession, tout en gardant une seule ligne de commande. Voici la notation à utiliser pour le même exemple :
html("http://edutechwiki.unige.ch/fr/Accueil") %>% html_nodes("title") %>% html_text()
Exemples d'extraction
De suite quelques simples exemples d'extraction de données avec le paquet rvest seront présentés. La plupart de ces exemples concerne des pages disponibles dans ce wiki, ainsi que les exemples puissent être testés facilement. Deux aspects sont cependant important à ce sujet :
- la structure des pages wiki peut changer facilement dans le temps, donc certains exemples pourraient donner des outputs différents ou même ne plus marcher du tout à cause des ces changements (voir la section sur les limites techniques du web scraping pour plus de détails);
- pour une "vraie" extraction de données de ce wiki, il faudrait considérer la possibilité d'utiliser l'API mise à disposition.
NB: les exemples illustrés de suite présupposent que le paquet rvest ait déjà été installée et chargée dans la session de travail de R (voir plus haut dans la page).
Extraire les URL des liens d'une page web
Dans ce premier exemple, nous allons extraire les liens d'une page de l'article "Web scraping" de EduTech Wiki en français. Nous allons d'abord extraire tous les liens, ensuite seulement les liens externes à la page (donc sans par exemple les raccourcis du sommaire), et enfin tous les liens externe au Wiki (donc sans les liens à d'autres pages du Wiki).
Commençons d'abord pour importer le code de la page et l'associer à la variable "page". Cette variable sera utilisée dans les trois extractions, il est donc bien de l'associer à une variable ainsi que chaque extraction ne requiert pas une nouvelle requête au serveur.
page <- html("http://edutechwiki.unige.ch/fr/Web_scraping")
Extraire tous les liens
Même si on est intéressés à tous les liens, en réalité on est intéressés seulement aux liens qui font partie du corpus de l'article. Donc il faudra identifier dans la page wiki seulement le contenu de l'article. Une analyse de la structure de la page Wiki nous suggère que le contenu d'un article est facilement identifiable à travers l'attribut id="content". Nous sommes donc intéressés à tous les balises a
présent à l'intérieur de cet élément.
html_nodes(page, "#content a") %>% html_attr("href")
La variable all_urls contient maintenant une liste tous les attributs "href" des balises a
présents dans le texte de l'article. Vous pouvez notez que la liste comprend aussi des liens qui n'ont pas d'attribut "href" (e.g. NA). Pour les enlever, on peut spécifier dans la selection l'attribut href :
html_nodes(page, "#content a[href]") %>% html_attr("href")
Extraire les liens vers d'autres pages
Pour extraire seulement les liens à une page externe, il faut spécifier un nouveau critère de sélection. Les ancrages internes se qualifient par le # au début du lien. On veut donc des liens qui ne commence pas avec ce caractère. Pour ce faire, on utilise le sélecteur CSS :not :
html_nodes(page, "#content a[href]:not([href^='#'])") %>% html_attr("href")
La variable page_urls ne contient plus les ancrages internes, mais seulement des liens à des pages externes.
Selon logique, si on voulait par contre retenir seulement les ancrages internes, il suffirait d'enlever le sélecteur :not :
html_nodes(page, "#content a[href][href^='#']") %>% html_attr("href")
Extraire des liens externes à EduTech Wiki
Enfin, nous voulons trouver seulement des références à des pages qui ne font pas partie de EduTech Wiki (ni anglais, ni français). Une possibilité serait d'exploiter le caractère la selection "a[href^='http']" parce que tout lien externe nécessite de commencer avec le protocol. Cependant, il y a la possibilité que des liens qui commencent avec http amènent quand même à l'intérieur du EduTech Wiki, soit à la version anglaise, soit parce que quelqu'un a copié/collé l'URL complet d'une page plutôt che utiliser le lien interne. On pourrait donc essayer d'exclure tout les liens qui contiennent le domaine "edutechwiki.unige.ch", mais ceci n'exclurait pas tous les liens internes à la page qui sont sous forme relative. Il faudrait donc combiner les deux critères. Cependant, en analysant mieux la structure de la page, on s'aperçoit que les liens externes reçoivent automatiquement un attribut class="external". À ce point on peut combiner cette classe avec le critère du domaine pour obtenir effectivement les liens externes :
html_nodes(page, "#content a.external:not([href*='edutechwiki.unige.ch'])") %>% html_attr("href")
Ce critère obtient le même résultat du plus complexe critère :
html_nodes(page, "#content a[href^='http']:not([href*='edutechwiki.unige.ch'])") %>% html_attr("href")
Cet exemple met en évidence l'importance de bien analyser la structure du code avant de choisir comment le sélectionner. Une vue d'ensemble plus méticuleuse avant de commencer le "scraping" peut faire épargner du temps et simplifier les critères d'extraction.
Extraire des éléments d'une liste "imbriquée"
Dans cet exemple, nous allons extraire les noms des catégories principales de ce wiki depuis la page d'accueil. L'intérêt de cet exemple consiste à extraire seulement les catégorie "de premier niveau" et pas toutes les sous-catégories.
En premier, il faut importer la page principale en R :
page <- html("http://edutechwiki.unige.ch/fr/Accueil")
L'analyse de la structure des éléments qui contiennent le nom de la catégorie permet de savoir que chaque nom de catégorie est un lien avec un attribut class ="CategoryTreeLabel". Nous allons donc utiliser cet indicateur pour extraire le texte des liens a avec cette classe.
html_nodes(page, "a.CategoryTreeLabel") %>% html_text()
Nous nous apercevons tout de suite qu'avec ce critère d'extraction nous avons effectivement obtenu les noms des catégories, mais non seulement celle de premier niveau. En effet, même les sous-catégories sont présentées comme des liens avec l'attribut class="CategoryTreeLabel" et correspondent donc au critère d'extraction. De plus, on s'aperçoit que les catégories de premier niveau ne présente pas des attributs différents par rapport aux sous-catégories. Pour les distinguer, il faudra donc trouver un critère de type hiérarchique.
On remonte donc dans la structure du DOM jusqu'à trouver le premier élément parent non partagé entre catégories de premier niveau et sous-catégories. Voici les deux parcours "en arrière" dans le DOM :
Catégorie | Lien avec nom | Niveau -1 | Niveau -2 | Niveau -3 |
---|---|---|---|---|
Catégorie principale | a.CategoryTreeLabel | div.CategoryTreeItem | div.CategoryTreeSection | div.CategoryTreeTag |
Sous-catégorie | a.CategoryTreeLabel | div.CategoryTreeItem | div.CategoryTreeSection | div.CategoryTreeChildren |
On constate donc que la première différentiation entre les deux types d'éléments se trouve au "3ème niveau supérieur".
Pour identifier seulement les catégories de premier niveau, il est donc suffisant de spécifier ces trois niveaux dans l'ordre hiérarchique correcte avant le lien de class="CategoryTreeLabel" :
html_nodes(page, ".CategoryTreeTag > .CategoryTreeSection > .CategoryTreeItem > .CategoryTreeLabel") %>% html_text()
Le output (au 10/12/2014) est le suivant :
[1] "Cours et travaux" "Education et pédagogie" [3] "Méthodes de design et de recherche" "Psychologie et apprentissage" [5] "Socio-économique et organisation" "Technologies" [7] "Technologies éducatives" "Variées"
Extraire des données d'un tableau HTML en tant que list (data.frame)
Dans cet example nous allons extraire les données concernant les pointages des contributions dans EduTech Wiki (français) - disponibles à la page http://edutechwiki.unige.ch/fr/Sp%C3%A9cial:ContributionScores - pour faire des statistiques simples : moyenne et écart-type.
Commençons d'abord pour importer cette page dans R :
page <- html("http://edutechwiki.unige.ch/fr/Sp%C3%A9cial:ContributionScores");
Nous voulons bases nos statistiques exclusivement sur les contributions des derniers 30 jours. Une analyse du code source de la page nous permet de déterminer que :
- Les tableaux avec les données sont facilement identifiables par un attribut class="contributionscores"
- Mais tous els tableaux ont exactement les même caractéristiques, donc il n'y a pas un attribut de type id ou class pour les discriminer
Pour extraire les données relatives au 30 derniers jours, nous allons donc identifier le tableau d'intérêt par son ordre d'apparition par rapport aux autres tableaux. Dans le cas spécifique, le tableau d'intérêt figure en 2ème position. Nous allons donc exploiter le fait que la fonction html_nodes() retourne un élément R de type list
. De ce fait, la notation .[[n]] placée après la fonction html_nodes() dans le flux d'extraction limite l'opération suivante à l'élément n dans al liste. Dans notre cas, n = 2 et donc :
table <- page %>% html_nodes("table.contributionscores") %>% .[[2]] %>% html_table()
La commande table
nous permet à ce point de voir le résultat de notre extraction. Les données sont déjà importé en tant que liste (essayez la commande typeof(table)
). Ceci signifie qu'on peut utiliser les notations suivantes pour accéder aux données :
#table[n-row, n-col] pour obtenir les coordonnées d'une case spécifique dans la liste, e.g. table[3,4] # #table[n-row,] (rien après la virgule) pour obtenir les données de la ligne n, e.g. table[5,] # #table[, n-col] (rien avant la virgule) pour obtenir les données de la colonne n, e.g. table[, 4]
De plus, en considération du fait que le tableau HTML présentait la première ligne en tant que th
, la fonction html_table() a automatiquement importé la première ligne en tant que variable qui identifier les colonnes. (On aurait pu en tout cas obtenir le même résultat en spécifiant l'option header="TRUE" dans la fonction html_table().) Nous pouvons observer la structure de notre élément "table" avec la commande str(table)
dont l'output sera similaire au suivant :
'data.frame': 39 obs. of 5 variables: $ Rang : int 1 2 3 4 5 6 7 8 9 10 ... $ Pointage : int 160 49 42 37 35 35 28 28 27 26 ... $ Pages : int 112 35 28 17 20 22 15 22 12 10 ... $ Changements : int 679 83 79 117 80 64 56 30 69 77 ... $ Nom d’utilisateur: chr "... ... ....
Cela signifie que nos colonnes peuvent être identifiées aussi avec les noms des entêtes :
table$Rang table$Pointages table$Pages table$Changements
Par contre, l'entête "Nom d'utilisateur" ne respecte pas la notation des noms des variables. On peut par contre changer les noms de variables, dans notre cas des colonnes :
colnames(table) <- c("Rang", "Pointages", "Pages", "Changements", "Utilisateurs")
Maintenant on peut accéder à la colonne avec la commande table$Utilisateurs
Cela dit, nous pouvons à ce point faire des statistiques sur les colonnes extraites de manière très simple. Par exemple si on veut calculer la moyenne et l'écart-type de la contribution en termes de pages, il suffit des commandes :
#Moyenne colonne mean(table$Pages) #Écart-type colonne var(table$Pages)
Exemples d'intégration avec fonctionnalités de R
Exportation dans un fichier de texte
Une des action qui peut se révéler utile est l'exportation du contenu extrait dans un fichier de texte ainsi qu'il puisse être analysé à l'aide d'un autre logiciel.
Simple exportation avec la fonction write()
La manière la plus simple pour créer un fichier de texte avec le contenu extrait d'une page est l'utilisation de la fonction de R write() - qui est un "wrapper" de la fonction cat() qu'on verra également dans l'exemple.
Dans cette exemple nous alons exporter dans un ficheir de type .txt le contenu textuel de la page Web scraping. Commençons d'abord pour importer la page et attribuer le contenu d'intérêt à une variable nommée "texte" :
library(rvest) page <- html("http://edutechwiki.unige.ch/fr/Web_scraping") texte <- html_nodes(page, "div#content") %>% html_text()
Nous pouvons obtenir un aperçu de ce que notre fichier de texte contiendra avec la fonction cat() avec comme seul argument la variable qui identifie notre contenu.
cat(texte)
Comme vous pouvez le noter, le formatage de la page n'est pas optimal. Si on analyse le contenu de la variable texte sans utiliser la fonction cat() - c'est-à-dire juste en saisissant le nom de la variable - nous pouvons noter qu'en effet notre contenu présente les caractères de tabulation (\t) et de retourne à la ligne (\n) :
"\n\t\t\t\n\n\t\t\t\t\t\tWeb scraping\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tDe EduTech Wiki\n\t\t\t\t\t\t\t\t\....
Nous pouvons utiliser les expressions régulières pour enlever ces caractères :
- Pour enlever seulement les tabulations :
texte <- gsub("\\t","",texte)
- Pour remplacer les retours à la ligne avec un simple espace :
texte <- gsub("\\n"," ",texte)
Une fois modifié le contenu de notre variable "texte", nous pouvons s'assurer de sa mise en page encore une fois avec la fonction cat(). Si le résultat est tel que nous le souhaitons, on peut enfin le transférer dans un fichier de texte avec la fonction write()
. Cette fonction accepte au moins deux arguments :
- Le contenu du fichier
- Un nom pour identifier le fichier
Il est bien, avant d'utiliser cette fonction, de changer le répertoire de travail de R (à travers le menu File) afin que le fichier créé sera placé directement dans ce dossier. Une fois déterminé le nouveau dossier de travail, nous pouvons exporter le contenu d'intérêt de notre page avec la fonction write()
:
write(texte, "web-scraping.txt")
Veuillez noter que quelques problèmes de formatage du texte peuvent rester, comme le montre une capture d'écran du fichier créé en suivant la procédure décrite dans cet exemple.
Liens
- Documentation de rvest (PDF)
- rvest: easy web scraping with R (Tutoriel base)
- Le projet du paquet sur GitHub (utile en cas de changements/mis à jour)