Usare proxy in R con httr2 e rvest: guida pratica per data scientist

Una guida code-first per configurare proxy residenziali in R con httr2 (req_proxy) e rvest, con rotazione sessioni, geo-targeting, SOCKS5, paginazione e pagine JS-rendered.

Using Proxies in R: A Code-First Guide with httr2 and rvest

Se colleghi dati pubblici dal web con R, prima o poi incontri un muro: rate limit, geo-block o un anti-bot che banna il tuo IP di datacenter. Usare proxy in R significa instradare le tue richieste httr2 e rvest attraverso un IP residenziale, in modo che il target veda un utente reale invece di un crawler. In questa guida mostro come configurare req_proxy() verso gate.proxyhat.com:8080, gestire geo-targeting e sessioni sticky, ruotare IP per pagina, e parsare tabelle HTML in un tidy data frame, con esempi runnable e consigli di produzione.

Perché serve un R proxy per il web scraping

Il R proxy non è un'esotica libreria statistica: è semplicemente l'uso di un proxy HTTP/SOCKS5 all'interno del flusso di richiesta di R. Il problema nasce perché i siti difendono i loro dati con tre meccanismi comuni:

  • Rate limiting per IP: dopo 50-200 richieste al minuto, il server risponde 429 Too Many Requests o 403 Forbidden.
  • Geo-wall: contenuti visibili solo da determinati paesi (es. prezzi regionali, SERP localizzate).
  • Anti-bot fingerprinting: Cloudflare, Datadome, Akamai riconoscono gli IP datacenter dai loro ASN (es. AWS, OVH, DigitalOcean) e li sfidano con CAPTCHA o JS challenge.

Un IP residenziale appartiene a un ISP domestico (Vodafone, Telecom, Comcast) e quindi ha una reputazione più alta ai occhi degli anti-bot. Secondo la documentazione di MDN sull'header Forwarded, il proxy può anche alterare header come X-Forwarded-For: questo significa che devi fidarti del proxy per non leakare il tuo IP reale. Per approfondire il pattern di rotazione, vedi RFC 7230 (HTTP/1.1 messaging) per la semantica delle connessioni keep-alive.

Stack moderno: httr2 + rvest

Il pacchetto httr2 di Hadley Wickham è la base moderna per HTTP in R. Costruisci una request con request(), aggiungi proxy con req_proxy(), esegui con req_perform(). Per il parsing, rvest espone read_html(), html_elements(), html_table() e, per pagine JS-rendered, read_html_live() basato su chromote.

Installazione e dipendenze

install.packages(c("httr2", "rvest", "purrr", "dplyr", "tibble", "chromote"))

Esempio base: GET con proxy verso ProxyHat

library(httr2)
library(rvest)

# ProxyHat residential HTTP gateway
PROXY_URL <- "http://gate.proxyhat.com:8080"
PROXY_USER <- "user-country-GB-city-london"
PROXY_PASS <- "your_password"

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) AppleWebKit/537.36") |>
  req_retry(max_tries = 3, backoff = ~ 2 ^ (.x - 1)) |>
  req_throttle(2 / 60)  # 2 richieste al minuto massimo

resp <- req_perform(req)
status_code(resp)  # 200
resp |> resp_body_json()
# $ip
# [1] "82.x.x.x"  # IP residenziale UK

Notare il pattern: req_proxy(url, username, password) è tutto ciò che serve. La rotazione e il geo-targeting si fanno nel username, non in parametri extra.

Geo-targeting e sessioni sticky nel username

ProxyHat codifica le opzioni nel campo username con la sintassi user-country-XX-city-YY e user-session-NAME. Questo vale sia per HTTP (porta 8080) sia per SOCKS5 (porta 1080).

Caso d'usoUsernamePorta
IP UK rotante per richiestauser-country-GB8080
IP di Londra (city-level)user-country-GB-city-london8080
Sessione sticky per login flowuser-session-abc1238080
SOCKS5 per tunneling TCPuser-country-DE1080

SOCKS5 su porta 1080

req_socks <- request("https://httpbin.org/headers") |>
  req_proxy("socks5://gate.proxyhat.com:1080",
           username = "user-country-DE",
           password = "your_password") |>
  req_user_agent("Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0")

resp <- req_perform(req_socks)
resp |> resp_body_json()$headers$`User-Agent`

SOCKS5 è utile quando il target blocca CONNECT su HTTP proxy o quando vuoi tunnelare protocolli non-HTTP. La latenza tipica aggiuntiva è 20-80 ms rispetto all'HTTP proxy.

Residenziale vs datacenter: quando serve cosa

Non tutti i job richiedono IP residenziali. La tabella confronta i tre tipi:

TipoReputazione IPLatencyCostoUse case tipico
DatacenterBassa (ASN noto)~50 msBassoAPI pubbliche, scraping non protetto
ResidenzialeAlta (ISP domestico)~200-600 msMedio-altoSERP, e-commerce, social
MobileAltissima (carrier)~300-800 msAltoApp mobile, account creation

