Tutoriel : Constituer un corpus Europresse utilisable dans R, Iramuteq et TXM

Note importante. Ce tutoriel est également disponible dans un format permettant une meilleure lisibilité des scripts R à cette adresse : https://rpubs.com/CorentinRoquebert/europresse

L’analyse textuelle est une technique de plus en plus utilisée en sociologie, comme en témoigne le dernier billet de ce présent blog. Textométrie, text analysis, lexicométrie : les termes sont nombreux et recouvrent des méthodes assez différentes pour “objectiver” les univers de sens compris dans un ensemble de textes. En sociologie, un des enjeux majeurs est alors souvent de mettre en lien des usages spécifiques et covariants de certains mots avec des caractéristiques des locuteurs, c’est-à-dire des variables au sens statistique : c’est, par exemple, ce que cherche à faire la sémiométrie. La presse est un bon exemple d’application de cela, puisque l’on peut mettre en relation un espace linguistique avec un espace social (le champ journalistique), constitué de journaux différents, publiant à des dates différentes etc.1 Mais, pour réaliser de telles analyses, encore faut-il avoir au départ un tableau avec les textes qu’on souhaite analyser et les caractéristiques de chacun de ces textes ! C’est la structuration d’un tel tableau qui est l’objet de ce tutoriel2.

Son objectif est de permettre facilement la constitution d’un corpus d’articles de presse à partir de la base de données Europresse3, pour pouvoir ensuite en faire des analyse avec des logiciels généralistes d’analyse des données (comme R) ou des logiciels spécialisés d’analyse textuelle (dont les fonctions d’importation depuis Europresse ne fonctionne pas toujours très bien), comme Iramuteq ou TXM. Pour en savoir plus sur chacun de ces logiciels, je vous invite à aller voir les liens fournis en note, pour R, Iramuteq et TXM8.

Dans ce tutoriel, toutes les manipulations (à part l’export initial depuis Europresse) se font dans R : l’intérét d’utiliser des scripts est à la fois de s’assurer une reproductibilité de la manipulation sur d’autres corpus et d’avoir une meilleure maîtrise de ce que l’on fait, d’autant que, comme on le verra, un certain nombre de décisions doivent être prises dans la constitution d’un tel corpus.

En revanche, s’il faut utiliser R pour appliquer ce tutoriel, celui-ci est pensé à destination de gens qui ne maîtrisent pas nécessairement ce logiciel (mais qui en connaissent quelques rudiments) : encore une fois, je vous renvoie à l’excellente introduction de Julien Barnier9 si vous désirez en savoir plus. Le but est d’éventuellement pouvoir uniquement recopier le code, tout en comprenant ce qu’il fait, mais également de permettre de modifier facilement certains paramètres de celui-ci.

Première étape : récupérer les articles sur Europresse

La première étape de ce tutoriel est de récupérer en version HTML des articles via Europresse. Pour cela, il faut un accès à Europresse, en passant par une connexion que vous pouvez obtenir par l’intermédiaire de votre institution (laboratoire, université, etc.). Une fois que vous avez cet accès, il faut se rendre à cette adresse : https://nouveau.europresse.com/Search/Reading.

Pour récupérer les HTML, voilà les différentes étapes :

  1. Cliquez en haut à droite sur “version classique” (par défaut, vous êtes probablement en version étudiant, qui est censée être plus “ergonomique”, mais ne permet pas d’obtenir les HTML)((Cette étape n’est peut-être plus nécessaire : en tout cas, en réessayant juste avant la publication de ce tutoriel, il semblerait que la “version étudiante” permette de télécharger les pages HTML dont on a besoin.)).
  2. Faites votre recherche. Je vous conseille de faire une “recherche avancée” en précisant les sources, l’endroit dans le texte où vous voulez vos ou votre mot-clé, etc.
  3. Sélectionnez, dans les résultats, uniquement ceux qui concernent la presse : cliquez sur les boutons “télévision et radio”, “médias sociaux”, etc., pour les enlever de la liste de résultats.
  4. Triez les résultats par date.
  5. “Scrollez” jusqu’au maximum de résultats qu’il est possible d’afficher (1000 aux dernières nouvelles) et cocher la case à côté de “trier les résultats” qui permet de sélectionner toutes ces premières occurrences (c’est là que la version classique est int?ressante : dans l’autre version, on doit cocher tous les articles à la main).
  6. Cliquez sur “sauvegarde de documents” et sélectionner en HTML.
  7. Déplacez le document téléchargé dans un dossier de votre ordinateur, dans lequel il n’y aura que ces documents HTML.
  8. Si vous avez plus de 1000 résultats pour votre requête, il faut répéter la procédure à partir de la date du dernier des articles que vous venez de télécharger. Quand on reclique sur “recherche avancée”, Europresse a pré-enregistré les paramètres de recherche, il n’y a donc qu’à modifier la date. Les résultats sont en effet classés par ordre décroissant de date, du plus récent au plus ancien : si le dernier article sélectionné datait du 3 décembre 2012, il faut faire une recherche qui ira jusqu’au 2 décembre 2012, et répéter la procédure autant de fois que nécessaire.

Ces fichiers HTML sont bien balisés (une balise auteur, une balise date etc.) et on va les décoder pour les rendre utilisables. En effet, maintenant qu’on a obtenu des pages HTML via Europresse, on aimerait pouvoir les lire facilement afin de faire des requêtes dessus. Pour cela, on va transformer ces HTML en base de données, c’est à dire en un tableau dans lequel nous aurons en lignes chacun des articles, et en colonnes les variables qui nous intéressent (le journal, le titre de l’article, la date de publication, l’auteur, le texte de l’article).

Deuxième étape : transformer les pages HTML en dataframe dans R

Préparation de l’environnement de travail dans R

À partir de maintenant, toutes les manipulations se feront dans l’environnement R Studio (de R) : il faut donc ouvrir le logiciel et recopier les scripts encadrés. Pour commencer, on va sélectionner son répertoire de travail, de la manière suivante :

