Si alguna vez intentaste recolectar datos públicos desde R y te encontraste con errores 403, bloqueos por IP o contenido que cambia según el país de origen, usar proxies en R es la pieza que te falta. Esta guía te muestra, con código ejecutable, cómo configurar httr2 y rvest para enrutar tus peticiones a través de proxies residenciales, rotar sesiones, geolocalizar peticiones y manejar páginas renderizadas con JavaScript — todo con patrones listos para producción.
Por qué necesitas proxies en R: el contexto técnico
Cuando haces web scraping en R con httr2 o rvest, tu tráfico sale desde la IP de tu máquina o servidor. Si el sitio de destino detecta que proviene de un rango de IP de un datacenter conocido (AWS, DigitalOcean, OVH), puede bloquearte automáticamente sin que hayas hecho nada malo. Los proxies residenciales asignan IPs asociadas a proveedores de internet reales (ISP), lo que hace que tu tráfico se vea como el de un usuario legítimo.
La diferencia es significativa: según datos de la industria, los sitios de comercio electrónico y motores de búsqueda bloquean entre el 30% y el 50% del tráfico proveniente de IPs de datacenter, mientras que las IPs residenciales tienen tasas de éxito notablemente más altas. Esto no es magia — los sistemas anti-bot como Cloudflare y Akamai mantienen listas de rangos de datacenter y los tratan con mayor sospecha por defecto.
Los proxies también resuelven otro problema: el geo-bloqueo. Muchos sitios muestran precios, productos o contenido diferente según el país de la petición. Con un proxy residencial puedes simular tráfico desde Londres, Berlín o São Paulo simplemente cambiando un parámetro en el nombre de usuario.
Los proxies residenciales no son un hack — son la forma estándar de acceder a datos públicos cuando tu IP de origen está restringida. La clave es hacerlo de forma ética y respetando los límites del sitio.
La pila moderna: httr2 + rvest + ProxyHat
httr2 es la reescritura moderna de httr, diseñada por Hadley Wickham y el equipo de tidyverse. Introduce un patrón de pipe encadenable con request() y una función específica para proxies: req_proxy(). Por su parte, rvest se apoya en httr2 (o xml2) para descargar y parsear HTML/XML de forma idiomática.
La conexión con ProxyHat es directa: usas req_proxy() con el gateway gate.proxyhat.com en el puerto 8080 para HTTP, o 1080 para SOCKS5. Las credenciales y los parámetros de geo-targeting van en el nombre de usuario.
Ejemplo 1: petición básica con req_proxy y rvest
library(httr2)
library(rvest)
library(tibble)
# Credenciales de ProxyHat
proxy_user <- "tu-usuario"
proxy_pass <- "tu-password"
# Construir la petición con proxy
req <- request("https://httpbin.org/ip") |>
req_proxy(
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") |>
req_headers("Accept-Language" = "en-US,en;q=0.9")
# Ejecutar y parsear
resp <- req |> req_perform()
if (resp_status(resp) == 200) {
body <- resp_body_string(resp)
cat(body, "\n")
} else {
message("Error: ", resp_status(resp))
}
Aquí req_proxy() acepta la URL completa del proxy (incluyendo el esquema http://) y las credenciales por separado. El User-Agent se establece con req_user_agent() para evitar el UA por defecto de R/curl, que es una señal inmediata de automatización. Puedes consultar más detalles sobre req_proxy en la documentación oficial de httr2.
Geo-targeting y sesiones sticky en el nombre de usuario
ProxyHat permite pasar parámetros de geo-localización y control de sesión dentro del campo username, usando un formato de guiones. Esto significa que no necesitas endpoints diferentes ni cabeceras extra — todo va en la credencial.
Geo-targeting por país y ciudad
# Tráfico desde Londres, Reino Unido
req_uk <- request("https://ejemplo.com/tienda") |>
req_proxy(
proxy = "http://gate.proxyhat.com:8080",
username = "tu-usuario-country-GB-city-london",
password = proxy_pass
) |>
req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
# Tráfico desde Berlín, Alemania
req_de <- request("https://ejemplo.com/tienda") |>
req_proxy(
proxy = "http://gate.proxyhat.com:8080",
username = "tu-usuario-country-DE-city-berlin",
password = proxy_pass
) |>
req_user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36")
El formato es usuario-country-CODIGO-country-ciudad, donde el código de país sigue el estándar ISO 3166-1 alpha-2. Puedes ver todas las ubicaciones disponibles en la página de ubicaciones de ProxyHat.
Sesiones sticky: mantener la misma IP entre peticiones
Por defecto, cada petición obtiene una IP nueva. Si necesitas mantener una sesión (por ejemplo, para mantener un carrito de compras o un flujo de login), añade session-ID al username:
# Sesión sticky con ID fijo
sticky_user <- "tu-usuario-session-mi-sesion-001"
req_sticky <- request("https://ejemplo.com/pagina-1") |>
req_proxy(
proxy = "http://gate.proxyhat.com:8080",
username = sticky_user,
password = proxy_pass
)
# La siguiente petición con el mismo session ID usará la misma IP
req_sticky_2 <- request("https://ejemplo.com/pagina-2") |>
req_proxy(
proxy = "http://gate.proxyhat.com:8080",
username = sticky_user,
password = proxy_pass
)
SOCKS5 en el puerto 1080
Para casos donde necesitas SOCKS5 (túneles más opacos, mejor manejo de UDP en algunos entornos), cambia el puerto a 1080 y el esquema:
req_socks <- request("https://ejemplo.com") |>
req_proxy(
proxy = "socks5://gate.proxyhat.com:1080",
username = "tu-usuario-country-US",
password = proxy_pass
)
Residenciales vs datacenter: cuándo elegir cada uno
| Característica | Residencial | Datacenter |
|---|---|---|
| Tasa de éxito en sitios con anti-bot | Alta (90%+ típico) | Baja-moderada (40-70%) |
| Velocidad / latencia | 200-800ms adicional | 50-150ms adicional |
| Coste por GB | Más alto | Más bajo |
| Geo-targeting a nivel de ciudad | Sí | Limitado |
| Ideal para | Scraping de SERP, e-commerce, social | APIs internas, verificación simple |
Para web scraping en R dirigido a sitios con protección anti-bot, los residenciales son la opción por defecto. Los datacenter son útiles cuando el sitio no bloquea y necesitas velocidad o volumen alto a bajo coste. Puedes comparar precios en la página de precios de ProxyHat.
Ejemplo completo: tabla paginada con purrr, rotación y reintentos
Este es el patrón de producción: iterar páginas con purrr::map, rotar el ID de sesión por página para cambiar de IP, y aplicar req_retry() y req_throttle() para ser resiliente y respetuoso.
library(httr2)
library(rvest)
library(purrr)
library(dplyr)
library(tibble)
proxy_user <- "tu-usuario"
proxy_pass <- "tu-password"
# Función para construir una petición con proxy y sesión rotativa
build_req <- function(url, session_id) {
request(url) |>
req_proxy(
proxy = "http://gate.proxyhat.com:8080",
username = paste0(proxy_user, "-session-", session_id),
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" = "text/html,application/xhtml+xml",
"Accept-Language" = "en-US,en;q=0.9"
) |>
req_retry(max_tries = 3, backoff = ~ 2 ^ .x) |>
req_throttle(delay = 1 / 2) # máx 2 peticiones/segundo
}
# Función para extraer la tabla de una página
scrape_page <- function(page_num) {
url <- paste0("https://ejemplo.com/tabla?page=", page_num)
session_id <- paste0("pg-", page_num, "-", as.integer(runif(1, 1000, 9999)))
tryCatch({
req <- build_req(url, session_id)
resp <- req_perform(req)
if (resp_status(resp) != 200) {
warning("Página ", page_num, " devolvió status ", resp_status(resp))
return(tibble())
}
html <- resp_body_string(resp) |> read_html()
tabla <- html |>
html_elements("table.datos") |>
html_table(convert = TRUE)[[1]]
message("Página ", page_num, ": ", nrow(tabla), " filas extraídas")
tabla
}, error = function(e) {
warning("Error en página ", page_num, ": ", conditionMessage(e))
tibble()
})
}
# Iterar 10 páginas
resultados <- 1:10 |>
map(scrape_page) |>
bind_rows()
print(resultados)
Notas clave sobre este patrón:
req_retry()reintenta hasta 3 veces con backoff exponencial (2, 4, 8 segundos). Esto maneja timeouts y errores 503 transitorios.req_throttle()limita la tasa a 2 peticiones por segundo, evitando saturar el servidor de destino y reducir el riesgo de bloqueos.- Rotación de sesión: cada página usa un
session_iddistinto, lo que fuerza una IP nueva por página. Si una IP está bloqueada, las demás páginas no se ven afectadas. tryCatch()aísla errores por página — una página fallida no rompe toda la recolección.
Para casos de uso más amplios de scraping, consulta nuestra guía de web scraping con proxies y la de seguimiento de SERP.
Páginas renderizadas con JavaScript: read_html_live() a través de proxy
Cuando el contenido se carga dinámicamente con JavaScript (React, Vue, SPAs), rvest::read_html() no es suficiente porque solo ve el HTML inicial. La función read_html_live() del paquete rvest (que usa chromote por debajo) lanza un navegador headless real y permite interactuar con la página.
Para enrutar chromote a través del proxy de ProxyHat, necesitas configurar las variables de entorno de Chrome antes de lanzar la sesión:
library(rvest)
library(tibble)
# Configurar Chrome para usar el proxy SOCKS5 de ProxyHat
Sys.setenv(
CHROMOTE_CHROME_FLAGS = paste0(
"--proxy-server=socks5://gate.proxyhat.com:1080",
" --disable-blink-features=AutomationControlled"
)
)
# Nota: la autenticación del proxy en Chrome headless requiere
# un interceptor de autenticación. Alternativamente, usa la URL
# con credenciales embebidas para HTTP:
# --proxy-server=http://gate.proxyhat.com:8080
# Abrir página con navegador real
html <- read_html_live("https://ejemplo.com/app-js")
# Esperar a que cargue el contenido dinámico
Sys.sleep(3)
# Extraer datos con selectores CSS
items <- html |>
html_elements(".producto-card") |>
html_text2()
print(head(items, 10))
Importante: read_html_live() es más lento (cada página puede tardar 3-5 segundos) y consume más recursos. Úsalo solo cuando read_html() no pueda acceder al contenido. Para volúmenes altos, considera si el sitio ofrece una API oficial o endpoints JSON internos que puedas consumir directamente.
La documentación de rvest sobre read_html_live detalla las opciones disponibles para interacción con formularios, clics y scroll.
Errores comunes y casos límite
1. Olvidar el User-Agent
Si no llamas req_user_agent(), httr2 envía un UA por defecto tipo libcurl/8.x R/4.x, que es inmediatamente reconocido como bot. Usa siempre un UA de navegador real y rota entre varios.
2. No manejar codificación de caracteres
Sitios con codificación no UTF-8 pueden devolver texto corrupto. Usa resp_body_string(resp) con read_html() en lugar de read_html(url) directo, y si es necesario, corrige con iconv() o stringi::stri_encode().
3. Sesiones sticky demasiado largas
Si mantienes el mismo session_id durante cientos de peticiones, la IP puede acabar bloqueada por exceso de actividad. Rota cada 20-50 peticiones según el sitio.
4. Ignorar robots.txt y términos de servicio
Antes de scrapearte un sitio, revisa su /robots.txt y los términos de servicio. Aunque robots.txt no es legalmente vinculante en todas las jurisdicciones, respetarlo es una señal de buena fe. La especificación de robots.txt del W3C define el estándar.
5. No respetar el GDPR en datos de la UE
Si recolectas datos personales de residentes de la Unión Europea, el RGPD (GDPR) aplica. Esto incluye nombres, emails, direcciones IP, y cualquier dato que permita identificar a una persona. Asegúrate de tener una base legal para el tratamiento y minimiza los datos personales recolectados. Consulta la página oficial del GDPR de la Comisión Europea para más información.
Configuración específica de ProxyHat y el SDK
El patrón mostrado arriba funciona directamente con ProxyHat usando gate.proxyhat.com:8080 para HTTP y :1080 para SOCKS5. La documentación completa de integración está en docs.proxyhat.com.
Si trabajas en pipelines mixtos con Python y Node.js, el SDK de ProxyHat sigue el mismo patrón de credenciales en el username, por lo que puedes compartir la misma lógica de geo-targeting y rotación de sesiones entre lenguajes. En R, la integración nativa con req_proxy() es suficiente y no requiere un SDK adicional.
Ejemplo con curl desde R (alternativa sin httr2)
# Usando curl directamente desde la línea de comandos vía system2()
cmd <- paste0(
'curl -x http://tu-usuario-country-FR:tu-password@gate.proxyhat.com:8080 ',
'-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)" ',
'-H "Accept-Language: fr-FR,fr;q=0.9" ',
'"https://ejemplo.com/datos-franceses"'
)
result <- system2("curl", args = strsplit(cmd, " ")[[1]], stdout = TRUE)
html <- paste(result, collapse = "\n") |> read_html()
Puntos clave (Key Takeaways)
- Usa
req_proxy()de httr2 para enrutar peticiones HTTP/SOCKS5 a través de ProxyHat congate.proxyhat.com:8080o:1080. - El geo-targeting y las sesiones van en el username:
usuario-country-GB-city-londonousuario-session-id123. - Combina
req_retry()yreq_throttle()para reintentos automáticos y control de tasa — esencial para recolección robusta. - Rota sesiones por página con IDs únicos para distribuir el tráfico entre múltiples IPs residenciales.
- Usa
read_html_live()solo para páginas que requieren JavaScript; para todo lo demás,read_html()es más rápido y eficiente. - Respeta robots.txt, ToS y GDPR; prefiere APIs oficiales cuando existan.
- Configura siempre un User-Agent realista con
req_user_agent()y cabecerasAccept-Languagecoherentes con el país del proxy.
Conclusión y próximos pasos
Usar proxies en R con httr2 y rvest es directo una vez entiendes el patrón: request() → req_proxy() → req_perform() → read_html(). La complejidad real está en la estrategia de rotación, la gestión de errores y el cumplimiento ético — no en la configuración técnica.
Para empezar, registra tu cuenta en dashboard.proxyhat.com, revisa las opciones de precios y prueba los ejemplos de este artículo con tus propios objetivos de scraping. Si necesitas cobertura en países específicos, consulta las ubicaciones disponibles.






