Une grande partie des données que l’on trouve sur Internet n’y sont pas présentées sous la forme d’un jeu de données : dans de très nombreux cas de figure, ces données peuvent être présentées, par exemple, sous la forme d’un tableau, ou d’une série de pages Web. Ce chapitre explique comment récupérer ces données, de manière à en permettre la manipulation dans R.

La récupération de données numériques, que l’on va illustrer à partir de trois sites Internet consacrés aux théories du complot circulant en France, est plus connue sous le nom de scraping ou de Web scraping. Il s’agit d’un ensemble de techniques, dont on présentera ici que les principaux aspects, appliqués à un cas d’étude précis.

Les sources de l’exemple

Ce chapitre s’intéresse à trois sites Internet consacrés aux théories du complot et à leurs diffuseurs, les « conspirationnistes ». Le site de Rudy Reichstadt, Conspiracy Watch, qui va devenir notre principale source de données, propose une définition de ce terme. La seconde source utilisée, le site Confusionnisme d’Ornella Guyet, utilise une définition différente, qui recoupe largement la première du point de vue des individus et des groupes qu’elle identifie. Notre troisième source, le site anonyme Conspis hors de nos vi[ll]es, ne propose pas de définition précise pour sa part, mais fournit quelques éléments supplémentaires de description.

Les termes de « théorie du complot » et de « conspirationnisme » étant difficiles à saisir en seulement quelques phrases, on renverra le lecteur à la note publiée par Rudy Reichstadt pour l’Observatoire des radicalités politiques de la Fondation Jean Jaurès. Cette note donne un bon aperçu des différents groupes impliqués dans la diffusion de ces « théories » en France, que l’on retrouve dans une cartographie en réseau de leurs sites Internet, réalisée par Joël Gombin en juillet 2014. Les données récupérées dans ce chapitre recoupent les informations fournies dans ces deux sources.

Les blogs

Les sites Internet auxquels on s’intéresse sont tous les trois publiés sous la forme de blogs. Ce détail est important, car pour en récupérer les informations publiées par ces sites, il va falloir comprendre la structure sous-jacente de ces blogs, c’est-à-dire la syntaxe HTML de leurs pages. Les sites Confusionnisme et Conspis hors de nos vi[ll]es sont les plus simples à comprendre. En effet, ils sont tous les deux publiés grâce au moteur de blog WordPress, qui permet de parcourir les différentes pages d’un blog en rajoutant le suffixe /page/n à l’adresse-racine du site, de la manière suivante :

http://confusionnisme.info/page/1
http://confusionnisme.info/page/2
http://confusionnisme.info/page/3
...
http://conspishorsdenosvies.noblogs.org/page/1
http://conspishorsdenosvies.noblogs.org/page/2
http://conspishorsdenosvies.noblogs.org/page/3
...

En navigant ces liens, on s’aperçoit que les deux sites en question n’ont publié qu’un nombre limité de billets : il n’y a que 4 pages de billets sur le premier, et 5 pages sur le second. Le site Conspiracy Watch est, en comparaison, beaucoup plus riche : en effet, comme l’indique le compteur visible en bas de chaque page, le site compte 60 pages de billets, auxquelles le lecteur peut accéder en utilisant un suffixe différent, lié à l’utilisation d’un moteur de blog différent de WordPress. Dans ce cas de figure, le suffixe ne renvoie pas à une « page », mais à un « compteur » de billets, où le dernier billet publié est numéroté 0 :

http://www.conspiracywatch.info/?start=0
http://www.conspiracywatch.info/?start=20
http://www.conspiracywatch.info/?start=40
...

Suivant ce schéma de pagination, qui commence à 0 puis augmente de 20 billets par page, la page 60 va correspondre au suffixe ?start=1180. On connaît donc désormais le nombres de pages à récupérer sur chacun des blogs étudiés, en notant bien que c’est le site Conspiracy Watch qui va fournir la très grande majorité des pages. On aurait pu « découvrir » ces informations de manière programmatique, en écrivant un peu de code pour ce faire, mais un repérage manuel du nombre de pages sur chacun des blogs est ici tout aussi rapide, même s’il faudra le mettre à jour lorsque les blogs auront publié de nouvelles pages de billets.

Les mots-clés

