Introduction au webscraping avec R

Auteur·rice

Thomas Delclite

Date de publication

7 décembre 2025

CC BY-NC-SA 4.0

CC BY-NC-SA 4.0

1 Introduction

Le webscraping est une technique permettant de récupérer des informations accessibles sur Internet, les transformant en données exploitables pour diverses analyses et applications. En pratique, un programme de webscraping est conçu pour naviguer sur Internet, collecter des informations et même remplir des formulaires. Cette capacité d’émuler les actions humaines permet une collecte de données à grande échelle.

L’objectif de ce cours est de vous faire découvrir les techniques de webscraping à partir du logiciel R. Vous pourrez apprendre à explorer un code source d’une page web et comprendre comment exploiter les patterns pour localiser des informations. Vous pourrez également découvrir le logiciel R et, à l’issue du cours, développer vos propres codes de webscraping et collecter plusieurs centaines ou milliers de données aisément.

Pour autant, le webscraping n’est pas une technique pouvant se substituer aux autres type d’enquêtes (sondage, terrain, etc.). Les données issues de sites Internet, bien qu’abondantes et apparemment riches en informations, manquent fréquemment d’un élément essentiel : le contexte explicatif, ou le « pourquoi » des actions observées. Les données collectées, en particulier celles qui sont obtenues de manière indirecte comme les « traces » laissées sur Internet, ne nous permettent pas toujours de comprendre les motivations sous-jacentes des utilisateur·rice·s derrière leurs actions, achats ou commentaires. De plus, notre connaissance de ces personnes se limite souvent à ce que leur comportement en ligne révèle, rendant difficile une analyse sociologique complète qui intègre des variables comme la classe sociale ou le genre, sans informations supplémentaires.

Cela signifie-t-il pour autant que nous devons rejeter cette source de données ? Non, Internet offre en effet une multitude de données qui peuvent être utilisées seules ou en complément d’autres méthodes de collecte de données. Ce cours explorera comment extraire des données suffisamment riches, parfois pouvant être analysées directement, parfois pouvant enrichir d’autres données ou recherches. Nous voyons ici l’apprentissage de l’outil et nous aborderons ça et là les limites et la pertinence de l’usage selon la recherche souhaitée.

Le webscraping est restreint à une contrainte importante à évoquer ici : les données ciblées doivent être accessibles légalement. Ce cours ne prévoit aucune utilisation de techniques de piratage ou d’autres moyens illicites pour accéder à des données privées. Toutes les données exploitables doivent être disponibles ouvertement sur le réseau Internet, que ce soit librement ou derrière une authentification.

Nous aborderons les principes fondamentaux du webscraping en utilisant le langage de programmation R. En maîtrisant les bases du webscraping, vous serez en mesure de naviguer sur le web et de collecter des informations, tout cela programmable et répétable à volonté. Ce cours vous guidera à travers les étapes initiales de la mise en place de R, de l’extraction de données web et de la création de base de données. Mais avant cela, nous allons aborder quelques notions importantes sur les objectifs du cours et sur la nature des données disponibles sur Internet.

1.1 Objectif : tableau(x) de données

Il est possible d’extraire des quantités astronomiques d’informations en copiant le contenu d’une page web. Avec l’ensemble de ces données, non structurées, il est envisageable de réaliser de l’analyse de données en masse, ou “Big Data”. Ce domaine est en constante évolution ces dernières années et les modèles d’intelligence artificielle permettent d’identifier des patterns complexes dans les données. Néanmoins, la qualité des données peut varier considérablement, ce qui peut rendre difficile l’obtention de résultats précis. De tels volumes de données nécessitent ensuite des programmes informatiques complexes dont les paramètres et étapes de décision sont souvent caché·e·s à l’utilisateur·rice. Le tout pour produire des corrélations entre des phénomènes dont personne ne sait comprendre la raison, seulement que cette corrélation est significative.

Ce n’est pas ce que nous ferons ici dans le cadre de ce cours. L’objectif de ce cours est justement de vous permettre de garder un contrôle sur chaque étape du processus, de l’extraction à la structuration des données. Pour cela, notre objectif ici sera simple : produire un ou plusieurs tableaux de données (à deux dimensions) dont chaque ligne sera une extraction issue d’Internet et chaque colonne sera une information concernant cette extraction.

Un soin tout particulier doit être apporté à la définition de la ligne du tableau à obtenir : que signifie une ligne du tableau ? S’agit-il d’un film, d’un billet de train pour un trajet donné, d’un article scientifique, d’un commentaire sur un réseau social ? Avant toute analyse de données, mais surtout avant même de commencer à écrire votre programme d’extraction, vous devez avoir une idée claire de ce que vous voulez obtenir à chaque ligne.

Un point important à noter dès à présent : si votre projet de webscraping ne concerne qu’une centaine de données, je vous conseille de faire cela à la main, c’est à dire de parcourir le site web et de stocker vos données dans un tableur. Cela sera plus rapide au final, car écrire un code de webscraping est long et est davantage prévu pour des extractions de grande ampleur (plusieurs milliers ou dizaine de milliers de lignes) ou à haute fréquence (une même extraction tous les jours/semaines/mois). Le but de ce cours est justement d’apprendre à coder un programme qui collecte et structure une quantité importante de données.

1.2 Les données sur Internet

Il faut avoir à l’esprit que toute information visible sur Internet est potentiellement récupérable. Que ce soit du texte, des chiffres, des images, pourquoi pas du son, si vous savez le voir à l’écran, il est possible de collecter l’information. Il y a même des données non visibles à l’écran et pourtant essentielles pour votre travail. Voyons d’abord les types de données et nous aborderons ensuite l’impact sur leur extraction.

1.2.1 Données et métadonnées

L’extraction la plus évidente concerne les données visibles sur votre écran d’ordinateur. Le nom du film, le prix du billet de train, la liste des auteurs et autrices de l’article, etc. Ces données consisteront souvent le cœur de votre recherche. Nous le verrons par la suite, mais disons le dès à présent, ne soyez pas trop économe dans les données à collecter. Hormis s’il s’agit de long textes, ou d’une extraction ayant lieu à trop haute fréquence, vous ne risquez rien à collecter plus de données que nécessaire. Si une information concerne votre sujet d’étude et qu’elle est disponible aisément (nous définirons davantage par la suite ce terme), n’hésitez pas à la collecter.

D’autres informations ne sont pas visibles à l’écran et sont pourtant tout aussi importantes, voire même sont plus importantes encore. Il s’agit des métadonnées, c’est à dire les informations “entourant” votre extraction et permettant de la situer, dans l’espace et dans le temps. Par exemple, il va s’agir de collecter la date et l’heure de votre extraction, car Internet est une source de données dynamique et les informations contenues peuvent évoluer au fil du temps.

Rappelons à ce titre que l’analyse de vos données se déroulera fréquemment bien après leur extraction. Dès lors, si une information a été oubliée lors de l’étape d’extraction, il ne sera pas toujours faisable de l’ajouter par la suite à vos tableaux de données.

1.2.2 Format des données

1.2.2.1 Données alphanumériques

La majorité des informations disponibles sur le web sont encodées sous forme de texte. Les éléments tels que les articles, les publications sur les réseaux sociaux, et même les métadonnées imbriquées dans les balises HTML, sont sous ce format. Les données numériques sont initialement traitées comme des séquences de caractères lors du webscraping. L’interprétation de ces informations en tant que données numériques exploitables est effectuée après le webscraping dans le traitement des données. Nous aborderons certaines des manipulations durant nos exemples.

