Travailler avec des textes:

Partie 1: Collecte de données textuelles par web scraping


L. Vaudor, ISIG, UMR 5600-EVS

Rencontres R 2018

Web scraping

Web scraping

Structure d’un document html

Outil SelectorGadget

Utilisation du package rvest hexlogo_rvest

Pour récolter le contenu textuel d’une page web, il faut être en mesure de:

Langage html

<html>
<style>
h1 {background-color: powderblue;}
.image {margin-left:50%;}
.comment{border-style:solid; background-color:LemonChiffon;}
.comment-author{font-style: italic;}
</style>
<h1> MA VIE A LA FERME </h1>
<div class="ingredients">
  <b> INGREDIENTS</b>
  <ul>
    <li> >1 cochon(s) </li>
    <li> >1 légume(s) </li>
  </ul>
</div>
<div class="right"><div class="image"><img src='images/cochon.png'></div></div>
<p> Je fais de la bouillie pour mes petits cochons.</p>
<p> Pour un cochon, pour deux cochons, pour trois cochons, pour quatre, puis pour cinq, pour six, pour sept, pour huit, pour neuf, boeuf! </p>
<b>Commentaires</b>
<div class="comment">Et pour une poule sur un mur qui picoterait du pain dur?
</div>
<div class="comment-author">Emma, 22 ans, Limoges</div>
<div class="comment">Je vois que vous êtes, telle la petite poule rousse, bien aimable. Avez-vous pu compter sur l'aide du chat et du canard pour semer vos 5 grains de blé?</div>
<div class="comment-author">Michel, 56 ans, Rennes</div>
</html>

Lire une page html

<html>
<style>
h1 {background-color: powderblue;}
.image {margin-left:50%;}
.comment{border-style:solid; background-color:LemonChiffon;}
.comment-author{font-style: italic;}
</style>
<h1> MA VIE A LA FERME </h1>
<div class="ingredients">
  <b> INGREDIENTS</b>
  <ul>
    <li> >1 cochon(s) </li>
    <li> >1 légume(s) </li>
  </ul>
</div>
<div class="right"><div class="image"><img src='images/cochon.png'></div></div>
<p> Je fais de la bouillie pour mes petits cochons.</p>
<p> Pour un cochon, pour deux cochons, pour trois cochons, pour quatre, puis pour cinq, pour six, pour sept, pour huit, pour neuf, boeuf! </p>
<b>Commentaires</b>
<div class="comment">Et pour une poule sur un mur qui picoterait du pain dur?
</div>
<div class="comment-author">Emma, 22 ans, Limoges</div>
<div class="comment">Je vois que vous êtes, telle la petite poule rousse, bien aimable. Avez-vous pu compter sur l'aide du chat et du canard pour semer vos 5 grains de blé?</div>
<div class="comment-author">Michel, 56 ans, Rennes</div>
</html>

Lire la page html dans R: on obtient

library(rvest)
html=read_html("data/blog_de_ginette.htm", encoding="UTF-8")
html
## {xml_document}
## <html>
## [1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset= ...
## [2] <body>\n<h1> MA VIE A LA FERME </h1>\r\n<div class="ingredients">\r\ ...

Extraire certains éléments d’une page html

<html>
<style>
h1 {background-color: powderblue;}
.image {margin-left:50%;}
.comment{border-style:solid; background-color:LemonChiffon;}
.comment-author{font-style: italic;}
</style>
<h1> MA VIE A LA FERME </h1>
<div class="ingredients">
  <b> INGREDIENTS</b>
  <ul>
    <li> >1 cochon(s) </li>
    <li> >1 légume(s) </li>
  </ul>
</div>
<div class="right"><div class="image"><img src='images/cochon.png'></div></div>
<p> Je fais de la bouillie pour mes petits cochons.</p>
<p> Pour un cochon, pour deux cochons, pour trois cochons, pour quatre, puis pour cinq, pour six, pour sept, pour huit, pour neuf, boeuf! </p>
<b>Commentaires</b>
<div class="comment">Et pour une poule sur un mur qui picoterait du pain dur?
</div>
<div class="comment-author">Emma, 22 ans, Limoges</div>
<div class="comment">Je vois que vous êtes, telle la petite poule rousse, bien aimable. Avez-vous pu compter sur l'aide du chat et du canard pour semer vos 5 grains de blé?</div>
<div class="comment-author">Michel, 56 ans, Rennes</div>
</html>

