R에서 프록시 사용하기: httr2와 rvest로 웹 스크래핑하는 완벽 가이드

httr2의 req_proxy와 rvest를 활용해 R에서 프록시 기반 웹 스크래핑을 구현하는 방법을 코드 예제와 함께 설명합니다. 지역 타겟팅, 스티키 세션, 페이지네이션, JS 렌더링까지 모두 다룹니다.

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

R에서 프록시 사용하기는 현대 데이터 수집 파이프라인의 핵심 기술입니다. httr2req_proxy()rvest를 조합하면, IP 차단이나 지역 제한 없이 웹 데이터를 안정적으로 수집할 수 있습니다. 이 가이드는 R 개발자와 데이터 분석가를 위해 ProxyHat 레지덴셜 프록시와 함께 실제 동작하는 코드를 단계별로 설명합니다.

웹 스크래핑을 하다 보면 어느 순간 HTTP 403 Forbidden이나 CAPTCHA 페이지를 만나게 됩니다. 이는 대상 서버가 요청의 출처 IP를 검사하고 데이터센터 IP를 차단하기 때문입니다. httr2의 req_proxy() 함수를 사용하면 R에서도 쉽게 프록시를 통해 요청을 보낼 수 있으며, rvest로 응답을 파싱할 수 있습니다.

R에서 프록시 사용하기: 왜 필요한가?

데이터센터 IP 대역은 AWS, GCP, Azure 등 클라우드 제공자에 속해 있어 대상 서버가 쉽게 식별할 수 있습니다. Cloudflare, Akamai, Datadome 같은 WAF/봇 방지 시스템은 데이터센터 IP를 자동으로 플래깅하고 차단합니다. 반면 레지덴셜 프록시는 실제 인터넷 서비스 제공자(ISP)에서 발급된 IP를 사용하므로 일반 사용자의 트래픽과 구분하기 어렵습니다.

다음과 같은 상황에서 R에서 프록시 사용이 필수적입니다:

  • 지역 제한 콘텐츠: 특정 국가에서만 접근 가능한 페이지를 수집해야 할 때
  • IP 기반 차단: 동일 IP에서 과도한 요청으로 인해 403/429 응답을 받을 때
  • SERP 추적: 검색 엔진 결과 페이지를 여러 지역에서 수집해야 할 때
  • 이커머스 가격 모니터링: 경쟁사 가격을 주기적으로 크롤링해야 할 때

ProxyHat은 99.9% 가동률100개 이상의 동시 세션을 지원하는 레지덴셜, 모바일, 데이터센터 프록시 서비스입니다. R에서 이 프록시를 활용하는 방법을 코드 중심으로 살펴보겠습니다.

httr2와 rvest: R 웹 스크래핑의 모던 스택

httr2는 Hadley Wickham이 설계한 차세대 HTTP 클라이언트로, 파이프라인 친화적인 API를 제공합니다. 핵심 함수는 다음과 같습니다:

  • request(url): HTTP 요청 객체 생성
  • req_proxy(url, username, password): 프록시 설정
  • req_retry(max_tries, backoff): 자동 재시도
  • req_throttle(rate): 요청 속도 제한
  • req_perform(): 요청 실행

rvest는 HTML/XML 파싱을 위한 tidyverse 패키지로, read_html(), html_elements(), html_table() 등의 함수를 제공합니다. 두 패키지를 조합하면 프록시 기반 웹 스크래핑 파이프라인을 간결하게 구성할 수 있습니다.

ProxyHat으로 첫 프록시 요청 보내기

가장 기본적인 형태부터 시작하겠습니다. ProxyHat 게이트웨이는 gate.proxyhat.com:8080에서 HTTP 프록시를 제공합니다. 사용자명과 비밀번호는 ProxyHat 문서에서 확인할 수 있습니다.

library(httr2)
library(rvest)

# ProxyHat 인증 정보
proxy_user <- "your_username"
proxy_pass <- "your_password"