Il convient de noter que certains textes sur Internet sont affichés sous la forme d’images. Ces images requièrent l’emploi de technologies de reconnaissance optique de caractères (OCR) pour leur conversion en texte exploitable. La mise en œuvre de l’OCR s’avère parfois complexe et est susceptible de générer des erreurs, en particulier lorsque la qualité de l’image est médiocre ou le texte stylisé. Cette démarche nécessite donc des outils spécifiques et peut compromettre la fiabilité des données extraites. Nous ne traiterons pas de ce sujet ici mais il existe de nombreux outils en R pour effectuer de l’OCR (par exemple : https://cran.r-project.org/web/packages/tesseract/vignettes/intro.html).

Enfin, vous trouverez parfois des données directement sous la forme de tableaux. Les tableaux, en raison de leur disposition structurée sur les pages web, représentent une source de données particulièrement adaptée au webscraping. Une table bien formée, marquée par des balises HTML telles que <table>, <tr>, <th> et <td> peut être collectée aisément. Les outils de webscraping identifient ces balises et extraient systématiquement les contenus de chaque cellule, offrant ainsi un accès direct à des ensembles de données organisées et prêtes à l’analyse. D’ailleurs, même un logiciel comme Excel permet maintenant d’extraire des tableaux à partir d’URL (https://support.microsoft.com/en-au/office/import-data-from-the-web-b13eed81-33fe-410d-9247-1747269c28e4).

1.2.3 Données audiovisuelles

À l’heure actuelle, il n’existe pas de méthode permettant l’extraction directe de données sous forme multimédia pour les stocker immédiatement dans un tableau structuré. Cette contrainte ne relève pas tant d’un problème d’extraction de données que de la conversion de contenu multimédia — tel que des images ou des enregistrements sonores — en un format qui pourrait être intégré dans une structure de tableau de données traditionnelle.

Pour les données audiovisuelles, la pratique courante consiste à télécharger le contenu dans un répertoire spécifiquement dédié, tout en enregistrant le nom du fichier dans une base de données. Bien que ce manuel ne traite pas des logiciels spécifiques à l’analyse de ces types de données, il est essentiel de reconnaître que des outils existent pour traiter ultérieurement ces informations. Le stockage structuré des noms de fichiers permet une récupération et une analyse facilitées par d’autres applications dédiées.

1.2.4 Accessibilité des données

1.2.4.1 Données archivées

Les sites web qui servent de dépôts pour des archives, tels que des recueils d’articles, d’ouvrages et d’informations diverses, présentent une dynamique particulière : les données y sont relativement stables. Les modifications sur ces plateformes sont généralement lentes, ce qui offre un avantage significatif pour le webscraping. Vous pouvez alors planifier des extractions en plusieurs phases sans risque majeur de perdre des données entre les sessions. De plus, il est souvent possible de retourner sur le site pour extraire des informations initialement négligées, garantissant ainsi une flexibilité dans la gestion des données recueillies.

1.2.4.2 Données éphémères

À l’opposé, nous trouvons des sites dont le contenu est dynamique. Ces plateformes, qui incluent notamment des sites d’actualités en temps réel et des réseaux sociaux, nécessitent une approche méthodologique spécifique en raison de la nature éphémère de leurs données. Une extraction effectuée à un moment donné peut être totalement différente d’une autre réalisée ultérieurement, rendant toute reprise de données impossible une fois l’extraction effectuée. La métadonnée temporelle devient alors cruciale : il est impératif de documenter précisément quand les données ont été collectées. Ce caractère définitif de l’extraction sur de tels sites implique de planifier soigneusement le programme de webscraping pour garantir l’intégralité et la pertinence des données recueillies.

1.3 Déontologie et règles en webscraping

J’essaie ici de faire le point sur le cadre juridique français et européen, sans être juriste moi-même. La réglementation a beaucoup évolué ces dernières années et, en 2025, voici les éléments essentiels à garder en tête.

Avant toute chose, rappelons que les propriétaires de site web dispose de plusieurs moyens légaux et techniques pour restreindre ou refuser l’accès à ses données :

  • Juridiquement, le webscraping réalise des actes de reproduction et d’extraction, qui sont, par principe, des droits exclusifs du titulaire de droits, et nécessitent donc une exception ou une autorisation ;

  • Au-delà du droit d’auteur, la Directive 96/9/CE introduit un droit sui generis protégeant les bases de données (même celles qui ne sont pas considérées comme originales). Ce droit protège le producteur contre l’extraction et/ou la réutilisation de la totalité ou d’une partie substantielle, évaluée qualitativement ou quantitativement, du contenu d’une base de données. L’extraction massive d’informations essentielles d’un site, par exemple pour reconstituer des annonces publiées, est reconnue par la jurisprudence comme présentant un risque pour l’amortissement de cet investissement ;

  • Les Condition Générales d’Utilisation (CGU) fixent les limites des usages autorisés. Elles doivent être respectées par les utilisateurs, qu’ils soient humains ou robots. La violation des CGU, si elles interdisent explicitement le webscraping, constitue une rupture contractuelle et peut entraîner des poursuites en dommages-intérêts. La jurisprudence européenne a historiquement reconnu la capacité des propriétaires de sites à limiter contractuellement l’utilisation de leurs bases de données ;

  • Le fichier robots.txt est une simple instruction destinée aux robots automatisant l’extraction de données. Bien que le robots.txt n’ait pas de valeur légale propre, son rôle devient significatif dans l’appréciation de la légalité du webscraping au regard des autres piliers du droit. D’un point de vue contractuel, un robots.txt renforçant les interdictions stipulées dans les CGU représente une opposition technique claire du titulaire du site. Le non-respect de cette instruction technique peut alors étayer une plainte pour violation contractuelle.

Par ailleurs, la législation européenne a introduit deux exceptions obligatoires concernant la Fouille de Textes et de Données (TDM). Ces exceptions ont été pensé concernant les entraînements de modèles d’intelligence artificielle et de machine learning, mais s’appliquent pour tout projet de recherche nécessitant de l’extraction de données. L’exception qui nous concerne ici est précisé à l’article 3, à propos de la fouille de textes et de données à des fins de recherche scientifique.

DIRECTIVE (UE) 2019/790 DU PARLEMENT EUROPÉEN ET DU CONSEIL sur le droit d’auteur et les droits voisins dans le marché unique numérique, Article 3

L’article 7 de cette directive prévoit que “toute disposition contractuelle contraire aux exceptions prévues aux articles 3, 5 et 6 est non exécutoire”. Cela signifie que si un organisme de recherche collecte des données dans le cadre de ses missions de recherche scientifique, toutes les interdictions évoquées ci-dessus (droit de propriété, droit sui generis, CGU, robots.txt) sont neutralisées par la loi européenne.

Attention, cette neutralisation n’est pas totale s’il s’agit de données à caractères personnelles, protégée par le RGPD. L’autorité de protection des données (CNIL) peut sanctionner un organisme pour avoir manqué à son obligation de protection et de minimisation dans sa collecte de données. Une fausse idée répandue est que les données personnelles accessibles sur des sites publics (réseaux sociaux, forums) cessent d’être soumises au RGPD. Ceci est incorrect. Le RGPD s’applique à toute information relative à une personne physique identifiée ou identifiable. Le webscraping constituant un traitement de ces données, l’organisme de recherche devient responsable du traitement et doit se conformer à toutes les obligations du Règlement.

Aussi, la collecte de données est autorisée uniquement si l’accès est dit “licite” aux données. Cela n’implique pas de devoir obtenir une permission d’extraire le contenu, mais d’avoir le droit de voir ce contenu. L’exception ne s’applique pas s’il est nécessaire de contourner illégalement des mesures de protection technique pour accéder aux données.

Pour résumer, le webscraping à des fins de Fouille de Textes et de Données (TDM) pour la recherche scientifique est, en principe, une activité légalement autorisée et encouragée par le droit européen. L’exception obligatoire de l’Article 3 de la Directive (UE) 2019/790 confère aux organismes de recherche un droit d’extraction qui neutralise l’opposition technique ou contractuelle des ayants droit, y compris les interdictions via robots.txt ou les CGU, sous réserve que l’accès initial au contenu soit licite. Cependant, cette autorisation légale ne confère pas une immunité générale. Les organismes de recherche doivent naviguer prudemment entre les exigences du RGPD, qui agissent comme des contraintes cumulatives.

Pour finir ce point juridique, voici quelques conseils de bonnes pratiques :

Minimisation de l’impact sur les serveurs web : Les actions de webscraping doivent être conçues de manière à minimiser l’impact sur les serveurs web des sites ciblés. Cela implique de limiter la fréquence des requêtes pour ne pas surcharger les serveurs. Il est recommandé d’étaler les requêtes sur des périodes plus longues ou de les effectuer durant les heures creuses.

Gestion respectueuse et éthique des données : Il est impératif de gérer les données collectées de manière éthique. Cela signifie éviter la collecte de données personnelles sans le consentement explicite des individus concernés et anonymiser toute information qui pourrait être utilisée pour identifier une personne.

Utilisation éthique et crédit des sources : vous devez réfléchir aux implications éthiques de votre extraction et s’assurer que l’utilisation des données ne porte pas préjudice aux individus ou aux entités impliquées. Il est également fondamental de créditer les sources des données collectées.

1.4 Découverte du logiciel

1.4.1 R et RStudio

RStudio est un environnement de développement intégré (IDE) populaire pour R, un langage de programmation principalement utilisé pour les statistiques et l’analyse de données. RStudio est conçu pour faciliter le travail sur R en proposant une interface claire et fonctionnelle. Vous devez installer à la fois R et RStudio sur votre ordinateur, puis lancez uniquement RStudio. Ce logiciel est divisé en quatre fenêtres principales, chacune ayant un rôle spécifique.

La fenêtre de script : La première fenêtre, souvent située en haut à gauche, est celle du script. Cette zone permet de créer des fichiers de script (.R), de les exécuter en partie ou en totalité, et de sauvegarder le travail. L’éditeur de script supporte également des fonctionnalités telles que la coloration syntaxique, le balisage automatique et le repliement de code, ce qui facilite la lecture et la rédaction du code. Les scripts ne sont “que” des fichiers texte, et ne contiennent aucun résultat ou calcul.

La fenêtre de console : Juste en dessous de la fenêtre de script se trouve la console. C’est ici que le code R est exécuté, et où les résultats des commandes sont affichés. Il est possible d’y entrer directement des commandes pour une exécution instantanée. Cette interaction directe avec l’environnement R est essentielle pour tester des morceaux de code rapidement, vérifier les résultats des analyses et gérer les packages R.

Remarque : il est souvent plus pratique d’afficher la fenêtre de console en haut à droite. Cela se fait dans le menu View -> Panes -> Console on Right.

La fenêtre d’environnement : En haut à droite, la fenêtre d’environnement montre une liste des objets R actuellement chargés, comme les vecteurs, les bases de données, et les fonctions. Cette fenêtre offre une vue d’ensemble et un accès rapide à l’ensemble des données avec lesquelles vous travaillez.

La fenêtre de fichiers, graphiques, packages et aide : En bas à droite, cette fenêtre multifonctionnelle permet de naviguer dans les fichiers du système, de visualiser les graphiques générés, de gérer les packages R et d’accéder à la documentation sur les fonctions et les packages R.

1.4.2 Gestion du proxy

L’utilisation d’un proxy dans le contexte du webscraping avec R est souvent nécessaire pour contourner des restrictions géographiques ou simplement prendre en compte les restrictions de votre organisation. Pour configurer un proxy en R, vous devez utiliser cette fonction : Sys.setenv(https_proxy = "http://login:mdp@proxy:port"). Cette fonction définit l’environnement du proxy. Ici, vous devez remplacer login, mdp, proxy, et port par vos informations d’authentification et les détails du proxy. Cette ligne indique à R de router toutes les connexions HTTPS à travers le proxy spécifié.

1.4.3 Gestion des packages

Le logiciel R est équipé d’une série de fonctions qui constituent la base de son fonctionnement. Pour répondre à des besoins spécifiques, il est nécessaire d’ajouter des packages. Ces derniers sont des collections de fonctions ciblées, conçues pour des domaines d’analyse particuliers. En tant que logiciel open-source, R permet à quiconque de développer et de proposer de nouveaux packages. Toutefois, tous les packages ne bénéficient pas de la même garantie de fiabilité. À cet égard, le Comprehensive R Archive Network (CRAN) joue un rôle essentiel en regroupant les packages et en s’efforçant de garantir leur qualité autant que possible. Je vous conseille donc de vous limiter aux packages accessibles directement via le CRAN, ou de faire preuve de prudence pour les autres packages.

Dans le cadre du cours, nous aurons essentiellement besoin de deux packages : tidyverse et rvest. Le package tidyverse est une collection de packages devenus populaires en analyse de données. Il contient les packages ggplot2 pour réaliser des graphiques, dyplyr pour manipuler des bases de données, tidyr pour nettoyer des bases de données, readr pour importer, purrr pour automatiser certaines étapes, stringr pour manipuler des chaînes de caractères, forcats pour manipuler des données stockées sous forme de facteurs (vous pouvez retrouver la présentation de ces packages ici : https://www.tidyverse.org/packages/). Tous ces packages facilitent l’écriture de code en R. Le package rvest permet spécifiquement de réaliser du webscraping. Il permet d’extraire le code source d’une page web puis de localiser et d’extraire des informations. Il permet même, depuis une mise à jour de 2024, de simuler un navigateur web et ainsi s’attaquer à des site Internet dynamique. Par ailleurs, rvest utilise le même langage que celui développé dans le tidyverse.

J’utilise ici les packages R les plus utilisés (et simple de mon point de vue) pour réaliser l’extraction de données sur Internet. Néanmoins, les packages évoluent, apparaissent, disparaissent, j’espère néanmoins que le package rvest sera supporté suffisamment longtemps. Mais surtout, chaque étape de programmation est expliquée afin qu’il soit possible, à moindre frais, d’adapter le code à un autre logiciel ou à un autre package.

La fonction install.packages est utilisée pour installer de nouveaux packages dans R. L’installation d’un package se fait généralement une seule fois par machine, sauf éventuelle mise à jour. L’installation nécessite une connexion internet pour télécharger le package.

install.packages('tidyverse')
install.packages('rvest')

La fonction library est utilisée pour charger un package dans votre session R. Une fois chargé, toutes les fonctions et les données du package deviennent disponibles durant cette session. Contrairement à install.packages, la fonction library doit être exécutée à chaque nouvelle session R dans laquelle vous souhaitez utiliser le package.

library(tidyverse, warn.conflicts = FALSE) 
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.1     ✔ stringr   1.5.2
✔ ggplot2   4.0.0     ✔ tibble    3.3.0
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.1.0     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(rvest, warn.conflicts = FALSE)

Remarque : en chargeant les librairies, vous risquez de voir beaucoup de messages d’informations et de warnings. Tout apparaît en rouge, mais cela n’est pas forcément grave. Il faut lire pour savoir quels sont les éventuels problèmes. Notamment, le package tidyverse génère souvent des conflits avec d’autres fonctions déjà présentes. Il faut alors vérifier si vous utiliser déjà ces fonctions et si le remplacement par la version du tidyverse est problématique ou non. Ici, j’ai caché les warnings pour la facilité de lecture.

2 Extraction de sites web statiques

2.1 Première extraction de données

2.1.1 Explorer une page web

Pour l’ensemble de cette section, nous allons scraper le site web de Statbel, l’office belge de la statistique, et plus précisément la page des actualités (ici nommé Nouvelles). Voici à quoi ressemblait la page web au 31 mai 2024 :

Page d’actualité de Statbel (31/05/2024)

Pour chaque actualité, il est possible de collecter le titre, le thème, la date de publication et le résumé. Il sera aussi possible d’accéder à la page de l’article complet avant de collecter plus de texte. L’objectif est d’obtenir in fine une base de données avec une ligne par actualité et une colonne par information sur l’actualité. Voici à quoi cela pourrait ressembler d’après la capture d’écran ci-dessus :

Objectif d’extraction
TX_TITRE TX_DATE TX_CATEGORIE TX_RESUME TX_TEXTE
Les entreprises laitières belges accroissent leur production de fromage et de crème en 2023 30 mai 2024 Agriculture & pêche Les entreprises laitières belges ont produit 80.900 tonnes de mozzarella en 2023…
L’inflation s’élève à 3,36% 30 mai 2024 Prix à la consommation Indice des prix à la consommation de mai 2024 En mai, l’inflation passe de 3,37% à 3,36%…

2.1.1.1 Extraction de la page web

Pour extraire la page web, la fonction read_html requiert simplement une URL à scraper :

read_html("https://statbel.fgov.be/fr/nouvelles")
{html_document}
<html lang="fr" dir="ltr" class="no-js" xml:lang="fr">
[1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8 ...
[2] <body class="path-news has-glyphicons">\n    <a href="#page" class="visua ...

Ici, R affiche le résultat de la fonction dans la console. Cela permet de comprendre l’effet de la fonction. Néanmoins, pour conserver l’information en mémoire et la réutiliser ensuite, il faut l’assigner à un objet R. Cela se fait à l’aide d’une flèche ( <- ) ou d’un égal ( = ). Dans le cadre de ce cours, j’utilise toujours la flèche.

page_actualites <- read_html("https://statbel.fgov.be/fr/nouvelles")

L’objet crée contient ce qu’on appelle le code source de la page web. C’est ce code source que nous allons explorer pour trouver des informations à l’intérieur. Pour cela, il nous faut comprendre la structure d’un code source.

2.1.1.2 Code source

Le code source d’une page web est ce que votre navigateur Internet reçoit comme information, afin de générer le site web. Le code source est une suite de commandes et d’instructions écrites généralement en HTML. Le HTML utilise ce qu’on appelle des “balises” pour organiser le contenu et les éléments sur une page web. Ces balises sont des mots clés entourés de chevrons (par exemple, <html>, <body>, etc.) qui indiquent au navigateur web comment afficher le contenu. Chaque page web commence par la balise <html> et se termine par </html>, encadrant toutes les informations de la page. Voici les balises de contenu les plus courantes :

  • <h1>, <h2>, <h3>, etc. : Ces balises de titre permettent de structurer le contenu textuel en hiérarchie, allant du plus important au moins important. <h1> est généralement utilisé pour le titre principal de la page, tandis que les autres servent pour les sous-titres et autres titres secondaires.
  • <p> : Cette balise est utilisée pour créer des paragraphes de texte, permettant d’organiser le contenu en blocs de texte clairement définis.
  • <a> : Cette balise crée des liens hypertextes. Elle nécessite l’attribut href qui spécifie l’URL de la page ou de la ressource vers laquelle le lien doit conduire.
  • <div> ou <span> : Les balises <div> et <span> sont utilisées pour définir une division ou une section dans un document HTML. Elle est souvent employée pour structurer la page et appliquer styles spécifiques.

Ces balises forment le squelette de la mise en page d’une page web. Nous n’abordons pas ici le développement web, donc vous n’avez pas besoin de comprendre exactement ce que chaque balise a comme effet sur le format d’une page web. Par contre, ces balises sont récurrentes de page en page sur un même site internet. Il est donc possible de comprendre cette structure pour ensuite demander à votre programme de trouver un élément spécifique d’une page.

Pour visualiser et interagir avec le code source d’une page web, vous pouvez utiliser l’un des navigateurs web tels que Chrome, Firefox ou Edge. Dans tous ces navigateurs, la procédure est assez similaire. Vous devez faire un clic droit sur l’élément que vous souhaitez explorer, puis sélectionner l’option “Inspecter” ou “Inspecter l’élément” dans le menu contextuel qui apparaît. Cette action ouvrira la console de développement du navigateur sur le côté ou en bas de votre fenêtre de navigateur, affichant le code HTML, CSS, et parfois JavaScript de la page. Voici la partie du code source de Statbel concernant le titre de la première actualité :

Code source de Statbel

Aussi, lorsque vous déplacez votre souris sur le code source ainsi affiché, vous voyez la partie du site web correspondante en surbrillance. Vous pouvez ainsi interactivement découvrir le code source de la page.

2.1.1.3 Explorer le code source

Il y a deux langages principaux pour extraire une balise avec rvest : CSS3 et XPath. XPath est un langage plus détaillé, mais plus lourd à écrire. Dans ce cours, j’utiliserai essentiellement CSS3 tout en montrant les cas pertinents en XPath. La fonction R est par contre toujours la même : html_element. En CSS3, pour récupérer les balises <h3>, il suffit d’indiquer “h3” comme argument de la fonction html_element. En XPath, il faut commencer par indiquer “//” pour signifier de parcourir tout le site web jusqu’à localiser cette balise <h3>.

page_actualites %>% html_element("h3")
page_actualites %>% html_element(xpath="//h3") #XPath
{html_node}
<h3 class="field-content">
[1] <a href="https://statbel.fgov.be/fr/themes/mobilite/transport/navigation- ...

La commande “pipe” (%>%), introduit par le package magrittr, est un opérateur qui simplifie la rédaction et la lecture du code en R. Il vous permet de passer le résultat d’une expression à la suivante, facilitant ainsi l’enchaînement d’étapes de modification de vos objets sans devoir emboîter toutes les fonctions. En pratique, vous utiliserez %>% pour enchaîner une opération : ici nous partons de l’objet page_actualites, sur lequel nous appliquons la fonction html_element. Vous pouvez utiliser le raccourci clavier Ctrl + Shift + M pour insérer le pipe à l’endroit du curseur.

Ces deux requêtes permettent de récupérer la première balise <h3> dans le code source. Pour extraire toutes les balises, il faut utiliser la fonction html_elements (avec un ‘s’ à la fin) :

page_actualites %>% html_elements("h3") #CSS3
page_actualites %>% html_elements(xpath="//h3") #XPath
{xml_nodeset (10)}
 [1] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/mob ...
 [2] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/nouvelles/ ...
 [3] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ind ...
 [4] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ind ...
 [5] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/mob ...
 [6] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ind ...
 [7] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ent ...
 [8] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/nouvelles/ ...
 [9] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ent ...
[10] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ind ...

Ici, c’est bon signe, nous collectons dix balises, ce qui équivaut aux dix titres d’articles sur le site de Statbel. Attention, une requête trop large vous donne beaucoup trop de résultats et sera inexploitable :

page_actualites %>% html_elements("div")
{xml_nodeset (160)}
 [1] <div class="dialog-off-canvas-main-canvas" data-off-canvas-main-canvas>\ ...
 [2] <div id="page">\n        <div class="wrapper-header">\n\n                ...
 [3] <div class="wrapper-header">\n\n                                         ...
 [4] <div class="navbar navbar-default container" id="navbar">\n              ...
 [5] <div class="wrapper-toolbar">\n                  <div class="toolbar">\n ...
 [6] <div class="toolbar">\n                      <div class="region region-t ...
 [7] <div class="region region-tools">\n    <nav class="language-switcher-lan ...
 [8] <div id="openfed-federal-header-wrapper">\n  <div id="openfed-federal-he ...
 [9] <div id="openfed-federal-header-link">\n   Autres informations et servic ...
[10] <div id="openfed-federal-header-logo">\n    <img src="/themes/custom/sta ...
[11] <div class="navbar-header">\n                                            ...
[12] <div class="region region-navigation">\n    \n<div id="block-sitebrandin ...
[13] <div id="block-sitebranding" class="block block--system-branding">\n     ...
[14] <div id="navbar-collapse" class="navbar-collapse collapse">\n            ...
[15] <div class="region region-navigation-collapsible">\n    <nav role="navig ...
[16] <div class="exposed-form form--inline form-inline">\n  <div class="form- ...
[17] <div class="form-item js-form-item form-type-textfield js-form-type-text ...
[18] <div data-drupal-selector="edit-actions-4" class="form-actions form-grou ...
[19] <div class="container">\n            <div class="row">\n              <d ...
[20] <div class="row">\n              <div class="col-sm-12" role="heading">\ ...
...

Dans ce cas, si une balise <div> vous intéresse, vous serez contraint·e d’utiliser des attributs afin de cibler plus efficacement votre balise. Deux attributs sont classiques en langage HTML : ‘id’ qui est un identifiant unique de la balise (par convention HTML, l’id doit être unique) et ‘class’ qui indique la classe de la balise (permettant d’appliquer un format en CSS).

En CSS3, il est très facile de demander à extraire une balise en connaissant son id ou sa classe. Pour l’id, vous devez indiquer après la balise un # suivi de l’id. Pour la classe, vous devez indiquer après la balise un point suivi de la classe :

# Ecriture générique
page_actualites %>% html_elements("div#ID")
page_actualites %>% html_elements("div.class")

Il y a aussi la possibilité d’extraire une balise à l’aide de n’importe lequel de ses attributs. Voici l’écriture à respecter :

# Ecriture générique
page_actualites %>% html_elements("div[attribut='value']")

En XPath, il n’y a aucun raccourci pour spécifier un id ou une classe, l’écriture standard est la suivante :

# Ecriture générique
page_actualites %>% html_elements(xpath="//div[@attribut='value']")

Ainsi, pour extraire les titres des actualités de Statbel, il est possible d’écrire la commande suivante :

page_actualites %>% html_elements("h3.field-content") 
page_actualites %>% html_elements(xpath="//h3[@class = 'field-content']")
{xml_nodeset (10)}
 [1] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/mob ...
 [2] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/nouvelles/ ...
 [3] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ind ...
 [4] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ind ...
 [5] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/mob ...
 [6] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ind ...
 [7] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ent ...
 [8] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/nouvelles/ ...
 [9] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ent ...
[10] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ind ...

Si nous cherchons à extraire la balise <div> qui est parent à la balise <h3>, il est possible d’écrire la commande suivante :

page_actualites %>% html_elements("div.views-field-title") 
page_actualites %>% 
  html_elements(xpath="//div[@class = 'views-field views-field-title']")
{xml_nodeset (10)}
 [1] <div class="views-field views-field-title"><h3 class="field-content"><a  ...
 [2] <div class="views-field views-field-title"><h3 class="field-content"><a  ...
 [3] <div class="views-field views-field-title"><h3 class="field-content"><a  ...
 [4] <div class="views-field views-field-title"><h3 class="field-content"><a  ...
 [5] <div class="views-field views-field-title"><h3 class="field-content"><a  ...
 [6] <div class="views-field views-field-title"><h3 class="field-content"><a  ...
 [7] <div class="views-field views-field-title"><h3 class="field-content"><a  ...
 [8] <div class="views-field views-field-title"><h3 class="field-content"><a  ...
 [9] <div class="views-field views-field-title"><h3 class="field-content"><a  ...
[10] <div class="views-field views-field-title"><h3 class="field-content"><a  ...

Remarque importante : en CSS3, il ne faut indiquer qu’une classe. En XPath, il faut toutes les indiquer. Mais par contre, il est possible de demander en Xpath à ce que seule une partie d’un attribut corresponde. Cela peut justement servir à isoler un seul attribut :

page_actualites %>% 
  html_elements(xpath="//div[contains(@class,'views-field-title')]")

Le langage HTML est en arborescence à partir de la balise <html>. Ainsi, en CSS3 comme en XPath, il est possible de parcourir cette arborescence pour, à partir d’une balise facile à localiser, arriver à la balise que l’on souhaite extraire. Par exemple, voici comme atteindre la balise <h3> présente en descendante de la balise <div> localisée précédemment :

page_actualites %>% html_elements("div.views-field-title h3") # En CSS3
page_actualites %>%
  html_elements(xpath="//div[contains(@class,'views-field-title')]/h3") # En XPath
{xml_nodeset (10)}
 [1] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/mob ...
 [2] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/nouvelles/ ...
 [3] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ind ...
 [4] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ind ...
 [5] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/mob ...
 [6] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ind ...
 [7] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ent ...
 [8] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/nouvelles/ ...
 [9] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ent ...
[10] <h3 class="field-content"><a href="https://statbel.fgov.be/fr/themes/ind ...

Comme vous pouvez le voir, le XPath et le CSS3 sont deux langages proches mais avec des subtilités. Pour comprendre davantage des différences, vous pouvez consulter cette ressource : https://www.testim.io/blog/xpath-vs-css-selector-difference-choose/. Également, vous remarquez que, pour atteindre une même localisation, ici la balise de titre, il y a de multiples voies possibles. Il n’y a donc pas une seule solution pour extraire un contenu d’un site web. Privilégiez au plus possible la simplicité et la pérennité du code.

Une grande partie de l’apprentissage en webscraping consiste à comprendre comment localiser un élément. Cela ne concerne pas directement le langage R mais uniquement l’exploration d’un code source. Les sélecteurs CSS3 ne sont pas forcément intuitifs au premier abord, et il faut s’entraîner pour ensuite être à l’aise et réaliser n’importe quelle extraction. Voici une ressource intéressante et très ludique pour vous exercer à ce sujet : https://flukeout.github.io/.

2.1.1.4 Extraire des éléments d’une balise

Les balises que nous extrayons contiennent l’ensemble de l’information entre l’ouverture et la fermeture de la balise. Bien souvent, vous souhaiterez extraire le texte contenu dans cette balise et visible sur le site web. Pour cela, la fonction html_text doit être utilisée à la suite de la fonction html_element :

page_actualites %>% html_element("h3") %>% html_text()
[1] "Hausse de 2,7% des marchandises débarquées dans les ports belges"

Ici, nous obtenons le texte du titre de la première actualité visible sur le site de Statbel. Pour obtenir les dix titres, il faut utiliser la fonction html_elements puis la fonction html_text :

page_actualites %>% html_elements("h3") %>% html_text()
 [1] "Hausse de 2,7% des marchandises débarquées dans les ports belges"                                                                           
 [2] "Mortalité jusqu’au 23 novembre"                                                                                                             
 [3] "Volume des ventes en hausse de 5,4% dans le commerce de détail - octobre 2025"                                                              
 [4] "Le volume des ventes augmente tant dans le commerce et la réparation de véhicules automobiles que dans le commerce de gros - septembre 2025"
 [5] "Baisse de 2% de la navigation intérieure"                                                                                                   
 [6] "Le volume des ventes dans les services en hausse de 3,1% par rapport à la même période de 2024 - septembre 2025"                            
 [7] "220 faillites durant la semaine 48"                                                                                                         
 [8] "Les 50+ surreprésentés parmi les personnes fortement limitées"                                                                              
 [9] "L’intelligence artificielle s’impose au cœur des entreprises belges"                                                                        
[10] "Indice du chiffre d'affaires industriel : septembre 2025"                                                                                   

Remarque : pour faciliter la lecture de votre code R, il est possible de faire un retour à la ligne après chaque commande %>% :

page_actualites %>% 
  html_elements("h3") %>% 
  html_text()

La fonction html_text permet d’extraire les informations visible à l’écran sur le site web, mais ne permet pas d’explorer le contenu des balises. Or, il y a un usage courant en webscraping qui nécessite d’extraire le contenu des balises : les lien URL. En effet, une balise <a> génère à l’écran un lien cliquable (souvent en bleu), et il est souvent très utile d’extraire ce lien pour ensuite extraire le code source de cette nouvelle page web (cela s’appelle du spider en webscraping, car on peut ainsi voyager de lien en lien). Chaque balise <a> contient un attribut href qui contient l’URL de la page web cible. Pour extraire ce lien, il faut utiliser la fonction html_attr avec comme argument l’attribut à extraire. Voici le code R pour extraire l’URL de tous les liens cliquables sur le site web (la fonction head permet d’afficher que les cinq premières URL pour ne pas encombrer l’affichage) :

page_actualites %>% html_elements("a") %>% html_attr("href") %>% head()
[1] "#page"                     "/nl/nieuws"               
[3] "/fr/nouvelles"             "/de/news"                 
[5] "/en/news"                  "https://www.belgium.be/fr"

À présent, à vous de parcourir le code source de la page de Statbel et d’extraire les informations suivantes : Thème, Résumé, Date de publication, URL de la page de l’article. Ouvrez RStudio et copiez le code ci-dessous :

Vous pouvez exécuter les différentes lignes afin d’installer puis charger les packages, extraire la page web de Statbel, puis d’afficher les dix titres d’actualité. Vous devez maintenant explorer le code source de Statbel et extraire le thème, le résume, la date de publication et l’URL de l’article. Pour tester vos résultats, ouvrez le logiciel RStudio et copier les lignes de code ci-dessous

# Installation des packages (une fois par PC)
install.packages('tidyverse')
install.packages('rvest')

# Chargement des packages
library(tidyverse) 
library(rvest)

page_actualites <- read_html("https://statbel.fgov.be/fr/nouvelles")

# Catégories
page_actualites %>% html_element("sélecteur CSS3") %>% html_text()

# Résumé 

# Dates de publication

# URL de l'article

Vous pouvez maintenant maintenant compléter ce code et tester les sélecteurs CSS3 ou XPath pour obtenir les informations des actualités. Pour poursuivre le cours et comparer à vos résultats, voici la solution (avec html_element afin d’éviter d’alourdir le document en affichant les dix résultats à chaque fois)

# Catégories
page_actualites %>% html_element("div.views-field-field-theme") %>% html_text()
[1] "Mobilité"
# Résumé 
page_actualites %>% html_element("div.views-field-body") %>% html_text()
[1] "  Au deuxième trimestre 2025, la quantité totale de marchandises débarquées dans les ports maritimes belges s’est élevée à un peu moins de 38 millions de tonnes. Cela représente une hausse...\n\n"
# Dates de publication
page_actualites %>% html_element("div.field-content time") %>% html_text()
[1] "5 décembre 2025"
# URL de l'article
page_actualites %>% html_element("h3 a") %>% html_attr("href")
[1] "https://statbel.fgov.be/fr/themes/mobilite/transport/navigation-maritime"

2.1.2 Risques des extractions isolées

Il est important d’avoir à l’esprit que la structure des contenus est variable. Tenter d’extraire chaque vecteur d’informations séparément, comme nous venons de faire, est risqué car certains articles peuvent omettre des éléments présents dans d’autres, tels qu’un texte d’introduction ou un thème. Cela va entraîner des vecteurs de tailles différentes, et vous serez incapable de savoir regrouper les données collectées.

La solution idéale pour contourner ce problème consiste à cibler votre extraire à partir d’une balise englobante l’ensemble de l’information que vous voulez obtenir. Et ensuite d’extraire, dans chaque ensemble les éléments que vous souhaitez. Cette méthode assure que chaque vecteur d’informations correspond à un article entier, avec des données positionnées de manière cohérente, même si certaines informations sont manquantes.

Balise regroupant une actualité

Pour Statbel, il faut donc extraire le bloc contenant toute l’actualité. Sur la capture ci-dessus, vous pouvez voir que la balise div avec la classe “views-row” contient tous les éléments que nous recherchons. Et, si vous faites l’expérience, vous découvrirez que toutes les actualités sont contenues, chacune, dans une balise identique. Le code ci-dessous devrait donc être efficace pour extraire les dix actualités :

page_actualites %>% html_elements("div.views-row")
{xml_nodeset (10)}
 [1] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [2] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [3] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [4] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [5] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [6] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [7] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [8] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [9] <div class="views-row">\n<div class="views-field views-field-field-image ...
[10] <div class="views-row">\n<div class="views-field views-field-field-image ...

En l’exécutant, on découvre que ce code extrait plus de dix actualités, ce qui n’est pas normal. Notre code collecte donc plus d’éléments et nous devons résoudre cela avant de poursuivre. En observant le code source, on remarque que le calendrier de diffusion est balisé de manière identique aux actualités, notre extraction est donc trop “large” et doit être reprise.

En analysant le code source, on remarque que le bloc d’actualité est entouré par une balise <div class='view-news-list'> et cette balise exclue le calendrier de diffusion. Nous devons donc demander en CSS3 d’extraire les balises <div class='view-row'> contenues dans la balise <div class='view-news-list'>. Voici le code pour faire cela :

page_actualites %>% html_elements("div.view-news-list div.views-row")
{xml_nodeset (10)}
 [1] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [2] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [3] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [4] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [5] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [6] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [7] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [8] <div class="views-row">\n<div class="views-field views-field-field-image ...
 [9] <div class="views-row">\n<div class="views-field views-field-field-image ...
[10] <div class="views-row">\n<div class="views-field views-field-field-image ...

Le premier segment du code cible tous les éléments <div> qui possèdent la classe view-news-list. Le deuxième segment du code indique, qu’à l’intérieur du <div> précédemment sélectionné avec la classe view-news-list, le sélecteur doit maintenant chercher d’autres <div> ayant la classe views-row. Voici l’écriture en langage XPath :

page_actualites %>% 
  html_elements(xpath="//div[contains(@class,'view-news-list')]//div[@class = 'views-row']")

Nous savons maintenant extraire la liste des dix actualités, nous allons conserver cette liste en créant un nouveau objet liste_actu :

liste_actu <- page_actualites %>% 
  html_elements("div.view-news-list div.views-row")

Cet objet R est une liste de dix éléments (vous pouvez d’ailleurs observer cet objet directement en RStudio à partir de votre environnement). Et chaque élément de cette liste est une partie de code source à partir de laquelle il est possible d’appliquer de nouvelle fonction html_element et html_text. En R, pour parcourir une liste, il faut utiliser le sélecteur [[ ]] en indiquant l’index de l’élément que l’on cherchant (l’index début à 1). Pour illustrer, nous allons à présent créer un objet actu à partir du premier élément de la liste (donc de la première actualité). À partir de ce nouveau objet actu, on peut appliquer les mêmes sélecteurs CSS3 pour obtenir le titre, la date, le thème et le résume de cette actualité :

# Exploration de la première actu 
actu <- liste_actu[[1]]

actu %>% html_element("h3") %>% html_text() # Titre
[1] "Hausse de 2,7% des marchandises débarquées dans les ports belges"
actu %>% html_element("time") %>% html_text() # Date
[1] "5 décembre 2025"
actu %>% html_element("div.views-field-field-theme") %>% 
  html_text() # Thème
[1] "Mobilité"
actu %>% html_element("div.views-field-body") %>% 
  html_text() # Résumé
[1] "  Au deuxième trimestre 2025, la quantité totale de marchandises débarquées dans les ports maritimes belges s’est élevée à un peu moins de 38 millions de tonnes. Cela représente une hausse...\n\n"

En XPath, il faut indiquer un . pour spécifier que l’on souhaite repartir de la dernière balise extraite. En effet, l’objet conserve l’information sur l’ensemble du site web. Voici l’ecriture :

actu %>% html_element(xpath = ".//h3") %>% html_text()
actu %>% html_element(xpath = ".//time") %>% html_text()
actu %>% html_element(xpath = ".//div[@class='views-field-field-theme']") %>% html_text()
actu %>% html_element(xpath = ".//div[@class='views-field-body']") %>% html_text()

2.1.3 Rebond sur d’autres URL

Nous avons ainsi extrait le titre, le thème, la date de publication et le résumé de chaque actualité sur la première page d’actualités de Statbel. Pour terminer cette première étape d’exploration, nous allons à présent extraire le texte complet de l’actualité, et non simplement le résumé. Pour cela, nous avons besoin d’accéder à l’article complet, et donc avant à l’URL de l’article. Nous devons donc localiser la balise <a> contenant l’URL puis extraire celle-ci à l’aide de la fonction html_attr.

url_article <- actu %>% html_element("h3 a") %>% html_attr("href")

Maintenant que nous avons extrait l’URL de cet article, nous pouvons extraire le code source de cette URL. Notez que contrairement à l’URL initiale de la liste des actualités de Statbel, cette URL n’est pas connue au moment de lancer le programme de webscraping. Il s’agit là d’une collecte dynamique d’information et de rebond d’une URL à une autre. Une fois l’extraction effectuée, il est possible d’explorer cette nouvelle page et d’en extraire les données comme pour la page initiale.

page_article <- read_html(url_article)

Dans cette section, nous avons exploré les fonctions essentielles pour localiser et extraire des informations à partir d’une page web, en utilisant des sélecteurs CSS ou avec le langage XPath. Dans le cas d’une extraction d’un bloc d’informations, Le processus commence par bien analyser le code source de la page web, d’identifier la balise générale qui encapsule l’ensemble des informations du bloc et, ensuite d’extraire chaque élément individuellement. Cette méthode assure que toutes les données seront collectées de manière structurée, même en l’absence de certains éléments.

Pour autant, les extractions abordées dans cette section sont ponctuelles et ne permettent pas d’obtenir une base de données complètes. Pour cela, nous allons voir comment systématiser l’extraction et regrouper les données obtenues.

2.2 Récupération d’un article d’actualité

A partir des éléments abordés lors de la section précédente, nous allons voir dans cette section comment extraire une actualité de Statbel et structurer toute l’information sous la forme de tableau. Cette section aborde les étapes de création et de remplissage de tableau. Pour autant, ce cours ne se substitue pas à une formation d’utilisation de R, et nous nous limiterons aux étapes les plus élémentaires.

2.2.1 Création d’une base de données

Avant de créer une base de données pour y stocker les informations collectées, vous devez réfléchir à la structure de vos données. Dans un premier temps, posez vous la question de la définition d’une ligne. Est-ce une actualité, un jour de données, un individu, un ménage, un post facebook ? Comprendre clairement la nature de chaque ligne est essentiel pour pouvoir ensuite préciser les colonnes de votre base de données. Ici, nous cherchons à collecter des actualités, et chaque ligne constituera une actualité distincte. Et chaque colonne sera un élément de cette actualité.

Avec le package tidyverse, il est possible de créer une base de données avec la fonction tibble. Il faut alors indiquer le nom de chaque colonne à créer ainsi que le format de la colonne (character ou numeric). Je vous conseille de créer initialement des colonnes en format caractère, puis éventuellement de convertir ces données plus tard :

tableau_actu <- tibble(
  TX_TITRE = character(),
  TX_DATE = character(),
  TX_CATEGORIE = character(),
  TX_RESUME = character(),
  TX_TEXTE = character()
)

tableau_actu
# A tibble: 0 × 5
# ℹ 5 variables: TX_TITRE <chr>, TX_DATE <chr>, TX_CATEGORIE <chr>,
#   TX_RESUME <chr>, TX_TEXTE <chr>

Vous pouvez également employer la fonction tibble pour créer une base de données en y incluant automatiquement une première ligne de données. Pour ce faire, il est nécessaire de spécifier le nom de chaque colonne ainsi que l’information correspondante à y stocker lors de la création du tibble. Dans ce cas, le type de données de chaque colonne (character ou numeric) est déterminé automatiquement en fonction de la nature des valeurs ajoutées. Cette fonctionnalité sera exploitée lors de l’automatisation du code plus tard.

2.2.2 Extraction de l’actualité

Le code ci-dessous reprend tous les éléments présentés dans la section précédente. Nous commençons par extraire la page des actualités de Statbel et lister les 10 blocs d’actualités. Puis, en sélectionnant la première actualité, nous extrayons chaque information que nous assignons dans des objets séparés. Ici, le code n’affiche rien à l’écran car tout est stocké sous forme d’objet R.

url <- "https://statbel.fgov.be/fr/nouvelles"

page_actualites <- read_html(url)

liste_actu <- page_actualites %>% 
  html_elements("div.view-news-list div.views-row")

actu <- liste_actu[[1]]

# Extraire les informations de l'actualité
titre <- actu %>% html_element("h3") %>% html_text()
date <- actu %>% html_element("time") %>% html_text()
categorie <- actu %>% html_element("div.views-field-field-theme") %>% html_text()
resume <- actu %>% html_element("div.views-field-body") %>% html_text()

# Récupérer l'URL de l'article et l'extraire
url_article <- actu %>% html_element("h3 a") %>% html_attr("href")
page_article <- read_html(url_article)

# Extraire le texte de l'article
texte_article <- page_article %>% html_element("div.field--name-body") %>% html_text()

2.2.3 Compléter le tableau

Les objets ainsi créés ne constituent pas encore une base de données. Nous devons ajouter une ligne dans le tableau pour structurer l’information. La fonction add_row permet, à partir d’un tableau précédemment créé, d’ajouter une ligne en indiquant la valeur à assigner à chaque colonne. En utilisant la fonction add_row, vous vous assurez de ne jamais écraser les données de votre tableau.

tableau_actu <- tableau_actu %>% 
  add_row(TX_TITRE = titre,
          TX_DATE = date,
          TX_CATEGORIE = categorie,
          TX_RESUME = resume,
          TX_TEXTE = texte_article)

tableau_actu
# A tibble: 1 × 5
  TX_TITRE                               TX_DATE TX_CATEGORIE TX_RESUME TX_TEXTE
  <chr>                                  <chr>   <chr>        <chr>     <chr>   
1 Hausse de 2,7% des marchandises débar… 5 déce… Mobilité     "  Au de… "Au deu…

Voilà, nous savons à présent collecter l’ensemble des informations d’une actualité et stocker cette information dans une base de données. Celle-ci comporte une seule ligne et autant de colonnes que le nombre d’information distinctes à stocker.

Néanmoins, à ce stade, on peut douter de la pertinence d’utiliser R et autant de ligne de code pour collecter aussi peu d’informations. Un simple copier-coller sera de fait bien plus efficace. Mais, avec quelques changements, les codes présentés ci-dessus vont permettre de collecter d’abord les 10 actualités d’une page web de Statbel, puis l’ensemble des actualités de tout le site web.

2.3 Extraction d’une page d’actualités

Nous allons à présent appliquer les fonctions vues précédemment pour extraire l’ensemble des dix actualités présentes sur la première page d’actualités de Statbel. En observant le code ci-dessus, la seule différence consistera à modifier l’étape suivante :

actu <- liste_actu[[1]]

2.3.1 Création d’une fonction

En effectuant une incrémentation de 1 à 10, nous modifions le contenu de l’objet actu, pour ensuite extraire et stocker les informations dans le tableau tableau_actu. Nous allons développer une fonction actu_to_table qui, à partir d’une actualité donnée en entrée, retourne un tableau. Ce tableau contiendra une ligne avec les informations précisément extraites de l’actualité correspondante.

actu_to_table <- function(actu){
  
  # Extraire les informations de l'actualité
  titre <- actu %>% html_element("h3") %>% html_text()
  date <- actu %>% html_element("time") %>% html_text()
  categorie <- actu %>% html_element("div.views-field-field-theme") %>% html_text()
  resume <- actu %>% html_element("div.views-field-body") %>% html_text()
  
  # Récupérer l'URL de l'article et l'extraire
  url_article <- actu %>% html_element("h3 a") %>% html_attr("href")
  page_article <- read_html(url_article)
  Sys.sleep(2)
  
  # Extraire le texte de l'article
  texte_article <- page_article %>% html_element("div.field--name-body") %>% html_text()
  
  tibble(TX_TITRE = titre,
          TX_DATE = date,
          TX_CATEGORIE = categorie,
          TX_RESUME = resume,
          TX_TEXTE = texte_article)
}

Remarque : Dans le cadre d’une fonction, toute assignation faite avec <- reste confinée à la fonction, et les modifications sont perdues après l’exécution de la fonction. Au lieu d’utiliser l’opérateur d’assignation habituel <-, vous pouvez utiliser la flèche <<-. L’utilisation de <<- permet d’assigner des valeurs à des variables qui résident dans l’environnement global, plutôt que dans l’environnement local de la fonction.

La fonction actu_to_table contient une étape de webscraping avec la fonction read_html. Lorsque vous exécutez un programme de webscraping qui interroge fréquemment un site web, il est crucial de gérer attentivement la fréquence à laquelle les requêtes sont envoyées. Ceci est important pour plusieurs raisons, notamment pour éviter de surcharger les serveurs du site avec un volume élevé de requêtes en peu de temps, ce qui pourrait mener à un ralentissement du serveur, voire à un blocage de votre accès par l’administrateur·rice du site. Pour contrôler la cadence des requêtes, on utilise souvent la fonction Sys.sleep en R, qui permet de mettre en pause l’exécution du script pendant un nombre spécifié de secondes.

Intégrer Sys.sleep après chaque appel à read_html est une pratique courante en webscraping. Cette méthode aide à simuler un comportement de navigation plus “humain”, réduisant ainsi le risque d’être identifié comme un robot par les mécanismes de protection du site. Par exemple, après avoir extrait le contenu d’une page, le script peut attendre quelques secondes avant de passer à la suite.

2.3.2 Usage des boucles

A présent, nous devons aborder les boucles pour appliquer cette nouvelle fonction aux dix actualités. La boucle for est l’une des plus simples à utiliser dans ce cadre, voici l’écriture avec un exemple simple qui incrémente de 1 à 10 une variable i pour en prendre la racine carré :

for (i in 1:10){
  print(sqrt(i))
}
[1] 1
[1] 1.414214
[1] 1.732051
[1] 2
[1] 2.236068
[1] 2.44949
[1] 2.645751
[1] 2.828427
[1] 3
[1] 3.162278

Remarque : lors de l’utilisation de boucles for en programmation, les résultats des calculs effectués à l’intérieur de la boucle ne sont pas automatiquement affichés dans la console. Pour visualiser ces résultats pendant l’exécution de la boucle, il est nécessaire d’intégrer explicitement la fonction print. Cette pratique est particulièrement utile pour le débogage ou le suivi de l’avancement des calculs, notamment dans des processus itératifs longs ou complexes.

Avec le package tidyverse, il est possible d’utiliser la fonction map pour appliquer une même fonction à un vecteur. Nous pouvons appliquer ceci avec notre exemple ci-dessus. La commande 1:10 permet d’obtenir un vecteur numérique allant de 1 à 10. On peut donc appliquer à ce vecteur la fonction sqrt via la fonction map.

1:5 %>% map(sqrt)
[[1]]
[1] 1

[[2]]
[1] 1.414214

[[3]]
[1] 1.732051

[[4]]
[1] 2

[[5]]
[1] 2.236068

Remarque : la sortie d’une fonction map est une liste dont les éléments sont identifiés avec des [[ ]]. Les listes permettent de contenir des éléments de nature différentes. Si vous souhaitez annuler cette liste et obtenir les éléments sous la forme d’un vecteur, il faut utiliser la fonction unlist :

1:5 %>% map(sqrt) %>% unlist
[1] 1.000000 1.414214 1.732051 2.000000 2.236068

2.3.3 Application de la fonction

Avec cette nouvelle fonction disponible, quelques lignes de codes permettent d’extraire les dix premières actualités de la page web d’actualité de Statbel. Voyons d’abord comment faire cela avec la boucle for :

page_actualites <- read_html("https://statbel.fgov.be/fr/nouvelles")

liste_actu <- page_actualites %>% html_elements("div.view-news-list div.views-row")

tableau_actu <- tibble(
  TX_TITRE = character(),
  TX_DATE = character(),
  TX_CATEGORIE = character(),
  TX_RESUME = character(),
  TX_TEXTE = character()
)

for (i in 1:10){
  print(i) # Afficher la progression de la boucle
  tableau_actu <- tableau_actu %>% 
    add_row(actu_to_table(liste_actu[[i]]))
}

La fonction map peut aussi être utilisée pour atteindre un objectif similaire, mais comme mentionné précédemment, elle renvoie une liste d’éléments, ce qui ne correspond pas tout à fait à notre besoin de structurer les données sous forme d’une base de données. Pour résoudre ce problème, la fonction map_df (où “df” signifie data.frame) s’avère utile. Cette fonction permet d’appliquer une fonction spécifique à chaque élément d’un vecteur et de regrouper les résultats directement sous forme de tableau. Dans notre cas, map_df facilite la création et le remplissage du tableau_actu, en transformant chaque résultat en une ligne du tableau de données :

page_actualites <- read_html("https://statbel.fgov.be/fr/nouvelles")
liste_actu <- page_actualites %>% html_elements("div.view-news-list div.views-row")

tableau_actu <- liste_actu %>% map_df(actu_to_table)

Le tableau tableau_actu contient à présent dix lignes et cinq colonnes et l’ensemble des dix actualités de la première page de Statbel.

tableau_actu
# A tibble: 10 × 5
   TX_TITRE                              TX_DATE TX_CATEGORIE TX_RESUME TX_TEXTE
   <chr>                                 <chr>   <chr>        <chr>     <chr>   
 1 194 faillites durant la semaine 20    23 mai… Entreprises  Durant l… "Durant…
 2 Modification des codes INS des commu… 22 mai… À propos de… Le code … "Le cod…
 3 Hausse de 1,6% du volume des ventes … 22 mai… Indicateurs… Le volum… "Le vol…
 4 Les femmes et les jeunes générations… 22 mai… Census       En Belgi… "En Bel…
 5 Baisse de 2,2% du volume des ventes … 22 mai… Indicateurs… Services… "Servic…
 6 28.157.734 animaux abattus en avril … 22 mai… Agriculture… Abattage… "Abatta…
 7 59 millions de litres de lait de con… 21 mai… Agriculture… En janvi… "En jan…
 8 2.444.610 nuitées en février 2024     21 mai… Entreprises  Le nombr… "Le nom…
 9 Mortalité jusqu’au 5 mai              17 mai… Population   Statbel … "Statbe…
10 Avril 2024: 925 faillites             17 mai… Entreprises  En avril… "En avr…

Comme vous pouvez observer, une fois la fonction actu_to_table, quelques lignes de code permettent d’obtenir toute l’information d’une page web. Nous obtenons un tableau en format R, mais il est aisé de l’exporter en format CSV. Attention, lorsque vous indiquez le chemin d’accès où exporter le fichier, vous devez indiquer les slash / au lieu des habituels antislash de Windows \. Il faut indiquer la version 2 de la fonction pour avoir le format CSV lisible en Europe avec des points virgules.

write_csv2(tableau_actu,file="C:/chemin/vers/vos/données/data.csv")

Nous avons appris à créer un tableau et une fonction, à extraire les informations d’une page web et à les structurer en base de données. De plus, nous savons comment sauvegarder ces informations sous forme d’un fichier compatible avec un tableur. Cependant, jusqu’ici, notre approche s’est limitée à une seule page web. Bien que notre code soit optimisé pour extraire les informations de dix actualités, notre objectif est d’étendre le webscraping aux pages suivantes du même site internet. Et ainsi de collecter, non pas seulement une dizaine, mais des centaines ou milliers d’actualités.

2.4 Extraction de l’ensemble du site web

2.4.1 Comprendre comment accéder aux autres pages

Au 1er juin 2024, le site internet de Statbel présente 288 pages d’actualités, chacune contenant 10 articles, totalisant ainsi près de 3 000 actualités à collecter. Pour accéder à toutes ces pages, vous devez comprendre le mécanisme de navigation entre les différentes pages du site. L’observation de la première page révèle un système de pagination situé au bas de la page. Ce système inclut des boutons permettant de passer directement aux pages 2 à 9, un bouton pour aller à la page suivante, ainsi qu’un bouton pour atteindre la dernière page. Une approche pour automatiser le parcours de ces pages consisterait à identifier et extraire l’URL du bouton ‘page suivante’ à chaque fois, permettant ainsi de naviguer progressivement à travers les pages après chaque lot de 10 actualités. Cela représente un exercice en R intéressant, cependant nous allons explorer une méthode différente dans ce contexte, bien plus simple.

Lorsqu’on clique sur le bouton de la page 2 du site web, on change de page web et on obtient cette URL de destination : https://statbel.fgov.be/fr/nouvelles?fulltext=&field_publication_date_value%5Bmin%5D=&field_publication_date_value%5Bmax%5D=&page=1. Nous allons décoder tous les éléments de cette URL :

  • https://statbel.fgov.be/fr/nouvelles : Il s’agit de l’URL de base du site Internet. C’est cette URL que nous utilisons pour notre première extraction, et elle reste identique pour les pages suivantes

  • ?fulltext=& : Le ? permet de déclarer un premier paramètre à cette URL, ici sans doute une mise en format du texte. Le paramètre reste vide car immédiatement après le = se trouve & qui est un séparateur entre deux paramètres.

  • field_publication_date_value%5Bmin%5D=&field_publication_date_value%5Bmax%5D=& : De manière identique au paramètre ‘fulltext’, tous ces paramètres sont vides, nous pouvons les ignorer ici

  • page=1 : il s’agit de l’index de la page web d’actualités. ‘page=1’ correspond donc à la deuxième page du site Internet. Nous pouvons donc en déduire (et vous pouvez le vérifier) que ‘page=0’ correspond à la première page d’actualités

Les paramètres vides dans une URL peuvent souvent être omis sans changer le résultat final. Par exemple, l’URL simplifiée https://statbel.fgov.be/fr/nouvelles?page=1 produit le même résultat que des versions avec paramètres vides. Le fait que l’index de la page peut être déclaré dans l’URL simplifie grandement notre approche de programmation, car elle permet de naviguer entre les pages en manipulant uniquement le paramètre de pagination directement depuis l’URL de base. En optant pour cette méthode, plutôt que de chercher et d’extraire des URL à partir du contenu de chaque page, nous bénéficions d’une programmation moins complexe et plus directe.

Nous allons commencer par créer un vecteur contenant les 288 pages à extraire. En pratique, pour éviter toute surcharge inutile des serveurs de Statbel, je vais me limiter ici aux 6 premières pages. Nous avons vu précédemment que la commande 0:5 crée un vecteur de 0 à 5. Nous allons combiner cela avec une nouvelle fonction paste permettant de concaténer des valeurs et/ou des vecteurs. Nous allons concaténer la chaîne de caractère “https://statbel.fgov.be/fr/nouvelles?page=” avec le vecteur 0:5, provoquant en sortie en vecteur de 5 chaînes de caractères pour les cinq URL. La fonction paste requiert un argument sep pour indiquer le séparateur à utiliser. Par défaut, paste utilise un espace comme séparateur entre chaque élément à concaténer :

vecteur_url <- paste("https://statbel.fgov.be/fr/nouvelles?page=",0:5,sep="") 
vecteur_url
[1] "https://statbel.fgov.be/fr/nouvelles?page=0"
[2] "https://statbel.fgov.be/fr/nouvelles?page=1"
[3] "https://statbel.fgov.be/fr/nouvelles?page=2"
[4] "https://statbel.fgov.be/fr/nouvelles?page=3"
[5] "https://statbel.fgov.be/fr/nouvelles?page=4"
[6] "https://statbel.fgov.be/fr/nouvelles?page=5"

2.4.2 Code complet

Nous avons à présent toutes les informations et toutes les fonctions nécessaires pour extraire toutes les actualités du site web. Comme précédemment, nous allons voir comment rédiger le code avec des boucles for puis avec des fonctions map.

2.4.2.1 Avec des boucles for

Nous allons commencer par déclarer le tableau qui contiendra toutes les données et le vecteur des pages à scraper. Ensuite, nous devons effectuer une première boucle for pour parcourir chaque page web. Puis, au sein de chaque page, nous devons effectuer une seconde boucle for pour parcourir chaque actualité. Chaque actualité sera ajouté dans le tableau global. Dans ce genre de double boucle, il est intéressant d’ajouter des fonctions print afin de savoir en temps réel l’état d’avancement du code.

# Déclaration du tableau
tableau_actu <- tibble(
  TX_TITRE = character(),
  TX_DATE = character(),
  TX_CATEGORIE = character(),
  TX_RESUME = character(),
  TX_TEXTE = character()
)

# Déclaration des pages d'actualités
vecteur_url <- paste("https://statbel.fgov.be/fr/nouvelles?page=",0:5,sep="") 

# Boucle sur les pages
for (url in vecteur_url){
  
  print(url) # Afficher l'URL en cours d'extraction
  
  page_actualites <- read_html(url)
  Sys.sleep(1)
  liste_actu <- page_actualites %>% html_elements("div.view-news-list div.views-row")
  
  # Boucle sur les actualités
  for (i in 1:10){
    
    print(i) # Afficher la progression de la boucle
    tableau_actu <- tableau_actu %>% 
      add_row(actu_to_table(liste_actu[[i]]))
    
  }
}

2.4.2.2 Avec la fonction map_df

L’utilisation de la fonction map_df présente l’avantage de ne pas nécessiter la création préalable du tableau, car elle permet de regrouper directement toutes les lignes issues de la fonction actu_to_table dans le tableau souhaité. Cependant, enchaîner plusieurs étapes avec map_df peut rendre le code difficile à lire, même si techniquement, cela reste une approche viable. Pour rendre le code plus clair, il est préférable de créer une nouvelle fonction url_to_table, qui prend en entrée une URL et qui donne en sortir le tableau avec les dix actualités.

url_to_table <- function(url){
  
  # Extraction de l'URL
  page_actualites <- read_html(url)
  Sys.sleep(1)
  
  # Liste des actualités
  liste_actu <- page_actualites %>% html_elements("div.view-news-list div.views-row")
  
  # actu_to_table sur la liste des actualités
  liste_actu %>% map_df(actu_to_table)
  
}

Avec l’introduction de cette nouvelle fonction, l’ensemble du processus de webscraping se simplifie considérablement, se réduisant à seulement deux lignes de code. La première ligne est destinée à définir la liste des URL à scraper. La seconde ligne utilise la fonction url_to_table pour extraire et compiler les informations de ces URL, stockant les informations extraites dans une base de données.

# Déclaration des pages d'actualités
vecteur_url <- paste("https://statbel.fgov.be/fr/nouvelles?page=",0:5,sep="")

tableau_actu <- vecteur_url %>% map_df(url_to_table)

Et voici notre tableau avec toutes les données :

tableau_actu
# A tibble: 50 × 5
   TX_TITRE                              TX_DATE TX_CATEGORIE TX_RESUME TX_TEXTE
   <chr>                                 <chr>   <chr>        <chr>     <chr>   
 1 194 faillites durant la semaine 20    23 mai… Entreprises  Durant l… "Durant…
 2 Modification des codes INS des commu… 22 mai… À propos de… Le code … "Le cod…
 3 Hausse de 1,6% du volume des ventes … 22 mai… Indicateurs… Le volum… "Le vol…
 4 Les femmes et les jeunes générations… 22 mai… Census       En Belgi… "En Bel…
 5 Baisse de 2,2% du volume des ventes … 22 mai… Indicateurs… Services… "Servic…
 6 28.157.734 animaux abattus en avril … 22 mai… Agriculture… Abattage… "Abatta…
 7 59 millions de litres de lait de con… 21 mai… Agriculture… En janvi… "En jan…
 8 2.444.610 nuitées en février 2024     21 mai… Entreprises  Le nombr… "Le nom…
 9 Mortalité jusqu’au 5 mai              17 mai… Population   Statbel … "Statbe…
10 Avril 2024: 925 faillites             17 mai… Entreprises  En avril… "En avr…
# ℹ 40 more rows

A l’issue de ce première exemple avec le site Internet de Statbel, nous avons exploré les étapes fondamentales pour extraire et manipuler des données depuis le web. Vous avez appris à configurer votre environnement dans RStudio, à installer et charger des packages essentiels, et à exécuter vos premiers scripts de webscraping. Le webscraping doit surtout être appris en étant confronté à des situations différentes, aussi nous allons reprendre les mêmes techniques de webscraping pour extraire des informations sur le site Internet Wikipédia. Ce sera l’objet de la section suivante.

Par ailleurs, si vous avec dès à présent une idée d’un site web contenant des données archivées sous un format plus ou moins proches que celles présentes sur le site de Statbel, n’hésitez pas à explorer dès à présent son code source et coder votre propre programme de webscraping.

2.5 Extraction du site web Wikipédia

Ce deuxième exemple de webscraping va réutiliser les fonctions vu précédemment pour les appliquer sur un nouveau projet. Chaque site web est différent donc les sélecteurs CSS3 devront être adaptés. Mais aussi, chaque logique d’extraction est différente. Ainsi, tout en gardant inchangées les fonctions à utiliser, analyser un autre projet permet de rendre plus évidente la pratique de webscraping. Pour cette section, sauf nécessité particulière, nous n’utiliserons que les sélecteurs CSS3 dans nos codes.

Il y a plusieurs raisons pour choisir Wikipédia pour ce deuxième exemple :

  • Wikipédia est librement accessible : Toute production réalisée sur Wikipédia est libre d’accès à quiconque, nous pouvons donc extraire sans problème des contenus pour les analyser ;

  • Wikipédia est pérenne : Bien sûr, il n’est pas possible de prédire l’avenir du site web, mais il s’agit d’un des plus vieux site web de compilation d’information, avec une structure HTML n’ayant que très peu évolué au fil du temps. Ceci permettra au code présenté ici de fonctionner encore dans quelques années, à moindre frais d’adaptation :

  • Wikipédia contient des informations structurées, des tableaux, des corpus, des images, tout ce dont nous pourrons avoir à traiter comme exemple ;

  • Wikipédia a une structure HTML simple à comprendre et à exploiter.

Notre première cible sera l’article Wikipédia sur la France. Celui-ci contient un long contenu texte, des encarts, des tableaux, des images, un historique riche. L’extraction se réalise à nouveau avec la fonction read_html :

page_france <- read_html("https://fr.wikipedia.org/wiki/France")

2.5.1 Extraction d’un encadré

Il nous faut à présent choisir un objectif d’extraction, c’est à dire une partie du site web que nous souhaitons obtenir sous la forme d’un tableau. Dans la grande majorité des articles de Wikipédia, un tableau récapitulatif est est affiché en haut à droite de l’article avec certaines informations structurées. Voici la capture de d’une partie de ce tableau en février 2023 pour l’article sur la France :

Encadré pour la France (février 2023)

Pour ce premier exercice, nous allons extraire les informations de cet encadré “Administration” et les structurer en tableau :

Objectif d’extraction
TX_ENCADRE TX_CATEGORIE TX_INFO
Administration Forme de l’état République unitaire semi-présidentielle
Administration Président de la république Emmanuel Macron
Administration

Bien entendu, comme pour l’exemple de Statbel, si vous n’avez besoin que de ce tableau pour la France, il sera bien plus rapide de le créer et le remplir manuellement. Néanmoins, réaliser un copier-coller des données de Wikipédia est long et nous envisageons ici, à la fin de cette section, d’avoir toutes les valeurs des encadrés de nombreux pays, c’est à dire plusieurs milliers de lignes.

Nous allons réaliser ceci en conservant en mémoire les deux objectifs plus généraux : de pouvoir extraire ensuite les autres encadrés de l’article sur la France ; de pouvoir extraire ces encadrés pour d’autres pages Wikipédia sur d’autres pays.

Mais avant de voir si grand, revenons à l’extraction et la structuration de l’encadré “Administration” pour la page Wikipédia sur la France. Voici le code source ce cet encadré :

Code Source de Wikipédia

Vous remarquez que le titre “Administration” est présent ainsi que les premières entrées du tableau (“Forme de l’état”, “République”, etc.), englobés dans des balises HTML que nous allons utiliser pour extraire les informations. Nous allons commencer par lister toutes les balises <caption> de la page web avec la commande html_elements :

html_elements(page_france,"caption")
{xml_nodeset (11)}
 [1] <caption class="hidden" style=""> </caption>\n
 [2] <caption style="background-color:#e3e3e3;">Administration</caption>\n
 [3] <caption style="background-color:#e3e3e3;">Géographie</caption>\n
 [4] <caption style="background-color:#e3e3e3;">Histoire</caption>\n
 [5] <caption style="background-color:#e3e3e3;">Démographie</caption>\n
 [6] <caption style="background-color:#e3e3e3;">Économie</caption>\n
 [7] <caption style="background-color:#e3e3e3;">Développement</caption>\n
 [8] <caption style="background-color:#e3e3e3;">Divers</caption>\n
 [9] <caption>Principales aires d'attraction des villes, unités urbains et co ...
[10] <caption>\nListe des membres du gouvernement Lecornu II\n</caption>
[11] <caption>Principaux organismes\n</caption>

On remarque ici que neuf balises sont listées, et nous retrouvons notre balise concernant “Administration” en deuxième position. Nous détectons les autres titres de l’encadré de Wikipédia, mais aussi d’autres éléments ne concernant pas cet encadré. Il y a donc plusieurs balises <caption> dans le code source, 6 d’entre elles (2 à 7) concernent des titres dans l’encart Wikipédia, les autres étant d’autres éléments présents ailleurs sur la page. Pour nous limiter aux résultats de l’encart, nous devons être plus précis dans notre recherche.

En regardant les balises HTML parentes de notre balise <caption>, on trouve une balise <table> contenant l’encadré “Administration”, ainsi qu’une balise <div> contenant l’ensemble des encadrés. Pour trouver cela, une fois la fenêtre du code source ouverte sur votre navigateur, vous pouvez survoler les différentes balises et voir la partie du site web correspondante être surligné.

Ainsi, la balise <div> contenant l’attribut class = "infobox_v3 noarchive" contient les encadrés que nous cherchons à extraire, et uniquement ces encadrés. Et dans cette balise <div>, la deuxième balise <table> contient l’encadré “Administration”, et nous pouvons anticiper que chaque encadré est contenu dans une balise <table>. L’enjeu du webscraping se situe essentiellement sur ce point : trouver une balise HTML contenant ce que vous cherchez, sans oublier des éléments et aussi sans extraire trop d’éléments.

Code Source de wikipédia

Ici, la balise <div> contient deux classes : “infobox_v3” et “noarchive”. Les différentes classes d’une balise sont séparées par un espace, et il possible d’extraire la balise en précisant l’une des classes. Ici nous allons utiliser la classe “infobox_v3”, car on anticipe qu’elle sera utilisé pour les autres encadrés :

html_elements(page_france,"div.infobox_v3")
{xml_nodeset (1)}
[1] <div class="infobox_v3 infobox infobox--frwiki noarchive" style="width:23 ...

Ici, nous obtenons bien une seule balise <div>, mais notre objectif reste d’obtenir l’encadré “Administration”. Nous allons donc, à partir de cette première balise <div>, descendre dans l’arborescence du code source pour atteindre la balise <table> :

html_elements(page_france,"div.infobox_v3 table")
{xml_nodeset (9)}
[1] <table style="width:100%"><tbody>\n<tr>\n<td colspan="1" align="center">\ ...
[2] <table>\n<caption class="hidden" style=""> </caption>\n<tbody>\n<tr>\n<th ...
[3] <table>\n<caption style="background-color:#e3e3e3;">Administration</capti ...
[4] <table>\n<caption style="background-color:#e3e3e3;">Géographie</caption>\ ...
[5] <table>\n<caption style="background-color:#e3e3e3;">Histoire</caption>\n< ...
[6] <table>\n<caption style="background-color:#e3e3e3;">Démographie</caption> ...
[7] <table>\n<caption style="background-color:#e3e3e3;">Économie</caption>\n< ...
[8] <table>\n<caption style="background-color:#e3e3e3;">Développement</captio ...
[9] <table>\n<caption style="background-color:#e3e3e3;">Divers</caption>\n<tb ...

On remarque ici que l’on extrait huit tables, mais le code ici ne nous permet pas de savoir ce que nous avons effectivement extrait. Dans ce cas, vous devez manuellement afficher certains éléments de la liste pour en comprendre le contenu. Pour cela, il faut d’abord assigner le résultat de la fonction html_elements à un objet (qui sera une liste), puis de demander à R d’afficher un élément précis de cette liste avec son index entre deux crochets :

liste_encadres <- html_elements(page_france,"div.infobox_v3 table")
liste_encadres[[1]]
{html_node}
<table style="width:100%">
[1] <tbody>\n<tr>\n<td colspan="1" align="center">\n<span class="mw-image-bor ...
liste_encadres[[2]]
{html_node}
<table>
[1] <caption class="hidden" style=""> </caption>\n
[2] <tbody>\n<tr>\n<th scope="row" style="width:9em;"><a href="/wiki/Liste_de ...
liste_encadres[[3]]
{html_node}
<table>
[1] <caption style="background-color:#e3e3e3;">Administration</caption>\n
[2] <tbody>\n<tr>\n<th scope="row" style="width:9em;">Forme de l'État</th>\n< ...

Ici, la table “Administration” se trouve en troisième position de la liste, on retrouve ce texte dans la balise <caption>. Le premier élément de la liste ne contient pas de balise <caption>, tandis que le deuxième contient une balise <caption> vide. A ce stade, nous allons simplement choisir le troisième encadré, avant d’obtenir l’encadré “Administration” :

encadre_administration <- liste_encadres[[3]]

Une fonction du package rvest permet d’obtenir, l’ensemble du texte de cet élément, c’est à dire celui qui est affiché sur un navigateur internet. Néanmoins, ceci est tout aussi inutile que de sauvegarder l’ensemble du texte de la page Wikipédia. Sans structuration des données en tableaux, ces données restent essentiellement inutilisables (voir ci-dessous). On remarque tout de même que l’ensemble des informations que l’on souhaite obtenir s’y trouve (Forme de l’état, président de la république, …)

html_text(encadre_administration)
[1] "AdministrationForme de l'État\n\nRépublique unitaire à régime semi-présidentiel\nPrésident de la République\n\nEmmanuel Macron\nPremier ministre\n\nSébastien Lecornu\nPrésident du Sénat\n\nGérard Larcher\nPrésidente de l'Assemblée nationale\n\nYaël Braun-Pivet\nParlement\n\nParlement français\nChambre hauteChambre basse\n\nSénatAssemblée nationale\nLangue officielle\n\nFrançais\nCapitale\n\nParis48° 52′ N, 2° 19,59′ E\n"

En observant le code source de la table, vous remarquez qu’il s’agit d’un enchaînement de balises <tr> pour indiquer une nouvelle ligne, de balises <th> pour la colonne de gauche et de balises <td> pour la colonne de droite. Il serait possible d’extraire ainsi chaque information (il s’agirait même d’un bel exercice de boucles en R), mais nous allons ici récupérer l’information souhaitée d’une manière bien plus simple. En effet, le package rvest contient la fonction html_table qui automatise cette extraction et structure une table directement dans un tableau, dès lors que le tableau respecte le formatage HTML standard (ce qui est le cas ici).

html_table(encadre_administration)
# A tibble: 9 × 2
  X1                                  X2                                        
  <chr>                               <chr>                                     
1 Forme de l'État                     République unitaire à régime semi-préside…
2 Président de la République          Emmanuel Macron                           
3 Premier ministre                    Sébastien Lecornu                         
4 Président du Sénat                  Gérard Larcher                            
5 Présidente de l'Assemblée nationale Yaël Braun-Pivet                          
6 Parlement                           Parlement français                        
7 Chambre hauteChambre basse          SénatAssemblée nationale                  
8 Langue officielle                   Français                                  
9 Capitale                            Paris48° 52′ N, 2° 19,59′ E               

Nous obtenons ainsi un tableau avec deux colonnes, l’une pour la catégorie d’informations, et un pour l’information elle-même. Nous sommes très proches du résultat attendu, il nous faut pour cela renommer les deux colonnes, ajouter la colonne avec le nom de l’encadré, le nom du pays et la date d’extraction. Ces trois informations seront identiques pour toutes les lignes du tableau, mais rappelez vous l’objectif final d’extraire tous les encadrés, tous les pays, et à des moments différents.

Renommer les colonnes se fait avec la fonction rename du package tidyverse. Il faut indiquer le nom de la nouvelle colonne comme substitut à la colonne actuelle, et il est possible d’enchaîner les commandes en les séparant par des virgules :

table_administration <- html_table(encadre_administration) %>% 
  rename(TX_CATEGORIE = X1,TX_INFO = X2)

table_administration
# A tibble: 9 × 2
  TX_CATEGORIE                        TX_INFO                                   
  <chr>                               <chr>                                     
1 Forme de l'État                     République unitaire à régime semi-préside…
2 Président de la République          Emmanuel Macron                           
3 Premier ministre                    Sébastien Lecornu                         
4 Président du Sénat                  Gérard Larcher                            
5 Présidente de l'Assemblée nationale Yaël Braun-Pivet                          
6 Parlement                           Parlement français                        
7 Chambre hauteChambre basse          SénatAssemblée nationale                  
8 Langue officielle                   Français                                  
9 Capitale                            Paris48° 52′ N, 2° 19,59′ E               

Pour ajouter le nom de l’encadré (ici “Administration”), il serait possible de créer manuellement une nouvelle colonne “TX_ENCADRE” avec la valeur “Administration”. Mais faire ceci ne serait pas très efficace, car nous devons anticiper l’automatisation pour l’ensemble des encadrés. Ainsi, il est préférable d’extraire le nom de l’encadré directement dans le code. Nous avions vu précédemment que le nom de l’encadré est contenu dans la balise <caption>, nous allons ici l’extraire à partir de l’objet encadre_administration. Comme nous nous attendons à un seul élément à extraire, nous pouvons utiliser la fonction html_element (sans le “s” en fin de fonction), celle-ci renvoi un objet seul et non une liste (dans le cas où plusieurs élément HTML serait disponible, la fonction renvoi le premier). Cet objet seul est plus facile à manipuler qu’une liste, et cela est cohérent dès lors que nous n’attendons qu’un seul titre d’encadré. Avec la fonction html_text, on extrait le texte de cet élément.

titre_encadre <- encadre_administration %>% html_element("caption") %>% html_text()
titre_encadre
[1] "Administration"

Ensuite, avec tidyverse, il est possible d’ajouter une colonne dans une base de données à l’aide de la fonction mutate. Nous allons ajouter deux colonnes : le titre de l’encadré, et un horodateur de l’extraction de la page web. Cette dernière permettra de réaliser des comparaisons historiques en enregistrant précisément le moment de l’extraction des données.

table_administration <- table_administration %>% 
  mutate(TX_ENCADRE = titre_encadre,
         TX_TIMESTAMP = now())

table_administration
# A tibble: 9 × 4
  TX_CATEGORIE                        TX_INFO     TX_ENCADRE TX_TIMESTAMP       
  <chr>                               <chr>       <chr>      <dttm>             
1 Forme de l'État                     République… Administr… 2025-12-07 21:16:04
2 Président de la République          Emmanuel M… Administr… 2025-12-07 21:16:04
3 Premier ministre                    Sébastien … Administr… 2025-12-07 21:16:04
4 Président du Sénat                  Gérard Lar… Administr… 2025-12-07 21:16:04
5 Présidente de l'Assemblée nationale Yaël Braun… Administr… 2025-12-07 21:16:04
6 Parlement                           Parlement … Administr… 2025-12-07 21:16:04
7 Chambre hauteChambre basse          SénatAssem… Administr… 2025-12-07 21:16:04
8 Langue officielle                   Français    Administr… 2025-12-07 21:16:04
9 Capitale                            Paris48° 5… Administr… 2025-12-07 21:16:04

Nous avons à présent un tableau de 9 lignes et de 4 colonnes, structurant toutes les informations de l’encadré “Administration”. Il y aurait encore du nettoyage à réaliser (par exemple la catégorie de la ligne 7 est “Chambre hauteChambre basse”, l’erreur provient de Wikipédia qui indique ces deux termes à la suite, avec uniquement un saut de ligne qui disparaît ici.), mais notre objectif d’extraction de données est atteint. La suite du travail va consister en une boucle pour obtenir plus d’encadrés, puis pour plus de pays.

Voici résumé les quelques commandes nécessaires pour obtenir le tableau d’encadré sur l’administration pour la France :

page_france <- read_html("https://fr.wikipedia.org/wiki/France")
liste_encadres <- html_elements(page_france,"div.infobox_v3 table")
encadre_administration <- liste_encadres[[3]]
table_administration <- html_table(encadre_administration) %>% 
  rename(TX_CATEGORIE = X1,TX_INFO = X2)
titre_encadre <- encadre_administration %>% html_element("caption") %>% html_text()
table_administration <- table_administration %>% 
  mutate(TX_ENCADRE = titre_encadre,TX_TIMESTAMP = now())

2.5.2 Extraction de plusieurs encadrés

Dans ce premier exemple, l’encadré “Administration” est localisé par le troisième élément de la liste liste_encadres. Pour localiser et structurer un autre encadré, il faut modifier cet index de liste. Pour traiter chaque encadré, nous allons créer une fonction encadre_to_table qui produira le tableau à partir d’un encadré. Voici l’écriture de cette fonction ainsi qu’un exemple d’utilisation de cette fonction :

encadre_to_table <- function(encadre){
  titre_encadre <- encadre %>% html_element("caption") %>% html_text()
  table_encadre <- html_table(encadre) %>% 
    rename(TX_CATEGORIE = X1,TX_INFO = X2) %>% 
    mutate(TX_ENCADRE = titre_encadre, TX_TIMESTAMP = now())
  
  table_encadre
}

encadre_to_table(liste_encadres[[4]])
# A tibble: 4 × 4
  TX_CATEGORIE        TX_INFO                     TX_ENCADRE TX_TIMESTAMP       
  <chr>               <chr>                       <chr>      <dttm>             
1 Plus grandes villes Paris, Marseille, Lyon,Tou… Géographie 2025-12-07 21:16:04
2 Superficie totale   672 051[1],[2],[N 1] km2(c… Géographie 2025-12-07 21:16:04
3 Superficie en eau   0,26 %                      Géographie 2025-12-07 21:16:04
4 Fuseau horaire      UTC +1 (HNEC, heure d'hive… Géographie 2025-12-07 21:16:04

Cette nouvelle fonction personnalisée permet de simplifier l’écriture du code. En effet, si la structuration d’un encadré sur Wikipédia est similaire quelle que soit la page extraite (et c’est là tout l’objet du Webscraping), écrire cette fonction résume votre code et permet de séparer les boucles R de navigation et les sélecteurs CSS3. Aussi, si le site web change de code source1, vous devrez uniquement mettre à jour la fonction et non chercher dans l’intégralité de votre code R là où la mise à jour est nécessaire.

L’utilisation d’une fonction personnalisée permet également d’utiliser les fonctionnalités du package purrr du tidyverse afin de réaliser, en une fois, la structuration de tous les encadrés pour obtenir une seule base de données en sortie. La fonction map permet, en indiquant une liste et une fonction, d’appliquer cette fonction à l’ensemble des éléments de la liste. Le résultat en sortie est une liste, de la même taille que la liste initiale. Une variante de la fonction map est la fonction map_df, qui regroupe les sorties sous la forme d’un tableau :

liste_encadres %>% map_df(encadre_to_table)
# A tibble: 55 × 4
   TX_CATEGORIE                        TX_INFO    TX_ENCADRE TX_TIMESTAMP       
   <chr>                               <chr>      <chr>      <dttm>             
 1 Drapeau de la France                "Armoirie…  <NA>      2025-12-07 21:16:05
 2 Devise                              "Liberté,… " "        2025-12-07 21:16:05
 3 Hymne                               "La Marse… " "        2025-12-07 21:16:05
 4 Fête nationale                      "14 juill… " "        2025-12-07 21:16:05
 5 · Événement commémoré               "La prise… " "        2025-12-07 21:16:05
 6 Forme de l'État                     "Républiq… "Administ… 2025-12-07 21:16:05
 7 Président de la République          "Emmanuel… "Administ… 2025-12-07 21:16:05
 8 Premier ministre                    "Sébastie… "Administ… 2025-12-07 21:16:05
 9 Président du Sénat                  "Gérard L… "Administ… 2025-12-07 21:16:05
10 Présidente de l'Assemblée nationale "Yaël Bra… "Administ… 2025-12-07 21:16:05
# ℹ 45 more rows

2.5.3 Extraction de plusieurs pages

Nous avons à présent la possibilité d’extraire l’ensemble des encadrés de la page Wikipédia sur la France de les structurer dans une base de données R. Pour généraliser cela et obtenir les encadrés pour plusieurs pays, nous allons créer une fonction url_to_encadres qui prendra en entrée une URL à extraire et en sortie la base de données des encadrés. Une telle fonction se servira de notre fonction personnalisée encadre_to_table, mais devra ajouter une nouvelle variable à la base de données. En effet, pour l’instant, le nom du pays n’est pas indiqué, donc si une extraction devait avoir lieu sur plusieurs pays à la suite, il ne serait pas possible, après coup, de retrouver le pays concerné dans les données.

En parcourant de nouveau le code source du site web Wikipédia, le nom du pays peut s’extraire avec une balise <div> ayant pour classe “entete” au début de l’infobox que nous avons extrait. Il est également possible de l’extraire avec une balise <span> ayant pour classe “mw-page-title-main”. Les deux résultats sont différents (ici “République Française” contre “France”), Lorsque vous êtes dans un tel cas de figure, à vous de choisir l’information qui semble la plus pertinente et la plus simple à extraire. Nous choisirons ici la deuxième solution, afin de récupérer uniquement “France”.

page_france %>% html_element("div.infobox_v3 div.entete") %>% html_text()
[1] "\nRépublique française\n"
page_france %>% html_element("span.mw-page-title-main") %>% html_text()
[1] "France"

Nous avons maintenant tous les éléments pour écrire notre fonction url_to_encadres qui prend l’URL d’une page Wikipédia en entrée pour en structurer et afficher les encadrés. Nous testons ensuite cette fonction avec la page Wikipédia sur la Belgique :

url_to_encadres <- function(url){
  page <- read_html(url)
  nom_pays <- page %>% html_element("span.mw-page-title-main") %>% html_text()
  liste_encadres <- html_elements(page,"div.infobox_v3 > table")
  table_encadres <- map_df(liste_encadres,encadre_to_table) %>% 
    mutate(TX_PAGE = nom_pays)
  
  table_encadres 
}
url_to_encadres("https://fr.wikipedia.org/wiki/Belgique")
# A tibble: 54 × 5
   TX_CATEGORIE                   TX_INFO TX_ENCADRE TX_TIMESTAMP        TX_PAGE
   <chr>                          <chr>   <chr>      <dttm>              <chr>  
 1 Drapeau de la Belgique         "Armoi…  <NA>      2025-12-07 21:16:05 Belgiq…
 2 Devise                         "en fr… " "        2025-12-07 21:16:05 Belgiq…
 3 Hymne                          "La Br… " "        2025-12-07 21:16:05 Belgiq…
 4 Fête nationale                 "21 ju… " "        2025-12-07 21:16:05 Belgiq…
 5 · Événement commémoré          "Prest… " "        2025-12-07 21:16:05 Belgiq…
 6 Forme de l'État                "Monar… "Administ… 2025-12-07 21:16:05 Belgiq…
 7 Roi                            "Phili… "Administ… 2025-12-07 21:16:05 Belgiq…
 8 Premier ministre               "Bart … "Administ… 2025-12-07 21:16:05 Belgiq…
 9 Président de la Chambre des r… "Peter… "Administ… 2025-12-07 21:16:05 Belgiq…
10 Président du Sénat             "Vince… "Administ… 2025-12-07 21:16:05 Belgiq…
# ℹ 44 more rows

Il est maintenant possible d’extraire, en une seule fois, plusieurs page web Wikipédia, et d’en extraire les encadrés. La fonction map_df est utilisable de la même manière si nous disposons d’un vecteur d’URL de pages web Wikipédia. Nous allons créer un vecteur avec toutes les URL que nous souhaitons parcourir. Pour conserver la thématique des pays, et en ciblant les pays européens, voici un exemple d’usage à partir d’un vecteur d’URL. La première ligne du code prépare un vecteur de nom de pays (j’ai pris soin ici de prendre des noms de pays en un mot et sans caractères spéciaux, afin d’être certain que l’URL des pages Wikipédia soit facile à créer). La deuxième ligne concatène l’URL de base de Wikipédia avec ce vecteur, créant ainsi un vecteur d’URL de pages à extraire.

vecteur_nom_pays <- c("France","Belgique","Allemagne","Espagne","Italie","Danemark")
vecteur_url_pays <- paste("https://fr.wikipedia.org/wiki/",vecteur_nom_pays,sep="")

vecteur_url_pays
[1] "https://fr.wikipedia.org/wiki/France"   
[2] "https://fr.wikipedia.org/wiki/Belgique" 
[3] "https://fr.wikipedia.org/wiki/Allemagne"
[4] "https://fr.wikipedia.org/wiki/Espagne"  
[5] "https://fr.wikipedia.org/wiki/Italie"   
[6] "https://fr.wikipedia.org/wiki/Danemark" 

Il reste à présent à utiliser la fonction map_df pour obtenir, à partir de ce vecteur d’URL, un tableau contenant toutes les informations des encadrés.

table_encadres <- vecteur_url_pays %>% map_df(url_to_encadres)
table_encadres
# A tibble: 306 × 5
   TX_CATEGORIE                   TX_INFO TX_ENCADRE TX_TIMESTAMP        TX_PAGE
   <chr>                          <chr>   <chr>      <dttm>              <chr>  
 1 Drapeau de la France           "Armoi…  <NA>      2025-12-07 21:16:06 France 
 2 Devise                         "Liber… " "        2025-12-07 21:16:06 France 
 3 Hymne                          "La Ma… " "        2025-12-07 21:16:06 France 
 4 Fête nationale                 "14 ju… " "        2025-12-07 21:16:06 France 
 5 · Événement commémoré          "La pr… " "        2025-12-07 21:16:06 France 
 6 Forme de l'État                "Répub… "Administ… 2025-12-07 21:16:06 France 
 7 Président de la République     "Emman… "Administ… 2025-12-07 21:16:06 France 
 8 Premier ministre               "Sébas… "Administ… 2025-12-07 21:16:06 France 
 9 Président du Sénat             "Gérar… "Administ… 2025-12-07 21:16:06 France 
10 Présidente de l'Assemblée nat… "Yaël … "Administ… 2025-12-07 21:16:06 France 
# ℹ 296 more rows

Ce code permet d’extraire une base de données de 275 lignes, aussi il devient difficile de visualiser ce que nous avons extrait et de vérifier la validité de notre code. Sans rentrer trop dans l’analyse de données avec R, les fonctions count et pivot_wider du package tidyverse permettent de générer un tableau de synthèse comptant le nombre de lignes par page Wikipédia extraite et nom de l’encadré. Cela vous donne déjà un aperçu de la collecte effectuée.

table_encadres %>%
  count(TX_PAGE,TX_ENCADRE) %>%
  pivot_wider(names_from = TX_PAGE, values_from = n)
# A tibble: 9 × 7
  TX_ENCADRE       Allemagne Belgique Danemark Espagne France Italie
  <chr>                <int>    <int>    <int>   <int>  <int>  <int>
1 " "                      4        4        4       4      4      3
2 "Administration"         9        9        6      10      9      7
3 "Divers"                 5        5        5       5      5      5
4 "Démographie"            3        3        3       3      3      3
5 "Développement"          5        5        5       5      5      5
6 "Géographie"             4        4        4       4      4      4
7 "Histoire"              12       16        6      19     18     13
8 "Économie"               7        7        7       7      6      7
9  <NA>                    1        1        1       1      1      1

L’extraction fonctionne ainsi sur tous les pays, car chaque page Wikipédia possède le même code source pour structurer les encadrés. Aussi, l’homogénéité de Wikipédia permet d’avoir les mêmes noms d’encadrés sur les différentes pages web. D’ailleurs, même si les noms des encadrés sont différents, un même code est utilisable pour extraire les encadrés pour des pages Wikipédia sur d’autres sujets que les pays.

3 Extraction de sites web dynamiques

Jusqu’à présent, les deux exemples abordés, Statbel et Wikipédia, sont deux sites faciles à extraire. Il n’y a aucun blocage en amont de l’accès au code source. Les balises HTML sont identiques à travers les pages et à travers le temps. Ce type de site web est fréquent sur Internet, et les packages abordés jusqu’à présent seront souvent suffisant pour vos projets.

Néanmoins, cette technique d’extraction de code source ne couvre pas l’ensemble des sites web dont le contenu est généré dynamiquement. Les sites web de réseaux sociaux, dont le contenu est affiché continuellement et de manière ininterrompu, sont en réalité vide, ou presque vide, lors de leur chargement. Dès lors, une extraction du code source va renvoyer un ensemble vide ou presque. Les sites web dynamiques peuvent être identifiés lors de l’ouverture des pages. Celles-ci apparaissent avec un temps de latence, ou progressivement (infinite scroll), et nécessitent des outils avancés que nous allons présenter dans cette section.

La plupart des sites web générant leur contenu de manière dynamique le font afin de rendre leur contenu dynamique et personnalisé en fonction de l’utilisateur·rice. Dans la même logique, beaucoup de sites web protègent leur contenu derrière un système de connexion et parfois d’abonnement. Cette démarche n’a pas pour objectif de contrer l’extraction des données, mais il faut bien reconnaître que c’est l’une des conséquences. Vous ne pouvez pas, dans ce cas, extraire un code source qui sera alors vide ou presque.

Aussi, pour contrer l’ensemble de ces problèmes, nous allons aborder ici les techniques pour émuler un navigateur web. Ce navigateur web aura les mêmes caractéristiques que votre navigateur préféré, et intégrera nativement la gestion des contenus générés de manière dynamique. Il sera possible d’interagir avec ce navigateur émulé, avec l’injection de cookies pour des identifiants et mots de passe, la simulation de touches de clavier ou de clics de souris ou encore le remplissage de formulaires.

3.1 Création et utilisateur de l’émulateur

Nous n’aurons pas besoin d’un package supplémentaire dans R, car le package rvest intègre nativement depuis janvier 2024 un émulateur de navigateur chrome basé sur le package chromote. Toutefois, vous avez besoin, si ce n’est pas déjà fait, d’installer chrome sur votre ordinateur. Pas besoin de compte google ou d’en faire votre navigateur par défaut, il n’est question ici que de l’utiliser pour les besoin du webscraping.

Actuellement (en date du 1er mars 2025), l’usage du package nécessite, au préalable, une commande pour activer la nouvelle version de l’émulateur sous chromote. Je suppose que cette commande ne sera plus nécessaire à l’avenir, mais dans le doute, la voici :

options(chromote.headless = "new")

Ici, la fonction read_html_live réalise le même travail que la fonction read_html abordé précédemment, mais exécute cela grâce à l’émulation du site web. D’ailleurs, votre objet page_html contient désormais plus qu’un simple code source. La meilleur manière de s’en assurer et d’afficher cet émulateur et la page qu’il contient.

chrome <- read_html_live("https://statbel.fgov.be/fr/nouvelles")
chrome$view()

Cette commande sur votre objet devrait normalement ouvrir chrome, et ouvrir l’émulateur avec la page web actuellement en cours. Si jamais vous voyez une page blanche, c’est sans doute qu’il faut encore accepter l’affichage de l’émulateur, en haut à droite de chrome, dans la barre de recherche.

Activer le devtools en cliquant sur ce bouton

Tout d’abord, cet objet page_html peut être utilisé comme précédemment : les fonctions html_element(s) ou html_text ont exactement les mêmes arguments et sorties. Jusqu’ici, il n’y a aucun intérêt à utiliser cet émulateur. Au contraire même, son usage est moins stable qu’une extraction de code source, donc il est préférable de l’éviter lorsque cela n’est pas nécessaire.

chrome %>% html_elements("h3") %>% html_text()

Prenons maintenant un autre exemple : le réseau social Bluesky. Celui-ci permet, même sans inscription, d’afficher les posts les plus populaires. Ici, une extraction simple du code source va renvoyer un ensemble vide, car le site web n’a “pas le temps” d’afficher les messages :

page_bluesky <- read_html("https://bsky.app/") 

page_bluesky %>% 
  html_elements("div.r-1cvj4g8") %>% 
  html_text2()
character(0)

A l’inverse, avec un read_html_live, suivi d’une pause afin de permettre le temps du chargement des posts, la fonction html_elements permet à présent d’obtenir un vecteur avec désormais une longue série de messages (la fonction head permet de se limiter ici à quelques messages pour l’affichage) :

rm(page_bluesky)
page_bluesky <- read_html_live("https://bsky.app/")
Sys.sleep(5)
page_bluesky %>% 
  html_elements("div.r-1cvj4g8") %>% 
  html_text2() %>% 
  head()
[1] "‪Landguy‬ ‪@landguyminor.bsky.social‬\n· 1j\nBlanketing clouds in the sky. #blueskyartshow #sky #clouds #photography #mountains #cloudy #eastcoastkin #artyear\nALT\n50\n305\n3,7 k"                                                                                                                                                                         
[2] "‪Nini_MacBright‬ ‪@ninimacbright.bsky.social‬\n· 12 min\nD’après mon calendrier de l’Avent, Noël est passé depuis 7 jours Je vous souhaite donc une très belle année 2026, et je vous donne rendez-vous dans 6 jours pour la Galette des Rois\n3\n1\n24"                                                                                                     
[3] "‪Forbes‬\n ‪@forbes.com‬\n· 5 h\nThe next Powerball drawing will take place on Monday night.\nPowerball Jackpot Climbs To $875 Million—But A Winner Would Get Much Less After TaxesThe next Powerball drawing will take place on Monday night.www.forbes.com\n115\n116\n567"                                                                                 
[4] "‪The Verge‬\n ‪@theverge.com‬\n· 1j\nMad Men’s special effects foreman hasn’t seen the infamous 4K remaster\nMad Men’s special effects foreman hasn’t seen the infamous 4K remaster“I had to watch a lot of videos of people vomiting.”buff.ly\n2\n3\n56"                                                                                                    
[5] "‪MsMeeea‬ ‪@msmeeea.bsky.social‬\n· 24 min\nJ'y repense. Au fait que dans Friends la relation entre Monica et Richard le pote de son père est montrée comme Romantique\n2\n15"                                                                                                                                                                               
[6] "‪Krista D. Ball: Canada's Snarky Potato‬ ‪@kristadb1.bsky.social‬\n· 3 h\nI spent most of the morning reading my new book, and then I started working on my Christmas bunting. I have everything cut out, and just need to work up with courage to hand sew everything in pretty embroidery stitches that I only vaguely remember how to do lol\n14\n13\n399"

Ce simple passage par l’émulateur permet donc d’obtenir un code source différent, et de profiter du contenu dynamique du site. Bluesky permet, en scrollant, de faire apparaître davantage de messages. Il est possible d’émuler ce scrolling avec la commande scroll_by, en indiquant le nombre de pixels. Avec un scrolling de 50000 pixels, on atteint facilement la fin de la page, et on active le chargement de la suite du site web. Avec une simple boucle, on peut atteindre 10 fois le bas de la page, et ainsi faire apparaître autant de nouveaux messages. La fonction length ci-dessous permet d’afficher le nombre de messages obtenus après 10 itérations :

for (k in 1:10){ 
  page_bluesky$scroll_by(50000) 
  Sys.sleep(2) 
}
page_bluesky %>% html_elements("div.r-13awgt0") %>% length()
[1] 462

Si votre objectif est de scroller une seule fois l’intégralité d’un site web, pour ensuite extraire le contenu de celui-ci, sans navigation supplémentaire, je vous conseille d’extraire le code de l’objet dynamique que vous avez créé. Ainsi, vous pourrez sauvegarder ce code source pour une exploration ultérieure, et vous risquez moins d’éventuelles coupures de réseau ou d’instabilités propre à chromote. Voici un code pour effectuer cela :

doc <- page_bluesky$session$DOM$getDocument()
page <- unlist(page_bluesky$session$DOM$getOuterHTML(doc$root$nodeId))
page_bluesky_statique <- read_html(page)

Une fois le code source récupéré, que ce soit en dynamique ou en statique, les codes pour transformer l’information en tableau structuré reste inchangé. Néanmoins, les codes sources de réseaux sociaux ont souvent des structures bien plus complexes à saisir que des sites internet statiques, avec des multiples argument class sans signification évidente. De plus, ces balises changent régulièrement, aussi votre code R sera sans doute à réviser régulièrement. Je vous livre ici une fonction permettant l’extraction des posts bluesky, fonctionnelle au 12/10/2025.

extract_post_bluesky <- function(post){
  
  url_profil <- post %>% html_element("a") %>% html_attr("href")
  name_profil <- post %>% 
    html_element("a[aria-label='Voir le profil']") %>% html_text()
  
  text <- post %>% html_element("div[data-testid='postText']") %>% html_text2()
  
  nb_reply <- post %>% 
    html_element("button[data-testid='replyBtn']") %>% 
    html_text2() %>% as.numeric()
  
  nb_repost <- post %>% 
    html_element("div[data-testid='repostCount']") %>% 
    html_text2() %>% as.numeric()
  
  nb_likes <- post %>% 
    html_element("button[data-testid='likeBtn']") %>% 
    html_text2() %>% as.numeric()
  
  tibble(URL = url_profil,
         NAME = name_profil,
         TEXT = text,
         NB_REPLY = nb_reply,
         NB_REPOST = nb_repost,
         NB_LIKES = nb_likes)
}

Ici, le fait de structurer votre fonction avec chaque élément bien séparé vous permettra de retourner ensuite la corriger au besoin. Il ne reste plus qu’à extraire les posts du jour et de les structurer en tableau à l’aide de votre fonction personnalisée.

liste_actu <- page_bluesky %>% html_elements("div.r-1cvj4g8")

table_bluesky <- liste_actu %>% map_df(extract_post_bluesky)

Parfois, les sites web détectent le navigateur utilisé, et peuvent adapter le code source en fonction du navigateur. Ainsi, les balises et les attributs de class pourraient être différentes. Si vous ne retrouvez pas votre balise, essayez de localiser une balise parente, et appliquer la fonction as.character() sur l’objet page_bluesky afin d’afficher l’ensemble du code source et de comprendre les modifications.

Aussi, comme déjà expliqué, le webscraping par émulation de navigateur est plus instable, car il repose sur un intermédiaire pour obtenir le code source. Il est important de générer des pauses régulières dans le code pour assurer le chargement des pages, et de garnir votre code de tests de présence de chaque élément que vous cherchez à collecter.

Enfin, il n’est pas possible, à ma connaissance, d’avoir deux émulateurs fonctionnant en parallèle. Vous devez donc prévoir votre code et vos boucles afin de collecter, en premier lieu, tous les éléments d’une page principale, avant d’aller explorer chacune des pages annexes. Une autre possibilité, plus stable à mon sens, est d’utiliser l’émulateur pour collecter les url des pages que vous cherchez à extraire puis, si ces url sont statiques, d’en extraire les codes sources avec un simple read_html. Bref, plus encore que pour les pages statiques, l’extraction de pages dynamiques requiert une conceptualisation de votre parcours d’extraction avant de passer à la rédaction du code.

3.2 Fonctionnalités accessibles avec l’émulateur

L’émulation du navigateur permet toute une série de nouvelles actions utiles pour le webscraping. Certaines sont utiles pour accéder aux données, d’autres sont utiles pour documenter votre travail d’extraction. Nous allons les lister ici, sans que celle-ci soit exhaustive. Gardez à l’esprit que les packages rvest et chromote évoluent beaucoup encore ces dernières années, et qu’il peut être utile d’aller relire régulièrement les documentations des deux packages2.

3.2.2 Enregistrement et cookies

De nombreux sites internet réclament une authentification pour accéder à leur contenu. Que ce soit un journal en ligne protégeant ses articles derrière un paywall, un site communautaire réservé à ses membres, ou un réseau social personnalisant son contenu, s’identifier sur un site internet avant de le consulter est une pratique courante. Celle-ci peut être simulé avec le rvest_live et le chromote, avec l’utilisation des cookies. Les cookies sont des fichiers textes stockés sur votre ordinateur contenant les éléments d’identification sur les sites internet, vous permettant d’éviter de devoir vous identifier à chaque visite. Il est possible d’enregistrer ces cookies, lors d’une première visite d’un site web, pour s’en servir lors des collectes automatiques.

NB : cela implique bien entendu de posséder un compte valide pour accéder au contenu du site web. Ce manuel ne vous explique absolument pas comment accéder à des contenus protégés d’une autre manière.

Vous pouvez tester une authentification avec le site internet https://web-scraping.dev qui contient une multitude d’exemples de webscraping à réaliser. Vous devez d’abord naviguer vers la page d’authentification puis vous identifier manuellement :

Vous vérifier visuellement que l’authentification s’est bien déroulé, puis vous collectez les cookies pour les sauver dans votre répertoire de travail actuel :

Imaginons que vous quittiez la session pour la relancer plus tard, nous pouvons simuler cela en supprimant tous les cookies actuels. Vous retournez alors sur la page d’authentification

Pour charger automatiquement les cookies. Il faut accéder à la fonction setCookies de notre objet et insérer les cookies préalablement sauvegardés.

Tant que le cookie est valide, il est possible de l’utiliser dans vos scripts afin de vous identifier puis de collecter les données avec les techniques abordées précédemment. Remarque tout de même concernant la collecte intensive de données : ici, vous êtes identifiés directement sur la page internet sur laquelle vous réaliser le webscraping. Une détection d’une activité suspecte pourra avoir des conséquences plus lourdes, par exemple une radiation de votre compte. Une fois encore, l’accès à un site web n’autorise pas automatiquement les pratiques d’extraction massive sur celui-ci.

Parfois, un simple rvest pour extraire un code source ne suffit plus : certains sites empêchent toute visite directe en ne servant qu’un squelette HTML vide et en chargeant le contenu ensuite via du JavaScript côté client, ou en filtrant les requêtes qui ne ressemblent pas à un navigateur humain. Dans ces cas-là, cette section a abordé un outil plus perfectionné : un navigateur contrôlé par chromote. Celui-ci permet de vous identifier facilement, de scroller, de cliquer, bref de simuler un comportement bien plus humain.

Mais attention : ces outils laissent des traces (en-têtes, empreintes de navigateur headless, timings d’exécution) et certains sites scrutent activement ces indices. Tout comportement inhabituel (trop de requêtes, navigation trop rapide, absence d’interactions usuelles) peut être détecté et entraîner un blocage ou un challenge tel un captcha. Que faire lorsque toute émulation d’un navigateur entraîne un blocage ? Ce sera l’objet de la dernière section.

3.3 Java et simulation de claviers et de souris

Pour cette section, je vais partir du principe que le site internet vous empêche d’accéder à son contenu à l’aide de son code source, et bloque tout accès à un émulateur. Un exemple actuel (et qui d’ailleurs à justifi” la rédaction de cette section) est le site internet https://www.sncf-connect.com/. Pour contourner ces obstacles et simuler des actions humaines, nous allons utiliser le package rJava pour interagir avec la librairie java.awt.Robot de Java, qui est conçue pour générer des touches de clavier et déplacer la souris nativement.

library(rvest)
library(rJava)
Warning: le package 'rJava' a été compilé avec la version R 4.5.2
library(clipr)
Welcome to clipr. See ?write_clip for advisories on writing to the clipboard in R.

La première étape est d’initialiser la machine virtuelle Java (JVM). C’est la première étape pour utiliser rJava. Vous devez ensuite créez une nouvelle instance de l’objet Robot de Java. Cet objet est notre interface pour simuler les entrées clavier et souris. Enfin, vous pouvez définir délai automatique (en millisecondes) entre les évènements générés par le robot (comme une pression de touche ou son relâchement). Un délai réaliste (ici 250 ms) est crucial pour éviter que le système ou le site web ne signale une activité trop rapide et non humaine.

.jinit()
jRobot <- .jnew("java/awt/Robot")
.jcall(jRobot,, "setAutoDelay",as.integer(250))

rJava dispose de fonctions pour simuler le déplacement de la souris et la pression ou le relâchement de touches du clavier. Néanmoins, les fonctions ne sont pas très faciles d’utilisation, je propose ici une série de fonctions personnalisées dédié au webscraping.

3.3.1 Fonctions personnalisées

Tout d’abord, je définis plusieurs paramètres pour des délais spécifiques pour différentes actions, permettant un contrôle fin du rythme de la simulation (par exemple, effacer est plus rapide qu’écrire).

AutoDelay_base <- 250
AutoDelay_erase <- 10
AutoDelay_write <- 200

Ensuite, pour pouvoir écrire dans votre navigateur, key_mapping est une liste R qui fait office de dictionnaire, mappant les caractères courants (lettres, chiffres) à leur code de touche Java. Ce mappage sera utilisé pour convertir une chaîne de caractères en une séquence d’évènements de clavier simulés.

key_mapping <- list(
  "A" = 65,  "B" = 66,"C" = 67,  "D" = 68,  "E" = 69,  "F" = 70,
  "G" = 71,  "H" = 72,"I" = 73,  "J" = 74,
  "K" = 75,  "L" = 76,  "M" = 77,  "N" = 78,
  "O" = 79,  "P" = 80,  "Q" = 81,  "R" = 82,
  "S" = 83,  "T" = 84,  "U" = 85,  "V" = 86,
  "W" = 87,  "X" = 88,  "Y" = 89,  "Z" = 90,
  "a" = 97,  "b" = 98,  "c" = 99,  "d" = 100,
  "e" = 101,  "f" = 102,  "g" = 103,  "h" = 104,
  "i" = 105,  "j" = 106,  "k" = 107,  "l" = 108,
  "m" = 109,  "n" = 110,  "o" = 111,  "p" = 112,
  "q" = 113,  "r" = 114,  "s" = 115,  "t" = 116,
  "u" = 117,  "v" = 118,  "w" = 119,  "x" = 120,
  "y" = 121,  "z" = 122,
  "0" = 96, "1" = 97, "2" = 98, "3" = 99, "4" = 100, "5" = 101,
  "6" = 102, "7" = 103, "8" = 104, "9" = 105, "/" = 47
)

jrobot_keyboard simule la pression puis le relâchement d’une seule touche à l’aide de son code numérique. jrobot_enter est une fonction simplifiée pour la touche “entrée”.

jrobot_keyboard <- function(key){
  jRobot$keyPress(as.integer(key))
  jRobot$keyRelease(as.integer(key))
}

jrobot_enter <- function(){
  jrobot_keyboard(10)
  Sys.sleep(1)
}

jrobot_erase simule la pression répétée de la touche “Retour Arrière”. Avant cela, elle réduit le délai (avec le paramètre global AutoDelay_erase) pour une suppression plus rapide.

jrobot_erase <- function(nb_erase,
                          AutoDelay_base = AutoDelay_base,
                          AutoDelay_erase = AutoDelay_erase){
  for (k in 1:nb_erase){
    jRobot$setAutoDelay(as.integer(AutoDelay_erase))
    jrobot_keyboard(8)
    jRobot$setAutoDelay(as.integer(AutoDelay_base))
  } 
}

send_sentence simule la saisie d’une chaîne de caractères complète. Elle définit le délai sur AutoDelay_write pour un rythme de frappe réaliste puis décompose la chaîne en caractères individuels. Pour chaque caractère, il le cherche dans le key_mapping et utilise les méthodes Java keyPress() et keyRelease() avec le code de touche correspondant. Attention, Cette fonction ne gère pas la distinction majuscule/minuscule qui nécessiterait de simuler l’appui sur la touche SHIFT simultanément pour les majuscules.

send_sentence <- function(sentence,
                          AutoDelay_base = AutoDelay_base,
                          AutoDelay_write = AutoDelay_write) {
  
  jRobot$setAutoDelay(as.integer(AutoDelay_write))
  # Iterate through each character in the sentence
  for (char in strsplit(sentence, NULL)[[1]]) {
    char <- toupper(char)
    if (char %in% names(key_mapping)) {
      key_code <- key_mapping[[char]]
      # Simulate key press and release
      jRobot$keyPress(as.integer(key_code))
      jRobot$keyRelease(as.integer(key_code))
    } else {
      cat("Character not found in key mapping:", char, "\n")
    }
  }
  jRobot$setAutoDelay(as.integer(AutoDelay_base))
}

Du côté de la souris, jrobot_mouseMove déplace le pointeur de la souris aux coordonnées spécifiées. Les coordonnées x et y sont des positions en pixels sur l’écran. C’est ici que réside la principale limitation de cette méthode : ces coordonnées sont absolues par rapport à votre écran. Pour que le script soit valide, vous devez connaître la position exacte en pixels de l’élément à cliquer sur la fenêtre active du navigateur. Par essai/erreur, vous devrez trouver l’emplacement du bouton, du champ, du raccourci sur lequel cliquer.

jrobot_click appelle mousePress(16) pour simuler l’appui sur le bouton gauche de la souris, puis appelle mouseRelease(16) pour relâcher le bouton. Il est possible de faire varier le temps d’attente pour laisser le temps à l’action d’être traitée par la page web.

jrobot_mouseMove <- function(x,y){
  jRobot$mouseMove(as.integer(x), as.integer(y))
}

jrobot_click <- function(vitesse=1){
  jRobot$mousePress(as.integer(16))
  jRobot$mouseRelease(as.integer(16))
  Sys.sleep(vitesse)
}

3.3.2 Illustration : extraction de SNCF Connect

Chaque script avec rJava nécessite de s’adapter à chaque écran. Néanmoins, pour montrer l’usage, je vais vous présenter pas-à-pas les étapes utilisées en décembre 2024 pour automatiser la navigation sur le site Internet SNCF Connect et l’extraction des prix de nombreux billets de trains. L’extraction repose sur deux grandes phases : la préparation de la fenêtre et l’exécution d’une séquence de recherche et de scraping pour chaque voyage.

3.3.2.1 Préparation de l’Environnement et de la Fenêtre

La première étape critique est de s’assurer que le navigateur est dans l’état correct pour que les coordonnées en pixels soient valides. Le script commence par des actions précises de souris pour faire le focus sur la bonne fenêtre et fermer d’éventuels pop-ups :

jRobot$setAutoDelay(as.integer(1))
jrobot_mouseMove(800,40)
jrobot_click(0.1)
jrobot_click(0.1)
jRobot$setAutoDelay(as.integer(AutoDelay_base))

Ces lignes positionnent le curseur aux coordonnées (800, 40) et simulent deux clics très rapides. Ceci est utilisé pour mettre double cliquer sur une icone, ici le navigateur Chrome, pour ouvrir le navigateur. Ensuite, le délai automatique du robot est rétabli à sa valeur de base pour des actions plus lentes.

jrobot_mouseMove(450,90)
jrobot_click()
Sys.sleep(5)
jrobot_mouseMove(1000,800)
jrobot_click() 

Le curseur est déplacé à (450, 90) pour un clic sur un bouton de favori. Le script attend ensuite 5 secondes (Sys.sleep(5)) pour permettre au site de charger. Le dernier mouvement et clic cible l’acceptation des cookies car ce pop-ups gêne les clics ultérieurs.

3.3.2.2 Faire une recherche de billets

La recherche de billets implique plusieurs étapes successives : indiquer la ville de départ et d’arrivée, choisir le jour et l’heure de départ, indiquer des détails sur les personnes participants au voyage. En supposant que ces éléments sont stockés dans des objets R (ce qui sera utile pour passer le code en fonction plus tard), nous pouvons appliquer ces étapes une à une.

Nous commençons par cliquer sur le champ dédié à la ville de départ pour insérer celle-ci. Nous faisons ensuite de même avec la ville d’arrivée. Nous simulons à chaque fois 15 pressions de la touche “Retour Arrière” pour effacer toute valeur préexistante dans le champ.

jrobot_mouseMove(500,310)
jrobot_click()
jrobot_erase(15)
send_sentence(ville_depart)
jrobot_enter()

jrobot_mouseMove(500,360) #350
jrobot_click()
jrobot_erase(15)
send_sentence(ville_arrivee)
jrobot_enter()

Pour la date de départ, il faut d’abord cliquer sur l’emplacement dédié, puis cliquer sur le champ spécifique à la date de départ et insérer celle-ci.

jrobot_mouseMove(500,440) #430
jrobot_click()
jrobot_enter()

jrobot_mouseMove(700,360) #350
jrobot_click()
jrobot_erase(10)

send_sentence(date_depart)

Pour l’heure, il n’est pas possible de l’entrer manuellement, il faut choisir par tranche de deux heures. Cela a demandé un peu d’ingéniosité pour transformer une heure numérique en un séquence d’action pour atteindre progressivement l’heure de départ souhaitée. Il s’agit de cliquer sur l’emplacement dédié à l’heure, puis appuyer sur 4 pour avoir l’heure la plus basse. Puis appuyer plusieurs fois sur la touche bas du clavier (touche 40) pour atteindre la bonne heure.

send_hours <- function(hour){
  jRobot$setAutoDelay(as.integer(AutoDelay_write))
  send_sentence("4")
  hour_num <- as.numeric(hour)
  if (hour_num > 4){
    ecart <- (hour_num-4)/2
    for (k in 1:ecart){jrobot_keyboard("40")}
  }
  jRobot$setAutoDelay(as.integer(AutoDelay_base))
}

jrobot_mouseMove(900,360) #350
jrobot_click()
send_hours(heure_depart)
jrobot_enter()

Il ne reste plus qu’à valider les choix. D’abord valider le choix de la date et de l’heure de départ, puis de valider la recherche complète. Le bouton pour valider la recherche de trajet étant un peu trop bas, il faut utiliser le bouton de scroll de la souris pour l’atteindre.

# Valider
jrobot_mouseMove(900,500) 
jrobot_mouseMove(900,810) #800
jrobot_click()

# Valider global
jrobot_mouseMove(900,300)
jrobot_click()
jRobot$mouseWheel(as.integer(3))
jrobot_mouseMove(750,850)
jrobot_click()
jrobot_enter()

Il n’y a plus qu’à attendre quelques secondes pour obtenir la page contenant les horaires et prix des billets de trains.

Bien sûr, il est possible de choisir d’autres options : date et heure d’arrivée, caractéristiques de voyageurs et voyageuses. Mais chaque option nécessite beaucoup d’étapes de test juste pour savoir où cliquer, aussi je vous conseille d’être très économe pour ce genre de projet.

3.3.2.3 Extraire les résultats

Ici, il n’y a pas de lien direct entre votre session R et votre navigateur. Aussi, il est impossible d’extraire le code source de manière identique à ce que nous avons vu précédemment. Mais il est possible d’atteindre le code source directement à partir du navigateur à partir de quelques clics.

Attention, cette technique a été utilisé avec le navigateur chrome, pour une configuration spécifique d’écran et de résolutions d’écran. Je vous indique le processus, mais il est certain que celui-ci devrait être adapté à chaque configuration.

Je commence par appuyer sur la touche F12 (code 123) pour ouvrir la console développeur du navigateur.

jrobot_keyboard(123)

Il s’en suit une séquence complexe de clics pour atteindre l’option “Copy outerHTML” de la console, de sorte à copier l’ensemble du code source dans le presse papier.

  jrobot_mouseMove(1370,260)
  Sys.sleep(1)
  jrobot_click() # Html
  Sys.sleep(1)
  jrobot_mouseMove(1400,500) # Copy
  Sys.sleep(1)
  jrobot_mouseMove(1700,520) # OuterHtml
  Sys.sleep(1)
  jrobot_click()

Il ne reste plus qu’à récupérer le contenu du presse papier dans un objet et d’utiliser la fonction read_html et le code source de la page est désormais exploitable avec les techniques abordées dans ce manuel.

my_data <- read_clip()
page_html <- read_html(paste(my_data,collapse = " "))

Pour limiter le nombre de requêtes, et donc limiter les risques d’erreurs et le temps d’exécution, il a fallu trouver des astuces pour ne pas retourner à l’écran principal de recherche de trains. Par exemple, il est possible de modifier l’heure de départ du train directement à partir de l’écran de liste des horaires et prix des billets. Aussi, avec un seul clic, il est possible d’inverser la ville de départ et celle d’arrivée.

Intégrer toutes ces étapes dans un seul code nécessite de nombreux tests pour vérifier que chaque clic atteigne le bon emplacement, et aussi que le script ne se perde pas en route. Le code complet intègre donc de multiples étapes de tests et de vérifications pour éviter que l’extraction soit hors de contrôle.

Avec cette méthode, vous pouvez effectuer du webscraping en simulant l’utilisateur·rice au niveau du système d’exploitation. Il est ainsi possible d’interagir avec des éléments qui ne sont pas visibles dans le code source HTML, et de contourner la majorité des outils de blocage. Les capcha restent insolubles, il vous faut alors mettre votre programme en pause le temps de résoudre vous mêmes celui-ci. Mais, de manière générale, cette approche est non portable et fragile. Les coordonnées en pixels (x, y) sont spécifiques à la taille de votre écran, à la résolution, à l’emplacement de la fenêtre du navigateur, et à la version du site. Le moindre changement de mise en page du site rend le script obsolète.

Je ne conseille cette méthode qu’en dernier recours, lorsque toutes les autres ont échouer, et que votre script ne sera exécuté que pour un temps restreint. Dans tous les autres cas de figure, l’usage de rvest est préférable pour sa stabilité.

4 Conclusion Générale

Ce syllabus a permis de parcourir les étapes fondamentales du webscraping avec le langage R, transformant des informations brutes disponibles sur Internet en tableaux de données structurés et exploitables. À travers l’utilisation des packages tidyverse et rvest, nous avons appris à naviguer dans le code source HTML d’une page web pour localiser précisément les informations grâce aux sélecteurs CSS3 et au langage XPath.

En maîtrisant la création de fonctions personnalisées (comme actu_to_table ou url_to_encadres) et l’usage de boucles ou de fonctions comme map_df, il est désormais possible de scraper des centaines de pages ou d’articles en quelques lignes de code. Nous avons également vu l’importance de cibler des balises englobantes pour garantir la cohérence des données, même lorsque certains éléments sont manquants.

Nous avons abordé plusieurs techniques pour accéder à des données sur des sites web dynamiques. L’émulation de navigateur web permet, dans la grande majorité des cas, de contourner les problèmes d’accès aux données et la gestion des cookies. Pour des cas très complexes, il est possible de prendre le contrôle de votre système d’exploitation, et alors contrôler votre souris et clavier pour simuler chaque clic. Cette méthode est très instable et non réplicable, et doit donc être utilisée qu’en dernier recours.

Ce cours de webscraping ne se limite pas à la technique. Il s’inscrit dans un cadre éthique sur l’accessibilité des données à des fins de recherches. À ce titre, il est important de minimiser la charge sur les serveurs (via Sys.sleep) et de gérer les données personnelles avec précaution. De plus, la nature dynamique du web impose une vigilance constante : les scripts doivent être maintenus face aux changements de structure des sites, et les données collectées doivent être horodatées pour conserver leur pertinence contextuelle.

En définitive, bien que le webscraping ne remplace pas les enquêtes de terrain traditionnelles par manque de contexte explicatif, il constitue un outil puissant pour constituer des bases de données inédites et enrichir des recherches.

Notes de bas de page

  1. Les site web voient leur code source modifier régulièrement, de manière plus ou moins radicale selon les mises à jour. Le code présenté dans ce manuel sera sans doute obsolète au moment de votre lecture. Néanmoins, la stabilité de Wikipédia permet d’espérer que les modifications seront mineures.↩︎

  2. Pour chromote : https://cran.r-project.org/web/packages/chromote/chromote.pdf. Pour rvest : https://cran.r-project.org/web/packages/rvest/rvest.pdf.↩︎