Sur chacun des blogs auxquels on s’intéresse, on trouve des billets très détaillés sur tel ou tel groupe diffusant une ou plusieurs « théories du complot ». Sur les blogs Confusionnisme et Conspiracy Watch, on trouve par exemple deux articles sur un groupuscule ayant appelé à un « Mouvement du 14 juillet » 2015. Sur le blog Conspis hors de nos vi[ll]es, qui a cessé de publier en mars 2012, le dernier billet évoque un autre exemple de ces groupes. Ces différents billets sont tous soigneusement catégorisés par de très nombreux mots-clés, qui incluent notamment les noms propres des individus cités ; ce billet, par exemple, se termine par les mots-clés suivants :

11 septembre, antiaméricanisme, apollo 11, etat islamique, etats-unis, laurent louis, lune

Ces mots-clés sont destinés à permettre aux lecteurs de naviguer plus facilement à travers les différents billets du site, ainsi qu’à faciliter l’indexation du blog par les moteurs de recherche. Ce que l’on se propose de faire ici consiste à récupérer, pour chacun des billets publiés par chacun des trois blogs, l’ensemble de ces mots-clés, ainsi que les titres, les dates de publication et les adresses Internet – les URL – des billets auxquels ils correspondent. Ces données permettront par la suite de construire un réseau de co-occcurrences de ces mots-clés, c’est-à-dire une représentation graphique des associations entre ces mots-clés sur la base des trois sources utilisées.

Récupération des données

Pour récupérer les données des trois blogs, on va commencer par charger quelques extensions utilisées dans plusieurs autres chapitres : l’extension dplyr va servir à manipuler les données au fur et à mesure de leur récupération ; l’extension readr va servir à sauvegarder le résultat final au format CSV ; l’extension lubridate va servir à convertir les dates de publication des billets vers un même format générique ; et l’extension stringr va servir à nettoyer le texte récupéré.

library(dplyr)
library(readr)
library(lubridate)
library(stringr)

Chargeons à présent l’extension rvest, qui va fournir les fonctions essentielles à la récupération des données de chacun des blogs. Comme l’explique l’auteur de l’extension, celle-ci est inspirée d’extensions équivalentes disponibles pour le langage Python. Sa fonctionnalité principale est de permettre à l’utilisateur, à l’aide d’une syntaxe simplifiée ou à l’aide de la syntaxe XPath, de sélectionner les différents éléments d’une page Web, à partir des balises HTML et CSS de cette page.1

library(rvest)

Récupération d’éléments HTML

Commençons par le blog Confusionnisme. Un rapide coup d’oeil au code source de sa page d’accueil montre que les billets publiés sur ce blog se trouvent dans une suite de structures : l’une d’entre elles, <div id="content">, qui se lit « diviseur à identifiant content », contient tous les billets, et à l’intérieur de cette structure, tous les titres de billets se trouvent dans un hyperlien <a> à l’intérieur d’une balise <h1 class="entry-title">, qui se lit « titre de niveau 1 de classe « entry-title ».

Récupérons désormais le code source de la page d’accueil du blog grâce à la fonction html. Une fois exécuté le code ci-dessous, affichez le contenu de l’objet h pour réaliser que vous venez de récupérer le code source HTML de la page d’accueil du blog :

h <- html("http://confusionnisme.info/")

Sélectionnons, à présent, toutes les balises correspondant aux identifiants notés ci-dessus, grâce à la fonction html_nodes. Pour gagner de la place, on n’affichera ici que les deux premiers titres de billets que renvoie cette dernière fonction :

html_nodes(h, "#content .entry-title a") %>%
  head(2)