# 기본 요청 생성 후 프록시 설정
resp <- request("https://httpbin.org/ip") |>
  req_proxy("gate.proxyhat.com", proxy_user, proxy_pass) |>
  req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)") |>
  req_perform()

# 응답 확인
status <- resp_status(resp)
body <- resp_body_json(resp)
print(body)
# $origin
# [1] "203.0.113.45"  # 프록시 IP가 표시됨

위 코드에서 req_proxy()는 HTTP CONNECT 터널을 통해 ProxyHat 게이트웨이와 연결을 설정합니다. 응답의 origin 필드에 표시되는 IP는 프록시 서버의 IP이며, 이를 통해 실제 IP가 노출되지 않습니다.

동일한 요청을 curl로 표현하면 다음과 같습니다:

curl -x http://your_username:your_password@gate.proxyhat.com:8080 \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)" \
  https://httpbin.org/ip

지역 타겟팅과 스티키 세션 구성

ProxyHat의 강력한 기능 중 하나는 사용자명에 지역 및 세션 정보를 인코딩할 수 있다는 것입니다. 이를 통해 특정 국가/도시의 IP로 요청을 보내거나, 여러 요청에 걸쳐 동일한 IP를 유지할 수 있습니다.

국가 및 도시 타겟팅

library(httr2)

# 영국 런던 IP로 요청 보내기
target_url <- "https://httpbin.org/ip"

resp_uk <- request(target_url) |>
  req_proxy(
    "gate.proxyhat.com",
    username = "user-country-GB-city-london",
    password = "your_password"
  ) |>
  req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)") |>
  req_perform()

print(resp_body_json(resp_uk))
# 영국 IP로 응답

사용자명 형식은 user-country-{국가코드}-city-{도시명}입니다. 국가 코드는 ISO 3166-1 alpha-2 형식(예: US, DE, JP, KR)을 따릅니다. 도시명은 소문자로 지정합니다. ProxyHat이 지원하는 전체 위치 목록은 프록시 위치 페이지에서 확인할 수 있습니다.

스티키 세션

여러 요청에 걸쳐 동일한 IP를 유지해야 하는 경우(예: 로그인 후 페이지 이동), 세션 ID를 사용자명에 포함합니다:

library(httr2)

# 스티키 세션: 동일 IP 유지
session_id <- "mysession-abc123"

build_sticky_req <- function(url) {
  request(url) |>
    req_proxy(
      "gate.proxyhat.com",
      username = paste0("user-session-", session_id),
      password = "your_password"
    ) |>
    req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
}

# 로그인 페이지 요청
resp_login <- build_sticky_req("https://example.com/login") |>
  req_perform()

# 동일 IP로 대시보드 페이지 요청
resp_dash <- build_sticky_req("https://example.com/dashboard") |>
  req_perform()

SOCKS5 프록시 사용

일부 환경에서는 HTTP보다 SOCKS5가 더 적합할 수 있습니다. ProxyHat은 포트 1080에서 SOCKS5를 지원합니다:

library(httr2)

# SOCKS5 프록시 (포트 1080)
resp_socks <- request("https://httpbin.org/ip") |>
  req_proxy(
    "gate.proxyhat.com:1080",
    username = "user-country-DE",
    password = "your_password"
  ) |>
  req_perform()

print(resp_body_json(resp_socks))

SOCKS5는 TCP 레벨에서 터널링을 제공하므로, HTTP CONNECT보다 낮은 오버헤드로 연결을 유지할 수 있습니다. 하지만 일부 R HTTP 클라이언트는 SOCKS5를 완전히 지원하지 않을 수 있으므로, curl 기반 백엔드를 사용하는 httr2가 권장됩니다.

페이지네이션 테이블 스크래핑 실전 예제

이제 실전 시나리오를 다뤄보겠습니다. 페이지네이션이 있는 HTML 테이블을 여러 페이지에서 수집하여 tidy data frame으로 변환하는 파이프라인을 구성합니다. purrr::map으로 페이지를 순회하고, 페이지마다 세션을 로테이션하며, req_retry()req_throttle()로 안정성을 확보합니다.

library(httr2)
library(rvest)
library(purrr)
library(dplyr)
library(tibble)

