Utiliser des proxys en R est devenu incontournable dès qu'on dépasse le simple téléchargement d'un CSV. Que vous fassiez du web scraping in R avec rvest, de la collecte via httr2, ou que vous interrogiez des API géo-restreintes, votre adresse IP finit par être rate-limitée ou bloquée. Ce guide montre comment configurer un R proxy avec la stack moderne (httr2 + rvest), gérer les sessions résidentielles, et bâtir un pipeline résilient prêt pour la production.
Pourquoi utiliser des proxys en R : le contexte technique
Quand vous envoyez une requête HTTP depuis R — via httr2::request(), curl::curl_fetch_memory() ou rvest::read_html() — votre machine se connecte directement au serveur cible. Le serveur voit votre IP réelle, votre user-agent par défaut, et éventuellement votre ASN (réseau d'origine). Après quelques dizaines de requêtes sur la même IP, la plupart des sites sérieux réagissent : HTTP 429 (too many requests), HTTP 403 (forbidden), challenge CAPTCHA, ou blocage temporaire.
Un proxy intercale un serveur entre R et la cible : R → proxy → site. Le site voit l'IP du proxy, pas la vôtre. En faisant tourner les IPs, on répartit la charge et on évite les limites par adresse. Mais tous les proxys ne se valent pas :
| Type de proxy | Origine de l'IP | Détection par anti-bot | Cas d'usage typique |
|---|---|---|---|
| Datacenter | Bloc d'hébergeur (AWS, OVH, DigitalOcean) | Faible coût mais ASN identifiable ; souvent bloqué | API publiques, tests, scraping léger |
| Résidentiel | FAI réel (box domestique) | Faible ; passe pour un utilisateur normal | Sites e-commerce, SERP, réseaux sociaux |
| Mobile | Réseau cellulaire (4G/5G) | Très faible ; rotation naturelle | Apps mobiles, comptes sensibles |
Les IPs datacenter sont bon marché mais leur ASN trahit leur origine : un site comme Amazon ou Booking peut rejeter une requête venant d'un bloc AWS avant même de lire le user-agent. Les proxys résidentiels utilisent des IPs de FAI réels, donc le trafic ressemble à celui d'un internaute ordinaire. Pour un scraping durable en R, c'est le compromis coût/fiabilité le plus courant. Voir la page Wikipedia sur les proxys pour le vocabulaire de base.
La stack moderne : httr2, rvest et req_proxy
httr2 est le successeur moderne de httr, conçu autour d'un pipeline d'objets req immuables. rvest s'appuie désormais sur httr2 en interne, ce qui rend l'intégration d'un proxy cohérente entre les deux. La fonction clé est req_proxy(), qui accepte une URL de proxy, un nom d'utilisateur et un mot de passe.
Installation et premier exemple
# install.packages(c("httr2", "rvest", "purrr", "chromote"))
library(httr2)
library(rvest)
# Proxy résidentiel ProxyHat — port HTTP 8080
proxy_url <- "http://gate.proxyhat.com:8080"
proxy_user <- "user-country-GB"
proxy_pass <- "VOTRE_CLE_API"
req <- request("https://httpbin.org/ip") |>
req_proxy(proxy_url, proxy_user, proxy_pass) |>
req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)") |>
req_retry(max_tries = 3, backoff = ~ 2 ^ .x) |>
req_throttle(2 / 1) # 2 requêtes par seconde max
resp <- req |> req_perform()
jsonlite::fromJSON(resp_body_string(resp))
# { "origin": "81.2.69.142" } -> IP du proxy, pas la vôtre
Ici, l'IP retournée par httpbin.org/ip est celle du proxy résidentiel, pas la vôtre. req_retry() réessaie automatiquement en cas d'échec transitoire (429, 503, timeout), avec un backoff exponentiel. req_throttle() limite le débit global pour ne pas saturer la cible.
Parsing HTML avec rvest
Une fois la réponse récupérée, on parse avec rvest. Le flux recommandé est de récupérer le body brut via httr2, puis de le passer à read_html() :
library(httr2)
library(rvest)
fetch_page <- function(url, session_id = NULL) {
user <- if (is.null(session_id)) {
"user-country-GB"
} else {
paste0("user-country-GB-session-", session_id)
}
req <- request(url) |>
req_proxy("http://gate.proxyhat.com:8080", user, "VOTRE_CLE_API") |>
req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)") |>
req_headers("Accept-Language" = "en-GB,en;q=0.9") |>
req_retry(max_tries = 4, backoff = ~ 2 ^ .x)
resp <- req |> req_perform()
read_html(resp_body_string(resp))
}
doc <- fetch_page("https://books.toscrape.com/catalogue/page-1.html")
titles <- doc |> html_elements("article h3 a") |> html_text2()
prices <- doc |> html_elements(".price_color") |> html_text2()
head(titles, 3)
html_elements() renvoie un vecteur de nœuds ; html_text2() extrait le texte nettoyé. Pour les tableaux HTML structurés, html_table() renvoie directement un data.frame — pratique pour des données tabulaires.
Géo-ciblage et sessions persistantes dans le nom d'utilisateur
ProxyHat encode les options de routage directement dans le nom d'utilisateur du proxy, selon un format lisible :
user-country-GB— IP au Royaume-Uni.user-country-GB-city-london— IP à Londres.user-country-DE-session-abc123— IP en Allemagne, session collante identifiée parabc123.user-session-xyz789— session collante sans contrainte géographique (IP aléatoire mais stable tant que le flag ne change pas).
Les sessions collantes (sticky sessions) gardent la même IP sortante pendant une période donnée — typiquement 10 à 30 minutes selon le fournisseur. C'est essentiel pour les sites qui lient l'authentification à l'IP, ou pour les flux paginés où chaque page doit paraître venir du même visiteur.
SOCKS5 sur le port 1080
ProxyHat expose aussi SOCKS5 sur le port 1080, utile quand le trafic doit traverser des environnements restrictifs ou quand on veut un tunnel TCP pur :
# SOCKS5 — port 1080
req <- request("https://httpbin.org/ip") |>
req_proxy("socks5://gate.proxyhat.com:1080",
"user-country-DE-city-berlin",
"VOTRE_CLE_API") |>
req_user_agent("Mozilla/5.0 (X11; Linux x86_64)")
resp <- req |> req_perform()
jsonlite::fromJSON(resp_body_string(resp))
Notez le préfixe socks5:// au lieu de http://. httr2 délègue la résolution DNS au proxy SOCKS, ce qui évite les fuites DNS côté client. Consultez la RFC 1928 (SOCKS5) pour les détails du protocole.
Exemple complet : table paginée vers un data frame tidy
Voici un exemple réaliste : on récupère les 50 pages d'un catalogue, on extrait un tableau HTML par page, et on combine le tout avec purrr::map_dfr(). Chaque page utilise une session différente pour répartir la charge.
library(httr2)
library(rvest)
library(purrr)
library(dplyr)
PROXY_HOST <- "http://gate.proxyhat.com:8080"
PROXY_PASS <- "VOTRE_CLE_API"
scrape_page <- function(page_num) {
url <- sprintf("https://books.toscrape.com/catalogue/page-%d.html", page_num)
session <- sprintf("sess-p%03d-%d", page_num, as.integer(Sys.time()))
user <- paste0("user-country-GB-session-", session)
req <- request(url) |>
req_proxy(PROXY_HOST, user, PROXY_PASS) |>
req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)") |>
req_headers("Accept-Language" = "en-GB,en;q=0.9") |>
req_retry(max_tries = 5,
backoff = ~ 2 ^ .x,
is_transient = ~ resp_status(.x) %in% c(429, 502, 503)) |>
req_throttle(2 / 1) # 2 req/s global
resp <- tryCatch(
req |> req_perform(),
error = function(e) {
message("Page ", page_num, " échouée: ", conditionMessage(e))
NULL
}
)
if (is.null(resp)) return(NULL)
html <- read_html(resp_body_string(resp))
books <- html |> html_elements("article")
tibble::tibble(
titre = books |> html_element("h3 a") |> html_text2(),
prix = books |> html_element(".price_color") |> html_text2(),
dispo = books |> html_element(".instock") |> html_text2() |> trimws(),
page = page_num
)
}
# 50 pages, rotation de session par page
df <- 1:50 |>
map_dfr(scrape_page)
glimpse(df)
# Rows: 1,000
# Columns: 4
# $ titre <chr> "A Light in the Attic", "Tipping the Velvet", …
# $ prix <chr> "£51.77", "£53.74", …
# $ dispo <chr> "In stock", "In stock", …
# $ page <int> 1, 1, 1, …
Quelques points clés :
- Rotation par page : chaque page utilise un identifiant de session unique, donc une IP différente. La charge est répartie sur le pool résidentiel.
- Retry conditionnel :
is_transientne réessaie que sur 429/502/503, pas sur 404 (page inexistante). - Throttle global :
req_throttle(2 / 1)impose 2 requêtes/seconde maximum, indépendamment du parallélisme. - tryCatch : une page qui échoue ne fait pas planter tout le pipeline ; on récupère les pages valides et on logge les erreurs.
Le résultat est un tibble propre de 1 000 lignes, prêt pour dplyr ou ggplot2. Pour nettoyer les prix, un simple df |> mutate(prix_num = parse_number(prix)) suffit.
Pages rendues en JavaScript avec read_html_live()
rvest propose read_html_live(), qui pilote un navigateur headless via le package chromote. C'est nécessaire pour les sites qui chargent leur contenu via React, Vue, ou des appels XHR après le rendu initial. On peut configurer le proxy au niveau du navigateur Chrome :
library(rvest)
library(chromote)
# Proxy résidentiel pour le navigateur headless
proxy_user_pass <- paste0("user-country-FR-city-paris:VOTRE_CLE_API")
browser <- ChromoteSession$new(
chromote = Chromote$new(
browser_args = c(
"--headless=new",
"--disable-gpu",
sprintf("--proxy-server=http://%s@gate.proxyhat.com:8080", proxy_user_pass)
)
)
)
# read_html_live utilise la session Chromote
page <- read_html_live("https://quotes.toscrape.com/js/")
quotes <- page |> html_elements(".quote .text") |> html_text2()
head(quotes, 2)
Attention : read_html_live() est plus lent (rendu complet, exécution JS) et consomme plus de ressources. Réservez-le aux pages qui en ont réellement besoin. Pour les pages statiques, restez sur httr2 + read_html() qui sont 10 à 50 fois plus rapides.
Bonnes pratiques de production
User-agent et en-têtes réalistes
Un user-agent par défaut de type libcurl/8.x R est un signal immédiat de bot. Utilisez un UA de navigateur courant et ajoutez des en-têtes cohérents :
req <- request(url) |>
req_proxy(PROXY_HOST, user, PROXY_PASS) |>
req_user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36") |>
req_headers(
"Accept" = "text/html,application/xhtml+xml,application/xml;q=0.9",
"Accept-Language" = "fr-FR,fr;q=0.9,en;q=0.8",
"Accept-Encoding" = "gzip, deflate, br",
"Referer" = "https://www.google.com/"
)
Gestion du rate-limiting
La règle empirique : visez moins de 2 requêtes/seconde par IP pour un site inconnu, et augmentez progressivement si les succès sont à 100%. Avec un pool résidentiel, on peut monter à 5–10 requêtes/seconde en répartissant sur plusieurs sessions, mais la courtoisie reste de mise. Un délai aléatoire (base + runif(1, 0, 2)) évite les motifs trop réguliers.
Logging et observabilité
req <- request(url) |>
req_proxy(PROXY_HOST, user, PROXY_PASS) |>
req_verbose(
req = TRUE,
resp_headers = TRUE,
performance = TRUE
)
req_verbose() sort les détails de chaque requête dans la console. En production, redirigez vers un fichier de log et surveillez le taux de succès. Un taux < 90% indique généralement un problème (proxys épuisés, cible down, pattern détecté).
Éthique et conformité
Le scraping n'est pas neutre juridiquement. Quelques principes de base :
- Préférez les API officielles quand elles existent. Beaucoup de sites offrent un accès structuré (JSON) avec une clé — plus fiable, plus légal, plus rapide.
- Respectez
robots.txtet les conditions d'utilisation. Le fichierrobots.txtindique ce que le site autorise pour les bots. - RGPD pour les données personnelles : si vous collectez des données identifiant des personnes au sein de l'UE, vous êtes soumis au RGPD. Une adresse IP est considérée comme une donnée personnelle.
- Limitez le volume : ne téléchargez pas plus que ce dont vous avez besoin. Le scraping massif peut constituer une charge déraisonnable.
- Vérifiez la juridiction : aux États-Unis, le Computer Fraud and Abuse Act et le hiQ v. LinkedIn ont posé des précédents, mais la jurisprudence évolue. En France, l'article 323-1 du Code pénal sanctionne l'accès frauduleux.
Pour un approfondissement, consultez la documentation ProxyHat et les pages web scraping et SERP tracking de notre site.
Configuration ProxyHat et pipelines mixtes
ProxyHat centralise la configuration : un seul identifiant, des flags dans le nom d'utilisateur, et deux ports (8080 HTTP, 1080 SOCKS5). Pas de SDK propriétaire à installer côté R — req_proxy() suffit. Pour les pipelines mixtes R + Python + Node, le même endpoint gate.proxyhat.com:8080 fonctionne avec requests (Python) ou undici (Node), avec les mêmes flags de session. Cela permet de partager un pool de proxys entre plusieurs langages sans réécrire la logique de rotation.
Consultez la tarification et la liste des localisations disponibles pour planifier votre volume. Un plan résidentiel typique démarre autour de 5 GB/mois, suffisant pour des dizaines de milliers de pages HTML légères.
Points clés à retenir
Key Takeaways :
httr2::req_proxy()est le point d'entrée unique pour configurer un proxy en R — compatible avecrvestviaread_html(resp_body_string(resp)).- Les proxys résidentiels passent pour du trafic normal ; les datacenter sont souvent bloqués par les anti-bots modernes.
- Le géo-ciblage et les sessions se contrôlent dans le nom d'utilisateur (
user-country-GB-session-xyz), pas dans l'URL.req_retry()+req_throttle()+tryCatch()forment la triade de la résilience.read_html_live()(chromote) gère le JS mais coûte 10–50× plus cher en ressources.- Respectez
robots.txt, le RGPD et préférez les API officielles quand elles existent.
FAQ
Qu'est-ce que l'utilisation de proxys en R ?
C'est la configuration d'un serveur mandataire entre R et la cible HTTP, via httr2::req_proxy() ou curl, pour masquer l'IP sortante, répartir la charge et contourner les blocages géographiques. Le proxy intercepte la requête de R, la relaie au site, et renvoie la réponse. Avec ProxyHat, on encode les options (pays, ville, session) dans le nom d'utilisateur.
Pourquoi utiliser un proxy en R plutôt qu'en Python ?
R n'a pas de désavantage intrinsèque pour le scraping : httr2 et rvest offrent une API pipeline aussi claire que requests/BeautifulSoup. L'avantage de R est l'intégration native avec dplyr, purrr et le tidyverse pour transformer les données immédiatement. Le proxy fonctionne de la même façon qu'en Python — seul le client HTTP change.
Quel type de proxy fonctionne le mieux pour le scraping en R ?
Les proxys résidentiels sont le meilleur compromis pour la majorité des cas : ils utilisent des IPs de FAI réels, donc passent les filtres anti-bot qui bloquent les datacenter. Pour les sites très protégés (réseaux sociaux, apps mobiles), les proxys mobiles (4G/5G) offrent une fiabilité encore supérieure, à un coût plus élevé. Les datacenter suffisent pour les API publiques sans protection.
Comment éviter les blocages quand on scrape en R avec un proxy ?
Combinez plusieurs techniques : rotation de sessions (une IP par page ou par lot), throttle à 2–5 req/s, user-agent réaliste, en-têtes cohérents (Accept-Language, Referer), retry avec backoff exponentiel sur 429/503, et respect du robots.txt. Évitez les motifs trop réguliers en ajoutant des délais aléatoires. Un taux de succès > 95% est l'objectif réaliste.
Peut-on utiliser SOCKS5 avec httr2 et rvest ?
Oui. req_proxy() accepte une URL avec le schéma socks5://. ProxyHat expose SOCKS5 sur le port 1080 : socks5://gate.proxyhat.com:1080. SOCKS5 est utile dans les environnements où le trafic HTTP proxy est filtré, et il délègue la résolution DNS au proxy, évitant les fuites DNS côté client.