setwd(dir = "C:/Users/Corentin/Dropbox/Sociologie/Communications et articles/Scripts R") # Mettre son répertoire de travail

Attention ! C’est (presque) le seul moment où il est nécessaire que vous changiez quelque chose dans le script, afin d’indiquer à R, par le bon chemin, l’endroit de votre ordinateur où vous voudrez stocker votre base de données. Pour plus de simplicité, le mieux est de choisir le dossier parent de l’endroit où vous avez mis les fichiers HTML.
Puis, on installe et charge les paquets dont on a besoin :

load.lib <- c("xml2", "XML", "stringr", "stringdist", "stringi","lubridate", "dplyr", "tidyr", "ggplot2") # Ce sont les paquets dont on va avoir besoin

install.lib <- load.lib[!load.lib %in% installed.packages()] # On regarde les paquets qui ne sont pas installés

for (lib in install.lib) install.packages(lib,dependencies=TRUE) # On installe ceux-ci

sapply(load.lib,require,character=TRUE) # Et on charge tous les paquets nécessaires


Deux fonctions pour décoder les pages HTML

On va alors charger une fonction qui permet de lire les HTML qu’on a obtenus sur Europresse. La fonction s’appelle LIRE, mais vous pouvez bien sûr la renommer à votre guise :

LIRE <- function(html) {
  
  doc <- htmlParse(html) # On parse le document
  
  articles <- getNodeSet(doc, "//article") # On récupère chaque article séparément
  
 journal <- sapply(articles, function(art) {
    journ <- xpathSApply(art, "./header/div[1]/span/text()", xmlValue)
    journ[[1]]
  })
  
  
  auteur <- sapply(articles, function(art) { # On récupère l'auteur de chaque article
    aut <- xpathSApply(art, "./header/div[@class='docAuthors']/text()", xmlValue)
    aut <- aut[[1]]
    if (is.null(aut)) aut <- NA
    aut
  })
  
  titre <- sapply(articles, function(art) { # On récupère le titre de chaque article
    tmp <- xpathSApply(art, "./header/div[@class='titreArticle']//text()", xmlValue)
    if (is.null(tmp)) tmp <- NA
    tmp <- paste(tmp, collapse = "")
    
    str_trim(tmp)
  })
  
  date <- sapply(articles, function(art) { # On récupère la date de chaque article
    tmp <- xpathSApply(art, ".//div[@class='publiC-lblNodoc']//text()", xmlValue)
    if (is.null(tmp)) tmp <- NA
    tmp <- substr(tmp, 6, 13)
    tmp
  })
  date <- as.Date(date, "%Y%m%d") # On met la date au bon format
  
  texte <- sapply(articles, function(art) { # Enfin, on récupère le texte de ces articles
    tmp <- xpathSApply(art, ".//div[@class='DocText clearfix']//text()", xmlValue)
    if (is.null(tmp)) tmp <- NA
    tmp <- paste(tmp, collapse = "")
    str_trim(tmp)
  })

  # Il ne reste plus qu'à compiler ces informations dans une base de données lisible.
    txt <- data.frame(Journal = journal,
                    Titre = titre,
                    Date = date,
                    Auteur = auteur,
                    Texte = texte)
  
  # Maintenant qu'on a toutes les infos, on enlève les lignes NA (celles qui n'ont pas de nom de journaux ou de titre) (étape qu'on peut enlever mais qui semble plus prudente)
    
  txt <- subset(txt, !is.na(Journal) & !is.na(Titre))
  
  txt
  
}

On a donc maintenant une fonction qui permet d’obtenir les informations sur les pages HTML structurées comme celles qu’on a téléchargées sur Europresse. Plutôt que de faire passer manuellement cette fonction sur toutes les pages HTML, on définit une deuxième fonction qui va être appliquée sur tous les fichiers HTML d’un dossier de notre ordinateur. Cette fonction s’appelle lire_dossier :

lire_dossier <- function(chemin) {

  list<-list.files(chemin, pattern= ".HTML", full.names=TRUE, recursive=TRUE)

  l <- lapply(list, function(file) {
    print(file)
    LIRE(html=file)
  })
  bind_rows(l)
  
}

Cette fonction va donc appliquer la fonction LIRE qui transforme les HTML en base de données sur tous les documents qui ont une extension .HTML dans un dossier déterminé. Ça tombe bien, on a mis tous nos documents dans le même dossier. On n’a donc qu’à appliquer cette fonction à cet endroit-là ! :

test <- lire_dossier("cheminquivaaubonendroit") # Mettre le chemin jusqu'au dossier où les pages HTML sont stockées
write.csv2(test, file="nomdemabase.csv", row.names = FALSE) # Choisissez un nom qui vous ressemble

“Cet-endroit là”? Comme pour la définition du répertoire de travail, il s’agit ici d’indiquer le chemin de votre ordinateur qui mène à l’endroit où vous avez copié les pages HTML. Attention, si un message d’erreur apparaît à ce moment-là (du type “Error in journ[[1]] : indice hors limites, etc.”), c’est sans doute que vous avez récupéré autre chose que des articles sur Europresse (il faut bien décocher les cases “télévision et radio”, “médias sociaux” etc. quand vous téléchargez).

Normalement, c’est bon ! Vous avez dans R une base de données qui apparait en haut à droite, ici sous le nom de “test” : le nombre d’observations correspond au nombre d’articles que vous avez récupéré et il doit y avoir 5 variables.

Par la même occasion, on en a profité pour faire une sauvegarde dans l’ordinateur, en .csv, de ce tableau de données.

La deuxième étape est donc terminée : on a la base de données qui nous intéresse. Il faut maintenant la nettoyer.

Troisième étape : nettoyer les variables et les textes

Supprimer les doublons d’articles

