RでWebスクレイピングを行うデータサイエンティストにとって、プロキシの活用は必須スキルです。本記事では、モダンなHTTPクライアントであるhttr2とHTMLパーサーrvestを組み合わせ、Residentialプロキシ経由で堅牢なデータ収集パイプラインを構築する方法を解説します。
Rプロキシの基礎:なぜhttr2とrvestでプロキシが必要なのか
多くのWebサイトは、単一IPからの大量リクエストを検知するとアクセスをブロックします。データセンターIPはAWSやGCPなどのクラウド事業者に属するため、CloudflareやAkamaiなどのWAFはそれらを容易に識別し、HTTP 403やCAPTCHAチャレンジを返します。一方、Residentialプロキシは実際のISPに割り当てられたIPアドレスを使用するため、通常のユーザートラフィックと区別が困難で、成功率が大幅に向上します。
RFC 7230(HTTP/1.1 Message Syntax)で定義されるプロキシ機能は、httr2ではreq_proxy()関数としてシンプルに公開されています。これにより、リクエストオブジェクトのビルダーパターンの中でプロキシを宣言的に設定できます。
httr2のreq_proxyでRプロキシを設定する
httr2は、Rの tidyverse 生態系における次世代HTTPクライアントです。パイプラインでリクエストを構築し、req_perform()で実行する設計になっています。以下は、ProxyHatのゲートウェイを経由してHTTPリクエストを送信する最小例です。
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") |>
req_retry(max_tries = 3, max_seconds = 30)
resp <- req_perform(req)
status <- resp_status(resp)
body <- resp_body_string(resp)
cat("Status:", status, "\n", body, "\n")
req_proxy()は引数としてプロキシURL、ユーザー名、パスワードを受け取ります。ProxyHatのゲートウェイは gate.proxyhat.com:8080(HTTP)でリッスンしており、ユーザー名の中にジオターゲティングやセッション制御のフラグをエンコードします。
ジオターゲティングとスティッキーセッション
ProxyHatでは、ユーザー名フィールドにフラグを埋め込むことで、国・都市レベルのジオターゲティングと、IP固定のスティッキーセッションを制御します。これにより、同じセッションIDを指定する限り、複数リクエストで同じ出口IPを維持できます。
国・都市指定の例
# イギリス・ロンドンのIPを指定
req_uk <- request("https://httpbin.org/ip") |>
req_proxy(
"http://gate.proxyhat.com:8080",
"user-country-GB-city-london",
"YOUR_PASSWORD"
) |>
req_user_agent("Mozilla/5.0 (compatible; ResearchBot/1.0)")
resp_uk <- req_perform(req_uk)
cat(resp_body_string(resp_uk))
スティッキーセッションの例
# 同一セッションIDでIPを維持
session_id <- paste0("sess-", sample(10000:99999, 1))
req_sticky <- request("https://httpbin.org/ip") |>
req_proxy(
"http://gate.proxyhat.com:8080",
paste0("user-session-", session_id),
"YOUR_PASSWORD"
) |>
req_retry(max_tries = 3)
# 同じセッションIDを使えば同じIPが維持される
for (i in 1:3) {
resp <- req_perform(req_sticky)
cat("Request", i, "->", resp_body_string(resp), "\n")
}
SOCKS5プロキシの利用
一部の環境ではHTTPプロキシが制限される場合があります。ProxyHatはSOCKS5プロトコルもポート1080で提供しています。httr2はSOCKS5をネイティブにサポートしていませんが、curlの--socks5オプション経由で利用可能です。
# SOCKS5経由のリクエスト(system curlを使用)
socks_url <- paste0(
"socks5://", proxy_user, ":", proxy_pass,
"@gate.proxyhat.com:1080"
)
cmd <- paste0(
'curl -s --socks5 "', socks_url, '" ',
'-H "User-Agent: Mozilla/5.0" ',
'"https://httpbin.org/ip"'
)
result <- system(cmd, intern = TRUE)
cat(result, "\n")
curl SOCKS5オプションのドキュメントによれば、SOCKS5はDNS解決もプロキシ側で行うため、ローカルDNS漏洩を防ぐ利点があります。
rvestプロキシ連携:HTMLパースとテーブル抽出
rvest::read_html()は内部でhttrまたはxml2を使用しますが、httr2のレスポンスボディを直接パースすることで、プロキシ設定を一元管理できます。
library(rvest)
library(httr2)
library(dplyr)
# プロキシ経由でHTMLを取得してパース
fetch_html <- function(url, country = "US") {
req <- request(url) |>
req_proxy(
"http://gate.proxyhat.com:8080",
paste0("user-country-", country),
"YOUR_PASSWORD"
) |>
req_user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36") |>
req_retry(max_tries = 3, max_seconds = 30) |>
req_throttle(1 / 2) # 1リクエスト/2秒に制限
resp <- req_perform(req)
if (resp_status(resp) != 200) {
warning("HTTP ", resp_status(resp), " for ", url)
return(NULL)
}
read_html(resp_body_string(resp))
}
# テーブル抽出の例
page <- fetch_html("https://example.com/data-table")
if (!is.null(page)) {
tables <- page |>
html_elements("table.data-table") |>
html_table(header = TRUE, trim = TRUE)
if (length(tables) > 0) {
df <- tables[[1]] |> as_tibble()
print(df)
}
}
ページネーションとpurrrによる並行収集
実務では複数ページにまたがるテーブルを収集することが一般的です。purrr::map_dfr()を使って各ページを取得し、tidyなデータフレームに結合します。各ページで異なるセッションIDを生成することで、IPローテーションを自動化します。
library(purrr)
library(httr2)
library(rvest)
library(dplyr)
PROXY_PASS <- "YOUR_PASSWORD"
fetch_page <- function(page_num) {
session_id <- paste0("page-", page_num, "-", as.integer(Sys.time()))
url <- paste0("https://example.com/items?page=", page_num)
req <- request(url) |>
req_proxy(
"http://gate.proxyhat.com:8080",
paste0("user-country-US-session-", session_id),
PROXY_PASS
) |>
req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)") |>
req_headers("Accept-Language" = "en-US,en;q=0.9") |>
req_retry(
max_tries = 4,
max_seconds = 60,
is_transient = ~ resp_status(.x) %in% c(429, 500, 502, 503)
) |>
req_throttle(1 / 3) # 1リクエスト/3秒
tryCatch({
resp <- req_perform(req)
if (resp_status(resp) != 200) return(NULL)
html <- read_html(resp_body_string(resp))
html |>
html_elements("table.results") |>
html_table(header = TRUE, trim = TRUE) |>
.[[1]] |>
as_tibble() |>
mutate(page = page_num)
}, error = function(e) {
message("Page ", page_num, " failed: ", conditionMessage(e))
NULL
})
}
# 1〜10ページを順次取得して結合
all_data <- map_dfr(1:10, fetch_page)
cat("Total rows:", nrow(all_data), "\n")
print(head(all_data))
req_retry()のis_transient引数に、HTTP 429(Rate Limit)や5xxエラーを一時的エラーとして指定することで、自動的にバックオフ付きで再試行されます。req_throttle()はドメインごとのリクエストレートを制限し、対象サイトへの負荷を抑えます。
JavaScriptレンダリングページとread_html_live()
Single Page Application(SPA)やReact/Vueで構築されたサイトは、サーバーサイドでHTMLが生成されない場合があります。rvest 1.0.0以降では、read_html_live()がChromote(ヘッドレスChrome制御)をラップし、JavaScript実行後のDOMを取得できます。
library(rvest)
# read_html_liveは内部でchromoteを使用
# プロキシ設定は環境変数で渡す
Sys.setenv(
HTTPS_PROXY = "http://YOUR_USERNAME:YOUR_PASSWORD@gate.proxyhat.com:8080"
)
page <- read_html_live("https://example.com/spa-app")
# JS実行後にDOM要素を取得
titles <- page |>
html_elements(".dynamic-title") |>
html_text()
print(titles)
chromoteはChromeの--proxy-serverフラグをサポートしており、環境変数HTTPS_PROXYを通じてプロキシを指定できます。ただし、ユーザー名にジオターゲティングフラグを含める場合はURLエンコードに注意が必要です。
プロキシタイプの比較:Residential vs Datacenter vs Mobile
| 特徴 | Residential | Datacenter | Mobile |
|---|---|---|---|
| IPソース | ISP契約者 | クラウド事業者 | 携帯キャリア |
| 検知難易度 | 低(通常ユーザーと同一) | 高(WAFが即座に識別) | 極低 |
| 成功率(目安) | 90%以上 | 30〜50% | 95%以上 |
| レイテンシ | 200〜800ms | 50〜200ms | 500〜1500ms |
| コスト | 中 | 低 | 高 |
| 適用用途 | SERPスクレイピング、価格監視 | 制限の緩い公開API | モバイル専用コンテンツ |
一般的なWebスクレイピングではResidentialプロキシが最適なバランスを提供します。ProxyHatは3タイプすべてを提供しており、プランごとに選択可能です。
エラー処理と本番運用のベストプラクティス
サーキットブレーカーパターン
連続するエラーが一定回数を超えた場合、処理を一時停止することで対象サイトへの負荷を防ぎます。
library(httr2)
# カスタムサーキットブレーカー
circuit_state <- new.env(parent = emptyenv())
circuit_state$failures <- 0
circuit_state$threshold <- 5
circuit_state$cooldown <- 60 # 秒
circuit_state$last_failure <- NULL
safe_perform <- function(req) {
if (circuit_state$failures >= circuit_state$threshold) {
elapsed <- as.numeric(Sys.time() - circuit_state$last_failure, units = "secs")
if (elapsed < circuit_state$cooldown) {
message("Circuit open. Waiting ", round(circuit_state$cooldown - elapsed), "s")
Sys.sleep(circuit_state$cooldown - elapsed)
}
circuit_state$failures <- 0
}
tryCatch({
resp <- req_perform(req)
if (resp_status(resp) %in% c(200, 301, 302)) {
circuit_state$failures <- 0
} else {
circuit_state$failures <- circuit_state$failures + 1
circuit_state$last_failure <- Sys.time()
}
resp
}, error = function(e) {
circuit_state$failures <- circuit_state$failures + 1
circuit_state$last_failure <- Sys.time()
stop(e)
})
}
ログ記録
library(logger)
log_setup <- function() {
log_appender(appender_tee("scrape.log"))
log_threshold(INFO)
}
log_request <- function(url, status, ip_hint) {
log_info("{url} -> {status} via {ip_hint} at {format(Sys.time())}")
}
倫理とコンプライアンス
Webスクレイピングを行う際は、以下の点に留意してください。
- robots.txtの尊重: 対象サイトのrobots.txtを確認し、許可されたパスのみアクセスする。
- 利用規約(ToS)の確認: サービスによってはスクレイピングを明示的に禁止している場合がある。
- GDPR対応: EUデータ主体の個人データを収集する場合、GDPRの要件に従う必要がある。
- 公式APIの優先: 多くのプラットフォームは公式APIを提供しており、スクレイピングより安定かつ合法的。
- レート制限の遵守:
req_throttle()で対象サイトに過度な負荷をかけないよう制御する。
WebスクレイピングのユースケースやSERPトラッキングのページで、具体的な適用シナリオを確認できます。また、利用可能なロケーションは200以上の国・都市に対応しています。
ProxyHat SDKとの連携
ProxyHatの接続パターンは、PythonのrequestsやNode.jsのaxiosでも同じゲートウェイ・認証形式を使用します。RパイプラインとPython/Nodeパイプラインを混在させる場合、同じ認証情報とジオターゲティングフラグをそのまま再利用できます。詳細はProxyHat公式ドキュメントを参照してください。
# Python側でも同じゲートウェイを使用
# import requests
# proxies = {
# "http": "http://user-country-US:pass@gate.proxyhat.com:8080",
# "https": "http://user-country-US:pass@gate.proxyhat.com:8080"
# }
# r = requests.get("https://httpbin.org/ip", proxies=proxies)
Key Takeaways
- httr2の
req_proxy()でProxyHatゲートウェイ(gate.proxyhat.com:8080)を簡潔に設定できる。- ユーザー名フィールドに
user-country-XX-city-yyやuser-session-abc123をエンコードしてジオターゲティングとスティッキーセッションを制御する。req_retry()とreq_throttle()でレート制限と一時的エラーに対応し、安定した収集を実現する。- JSレンダリングページには
read_html_live()(chromote)を使用し、環境変数でプロキシを渡す。- Residentialプロキシはデータセンタープロキシより検知されにくく、SERPスクレイピングや価格監視に適する。
- robots.txt、ToS、GDPRを遵守し、公式APIがあればそちらを優先する。
FAQ
Rでプロキシを使う方法とは?
httr2パッケージのreq_proxy()関数を使用して、リクエストオブジェクトにプロキシURL、ユーザー名、パスワードを指定します。ProxyHatの場合はreq_proxy("http://gate.proxyhat.com:8080", username, password)の形式で設定し、req_perform()で実行します。rvestのread_html()でレスポンスボディをパースできます。
rvestでプロキシを設定するには?
rvest単体ではプロキシ設定機能を提供していませんが、httr2でプロキシ経由のレスポンスを取得し、そのボディをread_html()に渡すことでプロキシ連携が可能です。このパターンにより、プロキシ設定とHTMLパースを分離でき、コードの保守性が向上します。
どのプロキシタイプがRのスクレイピングに最適?
一般的なWebスクレイピングにはResidentialプロキシが最適です。データセンターIPはWAFに検知されやすく成功率が30〜50%にとどまりますが、ResidentialプロキシはISP契約者のIPを使用するため成功率が90%以上に達します。モバイルプロキシはさらに検知されにくいですがコストが高く、モバイル専用コンテンツに適しています。
Rのスクレイピングでブロックを回避するには?
Residentialプロキシの使用、適切なUser-AgentとAccept-Languageヘッダーの設定、req_throttle()によるレート制限、req_retry()による429/5xxエラーの自動再試行、ページごとのセッションIDローテーションを組み合わせることで、ブロックを大幅に減らせます。また、robots.txtとToSを遵守することが長期的なアクセス維持に不可欠です。
httr2のreq_proxyでSOCKS5は使える?
httr2は現在HTTPプロキシのみをネイティブサポートしています。SOCKS5プロキシを使用する場合は、ProxyHatのポート1080に対してsystem()経由でcurlコマンドを呼び出すか、chromoteの環境変数HTTPS_PROXYにSOCKS5 URLを設定することで利用可能です。






