Использование прокси в R: руководство по httr2, rvest и ProxyHat

Практическое руководство по использованию резидентных прокси в R с httr2 и rvest: настройка req_proxy, геотаргетинг, сессии, пагинация, JS-рендеринг и production-паттерны для надёжного сбора данных.

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

Использование прокси в R — ключевой навык для data scientists и аналитиков, которые собирают данные с веб-сайтов. Будь то веб-скрапинг в R для ценового мониторинга, SERP-трекинга или исследования социальных сетей, рано или поздно вы столкнётесь с блокировками, CAPTCHA и географическими ограничениями. В этом руководстве мы разберём современный стек httr2 + rvest + резидентные прокси ProxyHat, с runnable-примерами от простого запроса до пагинации и JS-рендеринга.

Почему использование прокси в R необходимо: резидентные vs дата-центровые IP

Большинство сайтов с публичными данными используют антибот-защиту: Cloudflare, PerimeterX, Akamai Bot Manager, а также собственные rate-limiting правила. Когда вы делаете HTTP-запрос из R, целевой сервер видит ваш IP-адрес. Если вы делаете сотни запросов с одного дата-центрового IP, сайт быстро его заблокирует.

Резидентные прокси решают эту проблему: они маршрутизируют ваш трафик через IP-адреса реальных интернет-провайдеров (ISP). Для целевого сайта ваш запрос выглядит так, будто его делает обычный пользователь из конкретной страны или города. Дата-центровые IP, напротив, легко идентифицируются как бот-трафик — согласно данным Wikipedia, дата-центровые прокси часто попадают в публичные чёрные списки IP.

Характеристика Резидентные прокси Дата-центровые прокси Мобильные прокси
Источник IP Реальные ISP Дата-центры (AWS, OVH и др.) Мобильные операторы (4G/5G)
Success rate на защищённых сайтах 90–98% 30–60% 95–99%
Скорость Средняя (200–800ms) Высокая (50–200ms) Низкая (500–2000ms)
Стоимость Средняя Низкая Высокая
Обнаружение антибот-системами Низкое Высокое Очень низкое

Для R-разработчиков, которым важна надёжность сбора данных, резидентные прокси — оптимальный баланс цены и success rate. Ознакомиться с доступными локациями можно на странице локаций ProxyHat.

Современный стек R: httr2 и rvest

Пакет httr2 — это современная замена httr от Hadley Wickham. Он построен поверх curl и предлагает pipeline-интерфейс для построения запросов. Пакет rvest — стандарт де-факто для парсинга HTML в R, использующий xml2 и selectr под капотом.

Ключевая функция для прокси — req_proxy(). Она принимает URL прокси-сервера, имя пользователя и пароль. Вот базовый пример:

library(httr2)
library(rvest)

# Учётные данные ProxyHat
proxy_user <- "your_username"
proxy_pass <- "your_password"

# Базовый запрос через резидентный прокси
req <- request("https://httpbin.org/ip") |>
  req_proxy("http://gate.proxyhat.com:8080", proxy_user, proxy_pass) |>
  req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")

resp <- req |>
  req_perform()

# Парсинг JSON-ответа
resp |>
  resp_body_json()
# $origin
# [1] "198.51.100.42"  # резидентный IP, не ваш

Здесь req_proxy() устанавливает HTTP-прокси на уровне libcurl. Аутентификация передаётся через Basic Auth в URL. Для парсинга HTML-страницы вы комбинируете req_perform() с read_html():