Une des étapes cruciales ici est d’enlever les doublons : en effet, avec Europresse, il est fréquent de récupérer plusieurs fois le même article. Cela peut arriver soit parce qu’un même journal le publie plusieurs fois dès qu’une modification sur cet article apparaît, soit parce qu’un même article peut apparaître à la fois dans la version papier et la version en ligne du journal, ou alors parce qu’un article est une dépêche AFP qui est reprise (quasi) telle quelle par plusieurs journaux. Pour les enlever, on va le faire avec un indicateur qui mesure la similarité qui existe entre deux textes (c’est-à-dire le nombre de caractères qu’il faut déplacer pour passer d’un texte à un autre). Par ailleurs, pour des raisons de temps de calcul, on limite l’analyse de similarité à une chaîne de 100 caractères. Comme les articles se ressemblant diffèrent parfois uniquement par l’ajout d’un paragraphe au début ou à la fin de l’article, on compare à la fois les débuts et les fins d’article. Cependant, comme les premiers et derniers caractères d’un article peuvent se ressembler alors que les articles sont différents (par exemple, avec un journaliste qui donne son nom, met son adresse mail etc., ou pour des articles qui ont des chapeaux similaires car ils font partie d’une même série), on ne prend pas les premiers caractères (idem pour les derniers), mais les caractères situés entre la position 50 et la position 150 (idem pour les derniers).

Bien sûr, en fonction de la puissance de calcul de votre ordinateur, et globalement de votre usage, vous pouvez modifier ces paramètres dans le code ci-dessous.

Par la suite, il faut fixer un seuil à partir duquel on considère que deux textes se ressemblent “trop”. Ce seuil dépend du nombre de caractères qu’on analyse (une manière simple de le lire, est de se dire que le seuil qu’on fixe (voir le code plus bas) signifie le nombre de caractères qu’il faut déplacer pour passer d’un texte à un autre). Je conseille de voir ce que ça donne avec votre corpus particulier (voir les textes qu’il indique comme se ressemblant) et d’éventuellement ajuster le seuil si vous pensez que cela supprime trop ou pas assez de textes. (En réalité, je pense qu’il faudrait regarder la fonction de répartition des écarts entre textes pour essayer de voir s’il y a une rupture de cette fonction sous la forme d’un saut (à un endroit où on peut donc penser que la proximité entre deux textes n’est pas dû au hasard) qui légitimerait le choix d’un seuil) Pour ma part, j’ai choisi un seuil assez restrictif, quitte à enlever un peu trop d’articles. Regardez bien les commentaires sur le code, qui permet de comprendre les opérations qui sont effectuées ici :

test<- read.csv2("test.csv", stringsAsFactors = FALSE) #On repart éventuellement de ce qu'on avait en dur sur son ordinateur si on a fermé R entre temps...

articles %  
  filter(nchar(Texte) > 500) %>% # On ne garde que les textes assez longs (cela pourrait être fait à un autre moment)
  mutate(extrait_debut = str_sub(Texte, 50, 150), # On crée une colonne avec les caractères compris entre la place 50 et 150 du texte de chaque article. On peut ici modifier le paramètre pour regarder un autre endroit du texte.
         
         extrait_fin = str_sub(Texte, -150, -50)) # Idem avec la fin

## EXTRAIT DE DEBUT -------------------------------------------

## Calcul des paires de distance

  dist <- stringdistmatrix(articles$extrait_debut) # C'est ici qu'a lieu le calcul de distance entre tous les textes.

## Conversion en matrice 
m <- as.matrix(dist)
m[lower.tri(m)] <- 1000 # Dans la matrice, on met 1000 comme valeur pour toutes les valeurs en dessous de la diagonale, pour éviter d'avoir deux fois la même mesure
diag(m) <- 1000 # Dans la matrice, on met 1000 comme valeur pour la diagonale pour ne pas enlever un texte parce qu'il ressemble à lui-même...

## Sélection des paires proches
indices <- which(m < 50, arr.ind = TRUE) # On regarde les positions pour lesquelles l'indice de dissimilarité est inférieure à 50. C'est ici donc qu'on fixe le seuil et qu'on peut le changer !  

## Vérifications
test2 <- cbind(articles$extrait_debut[indices[,1]], articles$extrait_debut[indices[,2]])
View(test2) # On peut regarder ce qui a été considéré comme trop proche pour faire varier le seuil.

## Suppression des articles proches
articles % slice(-indices[,2])

## EXTRAIT DE FIN --------------------------------------------- #On fait la même manipulation pour l'extrait de fin !

## Calcul des paires de distance

  dist <- stringdistmatrix(articles$extrait_fin)

## Conversion en matrice 
m <- as.matrix(dist)
m[lower.tri(m)] <- 1000 
diag(m) <- 1000

## Sélection des paires proches
indices <- which(m < 50, arr.ind = TRUE)

## Vérifications
test <- cbind(articles$extrait_fin[indices[,1]], articles$extrait_fin[indices[,2]])
View(test)

## Suppression des articles proches
articles % slice(-indices[,2]) 

On a donc enlevé à la fois les textes qui se ressemblaient trop dans leur début et ceux qui se ressemblaient dans leur fin. Encore une fois : on peut faire varier ces paramètres (prendre des chaînes de caractères plus longues par exemple) (en revanche, il faut toujours comparer des chaînes de caractères de même longueur). Il est par ailleurs possible que, sur certains corpus (quand c’est de la presse uniquement en ligne par exemple), on identifie beaucoup trop de doublons si les débuts d’articles récupérés sur un même site sont un chapeau général de tous les articles de celui-ci : dans ce cas-là, il vaudrait mieux regarder beaucoup plus loin dans le texte (par exemple des caractères 250 à 350). Pour informations, dans le cas de mon corpus de thèse, cette manipulation enlève près d’un tiers des articles (passage de 11 000 à 7 500 articles). Pour information, dans le cas de mon corpus de thèse, cette manipulation enlève près d’un tiers des articles (passage de 11 000 à 7 500 articles).