Extraire certains éléments (des “nodes” ou “nodesets”):

html_nodes(html,"b")
## {xml_nodeset (2)}
## [1] <b> INGREDIENTS</b>
## [2] <b>Commentaires</b>
html_nodes(html,".comment-author") 
## {xml_nodeset (2)}
## [1] <div class="comment-author">Emma, 22 ans, Limoges</div>
## [2] <div class="comment-author">Michel, 56 ans, Rennes</div>
html_nodes(html,".ingredients") %>% 
  html_children()
## {xml_nodeset (2)}
## [1] <b> INGREDIENTS</b>
## [2] <ul>\n<li> &gt;1 cochon(s) </li>\r\n    <li> &gt;1 légume(s) </li>\r ...

Extraire le type de certains éléments

<html>
<style>
h1 {background-color: powderblue;}
.image {margin-left:50%;}
.comment{border-style:solid; background-color:LemonChiffon;}
.comment-author{font-style: italic;}
</style>
<h1> MA VIE A LA FERME </h1>
<div class="ingredients">
  <b> INGREDIENTS</b>
  <ul>
    <li> >1 cochon(s) </li>
    <li> >1 légume(s) </li>
  </ul>
</div>
<div class="right"><div class="image"><img src='images/cochon.png'></div></div>
<p> Je fais de la bouillie pour mes petits cochons.</p>
<p> Pour un cochon, pour deux cochons, pour trois cochons, pour quatre, puis pour cinq, pour six, pour sept, pour huit, pour neuf, boeuf! </p>
<b>Commentaires</b>
<div class="comment">Et pour une poule sur un mur qui picoterait du pain dur?
</div>
<div class="comment-author">Emma, 22 ans, Limoges</div>
<div class="comment">Je vois que vous êtes, telle la petite poule rousse, bien aimable. Avez-vous pu compter sur l'aide du chat et du canard pour semer vos 5 grains de blé?</div>
<div class="comment-author">Michel, 56 ans, Rennes</div>
</html>

Extraire le type des nodes ou nodesets:

html_nodes(html,".image") %>% 
  html_name()
## [1] "div"

Extraire le contenu de certains éléments

<html>
<style>
h1 {background-color: powderblue;}
.image {margin-left:50%;}
.comment{border-style:solid; background-color:LemonChiffon;}
.comment-author{font-style: italic;}
</style>
<h1> MA VIE A LA FERME </h1>
<div class="ingredients">
  <b> INGREDIENTS</b>
  <ul>
    <li> >1 cochon(s) </li>
    <li> >1 légume(s) </li>
  </ul>
</div>
<div class="right"><div class="image"><img src='images/cochon.png'></div></div>
<p> Je fais de la bouillie pour mes petits cochons.</p>
<p> Pour un cochon, pour deux cochons, pour trois cochons, pour quatre, puis pour cinq, pour six, pour sept, pour huit, pour neuf, boeuf! </p>
<b>Commentaires</b>
<div class="comment">Et pour une poule sur un mur qui picoterait du pain dur?
</div>
<div class="comment-author">Emma, 22 ans, Limoges</div>
<div class="comment">Je vois que vous êtes, telle la petite poule rousse, bien aimable. Avez-vous pu compter sur l'aide du chat et du canard pour semer vos 5 grains de blé?</div>
<div class="comment-author">Michel, 56 ans, Rennes</div>
</html>

Extraire le contenu des nodes ou nodesets:

html_nodes(html,"b") %>% 
  html_text() 
## [1] " INGREDIENTS" "Commentaires"

Extraire les attributs de certains éléments