# Сбор HTML-страницы через прокси
page_req <- request("https://example.com/products") |>
  req_proxy("http://gate.proxyhat.com:8080", proxy_user, 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" = "en-US,en;q=0.9")

resp <- req_perform(page_req)
html <- read_html(resp_body_string(resp))

# Извлечение заголовков товаров
titles <- html |>
  html_elements("h2.product-title") |>
  html_text2()

# Извлечение таблицы цен
price_table <- html |>
  html_element("table.prices") |>
  html_table()

head(price_table)

Геотаргетинг и sticky-сессии в имени пользователя

ProxyHat кодирует параметры геотаргетинга и сессий прямо в имени пользователя. Это значит, что вам не нужны дополнительные заголовки или query-параметры — всё управляется через URL прокси. Вот синтаксис:

  • user-country-US — IP из США
  • user-country-GB-city-london — IP из Лондона, Великобритания
  • user-session-abc123 — sticky-сессия: все запросы с этим идентификатором идут через один IP
  • user-country-DE-session-order456 — комбинирование страны и сессии
# Геотаргетинг: Великобритания, Лондон
uk_req <- request("https://example.co.uk") |>
  req_proxy(
    "http://gate.proxyhat.com:8080",
    "your_username-country-GB-city-london",
    "your_password"
  ) |>
  req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")

resp_uk <- req_perform(uk_req)

# Sticky-сессия: один IP для всех запросов в сессии
session_id <- paste0("sess-", sample(100000:999999, 1))
sticky_req <- request("https://example.com/account") |>
  req_proxy(
    "http://gate.proxyhat.com:8080",
    paste0("your_username-session-", session_id),
    "your_password"
  ) |>
  req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")

# Все запросы с этим session_id будут идти через один и тот же IP
resp_sticky <- req_perform(sticky_req)

SOCKS5 на порту 1080

Если целевой сайт блокирует HTTP-прокси или вам нужен SOCKS5 для других причин, ProxyHat поддерживает SOCKS5 на порту 1080. В httr2 это работает так же просто:

# SOCKS5 прокси через ProxyHat
socks_req <- request("https://httpbin.org/ip") |>
  req_proxy(
    "socks5://gate.proxyhat.com:1080",
    "your_username-country-DE",
    "your_password"
  ) |>
  req_user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")

resp_socks <- req_perform(socks_req)
resp_socks |> resp_body_json()

Согласно RFC 1928, SOCKS5 обеспечивает аутентификацию на транспортном уровне и поддерживает TCP-туннелирование. Для большинства задач веб-скрапинга в R разница между HTTP и SOCKS5 минимальна, но SOCKS5 может быть полезен при работе с сайтами, которые активно блокируют HTTP-прокси.

Production-пример: пагинация таблицы с purrr и ротацией сессий

Теперь соберём всё вместе. Представьте, что вам нужно собрать таблицу товаров с 20 страниц. Для каждой страницы мы создаём новую sticky-сессию (новый IP), используем req_retry() для повторов при ошибках и req_throttle() для ограничения частоты. Это классический паттерн rvest proxy для production-сбора:

library(httr2)
library(rvest)
library(purrr)
library(dplyr)
library(logger)

# Настройка логирования
log_info("Запуск сбора данных: 20 страниц")

proxy_user <- "your_username"
proxy_pass <- "your_password"
base_url <- "https://example-shop.com/products?page="

# Функция для одной страницы с ротацией сессий
fetch_page <- function(page_num) {
  session_id <- paste0("page-", page_num, "-", sample(100000:999999, 1))
  url <- paste0(base_url, page_num)
  
  req <- request(url) |>
    req_proxy(
      "http://gate.proxyhat.com:8080",
      paste0(proxy_user, "-session-", session_id),
      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,        # экспоненциальная задержка: 1s, 2s, 4s
      retry_on_failure = TRUE,
      is_transient = function(resp) {
        status <- resp_status(resp)
        status == 429 || status >= 500
      }
    ) |>
    req_throttle(2 / 1)  # максимум 2 запроса в секунду
  
  tryCatch({
    resp <- req_perform(req)
    html <- read_html(resp_body_string(resp))
    
    table <- html |>
      html_element("table.product-list") |>
      html_table()
    
    log_info("Страница {page_num}: получено {nrow(table)} строк")
    table
  }, error = function(e) {
    log_error("Страница {page_num}: {conditionMessage(e)}")
    NULL
  })
}

# Параллельный сбор с purrr::map
all_tables <- 1:20 |>
  purrr::map(fetch_page)

# Объединение в один data frame
tidy_df <- all_tables |>
  purrr::compact() |>
  purrr::list_rbind()

log_info("Итого: {nrow(tidy_df)} строк из {length(purrr::compact(all_tables))} страниц")
head(tidy_df)

Ключевые моменты этого паттерна:

  • Ротация сессий — каждая страница получает свой session_id, а значит, свой IP. Это предотвращает блокировку за слишком много запросов с одного адреса.
  • req_retry() — повторяет запрос при HTTP 429 и 5xx с экспоненциальной задержкой (1s, 2s, 4s). Это стандартный паттерн для устойчивого сбора данных.
  • req_throttle(2 / 1) — ограничивает частоту до 2 запросов в секунду, что снижает риск блокировок.
  • tryCatch — изолирует ошибки на отдельных страницах, не прерывая весь процесс.
  • logger — production-логирование для отладки и мониторинга.

JS-рендеринг через read_html_live() с тем же прокси

Многие современные сайты рендерят контент через JavaScript. Обычный read_html() видит только исходный HTML без динамического контента. Пакет rvest версии 1.0.99+ предлагает read_html_live(), который использует chromote (headless Chrome) для рендеринга страниц.

Чтобы направить headless Chrome через прокси ProxyHat, нужно задать прокси в настройках chromote:

library(rvest)
library(chromote)

# Настройка прокси для chromote (headless Chrome)
proxy_user <- "your_username-country-US"
proxy_pass <- "your_password"
proxy_url <- paste0("http://", proxy_user, ":", proxy_pass, "@gate.proxyhat.com:8080")

# Создаём chromote-объект с прокси
b <- ChromoteSession$new(
  browser = Chromote$new(
    args = c(
      paste0("--proxy-server=http://gate.proxyhat.com:8080"),
      "--disable-blink-features=AutomationControlled"
    )
  )
)

# Аутентификация прокси через CDP (Chrome DevTools Protocol)
b$Runtime$evaluate(
  expression = sprintf(
    'fetch("https://httpbin.org/ip", {headers: {"Proxy-Authorization": "Basic %s"}})',
    jsonlite::base64_enc(paste0(proxy_user, ":", proxy_pass))
  )
)

# Рендеринг JS-страницы
live_html <- read_html_live(
  "https://example.com/spa-products",
  chrome = b,
  options = list(
    wait_for = "table.product-list",
    timeout = 10000  # 10 секунд на рендеринг
  )
)

# Парсинг отрендеренного контента
table <- live_html |>
  html_element("table.product-list") |>
  html_table()

head(table)

Важно: read_html_live() требует установленный Chrome/Chromium и пакет chromote. Установите через install.packages("chromote"). Для production-окружения убедитесь, что Chrome доступен в PATH или укажите путь через SYS_CHROMOTE_CHROME.

Этика, комплаенс и лучшие практики

Веб-скрапинг — мощный инструмент, но он требует ответственного подхода. Вот основные принципы:

  1. Проверяйте robots.txt перед сбором данных. Файл /robots.txt указывает, какие страницы можно скрапить, а какие — нет. Хотя robots.txt не имеет юридической силы, его соблюдение — признак хорошего тона.
  2. Соблюдайте Terms of Service. Если сайт явно запрещает автоматизированный сбор данных в своих ToS, нарушение может иметь юридические последствия.
  3. GDPR для данных из ЕС. Если вы собираете персональные данные граждан ЕС, применим GDPR. Персональные данные — любая информация, связанная с идентифицированным лицом. Используйте минимизацию данных и anonymization.
  4. Предпочитайте официальные API. Если сайт предоставляет публичный API, используйте его вместо скрапинга. Это надёжнее, быстрее и юридически безопаснее.
  5. Ограничивайте частоту. Не превышайте 1–5 запросов в секунду на домен. req_throttle() в httr2 — ваш друг.
  6. Собирайте только публичные данные. Не пытайтесь обходить paywalls или аутентификацию без явного разрешения.

Подробнее о легитимных сценариях использования прокси читайте на странице веб-скрапинг и SERP-трекинг.

Настройка ProxyHat и интеграция с Python/Node

ProxyHat использует единый шлюз gate.proxyhat.com для всех типов прокси. Конфигурация для R идентична конфигурации для Python и Node.js — меняется только код клиента. Это удобно для команд, использующих смешанные пайплайны: например, R для статистического анализа и Python для масштабного сбора данных.

ProxyHat SDK для Python и Node.js зеркарует тот же паттерн: имя пользователя с геотаргетингом и сессиями, пароль, единый шлюз. Документация доступна на docs.proxyhat.com. Цены и тарифные планы — на странице цен ProxyHat.

# Сравнение: R (httr2) vs Python (requests) — один и тот же прокси

# R — httr2
library(httr2)
req <- request("https://httpbin.org/ip") |>
  req_proxy(
    "http://gate.proxyhat.com:8080",
    "user-country-FR-session-r123",
    "pass"
  ) |>
  req_perform()

# Python — requests (эквивалент)
# import requests
# proxies = {
#   "http": "http://user-country-FR-session-r123:pass@gate.proxyhat.com:8080",
#   "https": "http://user-country-FR-session-r123:pass@gate.proxyhat.com:8080"
# }
# r = requests.get("https://httpbin.org/ip", proxies=proxies)

Распространённые ошибки и edge cases

1. Забытый User-Agent

По умолчанию httr2 отправляет User-Agent: httr2/1.0.0 — это мгновенно выдаёт бота. Всегда используйте req_user_agent() с реалистичным значением браузера.

2. Игнорирование кодировки

Сайты на кириллице или других non-Latin скриптах могут возвращать контент в windows-1251 или других кодировках. read_html() обычно справляется автоматически, но при артефактах используйте resp_body_string() с явным указанием кодировки:

resp <- req_perform(req)
# Если кодировка определилась неверно
html <- read_html(
  resp_body_string(resp),
  encoding = "UTF-8"
)

3. Слишком агрессивная частота

Без req_throttle() цикл из 100 страниц может отправить все запросы за секунду. Это почти гарантированная блокировка. Используйте req_throttle(rate / period) — например, req_throttle(1 / 2) для 1 запроса каждые 2 секунды.

4. Утечка IP при redirect

При HTTP-редиректах libcurl может следовать по новому URL. Убедитесь, что req_options(followlocation = 1L) включён (по умолчанию он включён), и прокси применяется ко всем редиректам. В httr2 это работает из коробки.

5. Необработанные CAPTCHA

Даже резидентные прокси не гарантируют 100% обход CAPTCHA. Если вы получаете HTTP 403 или страницу с CAPTCHA, rotating на новый IP (новая сессия) обычно помогает. Для масштабных операций рассмотрите интеграцию с сервисами решения CAPTCHA.

Key Takeaways

  • httr2 + rvest — современный стек для веб-скрапинга в R. Используйте req_proxy() для маршрутизации через ProxyHat.
  • Резидентные прокси обеспечивают success rate 90–98% на защищённых сайтах против 30–60% у дата-центровых.
  • Геотаргетинг и сессии кодируются в имени пользователя: user-country-GB-city-london-session-abc123.
  • SOCKS5 доступен на порту 1080 для сценариев, где HTTP-прокси не подходит.
  • req_retry() + req_throttle() — обязательные инструменты для production-сбора: повторы при 429/5xx и ограничение частоты.
  • read_html_live() через chromote позволяет рендерить JS-страницы через тот же прокси.
  • Этика: соблюдайте robots.txt, ToS, GDPR, и предпочитайте официальные API, когда они доступны.
  • Единый шлюз gate.proxyhat.com:8080 работает одинаково для R, Python и Node.js — удобно для смешанных пайплайнов.

Часто задаваемые вопросы

Что такое использование прокси в R?

Использование прокси в R — это маршрутизация HTTP-запросов из R через промежуточный сервер, подменяющий исходный IP. Это позволяет обходить геоограничения, предотвращать блокировки и собирать публичные данные с защищённых сайтов. В R это реализуется через req_proxy() в httr2 или через настройки curl.

Зачем нужны резидентные прокси при сборе данных в R?

Резидентные прокси используют IP реальных ISP, поэтому антибот-системы воспринимают их как обычных пользователей. Дата-центровые IP часто попадают в чёрные списки и получают HTTP 403 или CAPTCHA. Для надёжного сбора данных с защищённых сайтов резидентные прокси — стандарт.

Какой тип прокси лучше всего подходит для R?

Для большинства задач веб-скрапинга в R оптимальны резидентные прокси с ротацией IP — они дают наибольший success rate. Для простых задач без жёсткой защиты подойдут дата-центровые. Мобильные прокси идеальны для мобильных платформ. Выбор зависит от целевого сайта и объёма запросов.

Как избежать блокировок при использовании прокси в R?

Используйте ротацию IP между запросами (новая сессия = новый IP), устанавливайте реалистичный User-Agent через req_user_agent(), ограничивайте частоту через req_throttle(), добавляйте повторы через req_retry(), и соблюдайте robots.txt. Не превышайте 1–5 запросов в секунду на домен.

Как настроить SOCKS5 прокси в R через httr2?

Для SOCKS5 используйте req_proxy(req, "socks5://gate.proxyhat.com:1080", username, password). SOCKS5 полезен, когда целевой сайт блокирует HTTP-прокси. В httr2 SOCKS5 работает через libcurl, который поддерживает этот протокол нативно согласно RFC 1928.

Готовы начать?

Доступ к более чем 50 млн резидентных IP в 148+ странах с AI-фильтрацией.

Смотреть ценыРезидентные прокси
← Вернуться в Блог