Pour finir cette étape, on on sauvegarde la nouvelle base de données “épurée” des doublons :

write.csv2(articles, file="tutosansdoublon.csv", row.names = FALSE)

Nettoyer les variables

Il s’agit maintenant de nettoyer ce corpus, en commençant par les variables associées à chaque article. La date est normalement déjà dans le bon format (sinon, il faut faire une petite manipulation avec le paquet lubridate). Le journal, en revanche, est assez mal référencé, ou plutôt, pour un même journal, il n’est pas toujours écrit de la même manière. Pour certains titres de presse, par exemple, il n’est pas uniquement indiqué le titre du journal, mais également le numéro dans lequel celui-ci est paru. Il faut donc harmoniser les noms pour en faire des catégories utiles. Le recodage du journal dépend bien sûr du corpus : faut-il séparer les versions papier et en ligne d’un même journal ? Faut-il faire des catégories qui regroupent différents journaux ? (par exemple “presse quotidienne régionale”) Il me semble préférable d’assimiler les versions papier et en ligne d’un journal, tout en rajoutant ailleurs (ce qu’on verra après) l’information selon laquelle un article est paru en ligne. Pour ma part, le recodage que je propose ici est double : d’un côté, en grande partie, il ne consiste qu’à normaliser la manière de nommer un certain nombre de grands titres de presse (c’est-à-dire ceux qui avaient le plus d’occurrences dans mon corpus) ; de l’autre, dans certains cas, il crée directement quelques catégories. Il a également été fait à partir d’un corpus que j’avais récupéré à partir de la catégorie d’Europresse “source en français”, ce qui multiplie les sources différentes possibles. Néanmoins, même si votre corpus est différent, la logique sera la même. L’idée est de créer une nouvelle variable qu’on va renseigner en fonction de ce qu’on trouve dans la variable “journal” :

tuto <- read.csv2("tutosansdoublon.csv",stringsAsFactors = F) # On repart de ce qu'on avait sauvegardé

tuto$CJournal<- "Autre" # Pour le moment, tous les articles appartiennent à cette catégorie. Cela permet d'avoir un rebut à la fin du recodage : les journaux qu'on n'a pas réussi à identifier (ou qui avait trop peu d'occurrences pour avoir une catégorie)

# Exemple pour le Figaro :
tuto$CJournal[stri_detect_fixed(tuto$Journal, "figaro",case_insensitive=T)] <- "Figaro"
# Ici le code met "Figaro" comme modalité de la nouvelle variable CJournal à partir du moment où la chaîne de caractères "figaro" est renseignée dans la variable initiale (Journal). On prend donc toutes les variations de ce journal (ici, on a tout aussi bien "Le Figaro", "le Figaro", "Figaro magazine", "Figaro Economie, n°1256", "Figaro web", les variations sont innombrables) et on les assimile. L'argument case_insensitive permet de ne pas se soucier des majuscules.

# Un autre cas : Le Monde
tuto$CJournal[stri_detect_fixed(tuto$Journal, "monde",case_insensitive=T)] <- "Monde"
# Ici, le cas est un peu plus épineux. En effet, il y a d'autres journaux qui ont la chaîne de caractères "monde" dans leur titre, comme "le Monde Diplomatique". Il est donc important de faire ce recodage au début, avant de faire celui où l'on va chercher "monde diplo" par exemple, qui va ainsi "corriger" l'erreur qu'on avait faite dans un premier temps. Il faut donc faire attention à l'ordre dans lequel on effectue ces recodages.


# On fait la même chose pour tous les journaux qui ont beaucoup de résultats dans notre recherche
tuto$CJournal[stri_detect_fixed(tuto$Journal, "libération",case_insensitive=T)] <- "Libération"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "express",case_insensitive=T)] <- "Express"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "progrès",case_insensitive=T)] <- "Progrès"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "point",case_insensitive=T)] <- "Point"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "parisien",case_insensitive=T)] <- "Parisien"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "sud ouest",case_insensitive=T)] <- "SudOuest"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "télégramme",case_insensitive=T)] <- "Télégramme"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "afp",case_insensitive=T)] <- "AFP"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "humanité",case_insensitive=T)] <- "Humanité"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "croix",case_insensitive=T)] <- "Croix"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "obs",case_insensitive=T)] <- "Obs" 
tuto$CJournal[stri_detect_fixed(tuto$Journal, "télérama",case_insensitive=T)] <- "Télérama"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "leurs act",case_insensitive=T)] <- "ValeursA"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "rue89",case_insensitive=T)] <- "Rue89"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "rue 89",case_insensitive=T)] <- "Rue89" #Deux cas possible...
tuto$CJournal[stri_detect_fixed(tuto$Journal, "Marianne",case_insensitive=T)] <- "Marianne"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "expansion",case_insensitive=T)] <- "Expansion"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "uffington",case_insensitive=T)] <- "HuffP"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "huffp",case_insensitive=T)] <- "HuffP" #Deux cas possible aussi
tuto$CJournal[stri_detect_fixed(tuto$Journal, "ouest-fr",case_insensitive=T)] <- "OF"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "hui en fr",case_insensitive=T)] <- "AJF"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "chos",case_insensitive=T)] <- "Echos"  
tuto$CJournal[stri_detect_fixed(tuto$Journal, "montagne",case_insensitive=T)] <- "Montagne"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "tribune",case_insensitive=T)] <- "Tribune"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "stratégies",case_insensitive=T)] <- "Stratégies"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "challenge",case_insensitive=T)] <- "Challenge"