# ProxyHat 설정
proxy_pass <- "your_password"
base_url <- "https://example.com/table?page=%d"

# 단일 페이지 스크래핑 함수
scrape_page <- function(page_num) {
  url <- sprintf(base_url, page_num)
  session_id <- paste0("sess-p", page_num, "-", as.integer(Sys.time()))

  req <- request(url) |>
    req_proxy(
      "gate.proxyhat.com",
      username = paste0("user-country-US-session-", session_id),
      password = proxy_pass
    ) |>
    req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)") |>
    req_headers("Accept" = "text/html,application/xhtml+xml") |>
    req_retry(
      max_tries = 3,
      backoff = ~ backoff_exponential(.x, max_backoff = 10)
    ) |>
    req_throttle(rate = 2 / 1)  # 초당 2개 요청

  resp <- tryCatch(
    req_perform(req),
    error = function(e) {
      message("Page ", page_num, " failed: ", conditionMessage(e))
      return(NULL)
    }
  )

  if (is.null(resp) || resp_status(resp) != 200) {
    return(tibble())
  }

  html <- resp_body_string(resp) |> read_html()

  tables <- html |> html_table(convert = TRUE)

  if (length(tables) == 0) {
    return(tibble())
  }

  tables[[1]] |> as_tibble()
}

# 10페이지 수집
pages <- 1:10
results <- map_dfr(pages, scrape_page)

print(nrow(results))
# [1] 250  # 10페이지 × 25행
print(colnames(results))

이 예제의 핵심 설계 포인트는 다음과 같습니다:

  • 세션 로테이션: 페이지마다 고유한 session_id를 생성하여 다른 IP로 요청을 보냅니다. 단일 IP에 대한 요청 집중을 방지합니다.
  • 지수 백오프 재시도: req_retry(max_tries = 3)로 최대 3회 재시도하며, 대기 시간은 지수적으로 증가합니다.
  • 요청 속도 제한: req_throttle(rate = 2 / 1)로 초당 최대 2개 요청으로 제한합니다. 평균 응답 시간이 200ms이더라도 대상 서버에 부하를 주지 않습니다.
  • 에러 처리: tryCatch로 실패한 페이지를 건너뛰고 빈 tibble을 반환하여 전체 파이프라인이 중단되지 않도록 합니다.

map_dfr은 각 페이지의 결과를 행으로 결합하므로, 최종적으로 하나의 tidy data frame을 얻을 수 있습니다. 수집된 데이터의 품질을 검증하려면 glimpse(results) 또는 summary(results)를 실행하세요.

JavaScript 렌더링 페이지 처리

많은 현대 웹사이트는 클라이언트 사이드 JavaScript로 콘텐츠를 렌더링합니다. 이 경우 read_html()만으로는 동적 콘텐츠를 가져올 수 없습니다. rvestread_html_live() 함수는 chromote 패키지를 통해 헤드리스 Chrome을 구동하여 JS 렌더링 페이지를 수집합니다.

library(rvest)
library(httr2)

# read_html_live()로 JS 렌더링 페이지 수집
# chromote가 Chrome을 통해 페이지 로드 후 HTML 반환

# ProxyHat 프록시를 통한 JS 렌더링 스크래핑
scrape_js_page <- function(url, country = "US") {
  session_id <- paste0("js-", as.integer(Sys.time()))
  proxy_url <- sprintf(
    "http://user-country-%s-session-%s:%s@gate.proxyhat.com:8080",
    country, session_id, "your_password"
  )

  # chromote의 프록시 설정 (환경 변수 또는 브라우저 옵션)
  Sys.setenv(CHROMOTE_PROXY = proxy_url)

  page <- read_html_live(url)

  # 동적 콘텐츠 추출
  elements <- page |>
    html_elements(".dynamic-content .item") |>
    html_text2()

  # 브라우저 세션 종료
  page$close()

  elements
}

# 실행
items <- scrape_js_page("https://example.com/js-app", country = "GB")
print(length(items))

