Ressources numériques en sciences humaines et sociales OpenEdition Nos plateformes OpenEdition Books OpenEdition Journals Hypothèses Calenda Bibliothèques OpenEdition Freemium Suivez-nous

Notice technique, illustrée par la communication officielle du Front national

Suite aux séries de billets sur les “unes” de Charlie Hebdo, les revues de la sociologie française et les réseaux législatifs, j’ai reçu quelques emails – merci ! – me demandant de réexpliquer “la base” du truc :

  • comment est-ce que l’on fait pour récupérer les données ?
  • comment est-ce que l’on fait pour en sortir un réseau ?

Je vais prendre un petit exemple facile pour illustrer le principe : comment récupérer la communication officielle du Front national à partir de son site Internet, et comment en extraire un réseau de co-occurrences.

Pour cet exemple, je suppose que vous connaissez un minimum de langage HTML. Si ce n’est pas le cas, vous aurez besoin d’une semaine au maximum pour apprendre tout ce qu’il faut savoir. Le langage HTML servant à “écrire Internet”, c’est aussi le langage le mieux documenté sur Internet : commencez ici, par exemple.

Les outils

Commençons par la base.

Il vous faudra une bonne connexion Internet : idéalement de la fibre optique, mais plus généralement, une connexion qui ne va pas tomber en rade et rajouter des erreurs de téléchargement dans une procédure qui risque déjà d’en générer par elle-même. Votre connexion Ethernet universitaire, qui est probablement filtrée d’une manière ou d’une autre, n’est peut-être pas la candidate idéale ; le wifi de votre université, encore moins.

Ensuite, il vous faut un peu d’espace disque. Utiliser un disque externe pour télécharger des centaines de fichiers est une très mauvaise idée : la moindre erreur d’écriture peut envoyer l’intégralité du disque dans les Limbes. L’idéal est d’utiliser une clé USB pas chère mais de bonne qualité, histoire que le disque dur ne soit jamais menacé. Si votre disque dur est un SSD, le risque de le foutre en l’air est toutefois minime.

Enfin, il vous faut un langage de programmation, et si possible, un langage qui va vous permettre d’enchaîner les opérations (téléchargement, visualisation, modélisation) de manière fluide. J’utilise R, mais Python est un autre bon candidat. R est bien documenté, bien illustré, la communauté répond vite et bien aux questions, et les toutes dernières techniques de modélisation y sont très bien représentées.

Il est vivement conseillé d’utiliser RStudio pour coder en R. Il vous faudra aussi un navigateur Internet avec une interface de développement intégrée, pour visualiser le code source des pages HTML que vous voulez récupérer : Chrome, Firefox, Opera ou Safari feront l’affaire.

Les packages

Quel que soit votre choix de langage, vous allez devoir vous servir d’extensions pour accomplir les différentes opérations dont vous avez besoin. Pour ce petit exemple, on va utiliser les cinq packages R suivants :

  • rvest, pour récupérer les données
  • lubridate, pour en convertir les dates
  • stringr, pour manipuler les données textuelles
  • dplyr, pour manipuler les données
  • ggplot2, pour visualiser les données

Incidemment, tous ces packages ont été développés par la même personne, avec l’aide de plusieurs autres.

On rajoutera quelques packages quand on en arrivera à l’étape “réseaux”.

La source

Toutes les donnés que l’on va récupérer proviennent de la section “Actualités” du site Internet frontnational.com. La structure de l’information qui s’y trouve est régulière : la section contient une suite d’articles qui appartiennent à une ou plusieurs catégories, avec parfois un ou plusieurs mots-clés supplémentaires. Chaque article possède un titre, qui renvoie à une page individuelle, et une date, exprimée en français :

Screen Shot 2015-03-15 at 12.58.52

Jetez à présent un coup d’oeil à la source HTML de la page “Actualités” avec les Developer Tools de votre navigateur (j’utilise ceux de Chrome) : en quelques clics et recherches par mots-clés, vous allez y trouver le code HTML qui structure l’information. Ici, les articles de la page se trouvent dans le diviseur #pk_content, les titres des articles sont codés comme des titres de niveau 3 de classe .pk_entry_title, et chaque titre fait l’objet d’un lien a :

Screen Shot 2015-03-15 at 13.06.58

Vous savez à présent ce que vous allez chercher dans chaque page. Dernière étape : combien de pages va-t-on récupérer ? Le système de pagination du site est ultra-simple à identifier :

Screen Shot 2015-03-15 at 13.19.13