# Unifier certains journaux par une catégorie. Ici, on change un peu le code : on met la même modalité si dans la variable initiale on a une des chaines de caractères qu'on demande :
tuto$CJournal[stri_detect_regex(tuto$Journal, "le journal de|voix du n|provence|midi libre|velle rép|bien public|populaire du centr|berry|indépendant|nord éclair|charente|yonne|est répu|havre|maine lib|dépêche|voix de|courrier|le journal du|Paris normandie|la république du|blique des py|la meuse|dernière h|aisne|dordogne|cho répub]|la libre|corse|union|nord littoral|centre presse|paris-normandie|presse océan]",case_insensitive=T)] <- "Rég"
# Ici, le but est de ne pas surcharger le nombre de titres de presse en unifiant tous les journaux régionaux qui avaient relativement peu de résultats dans mes requêtes (en revanche, les grands titres de la presse quotidienne régionale sont conservés en propre)


#On fait la même chose pour les médias étrangers :
tuto$CJournal[stri_detect_regex(tuto$Journal, "be site|le soir|avenir|dh|peuple|le vif", case_insensitive=T)] <- "Belge"
tuto$CJournal[stri_detect_regex(tuto$Journal, "le temps|\24 h|le matin|genève", case_insensitive=T)] <- "Suisse"
tuto$CJournal[stri_detect_regex(tuto$Journal, "devoir|soleil|infopresse|canada|montréal|la presse|acadi|le droit|actualité|src", case_insensitive=T)] <- "Canada"
# Je ne pense pas que les listes soient complètes, mais cela englobe les plus grands titres de ces pays qui sont sur Europresse.

#On rajoute une catégorie pour les journaux gratuits.
tuto$CJournal[stri_detect_fixed(tuto$Journal, "lyon plus",case_insensitive=T)] <- "Gratuit"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "métro",case_insensitive=T)] <- "Gratuit"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "20 minutes",case_insensitive=T)] <- "Gratuit"

#Et pour le reste de la presse étrangère
tuto$CJournal[stri_detect_fixed(tuto$Journal, "el watan",case_insensitive=T)] <- "Etranger"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "africa",case_insensitive=T)] <- "Etranger"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "orient-le",case_insensitive=T)] <- "Etranger" 
tuto$CJournal[stri_detect_fixed(tuto$Journal, "africa",case_insensitive=T)] <- "Etranger"
tuto$CJournal[stri_detect_fixed(tuto$Journal, "courrier inter",case_insensitive=T)] <- "Etranger" #mettre à la fin

# Ponctuellement on ramène des sites spéciaux sur des titres de presse (comme ici le site Ecrans, émanation de Libération)
tuto$CJournal[stri_detect_fixed(tuto$Journal, "crans",case_insensitive=T)] <- "Libération"

Ce recodage doit bien sûr être adapté pour chacun.

On peut également faire des catégories plus générales (par exemple, réunir toute la presse hebdomadaire), de cette manière :

# Ici, on refait une catégorie plus générale, où les modalités seront plus générales (presse quotidienne nationale généraliste, presse quotidienne régionale, presse hebdomadaire etc.), ici le recodage qui crée la catégorie "presse économique" :
tuto$CJ_rec2[tuto$CJournal == "Echos"] <- "Eco"
tuto$CJ_rec2[tuto$CJournal == "Challenge"] <- "Eco"
tuto$CJ_rec2[tuto$CJournal == "Expansion"] <- "Eco"
tuto$CJ_rec2[tuto$CJournal == "Stratégies"] <- "Eco"

On peut également créer une variable qui indique si l’article est publié en ligne ou non :

tuto$Internet <- 0
tuto$Internet[stri_detect_regex(tuto$Journal, "web|\\.com|\\.fr|blog",case_insensitive=T)] <- 1
tuto$Internet[stri_detect_fixed(tuto$Journal, "no.",case_insensitive=T)] <- 0

#On peut éventuellement en faire une catégorie à part dans le recodage des journaux, comme ceci :
tuto$CJ_rec2[tuto$Internet == 1] <- "Internet"

On peut également créer des variables indiquant si un mot est utilisé dans le texte (ou dans le titre) de l’article, de cette manière :

tuto$Tmot[stri_detect_fixed(tuto$Titre, "mot qui m'intéresse",case_insensitive=T)] <- 1
tuto$Tmot2[stri_detect_fixed(tuto$Texte, "expression qui m'intéresse",case_insensitive=T)] <- 1

Par la suite, on peut par exemple se servir de ces variables pour faire des partitions du corpus (issus par exemple de plusieurs requêtes).

Concernant la variable “Auteur”, celle-ci n’est pas très souvent renseignée (une fois sur deux en moyenne, d’après ce que j’ai vu), et je n’ai pas encore trouvé de manière acceptable de la recoder. Si vous avez des idées, n’hésitez pas à me contacter ! Le principal problème est que, même pour un même auteur, le nom n’est pas toujours écrit selon le même format : parfois Prénom Nom, parfois Nom Prénom, parfois INITIALE Nom, parfois tout en majuscules. En plus, les prénoms et noms composés empêchent d’utiliser facilement les techniques habituelles de recherche et de remplacement pour nettoyer le texte (par exemple en utilisant ce qu’on appelle les « expressions régulières »).

(facultatif) Avoir les titres, et uniquement les titres, des articles

La partie qui suit est moins importante (et à adapter en fonction des cas) : elle n’a de sens que si vous voulez analysez uniquement les titres des articles ou la différence entre les titres et les textes de ceux-ci.

En effet, dans la balise “titre” du document HTML construit par Europresse, se cache souvent beaucoup plus que celui-ci : on y récupère également, en fonction des cas, les rubriques dans lesquelles s’insèrent l’article ou le chapeau de celui-ci. Cela est lié au fait que la fonction de “décodage” des fichiers HTML qu’on a créé prend la balise titre la plus large possible, ce qui permet d’obtenir à la fois le titre et ces éléments de paratexte. Si on voulait séparer les éléments du paratexte (titre, rubrique et chapeau), il suffirait de modifier la fonction LIRE de cette manière (sachant que les noms sont assez mal balisés, on ne peut distinguer que le titre, un avant titre et un après titre) :