Le code ci-dessus signifie : « sélectionner tous les hyperliens <a>, à l’intérieur des éléments identifiés par la classe entry-title, à l’intérieur de l’élément portant l’identifiant content ». Comme l’on peut le voir, les identifiants des éléments HTML (id), qui sont censés être uniques, sont codés par un dièse (#), et les classes de ces mêmes éléments (class), qui peuvent se répéter, sont codées par un point (.). Ces codes sont identiques à ceux que l’on utilise pour attribuer des styles particuliers à ces éléments en langage CSS.

Les éléments HTML que l’on a sélectionnés contiennent aussi bien des balises HTML (telles que <a> et <i>) que du texte. Pour ne sélectionner que le texte, on rajoute la fonction html_text au code montré ci-dessus. Toujours par économie de place, on ne montre que les deux premiers résultats de ce nouvel enchaînement de fonctions :

html_nodes(h, "#content .entry-title a") %>%
  html_text %>%
  head(2)

Voilà qui permet donc de récupérer les titres des billets ! Pour récupérer les hyperliens vers ces billets, rien de plus simple : au lieu de récupérer le texte des titres, il suffit de demander à récupérer l’attribut href de chaque lien, en utilisant la fonction html_attr. On obtient cette fois-ci les hyperliens complets vers chaque billet :

html_nodes(h, "#content .entry-title a") %>%
  html_attr("href") %>%
  head(2)

Présentons encore un exemple de sélection d’éléments sur la page d’accueil de ce blog, cette fois-ci en montrant l’intégralité des éléments récupérés, car ils prennent peu de place à l’écran. Ici, on récupère les dates de publications des billets, qui se trouvent, toujours selon le code source de la page, dans une balise <time> qui se trouve dans une balise <header class="entry-meta">. Le code que l’on donne à la fonction html_nodes est donc :

html_nodes(h, "#content header.entry-header time") %>%
  html_text

On voit bien ici que les deux premières dates sont identiques aux dates qui figurent dans les hyperliens des deux premiers billets, tels que vus plus haut.

html_nodes(h, "#content header.entry-header time") %>%
  html_text

Terminons, enfin, par un exemple plus compliqué. Comme on l’a déjà écrit, chacun des billets du blog est accompagné de plusieurs mots-clés. Après inspection du code source, on voit que ces mots-clés se trouvent regroupés dans un élément appelé <span class="tag-links">. Visionnons les deux premiers éléments en question, toujours à l’aide de la même syntaxe de sélection :

html_nodes(h, ".tag-links") %>%
    head(2)

Pour pouvoir stocker tous les mots-clés d’un billet sur la même ligne d’un fichier CSV, qui contiendra aussi le titre du billet, son hyperlien et sa date de publication, il va falloir regroupr ces mots-clés. On va donc, à l’intérieur de chacun des éléments de la liste d’éléments <span>, extraire le texte des mots-clés, contenus dans les éléments <a>, et les “coller” ensemble grâce à la fonction paste0 et à son argument collapse :

html_nodes(h, ".tag-links") %>%
  sapply(function(x) html_nodes(x, "a") %>%
           html_text %>%
           paste0(collapse = ";")) %>%
  head(2)

L’astuce se trouve ici dans l’utilisation de la fonction sapply, qui permet de travailler sur chacun des éléments <span class="tag-links"> de manière séparée. L’utilisation de la fonction pipe %>% a par ailleurs permis de travailler de manière cumulative, par essai-erreur, tout en produisant un code final plutôt lisible.

Récupération de plusieurs pages

On sait désormais comment récupérer les informations que l’on veut collecter. Le blog Confusionnisme n’ayant que 4 pages, il va être très simple de les récupérer à l’aide d’une petite boucle qui récupère chaque page, en extrait les données inspectées ci-dessus, et les rajoute à un jeu de données initialement vide, nommé d1, grâce à la fonction rbind :

d1 = data_frame()

for(i in 1:4) {
  
  h = html(paste0("http://confusionnisme.info/page/", i))
  
  d1 = rbind(d1, data_frame(
    url = html_nodes(h, "#content .entry-title a") %>% html_attr("href"),
    title = html_nodes(h, "#content .entry-title a") %>% html_text,
    date = html_nodes(h, "#content header.entry-header time") %>% html_text,
    tags = html_nodes(h, ".tag-links") %>%
      sapply(function(x) html_nodes(x, "a") %>%
               html_text %>%
               paste0(collapse = ";"))
  ))
  
}

À la date de publication de ce blog, ce petit bout de code récupère les 36 billets étalés sur les 4 pages du site Confusionnisme. Comme le montre l’inspection du résultat, le jeu de données que l’on vient de constituer contient l’adresse, le titre, la date de publication et les mots-clés de ces billets :

View(d1)

Il ne reste plus qu’à convertir la variable date vers le format générique yyyy-mm-dd que propose R à travers la fonction as.Date. Pour convertir la variable, on utilise l’extension lubridate, qui peut facilement interpréter les mois écrits en langue française grâce à l’argument locale spécifié ci-dessous :

d1$date = parse_date_time(d1$date, "%d %m %Y", locale = "fr_FR") %>%
  as.Date

Utilisation de la syntaxe XPath

L’exemple que l’on vient de voir permet de récupérer les données du blog Confusionnisme. Il se trouve que ce code fonctionne presque aussi bien pour le blog Conspis hors de nos vi[ll]es : en effet, celui-ci utilisant aussi le moteur de blog WordPress, la structure de ses pages est quasiment identique à celle que l’on vient de voir. Voici le code complet pour récupérer les 5 pages de ce blog :

d2 = data_frame()

for(i in 1:5) {
  
  h = html(paste0("http://conspishorsdenosvies.noblogs.org/page/", i))
  
  d2 = rbind(d2, data_frame(
    url = html_nodes(h, "#content .entry-title a") %>% html_attr("href"),
    title = html_nodes(h, "#content .entry-title a") %>% html_text,
    date = html_nodes(h, "#content .entry-date") %>% html_text,
    tags = html_nodes(h, ".tag-links") %>%
      sapply(function(x) html_nodes(x, xpath = "a[@rel='tag']") %>%
               html_text %>%
               paste0(collapse = ";"))
  ))
  
}

d2$date = parse_date_time(d2$date, "%d/%m/%Y") %>% as.Date

On remarquera que plusieurs petites choses ont changé : par exemple, sur le blog Conspis hors de nos vi[ll]es, les dates sont affichées dans un format dd/mm/yyyy qui ne nécessite pas de conversion, car chaque élément de la date est donné sous la forme d’un chiffre. On remarquera aussi que l’emplacement de la date a changé, car le gabarit graphique du blog diffère de celui de Confusionnisme et place cette information dans un élément différent du code source de la page.

Le changement le plus important ici concerne l’utilisation de la syntaxe XPath : en effet, pour récupérer les mots-clés, il nous a fallu limiter ceux-ci à ceux se trouvant dans des hyperliens (<a>) dont la propriété rel est égale à tag, pour ne pas également récupérer les mots-clés correspondant à des catégories du blog. La syntaxe XPath est un peu plus alambiquée : ici, c’est l’expression a[@rel='tag'] qui accomplit l’opération souhaitée, à condition d’être bien passée à l’argument xpath de la fonction html_nodes.

Combinaison des résultats

Il nous reste un blog à couvrir : Conspiracy Watch. Le code pour celui-ci diffère assez fondamentalement des blogs précédents du point de vue de la syntaxe de ses pages, qui utilisent un moteur de blog complètement différent de WordPress. Après lecture de la source, on arrive au code suivant, qui récupère les mêmes variables que récupérées pour les deux autres blogs :

d3 = data_frame()

for(i in seq(0, 1180, 20)) {
  
  h = html(paste0("http://www.conspiracywatch.info/?start=", i))
  
  d3 = rbind(d3, data_frame(
    url = html_nodes(h, "#mod_1260437 .titre a") %>% html_attr("href"),
    title = html_nodes(h, "#mod_1260437 .titre") %>% html_text %>% str_trim,
    date = html_nodes(h, "#mod_1260437 .cel_pied .date") %>% html_text,
    tags = html_nodes(h, "#mod_1260437 .cel_pied") %>%
      sapply(function(x) html_nodes(x, ".objet-tag a") %>%
               html_text %>%
               paste0(collapse = ";"))
  ))
  
}

d3$url = paste0("http://www.conspiracywatch.info", d3$url)
d3$date = parse_date_time(d3$date, "%d %m %Y", locale = "fr_FR") %>% as.Date

Il ne reste plus qu’à combiner les différents résultats de nos récupérations, de les ordonner par date de publication, puis d’harmoniser les mots-clés a minima, en supprimant les traits d’union et en s’assurant qu’ils ne contiennent pas de lettres majuscules :

d <- rbind(d1, d2, d3) %>% arrange(date)
d$tags <- gsub("-", " ", d$tags) %>% tolower()

L’inspection du résultat montre que l’on dispose à présent d’un jeu de données contenant les métadonnées de 1,268 billets de blogs, dont l’immense majorité proviennent de Conspiracy Watch :

# nombre de billets récupérés
nrow(d)
# sources des billets
table(substr(d$url, 1, 25))

Il ne reste plus qu’à sauvegarder ce résultat, pour réutilisation future :

write_csv(d, "data/conspi.csv")

Ressources en ligne

Le CREST a mis en ligne un cours intitulé Stratégies Numériques en Sciences Sociales : https://www.css.cnrs.fr/strategies-numeriques/

Il fournit plusieurs vidéos et des exercices corrigées sur la récupération de données à partir de pages web.

Voir aussi Le Descriptoire: Recueil et analyse de texte avec R : http://perso.ens-lyon.fr/lise.vaudor/Descriptoire/_book/


  1. Si vous ne connaissez rien aux langages HTML et CSS, c’est le moment ou jamais d’en apprendre les bases ! Un excellent site de référence pour ce faire est W3 Schools.↩︎