Chaque page est numérotée de 2 à X, l’adresse étant de type /actualites/page/4, par exemple. La page finale est identifiable par essai-erreur : si vous essayez d’accéder à la page /actualites/page/200, vous obtenez une erreur. Cherchez un peu, et vous allez trouver qu’à l’heure actuelle, il y a 179 pages qui vous ramènent jusqu’à l’actualité du Front national en avril 2013—soit presque deux ans de données.

Le code

Pour récupérer les titres, liens, dates, catégories et mots-clés de tous les articles de la section “Actualités” du site Internet du Front national :

  1. On crée une boucle qui va de 179 à 1 ;
  2. On télécharge la page correspondant à cette valeur ; et
  3. On extrait ces informations de la page.

La base du truc ressemble à ça :

for(i in 179:1) {

  # l'adresse de la page sur le site Internet
  p = paste0("http://www.frontnational.com/actualites/page/", i)

  # le nom de fichier de la page téléchargée
  f = paste0("page", i, ".html")

  # télécharger la page si le fichier est absent
  if(!file.exists(f))
    download.file(p, f, mode = "wb", quiet = TRUE)

}

La condition if(!file.exists(f)) permet de relancer le code en cas d’erreur—mais il n’y en aura pas dans cet exemple : le site ne pose pas de souci, et ma connexion Internet n’en a pas posé non plus.

On dispose à présent de toutes les pages HTML de la section “Actualités”, et à chaque itération, l’adresse du fichier de la page est stocké dans la variable f. Pour extraire les informations de cette page :

# lire la page
h = html(f)

# extraire le lien de chaque article
url = html_nodes(h, "#pk_content h3.pk_entry_title a") %>% 
  html_attr("href")

# extraire le titre de chaque article
title = html_nodes(h, "#pk_content h3.pk_entry_title a") %>% 
  html_text()

# extraire les dates, catégories et mots-clés
meta = html_nodes(h, ".pk_entry_meta p") %>% 
  html_text()

On utilise ici les fonctions du package rvest. Rapide explication de texte :

  • Les deux premiers appels de la fonction html_nodes extraient les titres ;
  • html_attr("href") en extrait le lien individuel, html_text() en extrait le titre; et
  • l’opérateur %>% permet d’enchaîner les opérations de manière lisible

Cette syntaxe est une simplification de la syntaxe XPath, qui sert à parcourir les documents XML, dont font partie les documents HTML. Le package XML permet de faire la même chose, mais de manière moins lisible.

On obtient trois différentes variables textuelles à partir de ces opérations :

  • La variable url va contenir des liens de ce type :
    [1] "http://www.frontnational.com/2015/03/lettre-ouverte-de-marine-le-pen-aux-acteurs-des-politiques-du-handicap/"                              
    [2] "http://www.frontnational.com/2015/03/document-de-campagne-departementales-22-et-29-mars-2015/"                                             
    [3] "http://www.frontnational.com/2015/03/lettre-de-marine-le-pen-a-lattention-de-monsieur-olivier-schrameck-president-du-csa/"
  • La variable title va contenir des titres :
    [1] "Lettre ouverte de Marine Le Pen aux acteurs des politiques du handicap"                                
    [2] "Document de campagne départementales 22 et 29 mars 2015"                                               
    [3] "Lettre de Marine Le Pen à l’attention de Monsieur Olivier Schrameck, Président du CSA"
  • La variable meta va contenir le reste :
    [1] "13 mars 2015/Tribunes libres/Mots-clefs: Handicap"                          
    [2] "12 mars 2015/Annonces/Mots-clefs: Départementales 2015"                     
    [3] "12 mars 2015/Tribunes libres/Mots-clefs: CSA, Injures"

La dernière variable contient plusieurs informations, il va donc falloir un peu plus de code pour la séparer dans ses différents éléments :

  • On commence par extraire le texte de la date à partir d’une expression régulière qui en repère l’année (élément \\d{4}, qui cherche quatre chiffres consécutifs), puis on la convertit du format français “jour mois année” vers un format générique :
    date = str_extract(meta, "(.*)\\d{4}/")
    date = parse_date_time(str_sub(date, end = -2), 
                           "%d %B %Y", locale = "FR_fr")
    date = as.Date(date)
  • On extrait ensuite la ou les catégorie(s), qui se situent toujours après le premier caractère / de la variable meta (c’est-à-dire après la date). Pour ce faire, on utilise le caractère / pour diviser la variable en plusieurs petits bouts de texte, et l’on sélectionne le second :
    category = sapply(str_split(meta, "/"), function(x) x[2])
  • Enfin, on répète l’opération pour le bloc de texte correspondant aux mots-clés, en prenant soin de virer le texte dont on n’a pas besoin au passage. Il n’y pas toujours de mots-clés : on se prémunit donc contre cette possibilité en prévoyant la possibilité d’une variable manquante NA.
    tags = sapply(str_split(meta, "/"), function(x) {
      x = x[ grepl("Mots-clefs", x) ]
      x = gsub("Mots-clefs: ", "", x)
      ifelse(length(x), x, NA)
    })