# Il faut enlever la section "titre" de la fonction LIRE

# Puis rajouter ces trois paragraphes qui vont scinder les informations sur le titre :

titreseul <- sapply(articles, function(art) { # On récupère le titre uniquement de chaque article
  tmp <- xpathSApply(art, "./header/div[@class='titreArticle']/p[@class='titreArticleVisu rdp__articletitle']//text()", xmlValue)
  if (is.null(tmp)) tmp <- NA
  tmp <- paste(tmp, collapse = "")
  
  str_trim(tmp)
})

avanttitre <- sapply(articles, function(art) { # On récupère la balise d'avant le titre
  tmp <- xpathSApply(art, "./header/div[@class='titreArticle']/p[@class='sm-margin-bottom']//text()", xmlValue)
  if (is.null(tmp)) tmp <- NA
  tmp <- paste(tmp, collapse = "")
  
  str_trim(tmp)
})

aprestitre <- sapply(articles, function(art) { # On récupère la balise après le titre
  tmp <- xpathSApply(art, "./header/div[@class='titreArticle']/p[@class='rdp__subtitle']//text()", xmlValue)
  if (is.null(tmp)) tmp <- NA
  tmp <- paste(tmp, collapse = "")
  
  str_trim(tmp)
})


# Puis, il faut changer la data.frame qu'on crée en rajoutant les informations voulues
txt <- data.frame(Journal = journal,
                    titreseul = titreseul,
                    avanttitre = avanttitre,
                    aprestitre = aprestitre,
                    Date = date,
                    Auteur = auteur,
                    Texte = texte)

Avec ce script, on récupère de façon plus fine ce qu’on obtenait auparavant dans une seule catégorie “Titre” (de mon côté, comme j’ai préféré tout garder dans la même colonne, pour agréger un peu plus de données textuelles ensemble).

Nettoyage des textes

Ensuite, il est souvent nécessaire de nettoyer les textes. En effet, certaines choses parasitent les textes qu’on récupère :

# On enlève tout ce qui est entre balise (des balises restent en effet souvent) : 

tuto$Texte<- gsub ("", "", tuto$Texte)
tuto$Titre<- gsub ("", "", tuto$Titre)

# On enlève les adresses mails (souvent situés en début ou en fin d'article)
tuto$Texte<- gsub (" [^ ]*@.[^ ]* ", " ", tuto$Texte)
tuto$Titre<- gsub (" [^ ]*@.[^ ]* ", " ", tuto$Titre)

# Souvent, on retrouve en début de texte une mention "mis ? jour le ..."
tuto$Texte<- gsub(".is à jour le .{20}[^ ]* ", "", tuto$Texte) # On enlève dès qu'il est question de "mis à jour le" et les 20 caractères qui suivent jusqu'au prochain espace.

tuto$Texte<- gsub("propos recueillis par .{20}[^ ]* ", "", tuto$Texte) # On enlève dès qu'il est question de "propos recueillis par" et les 20 caractères qui suivent jusqu'au prochain espace.

# D'autres idées d'expressions r?currentes dans le "méta" des articles ?

tuto$Texte<-gsub("\\*", "", tuto$Texte) # On enlève les étoiles, qui peuvent poser problème à Iramuteq (plus que les autres caractères spéciaux)

Bien d’autres nettoyages mériteraient d’être faits, j’imagine : n’hésitez pas à m’écrire si vous voyez ceux que vous appliquerez et que vous voulez partager !

Finalement, on ne garde que les colonnes dont on a besoin et on sauvegarde à nouveau.

tuto2<- select(tuto, Journal, Titre, Date, Auteur, CJournal, Internet, Texte) # Ne pas oublier les variables qu'on a cr?? pendant le nettoyage (dans ce tuto, CJournal et Internet par ex)

write.csv2(tuto2,file="tutopropre.csv", row.names = FALSE)

À partir de là, on peut décider de rester dans R, en utilisant par exemple les paquets “tm” ou “tidytext” (on peut aussi utiliser Rtemis, qui utilise l’interface de R “R Commander”). À l’inverse, on peut avoir envie d’utiliser d’autres logiciels dont la fonction première est l’analyse de données textuelles. Nous allons maintenant voir comment transformer notre tableau pour qu’il soit utilisable dans deux de ces logiciels : Iramuteq et TXM (qui ont tous les deux la spécificité de se baser sur R).

Quatrième étape : transformer le tableau aux formats Iramuteq et TXM

Les étoiles d’Iramuteq

Iramuteq est un logiciel qui a pour principale vertu d’être la version libre du logiciel devenu payant Alceste. Développé à Toulouse par Pierre Ratinaud, il permet d’utiliser les algorithmes inventés par Max Reinert qui vont être labellisées comme “Méthode Alceste” ou “Méthode Reinert”4 : basé sur les techniques factorielles, il s’agit de réaliser une classification des textes d’un corpus afin d’en dégager les principaux “mondes lexicaux”, qui représentent des univers de sens dont on peut apprécier la proximité avec les caractéristiques des locuteurs. Le logiciel permet également de réaliser des traitements plus descriptifs (comme des nuages de mots), des analyses en termes de sur ou de sous-représentation de termes dans des sous-corpus (analyse de spécificité).

Le format de document que prend Iramuteq est un format particulier : un unique document .txt où les textes sont séparés par une ligne “étoilée” comportant les variables associées à chaque texte (ici, la date ou le type de journal par exemple). Ce script permet de passer de la base de données qu’on a construite jusqu’ici au format lu par Iramuteq.

iram <- read.csv2("tutopropre.csv",stringsAsFactors = F)

