[rvest] Le Web Scraping simplement
Références
Github: https://github.com/tidyverse/rvest
Functions: https://rvest.tidyverse.org/reference/index.html
Tuto 1: https://dcl-wrangle.stanford.edu/rvest.html
Tuto 2: https://steviep42.github.io/webscraping/book/
Tuto 3:https://awesomeopensource.com/project/yusuzech/r-web-scraping-cheat-sheet
Introduction
La librairie rvest permet de collecter des données en ligne, donc faire ce qu’on appelle du web scraping. L’avantage de cette librairie est qu’elle est extrêmement facile à utiliser et assez intuitive.
Librairie
library(rvest)
Tableaux
Nous allons télécharger les données du tableau wikipédia “List of countries by average wage” du lien https://en.wikipedia.org/wiki/List_of_countries_by_average_wage. Nous divisons ce travail en plusieurs étapes, même s’il peut être fait en une fonction grâce à la fonction “%>%”.
Note: Tout le long des étapes, nous n’avons créé aucune nouvelle variable, c’est l’avantage de la fonction %>%. Elle nous permet de tester des transformations sans avoir à modifier l’objet de base ou à devoir en créer. Nous aurions pu donc tout faire en une fois. Mais nous allons présenter la procédure en étape pour ce tutoriel.
Étape 1
Nous chargeons l’url avec la fonction read_html():
url <- "https://en.wikipedia.org/wiki/List_of_countries_by_average_wage"
page <- read_html(url)
page
## {html_document}
## <html class="client-nojs" lang="en" dir="ltr">
## [1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8 ...
## [2] <body class="mediawiki ltr sitedir-ltr mw-hide-empty-elt ns-0 ns-subject ...
Nous voyons une partie du code html.
Étape 2
Nous utilisons la fonction %>% pour passer le résultat de gauche dans la fonction de droite (en première position). Avec ça nous allons retrouver à l’aide du code css de la page web la liste des tableaux [plus d’information dans les tutos]. Nous utilisons donc la fonction html_nodes() pour cherche un “table” (généralement utiliser pour les tableaux en css:
page %>%
html_nodes("table")
## {xml_nodeset (5)}
## [1] <table class="wikitable sortable static-row-numbers plainrowheaders srn-w ...
## [2] <table class="wikitable sortable static-row-numbers plainrowheaders srn-w ...
## [3] <table class="nowraplinks hlist mw-collapsible autocollapse navbox-inner" ...
## [4] <table class="nowraplinks navbox-subgroup" style="border-spacing:0"><tbod ...
## [5] <table class="nowraplinks mw-collapsible autocollapse navbox-inner" style ...
Étape 3
Nous savons que les tableaux wikipédia sont des “wikitable sortable”, nous pouvons donc prendre le tableau 2, 3, 4 ou 5. Dans notre exemple, nous prenons le 2ème tableau. Nous allons donc prendre le 2ème élément en utilisant .[[2]]. Ici le “.” signifie que nous reprenons l’élément de gauche en tant qu’objet et les “[[]]” signifie que nous prenons un élément d’une liste:
page %>%
html_nodes("table") %>%
.[[2]]
## {html_node}
## <table class="wikitable sortable static-row-numbers plainrowheaders srn-white-background" border="1" style="text-align:right;">
## [1] <tbody>\n<tr class="static-row-header" style="text-align:center;vertical- ...
Étape 4
Maintenant que nous avons retrouvé un tableau, nous pouvons le transformer en un tableau lisible pour R en utilisant la fonction html_table(). Ce tableau est directement utilisable:
page %>%
html_nodes("table") %>%
.[[2]] %>%
html_table() -> tab1
tab1
## # A tibble: 13 x 2
## Country `Average monthly wage (PPP)`
## <chr> <chr>
## 1 Luxembourg * $5,265
## 2 Switzerland * $5,117
## 3 United States * $5,046
## 4 Norway * $4,317
## 5 Denmark * $4,288
## 6 Austria * $4,185
## 7 Canada * $3,992
## 8 Ireland * $3,944
## 9 France * $3,682
## 10 United Kingdom * $3,653
## 11 Sweden * $3,551
## 12 Finland * $3,549
## 13 Italy * $3,010
Note: Le tableau n’est pas encore vraiment utilisable puisqu’il faut encore le nettoyer un peu, mais nous avons un tableau de donnée au bon format.
Comme nous pouvons le voir, télécharger un tableau se fait très rapidement et demande peu d’effort. Si nous avions tout fait d’un coup, ça nous aurait pris moins de 30 secondes.
Web page
Dans cet exemple, nous allons présenter comment obtenir le contenu d’une page du site web du journal 20 minutes (Suisse francophone).
Afin de pouvoir sélectionner des éléments dans une page web, on utilise des “sélecteurs css”. L’écriture n’est pas si difficile à apprendre, mais il demande quand même un certains temps avant d’être utlisé efficacement. Vous pouvez trouver un petit tutoriel ici:
https://www.devenir-webmaster.com/V2/TUTO/CHAPITRE/HTML-CSS/20-les-selecteurs-css/
Pour ce tutoriel, il existe une méthode pour se simplifier la vie sur firefox ou chrome: des aides au scraping. Ces outils permettent de sélectionner les éléments css d’une page web et de nous retourner le sélecteur css.
Pour Chrome (selector gadget): https://chrome.google.com/webstore/detail/selectorgadget/mhjhnkcfbdhnjickkkdbjoemdmbfginb
Pour Firefox (ScrapeMate Beta): https://addons.mozilla.org/en-US/firefox/addon/scrapemate/
Note: Dans ce tutoriel, j’utilise firefox et donc ScrapeMate Beta. Mais ça ne change strictement rien au processus. De plus l’utilisation de ces sélecteurs ne garanti pas une précision dans la sélection ni le meilleurs moyen de sélectionner un éléments (il existe plusieurs méthodes). Même si utiliser ces deux addins marche la plupart du temps, il y a des cas où ils ne fonctionnent pas. Donc, si vous compter continuer dans le web scraping, apprend un peu de css est essentiel.
Étape 1:
Nous chargeons l’url avec la fonction read_html():
url <- "https://www.20min.ch/fr/story/il-decouvre-que-sa-maison-a-disparu-a-la-television-799776931140#Echobox=1609449835"
page <- read_html(url)
page
## {html_document}
## <html lang="fr">
## [1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8 ...
## [2] <body>\n<noscript><iframe src="https://www.googletagmanager.com/ns.html?i ...
Nous voyons une partie du code html.
Étape 2:
Avec l’un des outils présentés plus tôt, il faut choisir les éléments qui nous intéressent. Par exemple, nous prenons le sous-titre de la page:
page %>%
html_nodes("h2")
## {xml_nodeset (1)}
## [1] <h2>Les habitants de Gjerdrum étaient abasourdis, jeudi matin, au lendema ...
Nous obtenons un élément html. Mais il est possible de sélectionner une liste d’éléments dès le moment où tous ces éléments ont le même sélecteur css.
Note: dans notre exemple nous avons simplement h2 comme sélecteur css, mais la plus part du temps ils sont plus long. Ce qui fait que celui-ci soit cours vient d’une convention. Effectivement, les titres commencent toujours avec un “h” suivit d’un numéro. “h1” est le plus rand titre “h2” est deuxième plus grand titre, “h3” est le suivant, etc. Même si vous ne souhaitez pas apprendre les sélecteurs css, ceux-ci sont facile à comprendre.
Étape 3:
Nous utilisons la fonction html_text2() pour transformer les éléments en un texte lisible:
Note: la fonction html_text() fait le même travail, mais ne retire pas les espaces “en trop” et peut donc donner des formats de texte peu adaptés.
page %>%
html_nodes("h2") %>%
html_text2()
## [1] "Les habitants de Gjerdrum étaient abasourdis, jeudi matin, au lendemain de la coulée qui a avalé une partie de cette petite ville au nord-est d’Oslo. Dix personnes manquent toujours à l’appel."
Nous avons donc réussi à sélectionner le contenu qui nous intéressait. Mais nous pouvons faire exactement la même chose avec des contenus partageant également le même sélecteur css. Par exemple les paragraphes des textes.
Autre exemple paragraphes d’un texte
Voici le code pour sélectionné les paragraphes d’un texte:
page %>%
html_nodes("p") %>%
html_text2()
## [1] "Les secours étaient toujours à la recherche d’éventuels survivants, jeudi, au lendemain de l’énorme glissement de terrain qui a englouti de nombreuses maisons à Ask, dans la municipalité de Gjerdrum, une ville de 5000 habitants au nord-est de la capitale Oslo."
## [2] "Les forces de l’ordre ont passé la journée de mercredi à tenter de prendre contact avec les personnes logeant dans la quinzaine de propriétés touchées par la catastrophe, pour savoir si elles étaient saines et sauves. Une partie d’entre elles étaient en effet partie en vacances en cette période de fêtes. C’était le cas de Finn Nilsen, qui a apporté son témoignage poignant au «Aftenposten». Il se trouvait dans sa maison de campagne dans le district de Valdres, un peu plus au nord, lorsqu’il a reçu un coup de téléphone de la police, vers 4h30 du matin. Au bout du fil, un officier lui a appris la terrible nouvelle."
## [3] "«Finalement, je me suis assis devant la télévision avec une tasse de café. Soudain, j'ai vu que ma maison avait disparu. C'était un choc», raconte le septuagénaire au quotidien norvégien. «J’étais content que nous ne soyons pas chez nous», se souvient-il, avant de s’inquiéter du sort réservé à leurs voisins. «J'ai essayé de les appeler et leur ai envoyé un SMS. Mais je n'ai pas encore reçu de réponse. J'espère qu'ils vont bien»."
## [4] "En fin de journée, jeudi, dix personnes manquaient toujours à l’appel, dont des enfants. «Il est important de préciser que nous recherchons des survivants», a dit lors d’une conférence de presse le responsable de l’opération de sauvetage Roger Pettersen, sans préciser leur identité. Dix personnes ont également été blessées lors du glissement de terrain, dont une grièvement atteinte qui a été transférée dans un hôpital de la capitale."
Cette fois-ci, nous obtenons un vecteur de caractère puisque tous les paragraphes ont le même sélecteur.
Note: Vous voyez ici que pour le paragraphe, il suffit d’écrire “p” qui est aussi une convention
Autre astuces
Nous pouvons combiner tout ce que nous savons, pour sélectionner tout le contenu d’un article. Il suffit d’ajouter des virgules entre les éléments sélectionnés.
page %>%
html_nodes("h1, h2, p") %>%
html_text2()
## [1] "Glissement de terrain en Norvège: Il découvre que sa maison a disparu en regardant la télévision"
## [2] "Les habitants de Gjerdrum étaient abasourdis, jeudi matin, au lendemain de la coulée qui a avalé une partie de cette petite ville au nord-est d’Oslo. Dix personnes manquent toujours à l’appel."
## [3] "Les secours étaient toujours à la recherche d’éventuels survivants, jeudi, au lendemain de l’énorme glissement de terrain qui a englouti de nombreuses maisons à Ask, dans la municipalité de Gjerdrum, une ville de 5000 habitants au nord-est de la capitale Oslo."
## [4] "Les forces de l’ordre ont passé la journée de mercredi à tenter de prendre contact avec les personnes logeant dans la quinzaine de propriétés touchées par la catastrophe, pour savoir si elles étaient saines et sauves. Une partie d’entre elles étaient en effet partie en vacances en cette période de fêtes. C’était le cas de Finn Nilsen, qui a apporté son témoignage poignant au «Aftenposten». Il se trouvait dans sa maison de campagne dans le district de Valdres, un peu plus au nord, lorsqu’il a reçu un coup de téléphone de la police, vers 4h30 du matin. Au bout du fil, un officier lui a appris la terrible nouvelle."
## [5] "«Finalement, je me suis assis devant la télévision avec une tasse de café. Soudain, j'ai vu que ma maison avait disparu. C'était un choc», raconte le septuagénaire au quotidien norvégien. «J’étais content que nous ne soyons pas chez nous», se souvient-il, avant de s’inquiéter du sort réservé à leurs voisins. «J'ai essayé de les appeler et leur ai envoyé un SMS. Mais je n'ai pas encore reçu de réponse. J'espère qu'ils vont bien»."
## [6] "En fin de journée, jeudi, dix personnes manquaient toujours à l’appel, dont des enfants. «Il est important de préciser que nous recherchons des survivants», a dit lors d’une conférence de presse le responsable de l’opération de sauvetage Roger Pettersen, sans préciser leur identité. Dix personnes ont également été blessées lors du glissement de terrain, dont une grièvement atteinte qui a été transférée dans un hôpital de la capitale."
Voilà le travail, ce n’est pas si compliqué.
Étape 4:
L’avantage d’utiliser le web scraping, c’est que lorsque le travail est fait pour une page, il est reproductible sur des pages similaires. Il suffit donc d’autres liens pour obtenir le même résultats sur d’autres pages du journal:
page2 <- read_html("https://www.20min.ch/fr/story/la-nuit-la-plus-froide-de-lannee-au-sud-des-alpes-et-en-engadine-224123400441#Echobox=1609442466")
page2 %>%
html_nodes("h1, h2, p") %>%
html_text2()
## [1] "Météo: La nuit la plus froide de l’année au sud des Alpes et en Engadine"
## [2] "Il a fait un froid de canard durant la nuit du 30 au 31 décembre dans plusieurs régions de Suisse avec notamment une chute du mercure jusqu’à -24 degrés à Samedan, dans les Grisons."
## [3] "Cela fait huit ans qu’il n’avait plus fait aussi froid dans la plaine de Magadino au Tessin, où le thermomètre affichait un sympathique -11 degrés dans la nuit du 30 au 31 décembre."
## [4] "La nuit la plus froide de l’année a été enregistrée durant la nuit du 30 au 31 décembre au sud des Alpes et en Engadine. La température a plongé à -24 degrés à Samedan (GR) et à -11 dans la plaine de Magadino (TI)."
## [5] "Le mercure n’était plus descendu aussi bas depuis huit ans dans la plaine de Magadino, a indiqué jeudi MeteoSuisse sur Twitter. Les -24 degrés mesurés à Samedan constituent eux un record pour 2020 au niveau suisse. La température la plus élevée, 36,5 degrés, avait été mesurée à Bâle."
## [6] "Les précipitations ont, elles, été très inégalement réparties cette année selon les régions, note MeteoSuisse. Elles ont été les plus faibles en Valais central (545 millimètres) et les plus abondantes dans le Val Maggia, au Tessin (1500 à 2000 millimètres)."
## [7] "S’agissant des durées d’ensoleillement, les différences entre le nord et le sud ont été moins marquées que d’habitude. Les valeurs les plus hautes – plus de 2300 heures – ont été atteintes dans le Tessin central et méridional et sur le versant sud des Alpes valaisannes, les plus basses dans le Sernftal glaronais (1280 heures)."
## [8] "Les vents les plus violents – 200 km/h – ont soufflé sur le Gütsch, au-dessus d’Andermatt (UR). En plaine, ils ont atteint leur plus grande vitesse à Thoune avec 122 km/h."
## [9] "Globalement, l’année 2020 a été presque aussi chaude que 2018, année record, avait déjà indiqué MeteoSuise le 21 décembre. Un hiver d’une douceur record a été suivi du troisième printemps le plus chaud et d’un été qui a connu deux vagues de canicule modérées. Le début de l’année a en outre été marqué par une sécheresse persistante."
## [10] "Dix mois ont été plus chauds que la norme des années 1981 à 2010. Dans la plupart des régions, les températures ont été de 1,4 à 1,6 degré au-dessus de cette norme."
Utilisé avec des boucles (ou itération avec du mapping), il est possible de traiter automatiquement un grand nombre de page sans trop de problèmes.
**Note: Dans ce cas très précis où nous avons utilisé des sélecteurs conventionnel (h1, h2 et p) qui sont universelle, cette fonction pourrait en principe s’appliquer sur n’importe quelle page web, même s’il y a des limites, mais nous en parlerons en conclusion.
Conclusion
Cette méthodes n’est pas très difficile à appliquée et elle est très rapide pour récolté des données sur des pages web statiques. Mais ce n’est pas une solution miracle, car il existe de nombreux obstacles, en voici quelques-uns:
- Javascript cachant une partie des données
- Sites web bloquant les bots (parce que ce que nous faisons c’est littéralement un robot qui vient collecter des données)
- Site web bloquant l’accès au code source html
- Site web nécessitant une connexion (il est possible de passer à travers avec rvest, mais souvent ce genre de site est élaboré pour bloquer les bots avec des tests)
Dans tous les cas, si votre but est la collecte de textes brut ou de données librement accessible en ligne (sans restrictions ouverte ou implicite), la méthode que nous avons présenté devrait fonctionner la plupart du temps.
Noté aussi qu’il subsite un flou juridique en ce qui concerne la légalité du web scraping. De manière générale, si l’utilisation se fait à titre personnel sans but commerciale, ça devrait être bon la plupart du temps aussi.
Autre chose également, ce tuto est une introduction et ne traite pas des thèmes avancé. Mais j’en ferai surement un autre soit sur la chaîne youtube WeData, soit ici pour montrer d’autres utilisations, notamment la collecte de masse. En ce qui concerne la collecte de masse, je vous recommande en avance d’utiliser dans vos itérations la fonction Sys.time(3) qui va arrêter votre code pendant 3 secondes. Sinon R vas faire plusieurs requêtes en moins d’une seconde et c’est mauvais pour le serveur qui héberge le site. Stoper le code dans les boucles est nécessaire pour éviter deux cas malheureux:
- Vous faire banir du site par votre adresse IP. Donc vous n’aurez plus accès au site pendant un moment (ou définitivement) et vous ne pourrez donc plus collecter des données.
- (plus dramatique) vous pouvez faire crasher le site, ce qui d’une part pose problème aux propriétaires du site et à tous les utilisateurs. Il est même possible qu’il y ait des processus légaux.
Dans tous les cas, le web scraping est un super outil qui permet d’obtenir des données en ligne et il serait dommage de s’en priver.