Web scraping en R avec rvest

Analysez du HTML, sélectionnez des nœuds avec des sélecteurs CSS, et extrayez texte, attributs et tableaux dans un data frame

Programming

Un guide pratique du web scraping en R avec le package rvest. Lisez et analysez du HTML, sélectionnez des nœuds avec des sélecteurs CSS, extrayez texte, attributs et tableaux dans un data frame, et passez à l’échelle sur de nombreuses pages avec purrr — enseigné à la manière moderne de rvest 1.0 (html_element/html_elements et le pipe de base) sur un document HTML en mémoire reproductible, puis appliqué aux vrais schémas avec accès réseau.

Auteur·rice
Date de publication

18 juillet 2026

Modifié

18 juillet 2026

AstuceCe que vous allez apprendre
  • Lire et analyser du HTML avec rvestread_html() pour une URL en ligne, minimal_html() pour une chaîne de caractères.
  • Sélectionner des nœuds avec des sélecteurs CSS — par balise, .class, #id et descendance — à l’aide de html_element() (un seul) et html_elements() (plusieurs).
  • Extraire des données — nettoyer le texte avec html_text2(), les attributs (comme le href d’un lien) avec html_attr(), et des tableaux entiers dans un data frame avec html_table().
  • Passer à l’échelle avec purrr — transformer des nœuds répétés en un tibble bien rangé, et boucler sur de nombreuses pages.
  • L’API rvest 1.0 moderne — html_element/html_elements et le pipe de base |>, et non les fonctions supplantées html_node/%>%.

Vous avez besoin de données qui vivent sur une page web mais qu’aucun téléchargement bien rangé ne fournit ? Le web scraping est la technique qui consiste à les extraire par programmation — lire le HTML d’une page et en retirer les parties qui vous intéressent. En R, l’outil standard est rvest (issu du tidyverse), qui analyse le HTML et vous permet de sélectionner et d’extraire du contenu avec les mêmes sélecteurs CSS que ceux utilisés pour mettre en forme une page.

La plupart des tutoriels scrapent un site web en ligne et collent une capture d’écran du résultat — que personne ne peut reproduire, car le site change. Ce guide emprunte une voie plus propre : nous enseignons chaque mécanisme de base sur un petit document HTML en mémoire, si bien que chaque exemple ci-dessous s’exécute réellement et renvoie la sortie que vous voyez. Ensuite, nous montrons les recettes réelles avec accès réseau en direct — récupérer une URL, paginer, gérer les erreurs — sous forme de code à copier dans votre session, clairement signalé.

Note

Un mot sur les termes. Un élément HTML (ou nœud) est un morceau balisé d’une page — un titre <h1>, un paragraphe <p>, un lien <a>, un tableau <table>. Un sélecteur CSS est un motif court qui sélectionne des éléments dans la page : table sélectionne tous les tableaux, .price sélectionne tout ce qui porte class="price", #title sélectionne l’unique élément portant id="title". rvest utilise ces sélecteurs pour trouver ce que vous voulez extraire.

Installer et charger rvest

Installez rvest depuis le CRAN (il est fourni avec le tidyverse, vous l’avez donc peut-être déjà). Nous utiliserons aussi purrr pour la section de passage à l’échelle et tibble pour une sortie bien rangée.

install.packages(c("rvest", "purrr", "tibble"))

Chargez-le ensuite. Tout ce que contiennent les sections principales n’a besoin que de rvest lui-même :

library(rvest)

Lire et analyser une page HTML

Chaque scraping commence par l’analyse du HTML en un objet document que vous pouvez interroger. Pour un site en ligne, vous passeriez une URL à read_html(). Pour que ce guide reste reproductible, nous construisons le même type de document à partir d’une chaîne de caractères avec minimal_html() — il s’analyse exactement comme une page récupérée, mais son contenu est fixe, donc le code s’exécute partout.

Voici une petite page de boutique : un titre, une ligne d’introduction, une liste de liens de catégories et un tableau de produits. Lisez-la une fois dans page :

library(rvest)