En mettant toutes ces opérations à la chaîne, et en stockant les informations que l’on extrait dans un jeu de données, on arrive à 42 lignes de code qui collectent 3 581 liens et titres d’articles de la source. Tous les articles sont catégorisés, mais 728 d’entre eux n’ont pas de mots-clés.

Visualisation hyper-basique

Cherchons à visualiser un aspect très simple du corpus : qui parle au nom du Front national auprès des médias, et dans quelles proportions ?

Pour cela, on retient les 977 articles qui appartiennent à la catégorie “Médias”, et l’on extrait les noms des intervenants, encore une fois à l’aide d’une expression régulière1. L’expression en question est élaborée en jetant un rapide coup d’oeil aux titres des articles, et en rassemblant leurs intervenants dans une même expression un peu longue :

"Marine (L|l)e Pen|Marion( |-)Maréchal|Nicolas Bay|Gollnisch|F(l)?(orian|\\.) (P|p)hilippot|Louis Aliot|Steeve Briois|Sébastien Chenu|Stéphane Ravier|Thibault de la Tocnaye|Wallerand de Saint( |-)Just|Marie-Christine Arnautu|Bousquet-Cassagne|Gilbert Collard|Julien Sanchez|Julien Rochedy|Jean-Marie Le Pen|Lacoste-Lareymondie|Philippe Murer|Sophie Montel|Laurent Lopez|Gaëtan Dussausaye|David Rachline|Bernard Monot|Aymeric Chauprade|Michel Guiniot|Edouard Cavin|Jean-Claude Otto-Bruc|Dominique Martin"

Cette expression repère tous les intervenants, et ne laisse que 16 articles sans intervenants de côté.

Il ne reste plus qu’à visualiser la quantité d’articles produites par le Front national pour sa catégorie “Médias” entre avril 2013 et aujourd’hui, en coloriant les articles par les noms des intervenants, et après avoir regroupé les intervenants avec moins de 10 articles (seuil arbitraire) dans une catégorie résiduelle. Ce qui nous donne :

fn_medias_bars

Florian Philippot est en brun, et Marine Le Pen est en bleu (clair). Je vous promets que je n’ai pas choisi les couleurs : il s’agit des couleurs par défaut de l’excellent ggplot2.

Un dernier petit coup d’oeil, pour terminer, aux mois qui ont donné le plus d’articles, et aux intervenants de ces articles. Cette fois-ci, on utilise les fonctions du package dplyr pour agréger le corpus (objet m) par mois (variable ym) et par intervenant (variable who2), puis pour compter “leurs” articles, puis pour voir les dix premiers mois classés par nombre d’articles :

filter(m, who2 != "(autres)") %>% 
  group_by(who2, ym) %>% 
  summarise(n = n()) %>% 
  group_by() %>% 
  arrange(-n)

Ce qui nous donne Florian Philippot et Marine Le Pen en tête, et très loin devant le reste du peloton. Leurs mois d’activité médiatique les plus intenses correspondent aux élections municipales de mars 2014 et à la formation du premier gouvernement Valls :

1  florian philippot  2014-5 27
2      marine le pen  2014-3 23
3  florian philippot 2013-10 21
4  florian philippot  2015-2 20
5  florian philippot 2014-11 19
6  florian philippot  2013-6 18
7  florian philippot  2015-1 18
8  florian philippot 2014-12 17
9      marine le pen  2013-9 17
10 florian philippot 2013-12 16

Enfin, si l’on répète l’opération avec la catégorie “Interventions”, on trouve les députés Gilbert Collard et Marion Maréchal-Le Pen en tête de cortège, suivis des eurodéputés frontistes, mais sur des quantités moindres (il n’y a que 336 articles, soit trois fois moins de données) et plus irrégulières :

fn_interventions_bars

Pour reproduire ces résultats, allez voir le code, il y en a pour même pas 100 lignes.

Visualisation en réseau