read_html_live()는 실제 Chrome 브라우저를 사용하므로 JavaScript 실행, 쿠키 처리, DOM 조작을 자동으로 처리합니다. 다만 HTTP 요청당 약 2-5초의 렌더링 시간이 추가되므로, 정적 HTML로 충분한 경우에는 read_html()을 사용하는 것이 효율적입니다.

또한 req_user_agent()req_headers()를 통해 현실적인 브라우저 헤더를 설정하는 것이 중요합니다:

library(httr2)

# 현실적인 헤더 설정
req <- request("https://example.com/data") |>
  req_proxy("gate.proxyhat.com", "user-country-FR", "your_password") |>
  req_user_agent(
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
    "AppleWebKit/537.36 (KHTML, like Gecko) "
    "Chrome/120.0.0.0 Safari/537.36"
  ) |>
  req_headers(
    "Accept" = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language" = "fr-FR,fr;q=0.9,en;q=0.8",
    "Accept-Encoding" = "gzip, deflate, br",
    "Connection" = "keep-alive",
    "Upgrade-Insecure-Requests" = "1"
  ) |>
  req_perform()

프록시 유형 비교

용도에 맞는 프록시 유형을 선택하는 것이 성공과 비용의 균형에 중요합니다. 다음 표는 세 가지 주요 프록시 유형을 비교한 것입니다:

프록시 유형 IP 출처 탐지 위험 평균 지연 적합한 용도
레지덴셜 실제 ISP 낮음 200-500ms SERP, 이커머스, 소셜 미디어
데이터센터 클라우드 제공자 높음 50-100ms API, 비차단 사이트, 대량 단순 수집
모바일 이동통신사 매우 낮음 500-1500ms 계정 관리, 엄격한 봇 방지 환경

대부분의 R 웹 스크래핑 시나리오에서는 레지덴셜 프록시가 최적의 선택입니다. 데이터센터 프록시는 차단이 없는 API 엔드포인트에서만 사용하고, 모바일 프록시는 가장 엄격한 봇 방지 시스템을 통과해야 할 때 예산이 허용하는 경우에 고려하세요. ProxyHat 요금제에서 각 유형의 가격을 확인할 수 있습니다.

윤리와 컴플라이언스

프록시를 사용한 웹 스크래핑은 강력한 도구이지만, 윤리적 가이드라인과 법적 요구사항을 준수해야 합니다. 다음 원칙을 지키세요:

  • robots.txt 준수: 대상 사이트의 robots.txt를 확인하고, 허용되지 않은 경로는 수집하지 마세요. R에서는 robotstxt 패키지로 robots.txt를 파싱할 수 있습니다.
  • 공개 데이터만 수집: 로그인 뒤에 있는 비공개 데이터는 수집하지 마세요. 인증이 필요한 데이터는 공식 API를 사용하세요.
  • GDPR 준수: EU 데이터 주체의 개인정보를 수집하는 경우 GDPR 요구사항을 따라야 합니다. 개인 식별 정보(PII)는 익명화하거나 수집하지 마세요.
  • 적절한 요청 속도: req_throttle()로 요청 속도를 제한하여 대상 서버에 부하를 주지 마세요. 일반적으로 초당 1-2개 요청이 안전한 기준입니다.
  • 공식 API 우선: 대상 서비스가 API를 제공한다면, 스크래핑보다 API를 사용하는 것이 안정적이고 합법적입니다.

웹 스크래핑 사용 사례와 모범 사례는 웹 스크래핑 사용 사례 페이지에서 더 자세히 확인할 수 있습니다. SERP 추적에 관심이 있다면 SERP 추적 사용 사례도 참고하세요.

ProxyHat SDK와 혼합 파이프라인

R이 데이터 분석의 핵심이더라도, 프로덕션 환경에서는 Python이나 Node.js와 함께 사용하는 경우가 많습니다. ProxyHat SDK는 동일한 gate.proxyhat.com:8080 게이트웨이와 동일한 사용자명 인코딩 패턴을 사용하므로, R과 Python/Node.js 간에 프록시 설정을 일관되게 유지할 수 있습니다.