Pour commencer, on garde uniquement les variables qu’on a envie de conserver dans Iramuteq. Par exemple, si on veut faire un corpus à partir des titres d’articles récupérés, on va enlever le texte (et inversement si on s’intéresse au texte), mais aussi l’auteur, la variable journal non recodée, etc. La date devra également être recodée en fonction des objectifs de la recherche, (par exemple, ne garder que l’année de publication), car pour permettre une analyse dans Iramuteq il faut limiter le nombre de modalités de chaque variable. Par ailleurs, la dernière colonne doit être celle du texte qu’on veut analyser (car le format Iramuteq exige que les variables soient d’abord données avant le texte). Enfin, il ne doit pas y avoir d’accent dans le nom des variables ou des modalités (ce à quoi il faut penser au moment où on recode les journaux par exemple) :

iram$Annee <- year(iram$Date)

iram_text<- select(iram, c(Annee, CJournal, Internet, Texte)) # On étudie ici le texte, et on garde comme variable l'année de publication de l'article, le journal recodé en plusieurs catégories (cf au dessus) et une variable indicatrice qui indique si l'article a été publié sur internet ou non.

# Dans Iramuteq, ce qui indique qu'on passe à un autre texte est le fait qu'une ligne commence par quatre étoiles, il faut donc que la première variable commence par quatre étoiles. 

#L'idée général va alors d'être de reformuler les variables et modalités dans les termes d'Iramuteq, comme ceci :

iram_text$Annee <- paste("**** *Annee_", iram_text$Annee, sep = "") # On colle ce qui est nécessaire à Iramuteq (les étoiles) avec l'année qu'on avait de base dans notre variable

# Pour la deuxième variable et les suivantes, il n'y a plus besoin de rajouter les quatre étoiles au début, mais juste d'une pour introduire la variable :
iram_text$CJournal <- paste("*journal_", iram_text$CJournal, sep = "")

# On fait cela pour toutes les variables qui nous intéressent :
iram_text$Internet <- paste("*Internet_", iram_text$Internet, sep = "")

# De façon plus automatique, si on a beaucoup de variables, on peut le faire comme ceci, avec une boucle for, en prenant le nom des colonnes :
#   for (i in 5:46) {
#   iram_text[,i] <- paste('*', colnames(iram_text[i]), '_', iram_text[,i], sep="")
#   }

En ce qui concerne le texte, on peut décider d’enlever la ponctuation et de tout réduire en minuscule, mais ce n’est en théorie pas nécessaire.

# Facultatif
# iram_text$Texte <- gsub( "[^[:alnum:],]", " ", iram_text$Texte)
# iram_text$Texte <- tolower(iram_text$Texte)

En revanche, une étape importante est de sauter une ligne entre la ligne des variables et le texte. Il faut aussi sauter une ligne après le texte. On fait cela en rajoutant le symbole qui désigne le saut de ligne (antislash n en français, backlash n en anglais) :

iram_text$Texte<- paste("\n",iram_text$Texte, "\n", sep=" ")

Ca y est, on a le bon format !

Il n’y a alors plus qu’à enregistrer en format .txt ce dataframe :

write.table(iram_text, file= "tutoramuteq.txt", sep=" ", quote=FALSE, row.names=FALSE, col.names=FALSE)

Vous pouvez ouvrir le fichier pour vérifier que cela correspond bien au format voulu, et l’importer dans Iramuteq !

Les N-files de TXM

TXM permet de travailler de façon beaucoup plus proche des textes : il s’agit plus d’un outil pour linguistes que pour sociologues. En effet, les analyses syntaxiques que le logiciel permet sont beaucoup plus poussées. Par ailleurs, TXM est très utile en termes de “fouille de texte”, en permettant de naviguer facilement entre les textes de son corpus. En revanche, le format nécessaire est très différent. Si les formats acceptés par TXM sont multiples (notamment des formats XML qui nécessitent d’avoir annoté et balisé son corpus en amont), on privilégiera ici le format “txt + CSV”, qui signifie qu’on a un document txt par article de notre base de données (ces documents doivent avoir pour titre “1”, “2”, “3”, etc.) et un document CSV qui recense toutes les métadonnées sur nos articles.

On repart donc du même tableau nettoyé :

TXM <- read.csv2("tutopropre.csv",stringsAsFactors = F)

# On va changer de répertoire de travail, pour éviter de se retrouver avec 2 000 documents dans un répertoire en vrac. Ici, le script va tout mettre dans un sous-dossier intitulé "TXM" à partir du répertoire de travail défini au départ
# Attention : il faut créer ce sous-dossier TXM avant de lancer le script !
# Pour être sûr, on redéfinit son espace de travail à ce moment là :

setwd(dir = "C:/Users/Corentin/Dropbox/Sociologie/Communications et articles/Scripts R") #Mettre son répertoire de travail


for (i in 1:length(TXM$Texte)) {
    x <- paste("TXM/", i, ".txt", sep="")
   write.table(TXM$Texte, file=x, col.names=F, row.names=F)
 }

# On vérifie bien si dans le dossier TXM il y a autant de documents qu'on avait d'articles dans la base de données.

# Si c'est bon, maintenant on crée le document CSV qui contient les métadonnées :
# Comme dans le cas d'Iramuteq, on ne va prendre que les variables qui sont intéressantes quantitativement :
TXM$Annee <- year(TXM$Date) 
TXM <- select (TXM, CJournal, Annee)

TXM <- mutate(TXM, id=rownames(TXM)) # On rajoute une variable d'identification, nécessaire à TXM
TXM<- TXM[,c("id","CJournal","Annee")]

colnames(TXM) <- c("id", "CJournal", "Annee")

write.table(TXM, file="TXM/metadata.csv",col.names=T, row.names=F, sep=",", fileEncoding = "UTF-8")

C’est bon, vous devez avoir un fichier metadata.csv (nom imposé par TXM) au milieu de tous vos documents ! Il suffit alors d’utiliser la fonction d’importation “txt + CSV” de TXM !

Voilà, si vous avez des questions/remarques/problèmes, vous pouvez m’envoyer un mail à corentin.roquebert@gmail.com



