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'uso | Username | Porta |
|---|---|---|
| IP UK rotante per richiesta | user-country-GB | 8080 |
| IP di Londra (city-level) | user-country-GB-city-london | 8080 |
| Sessione sticky per login flow | user-session-abc123 | 8080 |
| SOCKS5 per tunneling TCP | user-country-DE | 1080 |
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:
| Tipo | Reputazione IP | Latency | Costo | Use case tipico |
|---|---|---|---|---|
| Datacenter | Bassa (ASN noto) | ~50 ms | Basso | API pubbliche, scraping non protetto |
| Residenziale | Alta (ISP domestico) | ~200-600 ms | Medio-alto | SERP, e-commerce, social |
| Mobile | Altissima (carrier) | ~300-800 ms | Alto | App 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()conis_transientritenta 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.Renvirono un secret manager. map_dfrfa row-binding automatico; gestisceNULLsaltando 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.
robotstxtpackage in R lo fa conpaths_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'headerRetry-Afterdi 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, usaresp_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.






