Chapitre 2 Extraction de données textuelles par web scraping

Principe: récupérer la donnée (notamment textuelle) présente sur des pages web.

Le plus souvent, il s’agit de récupérer des données sur une série de pages qui sont toutes organisées de la même manière (par exemple, données issues de pages de recette sur Marmiton, données issues de petites annonces sur Leboncoin, données correspondant à l’ensemble des équipes ou des joueurs sur un site sportif, etc.)

Utilisation du package rvest hexlogo_rvest

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

  • lire une page html dans R
  • repérer l’élément d’intérêt dans la page (connaissance basique du html, SelectorGadget)
  • parcourir l’arborescence de la page
  • sélectionner un élément de la page
  • récupérer le contenu de l’élément

2.1 Langage html

Le langage html est consitué de balises qui permettent de mettre en forme le contenu statique d’une page web.

Considérons ainsi cette page web, toute simple (ici encapsulée dans ce document):

Le code html correspondant à cette page est le suivant:

<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'>
<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>
</div>
<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>

2.2 Lire une page html

Lire la page html dans R: on obtient

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

2.3 Extraire certains éléments d’une page html

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

rvest::html_nodes(html,"b")
## {xml_nodeset (2)}
## [1] <b> INGREDIENTS</b>
## [2] <b>Commentaires</b>
rvest::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>
rvest::html_nodes(html,".ingredients") %>% 
  rvest::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\n  < ...

2.3.1 Extraire le type de certains éléments

Extraire le type des nodes ou nodesets:

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

2.3.2 Extraire le contenu de certains éléments

Extraire le contenu des nodes ou nodesets:

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

2.3.3 Extraire les attributs de certains éléments

Extraire les attributs des nodes ou nodesets:

rvest::html_nodes(html,"div") %>% 
  rvest::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"

2.4 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 <- xml2::read_html(page, encoding="UTF-8")
texte <- html %>%
  rvest::html_nodes(".comment") %>%
  html_text()
auteur <- html %>%
  rvest::html_nodes(".comment-author") %>%
  html_text()
tib_commentaires <- tibble::tibble(texte,auteur)
tib_commentaires
## # A tibble: 2 × 2
##   texte                                                                   auteur
##   <chr>                                                                   <chr> 
## 1 Et pour une poule sur un mur qui picoterait du pain dur? c'est bien be… Emma,…
## 2 Je vois que vous êtes, telle la petite poule rousse, bien aimable. Ave… Miche…

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 <- xml2::read_html(page, encoding="UTF-8")
  texte <- html %>%
    rvest::html_nodes(".comment") %>%
    html_text()
  auteur <- html %>%
    rvest::html_nodes(".comment-author") %>%
    html_text()
  tib_commentaires <- tibble::tibble(doc=rep(page,length(texte)),
                                     texte,
                                     auteur)
  return(tib_commentaires)
}

extrait_commentaires("data/blog_de_ginette.htm")
## # A tibble: 2 × 3
##   doc                      texte                                          auteur
##   <chr>                    <chr>                                          <chr> 
## 1 data/blog_de_ginette.htm Et pour une poule sur un mur qui picoterait d… Emma,…
## 2 data/blog_de_ginette.htm Je vois que vous êtes, telle la petite poule … Miche…
extrait_commentaires("data/blog_de_jean-marc.htm")
## # A tibble: 6 × 3
##   doc                        texte                                        auteur
##   <chr>                      <chr>                                        <chr> 
## 1 data/blog_de_jean-marc.htm Les thons, avec un t comme crocodile? ou le… "Eddi…
## 2 data/blog_de_jean-marc.htm Pourquoi ces thons ne préféraient-ils pas a… "Yves…
## 3 data/blog_de_jean-marc.htm Tout ça me fait penser au blog de Ginette s… "Robe…
## 4 data/blog_de_jean-marc.htm Je préfère la chanson qui parle de canards … "Edua…
## 5 data/blog_de_jean-marc.htm On ne comprend pas trop cette passion pour … "Lise…
## 6 data/blog_de_jean-marc.htm Et pendant ce temps-là, le roi des thons, a… "Nadi…

2.5 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.

Ici, on va supposer simplement qu’on a 3 pages structurées de la même manière:

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…).

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

list_commentaires <- purrr::map(pages, extrait_commentaires)
list_commentaires
## [[1]]
## # A tibble: 2 × 3
##   doc                      texte                                          auteur
##   <chr>                    <chr>                                          <chr> 
## 1 data/blog_de_ginette.htm Et pour une poule sur un mur qui picoterait d… Emma,…
## 2 data/blog_de_ginette.htm Je vois que vous êtes, telle la petite poule … Miche…
## 
## [[2]]
## # A tibble: 6 × 3
##   doc                        texte                                        auteur
##   <chr>                      <chr>                                        <chr> 
## 1 data/blog_de_jean-marc.htm Les thons, avec un t comme crocodile? ou le… "Eddi…
## 2 data/blog_de_jean-marc.htm Pourquoi ces thons ne préféraient-ils pas a… "Yves…
## 3 data/blog_de_jean-marc.htm Tout ça me fait penser au blog de Ginette s… "Robe…
## 4 data/blog_de_jean-marc.htm Je préfère la chanson qui parle de canards … "Edua…
## 5 data/blog_de_jean-marc.htm On ne comprend pas trop cette passion pour … "Lise…
## 6 data/blog_de_jean-marc.htm Et pendant ce temps-là, le roi des thons, a… "Nadi…
## 
## [[3]]
## # A tibble: 4 × 3
##   doc                      texte                                          auteur
##   <chr>                    <chr>                                          <chr> 
## 1 data/blog_de_norbert.htm A quel moment faut-il claquer du bec, si l'on… Jonas…
## 2 data/blog_de_norbert.htm Norbert, mon petit chat, ça fait bien longtem… Julie…
## 3 data/blog_de_norbert.htm L'ambiance doit être sympa quand les poules d… Micka…
## 4 data/blog_de_norbert.htm Vous devez avoir un grain pour écrire des cho… Vivia…
tibtot_commentaires <- list_commentaires %>%
  dplyr::bind_rows()
tibtot_commentaires
## # A tibble: 12 × 3
##    doc                        texte                                       auteur
##    <chr>                      <chr>                                       <chr> 
##  1 data/blog_de_ginette.htm   Et pour une poule sur un mur qui picoterai… "Emma…
##  2 data/blog_de_ginette.htm   Je vois que vous êtes, telle la petite pou… "Mich…
##  3 data/blog_de_jean-marc.htm Les thons, avec un t comme crocodile? ou l… "Eddi…
##  4 data/blog_de_jean-marc.htm Pourquoi ces thons ne préféraient-ils pas … "Yves…
##  5 data/blog_de_jean-marc.htm Tout ça me fait penser au blog de Ginette … "Robe…
##  6 data/blog_de_jean-marc.htm Je préfère la chanson qui parle de canards… "Edua…
##  7 data/blog_de_jean-marc.htm On ne comprend pas trop cette passion pour… "Lise…
##  8 data/blog_de_jean-marc.htm Et pendant ce temps-là, le roi des thons, … "Nadi…
##  9 data/blog_de_norbert.htm   A quel moment faut-il claquer du bec, si l… "Jona…
## 10 data/blog_de_norbert.htm   Norbert, mon petit chat, ça fait bien long… "Juli…
## 11 data/blog_de_norbert.htm   L'ambiance doit être sympa quand les poule… "Mick…
## 12 data/blog_de_norbert.htm   Vous devez avoir un grain pour écrire des … "Vivi…