Revenons sur la totalité du corpus. Ce corpus contient 3 581 articles constituant les “actualités” du Front national, et 80% des articles possèdent des mots-clés. Il y a 699 mots-clés différents dans le corpus. Voici les premiers d’entre eux, qui apparaissent plus de 100 fois dans le corpus, c’est-à-dire dans plus de 3% des articles possédant des mots-clés :

Européennes 2014 118
Immigration      133
Insécurité       102
Islam radical    111
Municipales 2014 261
Paris            150
Valls            122

Aucune surprise. Voyons à présent quelles associations de mots-clés reviennent fréquemment dans ce corpus.

Première étape : créer un jeu de données à deux colonnes, où chaque ligne contient une association de deux mots-clés conjointement présents sur un même article. En langage réseau, ça s’appelle une edge list non dirigée, et en langage R, ça peut se construire comme ça, à partir de la variable b$tags contenant les mots-clés de chaque article :

# associations de mots-clés dans chaque article
e = lapply(b$tags, function(x) {
  
  y = unique(unlist(strsplit(x, ",\\s?")))
  y = expand.grid(i = y, j = y, stringsAsFactors = FALSE)
  y = filter(y, i != j)
  y = apply(y, 1, function(x) paste0(sort(x), collapse = "///"))
  y = unique(y)
  data.frame(i = gsub("(.*)///(.*)", "\\1", y),
             j = gsub("(.*)///(.*)", "\\2", y),
             stringsAsFactors = FALSE)
  
})

# assemblage des associations en une seule liste
e = bind_rows(e)

# pondération de chaque association par sa fréquence
e = group_by(e, i, j) %>% summarise(n = n())

Pour la suite, il va nous falloir utiliser le package network pour convertir cette liste en objet “réseau”, auquel on va rajouter la pondération de chaque association de mots-clés, c’est-à-dire la fréquence de chacune de ces associations dans le corpus, que l’on a calculée à la toute fin du bloc de code précédent :

n = network(e[, 1:2 ], directed = FALSE)
set.edge.attribute(n, "weight", e[, 3])

Bon, à ce stade, on a le réseau complet des co-occurrences de mots-clés dans les articles “Actualités” du site du Front national, d’avril 2013 à aujourd’hui. Ça fait beaucoup, ce dont on peut se rendre compte en visualisant le réseau à l’état brut. On produit cette visualisation avec la fonction ggnet, dont on a déjà parlé :

fn_network_0

L’épaisseur des liens entre deux mots-clés est ici proportionnelle à leur fréquence d’apparition dans le corpus, simplifiée en cinq catégories correspondant à des fréquences d’associations de 1 (co-occurrence unique), 2, 3, 4 et 5 ou plus. Le code de visualisation ressemble à ça :

# diviser les co-occurrences en 5 catégories : 1, 2, 3, 4 et 5+
w = as.numeric(cut(e[, 3], c(0, 1, 2, 3, 4, Inf)))
w = w / max(w)
# visualiser le réseau complet en faisant varier l'épaisseur des liens
ggnet(n, size = 1,
  segment.color = "black",
  segment.alpha = .5,
  segment.size = w)

Ce que l’on voit au centre, ce sont les associations de mots-clés qui reviennent fréquemment ; ce que l’on voit en périphérie, ce sont les associations de mots-clés beaucoup moins fréquentes. Concentrons-nous sur le centre en ne retenant que les associations de mots-clés apparaissant au moins cinq fois dans le corpus :

# sous-réseau des co-occurrences de fréquence 5+
nn = n
delete.edges(nn, which(w < 1))
delete.vertices(nn, isolates(nn))

ggnet(nn, size = 1,
      segment.alpha = .5, segment.color = "black",
      label.nodes = TRUE,
      label.size = 3 * as.numeric(cut(degree(nn), unique(quantile(degree(n))), 
                                      include.lowest = TRUE)))

On a ici rajouté le nécessaire pour faire apparaître les mots-clés en question, et pour leur donner une taille proportionnelle au nombre d’associations dans lesquelles ils apparaissent. Le résultat ne va pas non plus vous étonner :

fn_network_5

Visualisation animée

On introduit à présent un dernier package, animation, qui va nous permettre de montrer le réseau de co-occurrences de mots-clés à chaque étape de l’affinage entre le réseau complet, qui montre toutes les co-occurrences, jusqu’à la dernière, c’est-à-dire la co-occurrence de mots-clés la plus fréquente du corpus.

Le code ressemble à ce que l’on a déjà vu, mais dans une boucle qui élimine progressivement les associations de mots-clés les moins fréquentes :

