Skip to content
Web Scraping by Language

Web Scraping With R

Pim

Pim · Scrappey Research

June 8, 2026 4 min read

Web Scraping With R — conceptual illustration
On this page

Web scraping with R means using the rvest package to download and parse HTML into tidy data frames, with CSS selectors or XPath. rvest is the standard, tidyverse-friendly scraping package. For finer control over requests use httr2 (the modern successor to httr), the polite package to respect robots.txt and rate limits, and RSelenium or chromote for JavaScript-rendered pages.

Quick facts

Core packagervest — read_html, html_elements, html_text2
HTTP requestshttr2 (modern successor to httr)
Politenesspolite — robots.txt + rate-limiting wrapper
JavaScript pagesRSelenium or chromote
Installinstall.packages(c("rvest","httr2","polite"))

Your first R scraper with rvest

rvest reads a page and lets you select nodes with CSS or XPath, then pull out text or attributes into a vector — which drops straight into a data frame. Install with install.packages("rvest").

library(rvest)
library(dplyr)

page <- read_html("https://books.toscrape.com/")

books  <- page %>% html_elements("article.product_pod")
titles <- books %>% html_element("h3 a") %>% html_attr("title")
prices <- books %>% html_element(".price_color") %>% html_text2()

df <- tibble(title = titles, price = prices)
print(df)
write.csv(df, "books.csv", row.names = FALSE)

Note the current rvest API: html_elements() (plural) returns all matches, html_element() (singular) returns one per parent, html_text2() reads cleaned text, and html_attr() reads an attribute. Older tutorials use the retired html_nodes()/html_node() names — the new ones are the way to go.

Requests and politeness with httr2 + polite

For custom headers, query parameters, or POST requests, use httr2 — the modern successor to httr. It builds requests with a readable pipeline:

library(httr2)
library(rvest)

page <- request("https://books.toscrape.com/") %>%
  req_user_agent("Mozilla/5.0 (compatible; research-bot)") %>%
  req_perform() %>%
  resp_body_html()

page %>% html_elements(".price_color") %>% html_text2()

To scrape responsibly, the polite package wraps rvest to honour robots.txt, identify your scraper, and rate-limit automatically — a best practice almost every competitor guide omits:

library(polite)
library(rvest)

session <- bow("https://books.toscrape.com/",
               user_agent = "polite R scraper ([email protected])")

prices <- scrape(session) %>%
  html_elements(".price_color") %>%
  html_text2()

bow() introduces you to the host and checks robots.txt; scrape() then fetches within those rules.

Scraping JavaScript-rendered pages

rvest only reads the HTML the server returns — it does not run JavaScript. For client-side-rendered pages, RSelenium drives a real browser:

library(RSelenium)

driver <- rsDriver(browser = "chrome", chromever = "latest", verbose = FALSE)
remote <- driver$client

remote$navigate("https://quotes.toscrape.com/js/")
quotes <- remote$findElements(using = "css", ".quote .text")
texts  <- sapply(quotes, function(q) q$getElementText()[[1]])
print(texts)

remote$close()
driver$server$stop()

chromote is a newer, lighter alternative that controls headless Chrome directly without a separate Selenium server — worth preferring for new projects. As always, you can also find the page's JSON API and fetch it directly with httr2.

Which R package should you use?

PackageRoleRuns JS?Best for
rvestDownload + parseNoThe standard — CSS/XPath to data frames
httr2HTTP clientNoCustom headers, POST, APIs
politePoliteness wrapperNorobots.txt + rate-limiting
RSeleniumBrowser automationYesJavaScript-rendered pages
chromoteHeadless ChromeYesLighter JS rendering, no Selenium server

Start with rvest, add httr2 for request control and polite for good manners; reach for RSelenium or chromote only when JavaScript is involved.

The hard part: handling anti-bot blocking

R is excellent for analysing scraped data, but rvest's requests carry a TLS fingerprint and headers that Cloudflare, DataDome, and Akamai flag as non-browser. You cannot turn a 403 or CAPTCHA page into a data frame.

Handling serious anti-bot protection — residential proxies and a real browser fingerprint — is awkward to build in R. A scraping API handles it server-side; you POST the URL with httr2 and parse the returned HTML with rvest:

Code example

r
library(httr2)
library(rvest)

resp <- request("https://api.your-scraping-provider.com/v1?key=YOUR_API_KEY") %>%
  req_body_json(list(cmd = "request.get", url = "https://example.com/protected")) %>%
  req_perform()

# Fully rendered, unblocked HTML -- parse it with rvest as usual.
html <- resp_body_json(resp)$solution$response
read_html(html) %>% html_elements(".price_color") %>% html_text2()

Related terms

Concept map

Concept map

How Web Scraping With R: A Complete 2026 Guide connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Web Scraping by Language
Building map…

Frequently asked questions

What is the best package for web scraping with R?

rvest is the standard — it downloads and parses HTML with CSS selectors or XPath and produces tidy vectors and data frames. Pair it with httr2 (the modern successor to httr) for request control and the polite package to respect robots.txt and rate limits. For JavaScript-rendered pages, use RSelenium or the lighter chromote.

Should I use httr or httr2 in R?

Use httr2. It is the modern successor to httr, with a cleaner request-building pipeline and better defaults. httr still works and appears in older tutorials, but new R scraping code should use httr2 (or just rvest, which handles the request for you with read_html).

Can R scrape JavaScript-rendered pages?

Yes, but not with rvest alone, which only reads server HTML. Use RSelenium to drive a real browser, or chromote to control headless Chrome without a separate Selenium server. Alternatively, locate the JSON API the page calls in your browser’s Network tab and request it directly with httr2 — usually the fastest option.

Why does my R scraper get blocked, and how do I scrape politely?

Use the polite package, which checks robots.txt, identifies your scraper, and rate-limits automatically. Set a descriptive User-Agent and throttle your requests. Against serious anti-bot vendors you also need residential proxies and a browser-grade fingerprint, which is hard in R — routing those requests through a scraping API that handles it server-side is the most reliable approach.

Last updated: 2026-06-08