<html>
<style>
h1 {background-color: powderblue;}
.image {margin-left:50%;}
.comment{border-style:solid; background-color:LemonChiffon;}
.comment-author{font-style: italic;}
</style>
<h1> MA VIE A LA FERME </h1>
<div class="ingredients">
  <b> INGREDIENTS</b>
  <ul>
    <li> >1 cochon(s) </li>
    <li> >1 légume(s) </li>
  </ul>
</div>
<div class="right"><div class="image"><img src='images/cochon.png'></div></div>
<p> Je fais de la bouillie pour mes petits cochons.</p>
<p> Pour un cochon, pour deux cochons, pour trois cochons, pour quatre, puis pour cinq, pour six, pour sept, pour huit, pour neuf, boeuf! </p>
<b>Commentaires</b>
<div class="comment">Et pour une poule sur un mur qui picoterait du pain dur?
</div>
<div class="comment-author">Emma, 22 ans, Limoges</div>
<div class="comment">Je vois que vous êtes, telle la petite poule rousse, bien aimable. Avez-vous pu compter sur l'aide du chat et du canard pour semer vos 5 grains de blé?</div>
<div class="comment-author">Michel, 56 ans, Rennes</div>
</html>

Extraire les attributs des nodes ou nodesets:

html_nodes(html,"div") %>% 
  html_attrs()
## [[1]]
##         class 
## "ingredients" 
## 
## [[2]]
##   class 
## "right" 
## 
## [[3]]
##   class 
## "image" 
## 
## [[4]]
##     class 
## "comment" 
## 
## [[5]]
##            class 
## "comment-author" 
## 
## [[6]]
##     class 
## "comment" 
## 
## [[7]]
##            class 
## "comment-author"

Passage au format rectangulaire, et mise en fonction

On extrait les données et on les met sous forme de table:

page="data/blog_de_ginette.htm"
html=read_html(page, encoding="UTF-8")
texte=html %>% html_nodes(".comment") %>% html_text()
auteur=html %>% html_nodes(".comment-author") %>% html_text()
tib_commentaires=tibble(texte,auteur)
tib_commentaires
## # A tibble: 2 x 2
##   texte                                                         auteur    
##   <chr>                                                         <chr>     
## 1 Et pour une poule sur un mur qui picoterait du pain dur? c'e~ Emma, 22 ~
## 2 Je vois que vous êtes, telle la petite poule rousse, bien ai~ Michel, 5~

On peut en fait écrire une fonction qui prendrait pour entrée l’url de la page considérée et nous renverrait ce même tableau en sortie:

extrait_commentaires=function(page){
  html=read_html(page, encoding="UTF-8")
  texte=html %>% html_nodes(".comment") %>% html_text()
  auteur=html %>% html_nodes(".comment-author") %>% html_text()
  tib_commentaires=tibble(doc=rep(page,length(texte)),
                          texte,
                          auteur)
  return(tib_commentaires)
}

extrait_commentaires("data/blog_de_ginette.htm")
## # A tibble: 2 x 3
##   doc                      texte                                  auteur  
##   <chr>                    <chr>                                  <chr>   
## 1 data/blog_de_ginette.htm Et pour une poule sur un mur qui pico~ Emma, 2~
## 2 data/blog_de_ginette.htm Je vois que vous êtes, telle la petit~ Michel,~
extrait_commentaires("data/blog_de_jean-marc.htm")
## # A tibble: 6 x 3
##   doc                        texte                            auteur      
##   <chr>                      <chr>                            <chr>       
## 1 data/blog_de_jean-marc.htm Les thons, avec un t comme croc~ "Eddie, 76 ~
## 2 data/blog_de_jean-marc.htm Pourquoi ces thons ne préféraie~ Yves, 40 an~
## 3 data/blog_de_jean-marc.htm Tout ça me fait penser au blog ~ Roberta, 18~
## 4 data/blog_de_jean-marc.htm Je préfère la chanson qui parle~ Eduardo, 29~
## 5 data/blog_de_jean-marc.htm On ne comprend pas trop cette p~ Lise, 35 an~
## 6 data/blog_de_jean-marc.htm Et pendant ce temps-là, le roi ~ Nadia, 43 a~

Itération

Imaginons maintenant qu’on ne traite pas seulement du blog de Ginette, mais aussi de tout un tas d’autres pages structurées de la même façon.