# animation
saveGIF({
  
  w = sort(unique(e[, 3]))
  for(i in w[ -length(w) ]) {
    
    nn = n
    delete.edges(nn, which(e[, 3] <= i))
    delete.vertices(nn, isolates(nn))
    
    g = ggnet(nn, size = 0,
              segment.alpha = .5, segment.color = "black",
              label.nodes = TRUE,
              label.size = 3 * as.numeric(cut(degree(nn), 
                                              unique(quantile(degree(n))),
                                              include.lowest = TRUE))) +
      ggtitle(paste("Associations apparaissant au moins", i, "fois"))
    
    print(g)
    
  }
}, movie.name = "fn_network.gif", interval = 1)

Ce qui nous donne une suite de réseaux de plus en plus “vides”, où les mots-clés “Taubira” et “Paris” ont une assez longue durée de vie, et où les mots-clés “Paris” et “insécurité” survivent jusqu’en fin de chaîne :

fn_network

Voici le code complet de la partie “réseaux”.

Bilan technique

Dans ce billet, on a utilisé :

  • R et tout plein de packages R
  • Un navigateur Internet
  • De la syntaxe HTML et XPath
  • Des expressions régulières2
  • Un tout petit peu d’analyse de réseaux

Toutes ces technologies sont gratuites et bien documentées.

Le corpus “Actualités du Front national” a servi d’exemple parce qu’il ne présentait que des difficultés minimales, qu’il ne m’a fallu que quatre minutes pour le télécharger, et que je ne comptais en présenter que des aspects très simples. Je n’ai même pas téléchargé les articles complets, par exemple, ni envisagé leur analyse textuelle.

Les exemples que je montre sur ce blog sont souvent plus larges : le corpus des revues et articles de la plateforme Cairn.info, par exemple, que j’ai utilisé pour parler des revues de sociologie, contient plus de 400 revues et à peu près 20 000 articles. Mais la logique de base reste proche de celle détaillée ci-dessus.

Mise à jour, 18 décembre 2015 : le Front national ayant légèrement modifié les réglages de son serveur, il faut désormais faire semblant d’utiliser un navigateur pour accéder à son site Internet de manière programmatique. Le code de ce billet a été mis à jour pour ce faire, ce qui permet au passage d’étendre les données jusqu’à décembre 2015, soit 228 pages d’actualités contenant 1 329 interventions de représentants du Front national dans les médias.

Le Canard enchaîné de cette semaine s’est par ailleurs fait le relais des conflits internes au Front national qui opposent Florian Philippot aux soutiens de Marion Maréchal-Le Pen, une source indiquant à cette occasion que Nicolas Bay était de plus en plus envoyé dans les médias par le parti pour infléchir la représentation de ce dernier par Florian Philippot.

On peut vérifier cette affirmation de plein de manières, mais l’on se contentera ici de la plus simple, qui consiste à calculer, à partir de notre source (le seul site Internet du Front national), le pourcentage d’interventions médias du Front national représenté par Florian Philippot. Les barres en rouge indiquent les mois d’élection (municipales, européennes, départementales et régionales), et l’on a retiré les mois de juillet/août pour ne pas inclure de ratios calculés sur des petits dénominateurs :

fn_medias_philippot_bars_updated

Ce graphique permet de constater qu’avec moins d’une invention média sur cinq, Florian Philippot est effectivement à son plus bas taux de représentation médiatique du Front national en décembre 2015. Une diminution similaire était observable au moment des élections municipales. Jugé sur le même critère, Nicolas Bay reste une figure moins médiatique du parti sur la période observée, sauf justement à quelques occasions, dont le mois de décembre 2015 :

fn_medias_bay_bars

Je profite de cette mise à jour pour signaler que Joël est apparu dans l’émission Envoyé spécial du 17 décembre 2015, pour parler justement de Marion Maréchal-Le Pen.

  1. Attention, celle-ci n’extrait que le premier intervenant : s’il y en a plusieurs, le deuxième passe à la trappe. Il n’y a que cinq articles où le problème se pose. []
  2. Alexandre fait remarquer qu’on devrait les appeler “expressions rationnelles”. S’il vous faut d’autres exemples, en voici quelques-uns. []

OpenEdition vous propose de citer ce billet de la manière suivante :
François (6 avril 2015). Notice technique, illustrée par la communication officielle du Front national. Polit’bistro. Consulté le 12 novembre 2024 à l’adresse https://doi.org/10.58079/syqv