Citer ce billet
Corentin Roquebert (2018, 4 décembre). Tutoriel : Constituer un corpus Europresse utilisable dans R, Iramuteq et TXM. QUANTI / Sciences sociales. Consulté le 19 mars 2024, à l’adresse https://doi.org/10.58079/t4bx

  1. Pour un exemple d’application au champ journalistique sur un corpus d’articles utilisant les scripts présentés ici, voir : https://www.cairn.info/revue-politiques-de-communication-2018-1-page-55.htm []
  2. Tout d’abord, un grand merci à Julien Barnier, pour ses conseils multiples dans l’élaboration des scripts R qui ont permis d’aboutir à ce tutoriel. Merci également aux relecteurs/trices et testeurs/euses qui ont aidé à son amélioration, en particulier Alexandra Roux, Pierre Mercklé et Sylvain Delouvée. []
  3. Europresse est une base de données accessible sur abonnement par Internet, géré par l’entreprise CEDROM SNI. Elle contient, entre autres, les articles de la majorité des titres de la presse française. Il existe toutefois des exceptions, comme Les Inrockuptibles ou une grande partie de la presse web. Son principal concurrent, Factiva, ne contient plus deux des principaux quotidiens français, Le Monde et Libération. []
  4. Pour une présentation synthétique et ses usages en sociologie : https://journals.openedition.org/sociologie/312 ; pour un autre exemple d’usage en sociologie : https://journals.openedition.org/sociologie/1794 []

Vous aimerez aussi...

8 réponses

  1. Anna Couthures-Idrizi dit :

    Bonjour,
    Vous oubliez une précision, et elle est de taille, les licences académiques Europresse souscrites par les bibliothèques universitaires n’autorisent pas par défaut le téléchargement massif de données….

    • Corentin Roquebert dit :

      Bonjour,
      Merci pour votre commentaire, qui concerne effectivement un détail qui a son importance.
      Le point que vous soulevez est intéressant. Avez-vous accès aux termes exacts de la licence d’utilisation “par défaut” ?
      S’il s’agit d’une interdiction d’un “téléchargement massif” de données, je ne pense pas que la technique présentée ici en soit un exemple. En effet, il n’y a ici usage d’aucun “robot” ou d’aucune technique de “webscraping” : on ne fait que récupérer “à la main”, ce que Cedrom-Sni nous laisse faire en “scrollant” et en téléchargeant les fichiers associés à notre recherche. Comme il n’y a pas d’automaticité de la démarche de récupération, je ne pense pas que cela pose problème, mais je me trompe peut-être… (à moins qu’Europresse n’interdise qu’on fasse de l’analyse textuelle sur les articles mis à disposition?)
      Par ailleurs, je suis rentré en contact avec Cedrom-Sni il y a quelques mois, car la procédure que j’indique ici ne fonctionnait plus (les versions HTML des documents n’étaient plus téléchargeables). Je leur ai expliqué ce que je faisais et ils m’ont dit qu’ils allaient régler le problème, ce qui a été fait. Je n’ai donc pas l’impression que, de leur point de vue, la démarche pose problème.

  2. Milan dit :

    Bonjour,
    Qu’est-ce qu’on peut faire quand il est impossible de télécharger les textes d’Europresse en HTML, mais seulement en PDF et en RSI ?

  3. D. Pélissier dit :

    Bonjour,
    MERCI beaucoup pour votre tuto qui m’a économisé de nombreuses heures et aussi a amélioré mon protocole. Respect !
    J’ai pu obtenir un fichier Iramuteq valide mais la phase de suppression des doublons n’a pas fonctionné.
    J’ai le message suivant (voir fin du message). Désolé, je ne connais pas R et ne sais pas lire cette littérature. Si vous avez une piste ? Merci pour toute information.

    Par ailleurs, sur le fichier Iramuteq : pour qu’il fonctionne au mieux, il faut rajouter une ligne vide au départ et le convertir en UTF-8 si on travaille sur Windows.

    Message d’erreur au moment de la suppression des doublons :

    > test
    > articles %
    + filter(nchar(Texte) > 500) %>% # On ne garde que les textes assez longs (cela pourrait être fait à un autre moment)
    + mutate(extrait_debut = str_sub(Texte, 50, 150), # On crée une colonne avec les caractères compris entre la place 50 et 150 du texte de chaque article. On peut ici modifier le paramètre pour regarder un autre endroit du texte.
    +
    + extrait_fin = str_sub(Texte, -150, -50)) # Idem avec la fin
    Error: Evaluation error: ‘nchar()’ requires a character vector.

    • BouilletJ dit :

      Ce problème peut être résolu en corrigeant légèrement le script :
      articles %
      filter(nchar(as.character(test$Texte)) > 500) %>%
      mutate(extrait_debut = str_sub(Texte, 50, 150),
      extrait_fin = str_sub(Texte, -150, -50))

      Merci pour ce tuto super bien fait et très compréhensible !

  4. Minel dit :

    Bonjour
    Je vous signale une seconde erreur :
    dans l’instruction
    write.table(TXM$Texte, file=x, col.names=F, row.names=F) il manque l’indexation du texte
    l’instruction correcte est la suivante
    write.table(fileTXM$Texte[i], file=x, col.names=F, row.names=F)

  5. Chaaa dit :

    Bonjour,
    Le script ne fonctionne pas pour moi, mais je pense que c’est parce que j’ai un unique fichier HTML avec tous mes articles, plutôt que des fichiers séparés dans un dossier. Je n’arrive cependant pas à les télécharger sous forme de dossier. Cette option est “grisée” sur europresse… Que faire ?
    Merci d’avance !

  6. Fairouz dit :

    Bonjour,
    Je me pose la même question que Milan.
    Qu’est-ce qu’on peut faire quand il est impossible de télécharger les textes d’Europresse en HTML, mais seulement en PDF et en RSI ?

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

Rechercher dans OpenEdition Search

Vous allez être redirigé vers OpenEdition Search