예를 들어, Python의 requests 라이브러리에서 동일한 프록시를 사용하는 코드는 다음과 같습니다:

# Python 예시 (참고용)
import requests

proxies = {
    "http": "http://user-country-US-session-abc123:pass@gate.proxyhat.com:8080",
    "https": "http://user-country-US-session-abc123:pass@gate.proxyhat.com:8080"
}

resp = requests.get("https://httpbin.org/ip", proxies=proxies)
print(resp.json())

R 파이프라인에서 수집한 데이터를 Python 전처리 스크립트로 넘기거나, 그 반대의 경우에도 동일한 프록시 인증 패턴을 사용하면 코드 유지보수가 단순해집니다.

핵심 요약

Key Takeaways:

  • httr2req_proxy("gate.proxyhat.com", username, password)로 R에서 프록시를 설정합니다.
  • 사용자명에 user-country-GB-city-london 형식으로 지역 타겟팅을 인코딩합니다.
  • user-session-{id}로 스티키 세션을 구성하고, 페이지마다 세션을 로테이션합니다.
  • req_retry()req_throttle()로 안정성과 예의 바른 요청 속도를 확보합니다.
  • JS 렌더링 페이지는 read_html_live() (chromote)를 통해 프록시와 함께 사용합니다.
  • 레지덴셜 프록시는 데이터센터 IP 차단을 우회하는 가장 효과적인 방법입니다.
  • robots.txt, GDPR, 공식 API 우선 원칙을 항상 준수하세요.

자주 묻는 질문

R에서 프록시 사용이란 무엇인가요?

R에서 프록시 사용이란 httr2, rvest, curl 등 R의 HTTP 클라이언트를 통해 프록시 서버로 요청을 라우팅하는 것을 의미합니다. req_proxy() 함수로 게이트웨이 주소와 인증 정보를 지정하면, 모든 HTTP 요청이 프록시를 거쳐 대상 서버로 전달됩니다. 이를 통해 IP 차단 우회, 지역 제한 콘텐츠 접근, 대량 데이터 수집 시 분산된 IP 사용이 가능해집니다.

R 웹 스크래핑 시 어떤 프록시 유형이 가장 적합한가요?

R 웹 스크래핑에는 레지덴셜 프록시가 가장 적합합니다. 실제 ISP에서 발급된 IP를 사용하므로 대상 서버가 봇으로 탐지할 확률이 낮습니다. 데이터센터 프록시는 속도가 빠르지만 차단 위험이 높고, 모바일 프록시는 탐지 위험이 가장 낮지만 비용이 높습니다. 용도와 예산에 따라 선택하세요.

R에서 스크래핑할 때 차단을 어떻게 피하나요?

차단을 피하려면 레지덴셜 프록시 사용, req_throttle()로 요청 간 지연 설정, 세션 로테이션, 적절한 User-Agent 헤더 설정이 필요합니다. 또한 robots.txt를 준수하고, 단일 IP로 과도한 요청을 보내지 않으며, req_retry()로 지수 백오프 재시도를 구현하는 것이 중요합니다.

httr2의 req_proxy는 어떻게 작동하나요?

httr2req_proxy()는 HTTP CONNECT 터널을 통해 프록시 서버와 연결을 설정합니다. 게이트웨이 호스트명, 포트, 사용자명, 비밀번호를 인자로 받으며, 인증은 Basic Auth로 처리됩니다. 이후 모든 요청이 프록시를 통해 전달되며, 응답도 동일한 경로로 반환됩니다.

지금 바로 ProxyHat 프록시로 R 웹 스크래핑 파이프라인을 구축해 보세요. 레지덴셜, 데이터센터, 모바일 프록시를 모두 지원하며, gate.proxyhat.com:8080 게이트웨이로 즉시 시작할 수 있습니다.

시작할 준비가 되셨나요?

AI 필터링으로 148개국 이상에서 5천만 개 이상의 레지덴셜 IP에 액세스하세요.

가격 보기레지덴셜 프록시
← 블로그로 돌아가기