On dispose maintenant de la fonction extrait_commentaires() qui prend pour entrée le nom de la page html, et renvoie en sortie un tableau relatif aux commentaires sur la page. On voudrait appliquer cette fonction, de manière itérative à l’ensemble des pages html qui nous intéressent.

Le package purrr permet d’appliquer une fonction à différents éléments d’une liste ou d’un vecteur, de manière itérative (…il est évidemment possible de réaliser la même opération à l’aide d’une boucle for…).

Itération avec purrr

pages=c("data/blog_de_ginette.htm",
        "data/blog_de_jean-marc.htm",
        "data/blog_de_norbert.htm")

list_commentaires=map(pages, extrait_commentaires)
list_commentaires
## [[1]]
## # A tibble: 2 x 3
##   doc                      texte                                  auteur  
##   <chr>                    <chr>                                  <chr>   
## 1 data/blog_de_ginette.htm Et pour une poule sur un mur qui pico~ Emma, 2~
## 2 data/blog_de_ginette.htm Je vois que vous êtes, telle la petit~ Michel,~
## 
## [[2]]
## # A tibble: 6 x 3
##   doc                        texte                            auteur      
##   <chr>                      <chr>                            <chr>       
## 1 data/blog_de_jean-marc.htm Les thons, avec un t comme croc~ "Eddie, 76 ~
## 2 data/blog_de_jean-marc.htm Pourquoi ces thons ne préféraie~ Yves, 40 an~
## 3 data/blog_de_jean-marc.htm Tout ça me fait penser au blog ~ Roberta, 18~
## 4 data/blog_de_jean-marc.htm Je préfère la chanson qui parle~ Eduardo, 29~
## 5 data/blog_de_jean-marc.htm On ne comprend pas trop cette p~ Lise, 35 an~
## 6 data/blog_de_jean-marc.htm Et pendant ce temps-là, le roi ~ Nadia, 43 a~
## 
## [[3]]
## # A tibble: 4 x 3
##   doc                      texte                          auteur          
##   <chr>                    <chr>                          <chr>           
## 1 data/blog_de_norbert.htm "A quel moment faut-il claque~ Jonas, 37 ans, ~
## 2 data/blog_de_norbert.htm Norbert, mon petit chat, ça f~ Julie, 34 ans, ~
## 3 data/blog_de_norbert.htm L'ambiance doit être sympa qu~ Mickaël, 23 ans~
## 4 data/blog_de_norbert.htm Vous devez avoir un grain pou~ Viviane, 58 ans~
tibtot_commentaires <- list_commentaires %>%
  bind_rows()
tibtot_commentaires
## # A tibble: 12 x 3
##    doc                        texte                             auteur    
##    <chr>                      <chr>                             <chr>     
##  1 data/blog_de_ginette.htm   Et pour une poule sur un mur qui~ Emma, 22 ~
##  2 data/blog_de_ginette.htm   Je vois que vous êtes, telle la ~ Michel, 5~
##  3 data/blog_de_jean-marc.htm Les thons, avec un t comme croco~ "Eddie, 7~
##  4 data/blog_de_jean-marc.htm Pourquoi ces thons ne préféraien~ Yves, 40 ~
##  5 data/blog_de_jean-marc.htm Tout ça me fait penser au blog d~ Roberta, ~
##  6 data/blog_de_jean-marc.htm Je préfère la chanson qui parle ~ Eduardo, ~
##  7 data/blog_de_jean-marc.htm On ne comprend pas trop cette pa~ Lise, 35 ~
##  8 data/blog_de_jean-marc.htm Et pendant ce temps-là, le roi d~ Nadia, 43~
##  9 data/blog_de_norbert.htm   "A quel moment faut-il claquer d~ Jonas, 37~
## 10 data/blog_de_norbert.htm   Norbert, mon petit chat, ça fait~ Julie, 34~
## 11 data/blog_de_norbert.htm   L'ambiance doit être sympa quand~ Mickaël, ~
## 12 data/blog_de_norbert.htm   Vous devez avoir un grain pour é~ Viviane, ~