Per SERP tracking e web scraping su siti con anti-bot aggressivo, il residenziale è la scelta predefinita. Per API pubbliche con rate limit blando, il datacenter basta e avanza, risparmiando banda preziosa.

Esempio worked: tabella paginata in tidy data frame

Scenario: una pagina elenco con 20 pagine, ogni pagina ha una tabella HTML. Vogliamo un tibble finale con tutte le righe. Ruotiamo sessione per pagina per distribuire il carico su IP diversi.

library(httr2)
library(rvest)
library(purrr)
library(dplyr)
library(tibble)

fetch_page <- function(page) {
  session_id <- paste0("page-", page, "-", as.integer(Sys.time()))
  username <- paste0("user-country-GB-session-", session_id)

  req <- request("https://example.com/list") |>
    req_url_query(page = page) |>
    req_proxy("http://gate.proxyhat.com:8080",
             username = username,
             password = Sys.getenv("PROXYHAT_PASS")) |>
    req_user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)") |>
    req_headers("Accept-Language" = "en-GB,en;q=0.9") |>
    req_retry(max_tries = 4,
              backoff = ~ 2 ^ (.x - 1),
              is_transient = ~ resp_status(.x) %in% c(429, 503, 504)) |>
    req_throttle(10 / 60)  # max 10 req/min

  tryCatch({
    resp <- req_perform(req)
    if (resp_status(resp) != 200) return(NULL)

    nodes <- resp_body_string(resp) |> read_html()
    tbl <- nodes |> html_elements("table.data-table") |> html_table(trim = TRUE)
    if (length(tbl) == 0) return(NULL)
    tbl[[1]] |> mutate(page = page)
  }, error = function(e) {
    message("Page ", page, " failed: ", conditionMessage(e))
    NULL
  })
}

pages <- 1:20
df <- map_dfr(pages, fetch_page)
dim(df)
# [1] 400   5

Note di produzione:

  • req_retry() con is_transient ritenta su 429/503/504 con backoff esponenziale.
  • req_throttle() limita globalmente il rate; per job lunghi usa 5-10 req/min per IP.
  • La password va in Sys.getenv(), mai hardcoded nel sorgente: usa .Renviron o un secret manager.
  • map_dfr fa row-binding automatico; gestisce NULL saltando la pagina.

Esecuzione concorrente con future + furrr

library(future)
library(furrr)
plan(multisession, workers = 4)

df <- future_map_dfr(pages, fetch_page,
                    .options = furrr_options(seed = TRUE))
plan(sequential)

Attenzione: con 4 worker concurrently, il rate aggregato è 4× quello di un singolo worker. Se il target ha rate limit per IP, ruota sessioni diverse per worker per evitare che lo stesso IP venga penalizzato. ProxyHat supporta 100+ sessioni simultanee senza conflitto.

Pagine JS-rendered con read_html_live

Se la tabella è renderizzata da React/Vue dopo il caricamento, read_html() non la vede. rvest::read_html_live() usa chromote per pilotare una istanza Chrome headless. Per instradare Chrome via ProxyHat, configura chromote con --proxy-server:

library(rvest)
library(chromote)

# Avvia Chrome con proxy HTTP ProxyHat
b <- ChromoteSession$new(
  chromote = Chromote$new(
    args = c("--proxy-server=http://gate.proxyhat.com:8080",
            "--disable-blink-features=AutomationControlled")
  )
)

# NOTA: l'autenticazione HTTP proxy in Chrome headless richiede
# un proxy locale che inoltra con credenziali (es. cntlm o px).
# In alternativa, usa SOCKS5 con credenziali nel URL:
# --proxy-server=socks5://gate.proxyhat.com:1080

page <- read_html_live("https://example.com/spa", chromote = b)
tbl <- page |> html_elements("table.results") |> html_table()

Per siti con anti-bot serio, combina read_html_live() con un req_user_agent() realistico e header Accept-Language coerenti con il geo del proxy. Un IP UK con Accept-Language: en-US è un segnale di anomalia che alcuni anti-bot marcano.

Header realistici con req_headers

req <- request("https://example.com/api/data") |>
  req_proxy("http://gate.proxyhat.com:8080",
           username = "user-country-FR-city-paris",
           password = Sys.getenv("PROXYHAT_PASS")) |>
  req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") |>
  req_headers(
    "Accept" = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
    "Accept-Language" = "fr-FR,fr;q=0.9,en;q=0.8",
    "Accept-Encoding" = "gzip, deflate, br",
    "Sec-Fetch-Dest" = "document",
    "Sec-Fetch-Mode" = "navigate",
    "Sec-Fetch-Site" = "none",
    "Upgrade-Insecure-Requests" = "1"
  ) |>
  req_retry(max_tries = 3)

ProxyHat SDK per pipeline miste Python/Node

