Se collezioni dati pubblici in R e improvvisamente le tue richieste restituiscono HTTP 403 o pagine vuote, il problema quasi mai è il codice: è l'IP. In questa guida pratica vediamo usare proxy in R con la stack moderna httr2 + rvest, configurando residential proxy rotanti, geo-targeting e sessioni sticky per scraping affidabile.
Perché serve un R proxy per il web scraping
I siti moderni usano bot detection basata su reputazione IP, TLS fingerprinting e rate limiting. Gli IP datacenter — quelli tipicamente usati dai provider cloud — hanno una reputazione bassissima perché associati a traffico automatizzato. Un residential proxy, invece, instrada le richieste attraverso IP assegnati a dispositivi consumer reali (ISP, mobile carrier), rendendo il tuo traffico indistinguibile da quello di un utente legittimo.
Secondo MDN, lo User-Agent da solo non basta più: i sistemi anti-bot come Cloudflare e Datadome combinano IP reputation, header order e comportamento. Per dati geo-restritti (es. listini regionali, SERP localizzate) un IP residenziale nella giusta nazione è spesso l'unico modo per ottenere la versione corretta della pagina.
Residential vs datacenter vs mobile: confronto rapido
| Tipo | Reputazione IP | Costo | Latency tipica | Casi d'uso |
|---|---|---|---|---|
| Datacenter | Bassa | Basso ($0.5–2/GB) | 50–200 ms | API pubbliche, test, fetch non bloccati |
| Residential | Alta | Medio ($3–12/GB) | 200–800 ms | Scraping SERP, e-commerce, geo-walled |
| Mobile | Altissima | Alto ($10–30/GB) | 500–2000 ms | Social, app API, siti molto aggressivi |
La stack moderna: httr2 + rvest
httr2 è il successore moderno di httr, con un'API pipeline basata su request() e metodi concatenabili. rvest resta lo standard per parsing HTML in R. La funzione chiave per i proxy è req_proxy(), che imposta proxy HTTP con autenticazione.
Ecco un esempio minimale di httr2 req_proxy con ProxyHat:
library(httr2)
library(rvest)
# ProxyHat residential HTTP proxy
proxy_url <- "http://gate.proxyhat.com:8080"
proxy_user <- "user-country-IT"
proxy_pass <- "la_tua_password"
req <- request("https://httpbin.org/ip") |>
req_proxy(proxy_url, username = proxy_user, password = proxy_pass) |>
req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") |>
req_retry(max_tries = 3, max_seconds = 30)
resp <- req |> req_perform()
body <- resp |> resp_body_json()
print(body$origin)
Se l'IP restituito corrisponde a un ISP italiano, il geo-targeting funziona. req_retry() gestisce automaticamente i transient failures (5xx e connection errors).
Geo-targeting e sessioni sticky nel username
ProxyHat codifica geo-targeting e sessioni direttamente nel username, usando un formato con trattini. Questo vale sia per HTTP (porta 8080) sia per SOCKS5 (porta 1080).
# Geo-targeting: Regno Unito, Londra
proxy_user <- "user-country-GB-city-london"
# Sessione sticky: stesso IP per tutte le richieste di una sessione
proxy_user <- "user-session-abc123"
# Combinazione: geo + sessione
proxy_user <- "user-country-DE-city-berlin-session-r42"
Le sessioni sticky sono fondamentali quando il target usa CSRF token o carrelli che dipendono dall'IP. Per il paginated scraping, invece, spesso conviene ruotare la sessione ad ogni pagina per distribuire il carico e ridurre il rischio di rate limit.
SOCKS5 su porta 1080
Per scenari dove l'HTTP CONNECT non è sufficiente (es. alcuni firewall aziendali o target che ispezionano il TLS del proxy), ProxyHat espone SOCKS5 su gate.proxyhat.com:1080. httr2 supporta SOCKS5 via req_proxy() con URL socks5://:
req <- request("https://httpbin.org/ip") |>
req_proxy("socks5://gate.proxyhat.com:1080",
username = "user-country-FR",
password = "la_tua_password") |>
req_user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)")
resp <- req |> req_perform()
Verifica la disponibilità SOCKS5 nel tuo piano ProxyHat sulla pagina pricing.
Esempio pratico: tabella paginata in tidy data frame
Vediamo un caso realistico: estrarre una tabella paginata (50 pagine, 20 righe ciascuna) ruotando sessione ad ogni pagina, con throttle e retry. Usiamo purrr::map_dfr per accumulare i risultati in un unico data frame.
library(httr2)
library(rvest)
library(purrr)
library(dplyr)
library(logger)
log_info("Avvio scraping paginato con proxy rotanti")
base_url <- "https://example.com/table?page="
proxy_pass <- Sys.getenv("PROXYHAT_PASS")
fetch_page <- function(page) {
session_id <- paste0("sess-page-", page, "-", sample(1000:9999, 1))
proxy_user <- paste0("user-country-IT-session-", session_id)
req <- request(paste0(base_url, page)) |>
req_proxy("http://gate.proxyhat.com:8080",
username = proxy_user, password = proxy_pass) |>
req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") |>
req_headers("Accept-Language" = "it-IT,it;q=0.9,en;q=0.8") |>
req_retry(max_tries = 4, max_seconds = 60,
backoff = ~ rnorm(1, mean = 2, sd = 0.5) * .x) |>
req_throttle(2 / 60) # max 2 richieste al minuto
resp <- tryCatch(
req_perform(req),
error = function(e) {
log_warn("Pagina {page} fallita: {conditionMessage(e)}")
NULL
}
)
if (is.null(resp)) return(NULL)
html <- resp |> resp_body_html()
tbl <- html |>
html_element("table.data-table") |>
html_table()
if (is.null(tbl) || nrow(tbl) == 0) {
log_warn("Nessuna tabella alla pagina {page}")
return(NULL)
}
tbl$page <- page
tbl
}
results <- map_dfr(1:50, fetch_page)
log_info("Righe totali estratte: {nrow(results)}")
Note di produzione:
req_throttle(2/60)limita a 2 richieste/minuto — adatto per target aggressivi. Per SERP scraping, 1 req/3–5 secondi è un buon default.backoffcon jitter esponenziale evita thundering herd quando più worker riprovano contemporaneamente.- Salva i risultati incrementalmente (es.
write_csvogni 10 pagine) per non perdere tutto in caso di crash. - Usa
loggerper tracciare success rate e latenza per pagina.
Pagine JS-rendered con read_html_live()
Se il target renderizza i dati via JavaScript (SPA, React, Vue), read_html() non basta. rvest >= 1.0.4 offre read_html_live(), che usa chromote (Chrome headless via DevTools Protocol).
Per instradare Chrome attraverso ProxyHat, passiamo --proxy-server alla sessione chromote:
library(rvest)
library(chromote)
# Avvia Chrome con proxy HTTP ProxyHat
# L'autenticazione avviene via header Proxy-Authorization gestito da Chrome
proxy_user <- "user-country-ES-session-js7"
proxy_pass <- Sys.getenv("PROXYHAT_PASS")
proxy_auth <- jsonlite::base64_encode(paste0(proxy_user, ":", proxy_pass))
b <- ChromoteSession$new(
browser = ChromoteBrowser$new(
args = c(
"--proxy-server=http://gate.proxyhat.com:8080",
"--disable-blink-features=AutomationControlled",
"--no-sandbox"
)
)
)
# Inietta header Proxy-Authorization via CDP Network domain
b$Network$setExtraHTTPHeaders(headers = list(
"Proxy-Authorization" = paste("Basic", proxy_auth)
))
page <- read_html_live("https://example.com/spa-table",
chrome = b,
wait = "table tbody tr")
tbl <- page |>
html_elements("table tbody tr") |>
map_dfr(~ {
cells <- html_elements(.x, "td")
tibble(
nome = html_text(cells[[1]]),
valore = html_text(cells[[2]])
)
})
print(head(tbl))
b$close()
Nota:read_html_live()è più lento (2–5s per pagina) e consuma più risorse. Usalo solo quandoread_html()non restituisce i dati. Per pipeline Python/Node che condividono la stessa logica di rotazione, il ProxyHat SDK usa lo stesso pattern di username con flagcountry,city,session.
Header realistici e anti-fingerprinting
Oltre al proxy, gli header HTTP devono sembrare umani. Alcune regole pratiche:
- Usa
User-Agentcoerenti conAccept-Languageesec-ch-ua(Chrome moderno). - Mantieni l'ordine degli header come nei browser reali (httr2 lo gestisce, ma evita header custom sospetti).
- Non inviare
X-Requested-Withse non lo fa il browser legittimo. - Per pagine protette, aggiungi
Referercoerente con il flusso di navigazione.
req <- request("https://shop.example.com/products") |>
req_proxy("http://gate.proxyhat.com:8080",
username = "user-country-IT-city-milan",
password = proxy_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" = "it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7",
"Accept-Encoding" = "gzip, deflate, br",
"Sec-Fetch-Dest" = "document",
"Sec-Fetch-Mode" = "navigate",
"Sec-Fetch-Site" = "none",
"Upgrade-Insecure-Requests" = "1"
)
Etica, compliance e best practice
Lo scraping non è illegale di per sé, ma ha confini legali precisi. Alcuni principi:
- Preferisci API ufficiali. Se il target offre un'API (anche a pagamento), spesso è più affidabile e legalmente più sicura del scraping. Vedi la pagina su SERP tracking per approcci strutturati.
- Rispetta
robots.txt. Anche se non vincolante in tutte le giurisdizioni, ignorarlo è un segnale negativo in caso di disputa. La W3C robots.txt community mantiene la specifica de facto. - GDPR per dati personali. Se raccogli dati di persone nell'UE (nome, email, profili social), il GDPR si applica. Serve base giuridica (consenso, interesse legittimo) e rispetto dei diritti dell'interessato. La Commissione Europea fornisce orientamenti ufficiali.
- Leggi i ToS. Molti siti vietano esplicitamente lo scraping; la violabilità dipende dalla giurisdizione (in USA, hiQ Labs v. LinkedIn ha parzialmente legitimizzato lo scraping di dati pubblici, ma la giurisprudenza è fluida).
- Rate limiting responsabile. Non saturare il target:
req_throttle()con 1–2 req/sec è raramente problematico.
Per approfondire i casi d'uso legittimi, consulta la pagina web scraping e l'elenco delle locazioni proxy disponibili.
Key takeaways
- httr2 + rvest è la stack moderna per il web scraping in R;
req_proxy()è il modo idiomatico per configurare proxy HTTP/SOCKS5. - I residential proxy sono necessari per target bloccati o geo-walled; i datacenter IP vengono filtrati dalla maggior parte dei sistemi anti-bot moderni.
- Geo-targeting e sessioni sticky si codificano nel username ProxyHat (
user-country-XX-city-yy-session-zz), non nell'URL. - Per paginazione, ruota la sessione ad ogni pagina con
purrr::map,req_retry()ereq_throttle()per resilienza. - Per pagine JS-rendered, usa
read_html_live()con chromote e--proxy-server. - Rispetta
robots.txt, GDPR e ToS; preferisci API ufficiali quando disponibili.
FAQ
Cos'è usare proxy in R?
Significa instradare le richieste HTTP/SOCKS5 generate da codice R (tipicamente con httr2, httr o curl) attraverso un server proxy che nasconde l'IP originario e presenta un IP diverso al target. In R, la funzione idiomatica è httr2::req_proxy(), che accetta URL, username e password per l'autenticazione.
Perché i proxy importano per il web scraping in R?
Perché i siti moderni bloccano IP datacenter e rate-limitano per IP. Senza proxy, dopo poche decine di richieste ricevi 403/429. I residential proxy distribuiscono il traffico su IP consumer reali, riducendo il tasso di blocco dal 80–100% a sotto il 5% su target moderatamente protetti.
Quale tipo di proxy funziona meglio per rvest proxy?
Dipende dal target. Per SERP e e-commerce con protezione moderata, i residential proxy offrono il miglior rapporto costo/affidabilità ($3–12/GB). Per social e app molto aggressive, i mobile proxy hanno reputazione massima ma costano $10–30/GB. I datacenter vanno bene solo per API pubbliche senza anti-bot.
Come evitare blocchi usando proxy in R?
Combina quattro elementi: (1) residential proxy con rotazione per richiesta o sessione; (2) header realistici (req_user_agent(), Accept-Language, sec-ch-ua); (3) throttle con req_throttle() a 1–2 req/sec; (4) retry con backoff esponenziale via req_retry(). Evita pattern di accesso prevedibili (stesso User-Agent per migliaia di richieste, orari fissi).
Posso usare SOCKS5 con httr2?
Sì. req_proxy() accetta URL con schema socks5://. Su ProxyHat, SOCKS5 è disponibile su gate.proxyhat.com:1080, mentre HTTP usa la porta 8080. L'autenticazione (username con geo/session flags + password) è identica per entrambi i protocolli.