page <- minimal_html('
  <html>
  <body>
    <h1 id="title">Datanovia Gadget Store</h1>
    <p class="intro">Our three best sellers this week.</p>
    <ul id="nav">
      <li><a href="https://example.com/laptops">Laptops</a></li>
      <li><a href="https://example.com/phones">Phones</a></li>
      <li><a href="https://example.com/tablets">Tablets</a></li>
    </ul>
    <table id="products">
      <tr><th>Product</th><th>Price</th><th>Rating</th></tr>
      <tr><td>Laptop</td><td>1200</td><td>4.5</td></tr>
      <tr><td>Phone</td><td>800</td><td>4.2</td></tr>
      <tr><td>Tablet</td><td>450</td><td>4.7</td></tr>
    </table>
  </body>
  </html>
')

page
{html_document}
<html>
[1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8 ...
[2] <body>\n    <h1 id="title">Datanovia Gadget Store</h1>\n    <p class="int ...

page est un document analysé. Pour un site réel, le seul changement est la source :

# The live-network equivalent — fetch and parse a real page
page <- read_html("https://example.com/store")
Note

Ceci s’exécute en local, pas ici. Toute cellule marquée « accès réseau en direct » (comme read_html("https://…")) accède à internet, elle ne peut donc pas faire partie de cette page reproductible — copiez-la dans votre propre session R pour l’exécuter. Tout ce qui repose sur minimal_html() s’exécute ici même.

Sélectionner un nœud ou plusieurs : html_element et html_elements

Deux fonctions font la sélection, et la différence compte :

  • html_element() renvoie le premier élément correspondant (un seul nœud).
  • html_elements() renvoie tous les éléments correspondants (un ensemble de nœuds).

Passez un sélecteur CSS à l’une ou l’autre. Récupérez le titre de la page avec #title (l’id), puis nettoyez le texte avec html_text2(), qui supprime les espaces superflus et réduit les suites d’espaces comme on s’y attend :

library(rvest)
page <- minimal_html('
  <h1 id="title">Datanovia Gadget Store</h1>
  <p class="intro">Our three best sellers this week.</p>
')

page |>
  html_element("#title") |>
  html_text2()
[1] "Datanovia Gadget Store"

Le pipe de base |> se lit de gauche à droite : prendre la page, sélectionner l’élément #title, extraire son texte. Pour saisir chaque élément d’un ensemble, passez à html_elements(). Ici, nous récupérons d’un coup les trois libellés de liens de catégories :

library(rvest)
page <- minimal_html('
  <ul id="nav">
    <li><a href="https://example.com/laptops">Laptops</a></li>
    <li><a href="https://example.com/phones">Phones</a></li>
    <li><a href="https://example.com/tablets">Tablets</a></li>
  </ul>
')

page |>
  html_elements("a") |>
  html_text2()
[1] "Laptops" "Phones"  "Tablets"

html_element() n’aurait renvoyé que "Laptops". Lorsque vous voulez une colonne de valeurs, vous voulez presque toujours html_elements().

Note

rvest moderne. html_element/html_elements a remplacé les anciens html_node/html_nodes dans rvest 1.0.0. Ils se comportent de la même façon ; utilisez la paire moderne.

Sélecteurs CSS : balise, classe, id et descendance

Un sélecteur peut cibler des éléments de quatre façons courantes. Construisez une page avec quelques prix et un titre, puis sélectionnez de chaque façon :

library(rvest)
page <- minimal_html('
  <div class="product">
    <h2 class="name">Laptop</h2>
    <span class="price">1200</span>
  </div>
  <div class="product">
    <h2 class="name">Phone</h2>
    <span class="price">800</span>
  </div>
')

# By tag: every <h2> element
page |> html_elements("h2") |> html_text2()
[1] "Laptop" "Phone" 
# By class: everything with class="price"
page |> html_elements(".price") |> html_text2()
[1] "1200" "800" 
# Descendant: a .name that sits inside a .product
page |> html_elements(".product .name") |> html_text2()
[1] "Laptop" "Phone" 

Les motifs se composent de la même manière que dans une feuille de style : .product .name (avec une espace) signifie un .name n’importe où à l’intérieur d’un .product. Pour trouver le bon sélecteur sur un site réel, ouvrez-le dans votre navigateur, faites un clic droit sur l’élément voulu et choisissez Inspecter — les outils de développement vous montrent les balises, les classes et les id à cibler.

Extraire des attributs : les liens et leur href

Le texte n’est que la moitié de l’histoire. Les liens portent leur destination dans un attribut — le href. Récupérez les attributs avec html_attr(). Une tâche courante consiste à collecter le texte visible de chaque lien accompagné de son URL :

library(rvest)
page <- minimal_html('
  <ul id="nav">
    <li><a href="https://example.com/laptops">Laptops</a></li>
    <li><a href="https://example.com/phones">Phones</a></li>
    <li><a href="https://example.com/tablets">Tablets</a></li>
  </ul>
')

links <- page |> html_elements("a")

data.frame(
  text = links |> html_text2(),
  url  = links |> html_attr("href")
)
     text                         url
1 Laptops https://example.com/laptops
2  Phones  https://example.com/phones
3 Tablets https://example.com/tablets

Sélectionnez les éléments <a> une fois, puis extrayez deux choses du même ensemble — le texte et le href — et rassemblez-les dans un data frame. html_attr() fonctionne pour n’importe quel attribut : "src" sur une <img>, "class", "id", ou une valeur data-* personnalisée.

Extraire un tableau directement dans un data frame

De nombreuses pages présentent des données dans un <table> HTML, et rvest en lit un en un seul appel : sélectionnez le tableau, passez-le à html_table(), et vous obtenez un tibble dont la ligne d’en-tête donne les noms de colonnes.

library(rvest)
page <- minimal_html('
  <table id="products">
    <tr><th>Product</th><th>Price</th><th>Rating</th></tr>
    <tr><td>Laptop</td><td>1200</td><td>4.5</td></tr>
    <tr><td>Phone</td><td>800</td><td>4.2</td></tr>
    <tr><td>Tablet</td><td>450</td><td>4.7</td></tr>
  </table>
')

products <- page |>
  html_element("#products") |>
  html_table()

products
# A tibble: 3 × 3
  Product Price Rating
  <chr>   <int>  <dbl>
1 Laptop   1200    4.5
2 Phone     800    4.2
3 Tablet    450    4.7

Voilà le tableau entier sous forme de data frame — les colonnes Product, Price et Rating, prêtes à analyser. html_table() interprète les colonnes numériques comme des nombres, si bien que Price et Rating reviennent prêtes pour l’arithmétique. Si une page comporte plusieurs tableaux, utilisez html_elements("table") |> html_table() pour obtenir une liste de data frames et choisir celui dont vous avez besoin.

Passer à l’échelle avec purrr : de nombreux nœuds en un tibble bien rangé

Les pages réelles répètent une structure — une grille de cartes produits, une liste de résultats de recherche. Le schéma est le suivant : sélectionner le conteneur répété avec html_elements(), puis appliquer purrr::map() sur chacun pour en extraire les champs. Cela transforme une page de cartes en un tibble bien rangé.

Ici, trois cartes produits contiennent chacune un nom, un prix et une note. Nous sélectionnons les cartes, appliquons un petit extracteur sur chacune avec purrr::map(), puis empilons les lignes par carte dans un seul data frame avec purrr::list_rbind() — l’idiome purrr actuel, qui a remplacé le désormais supplanté map_dfr() dans purrr 1.0.0 :

library(rvest)
library(purrr)
library(tibble)

page <- minimal_html('
  <div class="product">
    <h2 class="name">Laptop</h2>
    <span class="price">1200</span>
    <span class="rating">4.5</span>
  </div>
  <div class="product">
    <h2 class="name">Phone</h2>
    <span class="price">800</span>
    <span class="rating">4.2</span>
  </div>
  <div class="product">
    <h2 class="name">Tablet</h2>
    <span class="price">450</span>
    <span class="rating">4.7</span>
  </div>
')

cards <- page |> html_elements(".product")

products <- cards |>
  map(function(card) {
    tibble(
      name   = card |> html_element(".name")   |> html_text2(),
      price  = card |> html_element(".price")  |> html_text2() |> as.numeric(),
      rating = card |> html_element(".rating") |> html_text2() |> as.numeric()
    )
  }) |>
  list_rbind()

products
# A tibble: 3 × 3
  name   price rating
  <chr>  <dbl>  <dbl>
1 Laptop  1200    4.5
2 Phone    800    4.2
3 Tablet   450    4.7

À l’intérieur de la fonction, remarquez le retour à html_element() (au singulier) — chaque carte a exactement un nom, un prix et une note, vous voulez donc la première (et unique) correspondance par carte. Sélectionner à partir de card plutôt que de page garde ensemble les champs de chaque ligne. Ce schéma « sélectionner l’ensemble, puis mapper dessus » est le cheval de bataille de tout scraping non trivial.

A bar chart of three product ratings — Laptop 4.5, Phone 4.2, Tablet 4.7 — extracted from an HTML page into a data frame with rvest in R.

Une fois les données converties en tibble, elles alimentent directement le reste de votre flux de travail R — ici, les trois notes scrapées, tracées à l’instant même où elles quittent la page.

Boucler sur de nombreuses pages

Les sites paginés répartissent les résultats sur ?page=1, ?page=2, et ainsi de suite. Le schéma de passage à l’échelle s’étend proprement : mapper sur les numéros de page, récupérer et analyser chacune, puis empiler les résultats. Comme cela sollicite un serveur en ligne, c’est une recette à exécuter en local — mais la forme est exactement celle, reproductible, ci-dessus, avec read_html() à la place de minimal_html() :

library(rvest)
library(purrr)
library(tibble)

scrape_page <- function(page_number) {
  url <- paste0("https://example.com/products?page=", page_number)
  page <- read_html(url)

  page |>
    html_elements(".product") |>
    map(function(card) {
      tibble(
        name  = card |> html_element(".name")  |> html_text2(),
        price = card |> html_element(".price") |> html_text2()
      )
    }) |>
    list_rbind()
}

# Fetch pages 1 to 5 and combine into one tibble
all_products <- map(1:5, function(p) {
  result <- scrape_page(p)
  Sys.sleep(1)   # pause 1s between requests — be polite to the server
  result
}) |> list_rbind()

Sys.sleep(1) marque une pause d’une seconde entre les requêtes pour ne pas surcharger le serveur — une politesse élémentaire qui vous évite aussi d’être limité en débit ou bloqué.

Sessions et gestion des erreurs

Deux autres schémas avec accès réseau valent la peine d’être connus. Une session conserve l’état (cookies, en-têtes) d’une requête à l’autre sur le même site, ce que certaines pages exigent — démarrez-en une avec session() et déplacez-vous avec session_jump_to() :

# A browsing session that persists cookies/headers
s <- session("https://example.com")
s <- s |> session_jump_to("https://example.com/products")

s |> html_elements(".product .name") |> html_text2()

Et parce qu’une récupération en ligne peut échouer — un 404, un dépassement de délai, une requête bloquée — enveloppez read_html() dans tryCatch() pour qu’une seule page défaillante ne fasse pas planter un long scraping :

safe_read <- function(url) {
  tryCatch(
    read_html(url),
    error = function(e) {
      message("Skipping ", url, ": ", conditionMessage(e))
      NULL   # return NULL instead of stopping
    }
  )
}

page <- safe_read("https://example.com/does-not-exist")
if (is.null(page)) message("No page — moving on.")

Renvoyer NULL en cas d’échec permet à votre boucle map() de sauter la page défaillante et de continuer. Combinez tryCatch() avec Sys.sleep() et vous avez l’ossature d’un scraper robuste et respectueux.

Scraper poliment et légalement

Avant de scraper un site, vérifiez ce qu’il autorise. La plupart des sites publient un fichier robots.txt (à l’adresse https://site.com/robots.txt) indiquant quels chemins les clients automatisés peuvent récupérer, et leurs conditions d’utilisation peuvent restreindre le scraping ou la réutilisation des données. Quelques habitudes vous gardent du bon côté :

  • Lisez le robots.txt et les conditions d’utilisation. Le package polite peut vérifier le robots.txt pour vous et gérer les limites de débit.
  • Limitez votre propre débit avec Sys.sleep() entre les requêtes, comme ci-dessus.
  • Préférez une API officielle quand il en existe une — elle est plus rapide, plus stable et clairement autorisée.
  • Mettez en cache ce que vous récupérez pour ne pas retélécharger la même page pendant le développement.
Important

Ceci constitue des indications générales, pas un conseil juridique. Le droit du scraping et les conditions d’un site varient selon la juridiction et selon le site — en cas de doute sur un site ou un jeu de données précis, consultez ses conditions et sollicitez un avis qualifié.

Problèmes courants

html_element() renvoie NA ou rien. C’est presque toujours que le sélecteur CSS ne correspond pas. Ouvrez la page dans l’outil Inspecter de votre navigateur et confirmez la balise, la classe ou l’id exacts — une classe que vous pensiez être .price est peut-être .product-price. Rappelez-vous que html_element() ne renvoie que la première correspondance ; si vous attendiez plusieurs valeurs, utilisez html_elements().

Vous obtenez une seule valeur alors que vous en vouliez plusieurs (ou l’inverse). C’est la distinction entre html_element et html_elements. Le singulier renvoie le premier nœud ; le pluriel renvoie l’ensemble complet. Pour une colonne de data frame, utilisez le pluriel.

read_html() renvoie une page de connexion ou un document presque vide. Le contenu est probablement rendu par JavaScript après le chargement de la page, il n’est donc pas dans le HTML initial que rvest récupère. rvest ne lit que du HTML statique — pour les sites très chargés en JavaScript, il vous faut un navigateur headless (voir la question sur RSelenium ci-dessous), ou vérifiez si le site expose une API que vous pouvez appeler directement.

Foire aux questions

Le web scraping est en soi une technique neutre — la légalité dépend de ce que vous scrapez et comment, pas du langage. Des données publiques et factuelles récupérées poliment présentent généralement un risque moindre ; scraper des données personnelles, du contenu sous copyright, ou des chemins que le robots.txt ou les conditions d’utilisation d’un site interdisent ne l’est pas. Vérifiez toujours le robots.txt et les conditions du site, limitez le débit de vos requêtes, et préférez une API officielle quand il en existe une. Ceci constitue une information générale, pas un conseil juridique — les règles varient selon la juridiction et selon le site.

Utilisez rvest pour la grande majorité des scrapings : il récupère et analyse du HTML statique et il est rapide, simple et léger en dépendances. Ne recourez à RSelenium (ou à un autre outil de navigateur headless comme chromote) que lorsque les données sont rendues par JavaScript après le chargement de la page, ou lorsque vous devez interagir avec la page — cliquer sur des boutons, remplir des formulaires, faire défiler — avant que le contenu n’apparaisse. Si read_html() renvoie les données voulues, vous n’avez pas besoin de Selenium.

Lisez la page avec read_html(url), sélectionnez le tableau avec un sélecteur CSS, et passez-le à html_table() : read_html(url) |> html_element("table") |> html_table() renvoie un data frame dont la ligne d’en-tête donne les noms de colonnes. Si la page comporte plusieurs tableaux, utilisez html_elements("table") |> html_table() pour obtenir une liste de data frames et choisir celui dont vous avez besoin par sa position.

html_element() renvoie le premier nœud correspondant à votre sélecteur (un seul élément) ; html_elements() renvoie tous les nœuds correspondants (un ensemble). Utilisez le singulier lorsqu’il y a exactement une chose à saisir — un titre de page, ou un champ unique dans une seule carte — et le pluriel lorsque vous voulez une colonne de valeurs ou un ensemble sur lequel mapper. Les confondre est la cause la plus fréquente d’un résultat étonnamment court ou long.

Non. Le cœur de rvest — read_html, html_element/html_elements, html_text2, html_attr, html_table — est autonome. Pour transformer des nœuds répétés en un data frame bien rangé, purrr (map() + list_rbind()) et tibble sont les partenaires naturels, et vous pouvez ajouter dplyr ensuite pour filtrer, modifier ou résumer les données scrapées — mais rien de tout cela n’est requis pour extraire les données en premier lieu.

Testez vos connaissances

On vous donne cette page en mémoire. Sélectionnez les trois cartes de livres et construisez un tibble avec une colonne title et une colonne year numérique.

library(rvest)
library(purrr)
library(tibble)

page <- minimal_html('
  <div class="book"><h3 class="title">R Graphics</h3><span class="year">2019</span></div>
  <div class="book"><h3 class="title">Practical Guide</h3><span class="year">2017</span></div>
  <div class="book"><h3 class="title">ggplot2 Essentials</h3><span class="year">2022</span></div>
')

Sélectionnez le conteneur répété avec html_elements(".book"), puis mappez avec map() sur l’ensemble et empilez les lignes avec list_rbind(). À l’intérieur de la fonction, utilisez html_element() (au singulier) pour .title et .year, et enveloppez l’année dans as.numeric().

books <- page |>
  html_elements(".book") |>
  map(function(card) {
    tibble(
      title = card |> html_element(".title") |> html_text2(),
      year  = card |> html_element(".year")  |> html_text2() |> as.numeric()
    )
  }) |>
  list_rbind()

books
#> # A tibble: 3 x 2
#>   title               year
#>   <chr>              <dbl>
#> 1 R Graphics          2019
#> 2 Practical Guide     2017
#> 3 ggplot2 Essentials  2022

Sélectionnez l’ensemble, mappez dessus, extrayez un champ par carte avec le singulier html_element() — le même schéma que pour les cartes produits ci-dessus.

Vérification rapide. Vous exécutez page |> html_element(".price") |> html_text2() sur une page comportant cinq éléments .price. Combien de valeurs reviennent ?

Une — le texte du premier élément .price. html_element() (au singulier) renvoie toujours un seul nœud. Pour obtenir les cinq, utilisez html_elements() (au pluriel).

Conclusion

rvest transforme une page web en données avec une poignée de verbes composables : read_html() (ou minimal_html()) pour analyser, html_element/html_elements() pour sélectionner avec des sélecteurs CSS, et html_text2(), html_attr() et html_table() pour extraire texte, attributs et tableaux. Ajoutez purrr::map() + list_rbind() pour replier des nœuds répétés et de nombreuses pages en un seul tibble bien rangé, enveloppez les récupérations en ligne dans tryCatch() et Sys.sleep(), et vérifiez le robots.txt avant de commencer. Parce que nous avons enseigné les mécanismes sur un document en mémoire, chaque exemple de base ici est reproductible par copier-coller — remplacez minimal_html() par read_html(url) et le même code scrape le web réel.

Pour aller plus loin

🟢 Avec un agent IA

Demandez à Prova « scrape le tableau de produits de cette page dans un data frame avec rvest — les noms, les prix et les notes » — elle répond avec du code que vous pouvez exécuter sur votre propre page cible. The runtime is the judge. Demander à Prova →

Cette page vous a-t-elle été utile ?

Recevez les nouvelles leçons R & Python par e-mail

Pratique, reproductible, sans spam. Désinscription à tout moment.

Double opt-in. Nous ne partageons jamais votre e-mail.

Partager cette pageXLinkedInRedditHN

Citation

BibTeX
@online{kassambara2026,
  author = {Kassambara, Alboukadel},
  title = {Web scraping en R avec rvest},
  date = {2026-07-18},
  url = {https://www.datanovia.com/blog/web-scraping-in-r-with-rvest},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
Kassambara, Alboukadel. 2026. “Web scraping en R avec rvest.” July 18. https://www.datanovia.com/blog/web-scraping-in-r-with-rvest.