Se la tua pipeline mescola R per l'analisi e Python/Node per la raccolta, il pattern ProxyHat è identico: gateway gate.proxyhat.com, credenziali nel username, rotazione per-request. Il ProxyHat SDK espone gli stessi flag (country, city, session) in Python e Node, così puoi condividere configurazione tra team. Per i paesi disponibili e i piani, consulta il dashboard.

Etica e compliance

Il web scraping non è illegale di per sé, ma ha confini:

  • robots.txt: rispettalo. robotstxt package in R lo fa con paths_allowed().
  • Termini di servizio: leggili. Molti SaaS vietano scraping esplicito; violarli può rompere contratti o causare cause civili.
  • GDPR (EU): se colleghi dati personali di persone nell'UE (nome, email, IP), serve base legale. Dati pubblici anonimi (prezzi, tabelle statistiche) sono generalmente più sicuri, ma valuta caso per caso.
  • Preferisci API ufficiali: se il sito offre un'API JSON documentata, usala. È più stabile, più veloce e meno rischiosa del scraping HTML.

Per approfondire la compliance, la pagina ufficiale della Commissione Europea sulla protezione dei dati è il riferimento normativo. Per robots.txt, la RFC 9309 definisce lo standard.

Errori comuni e edge case

  • Password nel sorgente: usa Sys.setenv(PROXYHAT_PASS = "...") o .Renviron. Un commit su Git con credenziali è una fuga.
  • Sessione sticky per troppo tempo: una sessione lunga su un sito con anti-bot può comunque essere fingerprintata. Ruota ogni 50-200 richieste.
  • Ignorare Retry-After: req_retry() rispetta l'header Retry-After di default, ma se lo disabiliti, rischi ban permanenti.
  • Mixed content con HTTPS: se la pagina ha asset HTTP, il proxy potrebbe bloccarli. Usa req_options(ssl_verifyhost = TRUE) e non disabilitare TLS verification.
  • Encoding UTF-8: read_html() gestisce encoding automaticamente, ma se vedi mojibake, usa resp_body_string(resp, encoding = "UTF-8") esplicito.

Key takeaways

  • req_proxy('http://gate.proxyhat.com:8080', user, pass) è tutto ciò che serve per abilitare un R proxy in httr2.
  • Geo-targeting e sessioni sticky vanno nel username con sintassi user-country-GB-city-london-session-abc123.
  • SOCKS5 su porta 1080 per tunneling TCP o target che bloccano CONNECT.
  • Combina req_retry() + req_throttle() per resilienza; ruota sessione per pagina per distribuire il carico.
  • Per pagine JS-rendered, usa read_html_live() con Chrome --proxy-server.
  • Rispetta robots.txt, ToS e GDPR; preferisci API ufficiali quando disponibili.

FAQ

Cos'è usare proxy in R?

Significa instradare le richieste HTTP/SOCKS5 generate da R (tipicamente via httr2::req_proxy() o curl::new_handle(proxy = ...)) attraverso un server proxy che nasconde il tuo IP reale. In R moderno, il pattern è request(url) |> req_proxy('http://gate.proxyhat.com:8080', user, pass) |> req_perform(), con credenziali e opzioni di rotazione codificate nel username.

Perché usare proxy in R è importante per chi raccoglie dati?

Perché i siti applicano rate limit per IP, geo-block e anti-bot fingerprinting basati su ASN. Senza proxy, dopo 50-200 richieste il tuo IP viene bannato o sfidato con CAPTCHA. Un proxy residenziale presenta un IP da ISP domestico, riducendo il rischio di blocco e permettendo geo-targeting (es. prezzi regionali, SERP localizzate).

Quale tipo di proxy funziona meglio per usare proxy in R?

Dipende dal target. Per SERP, e-commerce e siti con anti-bot aggressivo, il residenziale è la scelta predefinita (reputazione IP alta, ~200-600 ms latency). Per API pubbliche con rate limit blando, il datacenter basta e costa meno. Per app mobile e account creation, il mobile proxy ha la reputazione più alta ma latency più variabile (300-800 ms).

Come evitare i blocchi quando si usa un proxy in R?

Combina più strategie: ruota sessione per richiesta o per gruppo di richieste (50-200), usa req_retry() con backoff esponenziale su 429/503/504, limita il rate con req_throttle(), imposta un req_user_agent() realistico e header Accept-Language coerenti con il geo del proxy, e rispetta robots.txt e i ToS del sito.

Posso usare lo stesso proxy in Python e R?

Sì. ProxyHat usa gateway identici (gate.proxyhat.com:8080 HTTP, :1080 SOCKS5) e la stessa sintassi di username per country/city/session in tutti i linguaggi. Il ProxyHat SDK espone wrapper equivalenti in Python e Node, così puoi condividere configurazione tra team polyglot.

Pronto per iniziare?

Accedi a oltre 50M di IP residenziali in oltre 148 paesi con filtraggio AI.

Vedi i prezziProxy residenziali
← Torna al Blog