Si has intentado recolectar datos públicos en R y te has topado con respuestas 403 Forbidden, bloques por IP o contenido que cambia según el país de origen, usar proxies en R es la pieza que falta. Esta guía muestra cómo integrar proxies residenciales con la stack moderna de R — httr2 para construir peticiones HTTP y rvest para parsear HTML — con ejemplos ejecutables, rotación de sesiones y patrones de producción.
Por qué necesitas proxies residenciales en R
La mayoría de sitios modernos aplican protección anti-bot que va más allá de un simple User-Agent. Sistemas como Cloudflare, Akamai o DataDome inspeccionan la reputación de la IP de origen. Las IPs de datacenter — las que obtienes de proveedores cloud convencionales — tienen una tasa de bloqueo mucho mayor porque se asocian con tráfico automatizado. Las IPs residenciales, en cambio, pertenecen a dispositivos domésticos reales y se mezclan con tráfico humano normal.
Según la documentación de DataDome sobre bot management, las soluciones anti-bot modernas combinan fingerprinting del navegador, análisis de reputación de IP y patrones de comportamiento. Un R proxy residencial no resuelve todo, pero elimina la variable más visible: la reputación de la IP.
| Tipo de proxy | Reputación de IP | Velocidad típica | Tasa de bloqueo | Caso ideal |
|---|---|---|---|---|
| Datacenter | Baja (marcada como hosting) | 50–100 ms | Alta en sitios protegidos | APIs internas, endpoints sin anti-bot |
| Residencial | Alta (ISP doméstico real) | 200–800 ms | Baja | Scraping de e-commerce, SERP, geo-walled content |
| Móvil | Muy alta (operadora 4G/5G) | 300–1500 ms | Muy baja | Redes sociales, captchas agresivos |
La stack moderna: httr2 + rvest
httr2 es el sucesor moderno de httr, diseñado por Hadley Wickham y el equipo tidyverse. A diferencia de httr, que construye peticiones imperativamente, httr2 usa un patrón encadenable con request() y modificadores como req_proxy(), req_headers(), req_retry() y req_throttle(). Esto encaja naturalmente con pipes (%>% o |>).
La documentación oficial de httr2 describe el patrón de construir una petición base y luego añadir capas. Para web scraping en R, la combinación con rvest es directa: httr2 maneja el transporte y rvest el parseo.
Instalación y dependencias
install.packages(c("httr2", "rvest", "purrr", "tibble", "dplyr"))
# Para páginas con JS:
install.packages("readr")
# read_html_live() requiere chromote + Chrome instalado
Ejemplo 1: Petición básica con proxy y parseo con rvest
library(httr2)
library(rvest)
# ProxyHat HTTP gateway — credenciales en el dashboard
proxy_user <- "user-country-US"
proxy_pass <- "tu_password"
req <- request("https://httpbin.org/ip") |>
req_proxy(
url = "http://gate.proxyhat.com",
port = 8080,
username = proxy_user,
password = proxy_pass
) |>
req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") |>
req_timeout(30)
resp <- req |> req_perform()
# Parsear la respuesta con rvest
html <- resp |> resp_body_string() |> read_html()
html |> html_text2()
# { "origin": "XX.XX.XX.XX" } — la IP del proxy residencial
Aquí req_proxy() configura el proxy a nivel de la petición. El formato httr2 req_proxy acepta URL, puerto, usuario y contraseña por separado, lo que evita escapar credenciales manualmente en una URL.
Geo-targeting y sesiones sticky en el username
ProxyHat codifica parámetros de geo-localización y sesiones directamente en el nombre de usuario. Esto significa que no necesitas endpoints separados ni headers especiales: basta con modificar el string del username.
Geo-targeting por país y ciudad
# IP residencial en Londres, Reino Unido
req_uk <- request("https://example.co.uk/products") |>
req_proxy(
url = "http://gate.proxyhat.com",
port = 8080,
username = "user-country-GB-city-london",
password = "tu_password"
) |>
req_user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)")
resp_uk <- req_uk |> req_perform()
Sesión sticky (IP persistente)
Por defecto, ProxyHat rota la IP en cada petición. Para flujos que requieren mantener la misma IP — login seguido de navegación, o paginación que valida sesión — usa el flag session:
# Mantener la misma IP durante toda la sesión
sticky_user <- "user-country-DE-session-misession001"
req_sticky <- request("https://tienda.example.de/login") |>
req_proxy("http://gate.proxyhat.com", 8080,
username = sticky_user,
password = "tu_password") |>
req_headers("Accept-Language" = "de-DE,de;q=0.9")
# Todas las peticiones con el mismo session ID saldrán de la misma IP
SOCKS5 en el puerto 1080
Para conexiones que requieren SOCKS5 — por ejemplo, ciertos clientes que no soportan CONNECT HTTP o entornos con firewares restrictivos — ProxyHat expone el puerto 1080:
req_socks <- request("https://httpbin.org/ip") |>
req_proxy(
url = "socks5://gate.proxyhat.com",
port = 1080,
username = "user-country-FR",
password = "tu_password"
) |>
req_timeout(45)
resp_socks <- req_socks |> req_perform()
Nota: El SDK de ProxyHat sigue el mismo patrón de credenciales en el username, lo que permite mezclar pipelines de R con Python y Node.js sin cambiar la configuración del proxy. Consulta la documentación oficial de ProxyHat para detalles del SDK.
Ejemplo práctico: tabla paginada a data frame con purrr
El escenario más común de rvest proxy es extraer una tabla HTML que se repite en múltiples páginas. Aquí construimos un colector resiliente que rota la sesión por página, aplica reintentos exponenciales y limita la tasa de peticiones.
library(httr2)
library(rvest)
library(purrr)
library(dplyr)
library(tibble)
# Función para construir una petición con sesión rotativa por página
build_req <- function(page_num, base_url) {
session_id <- paste0("page-session-", page_num, "-", as.integer(Sys.time()))
user <- paste0("user-country-US-session-", session_id)
request(paste0(base_url, "?page=", page_num)) |>
req_proxy("http://gate.proxyhat.com", 8080,
username = user,
password = "tu_password") |>
req_user_agent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
) |>
req_headers(
"Accept" = "text/html,application/xhtml+xml",
"Accept-Language" = "en-US,en;q=0.9"
) |>
req_retry(
max_tries = 4,
max_seconds = 60,
backoff = ~ 2 ^ (retry_after - 1) # backoff exponencial: 1s, 2s, 4s, 8s
) |>
req_throttle(2 / 1) # máx 2 peticiones por segundo
}
# Extraer tabla de una sola página
scrape_page <- function(page_num, base_url) {
req <- build_req(page_num, base_url)
tryCatch({
resp <- req_perform(req)
if (resp_status(resp) != 200) {
message("Página ", page_num, " — status ", resp_status(resp))
return(NULL)
}
html <- resp_body_string(resp) |> read_html()
tables <- html |> html_table()
if (length(tables) == 0) {
message("Página ", page_num, " — sin tablas")
return(NULL)
}
# Tomar la primera tabla y limpiar nombres
df <- tables[[1]]
names(df) <- gsub("[^a-zA-Z0-9_]", "_", tolower(names(df)))
df$page <- page_num
df
}, error = function(e) {
message("Error en página ", page_num, ": ", conditionMessage(e))
NULL
})
}
# Recorrer 10 páginas con purrr::map_dfr
base_url <- "https://example.com/products"
result <- map_dfr(1:10, function(p) {
scrape_page(p, base_url)
})
glimpse(result)
# Rows: 250 | Columns: 6
# $ product_id <chr> "A100", "A101", ...
# $ name <chr> "Widget Pro", ...
# $ price <chr> "$19.99", "$24.99", ...
# $ stock <int> 45, 12, 0, ...
# $ page <int> 1, 1, 1, ...
Cada página usa un session-id distinto, lo que fuerza rotación de IP. req_retry() reintenta hasta 4 veces con backoff exponencial. req_throttle(2 / 1) limita a 2 peticiones por segundo, lo que equivale a ~120 peticiones por minuto — suficiente para la mayoría de sitios sin disparar rate limits.
Concurrencia con futuros
Para acelerar la recolección, puedes paralelizar con el paquete furrr:
library(furrr)
plan(multisession, workers = 4) # 4 procesos en paralelo
result <- future_map_dfr(1:10, function(p) {
scrape_page(p, base_url)
}, .options = furrr_options(seed = TRUE))
plan(sequential) # cerrar workers
Con 4 workers y 50 sesiones concurrentes disponibles en tu plan de ProxyHat, una colección de 10 páginas puede completarse en menos de 15 segundos frente a ~5 segundos por página en secuencial.
Páginas renderizadas con JavaScript: read_html_live()
rvest incluye read_html_live(), que lanza un navegador Chrome real vía chromote y permite interactuar con páginas que cargan contenido dinámicamente. Para enrutar este navegador a través del proxy de ProxyHat, configura Chrome con el flag --proxy-server:
library(rvest)
library(chromote)
# Configurar Chrome para usar ProxyHat como proxy HTTP
proxy_url <- "http://gate.proxyhat.com:8080"
# chromote permite pasar argumentos a Chrome
b <- ChromoteSession$new(
chromote = Chromote$new(
args = c(
paste0("--proxy-server=", proxy_url),
"--disable-blink-features=AutomationControlled",
"--no-sandbox"
)
)
)
# Navegar a la página y esperar a que cargue el contenido dinámico
page <- read_html_live(
"https://spa.example.com/products",
chromote = b
)
# Esperar a que aparezca un selector específico
page |> live_wait_for(".product-card", timeout = 10000)
# Extraer datos como con rvest normal
cards <- page |> html_elements(".product-card")
names <- cards |> html_element(".product-name") |> html_text2()
prices <- cards |> html_element(".price") |> html_text2()
tibble(name = names, price = prices)
La autenticación del proxy en Chrome requiere manejo adicional. Para proxies con credenciales, Chrome no envía usuario y contraseña automáticamente en el flag --proxy-server. Una alternativa es usar un proxy sin autenticación en localhost que reenvía a ProxyHat, o configurar la extensión de autenticación de proxy. Para la mayoría de casos de web scraping en R, la ruta httr2 + req_proxy es más sencilla y suficiente.
Headers realistas y anti-fingerprinting
Incluso con un proxy residencial, unos headers inconsistentes pueden delatar tu tráfico. La documentación de MDN sobre User-Agent explica cómo los navegadores construyen este header. La regla: que tus headers sean coherentes entre sí.
# Headers coherentes para simular Chrome en Windows
realistic_req <- request("https://example.com") |>
req_proxy("http://gate.proxyhat.com", 8080,
username = "user-country-US",
password = "tu_password") |>
req_user_agent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.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" = "en-US,en;q=0.5",
"Accept-Encoding" = "gzip, deflate, br",
"Sec-Fetch-Dest" = "document",
"Sec-Fetch-Mode" = "navigate",
"Sec-Fetch-Site" = "none",
"Sec-Fetch-User" = "?1",
"Upgrade-Insecure-Requests" = "1"
)
Errores comunes y casos límite
- Olvidar
req_timeout(): sin timeout, una petición colgada puede bloquear tu script indefinidamente. Usa siemprereq_timeout(30)o superior. - No manejar
resp_status():req_perform()no lanza error automáticamente en 4xx/5xx. Usareq_error()o verifica el status manualmente. - Sesiones sticky demasiado largas: mantener la misma IP durante horas aumenta el riesgo de bloqueo. Rota el
session-idcada 50–100 peticiones. - Encoding incorrecto: si
resp_body_string()devuelve caracteres extraños, verifica el charset conresp_content_type(resp)y usaiconv()si es necesario. - No respetar
robots.txt: antes de cualquier scraping, revisa/robots.txtdel sitio objetivo. Algunos endpoints están explícitamente excluidos.
Patrón de circuit breaker en R
# Circuit breaker simple: detener si hay 3 errores consecutivos
scrape_with_breaker <- function(pages, base_url, max_consecutive_errors = 3) {
consecutive_errors <- 0
results <- list()
for (p in pages) {
df <- scrape_page(p, base_url)
if (is.null(df)) {
consecutive_errors <- consecutive_errors + 1
if (consecutive_errors >= max_consecutive_errors) {
message("Circuit breaker activado en página ", p,
". Deteniendo recolección.")
break
}
} else {
consecutive_errors <- 0
results[[length(results) + 1]] <- df
}
}
bind_rows(results)
}
Ética, cumplimiento y alternativas
El scraping de datos públicos es legal en muchas jurisdicciones, pero hay límites importantes:
- GDPR (UE): si recolectas datos personales de personas en la UE, necesitas una base legal. La página oficial de la Comisión Europea sobre protección de datos describe las obligaciones. Datos de empresas o productos públicos generalmente no son datos personales, pero conviene revisar caso por caso.
- robots.txt y Términos de Servicio:
robots.txtes una convención, no una ley, pero ignorarlo puede interpretarse como acceso no autorizado. Lee los ToS del sitio. - Preferir APIs oficiales: si el sitio ofrece una API pública o un feed, úsalo. Es más fiable, legalmente más seguro y técnicamente más estable que scraping HTML.
- Rate limiting responsable: incluso con proxies, limita tu tasa.
req_throttle()existe por una razón.
Para casos de uso específicos de recolección de datos, consulta las páginas de web scraping y SERP tracking de ProxyHat. Para ver la cobertura geográfica disponible, visita ubicaciones de ProxyHat.
Configuración de ProxyHat para pipelines R
La configuración de ProxyHat para R se reduce a tres parámetros fijos y variables en el username:
| Parámetro | Valor | Notas |
|---|---|---|
| Gateway HTTP | gate.proxyhat.com:8080 | Proxy HTTP/HTTPS por defecto |
| Gateway SOCKS5 | gate.proxyhat.com:1080 | Para clientes que requieren SOCKS5 |
| Username base | user | Se modifica con flags de geo y sesión |
| Geo-targeting | user-country-XX-city-yyy | XX = código ISO 3166-1 alpha-2 |
| Sticky session | user-session-ID | ID arbitrario; misma IP mientras dure |
Para ver planes y precios, visita la página de precios de ProxyHat.
Key Takeaways
- httr2 + rvest es la stack recomendada para scraping con proxies en R moderno.
req_proxy()maneja la autenticación sin escapar URLs manualmente. - Los proxies residenciales reducen drásticamente los bloqueos frente a IPs de datacenter, especialmente en sitios con anti-bot como Cloudflare o DataDome.
- Geo-targeting y sesiones se codifican en el username (
user-country-GB-city-london-session-xyz), sin endpoints extra. req_retry()con backoff exponencial yreq_throttle()son esenciales para recolección resiliente en producción.- Para páginas con JS,
read_html_live()con chromote permite renderizar, pero la autenticación de proxy requiere configuración adicional. - La ética importa: respetar robots.txt, GDPR y preferir APIs oficiales cuando existan.
Preguntas frecuentes
¿Qué es usar proxies en R?
Usar proxies en R significa enrutar las peticiones HTTP de tu código R — típicamente con httr2, curl o rvest — a través de un servidor intermediario que cambia tu IP de origen. En httr2, se configura con req_proxy("http://gate.proxyhat.com", 8080, username, password). Esto permite acceder a contenido geo-restringido, evitar bloqueos por IP y distribuir peticiones entre múltiples IPs residenciales.
¿Por qué importa usar proxies en R para usuarios de proxies?
Porque sin un proxy, tu IP de datacenter o doméstica queda expuesta y es fácilmente bloqueada por sistemas anti-bot. Un proxy residencial presenta una IP de ISP doméstico real, lo que reduce la tasa de bloqueo del 50–80% (datacenter) a menos del 5% en sitios protegidos. Para recolección de datos a escala, esto es la diferencia entre un script que corre en minutos y uno que falla en la tercera petición.
¿Qué tipo de proxy funciona mejor para usar proxies en R?
Para scraping web en R, los proxies residenciales son la mejor opción general: combinan reputación de IP alta con latencia aceptable (200–800 ms). Los proxies móviles ofrecen la máxima confianza pero son más lentos (300–1500 ms) y caros. Los datacenter son rápidos (50–100 ms) pero se bloquean fácilmente. Para SERP tracking y e-commerce, residencial es el equilibrio ideal.
¿Cómo evitas bloqueos al implementar proxies en R?
Rota el session-id cada 50–100 peticiones para no mantener la misma IP demasiado tiempo. Usa req_throttle() para limitar la tasa (2 peticiones/segundo es un buen punto de partida). Configura req_retry() con backoff exponencial. Envía headers realistas y coherentes con req_user_agent() y req_headers(). Respeta robots.txt y, si el sitio ofrece API oficial, úsala en su lugar.
¿Puedo usar SOCKS5 con httr2 en R?
Sí. req_proxy() acepta URLs con esquema socks5://. Para ProxyHat, usa req_proxy("socks5://gate.proxyhat.com", 1080, username, password). SOCKS5 es útil en entornos donde CONNECT HTTP no funciona o para ciertos clientes que solo soportan SOCKS.






