Rでデータ収集を行うデータサイエンティストやアナリストにとって、Rでプロキシを使用することは単なるオプションではなく、スクレイピングの成功率と信頼性を左右する必須スキルです。本記事では、モダンなHTTPクライアントである httr2 の req_proxy() と、HTMLパーサー rvest を組み合わせ、ProxyHatのレジデンシャルプロキシを活用した実践的なコード例を多数紹介します。
対象読者は、Rで分析パイプラインを構築しているデータサイエンティスト、アナリスト、R開発者です。PythonやNode.jsのバックグラウンドがある方にも、Rエコシステムでのプロキシ利用パターンがそのまま応用できます。
Rプロキシの基礎: なぜhttr2とrvestでプロキシが必要なのか
Webスクレイピングでは、対象サイトがリクエスト元IPを制限することが一般的です。特にデータセンターIPは、AWS、GCP、AzureなどのクラウドプロバイダーIP帯域として広く識別されており、多くのサイトがHTTP 429や403で即座にブロックします。
一方、レジデンシャルプロキシは一般家庭のISPに属するIPアドレスを使用するため、通常のユーザーからのアクセスと区別が困難です。これにより、地理的制限(geo-wall)のあるコンテンツへのアクセスや、IPベースのレート制限の回避が可能になります。
Rプロキシの利用場面には以下が含まれます:
- SERPデータの収集(検索結果ページの定期的な取得)
- eコマースサイトの価格モニタリング
- 地理的に制限された公開データへのアクセス
- APIを提供していないサイトからの構造化データ抽出
Rの httr2 パッケージは、パイプベースのAPIでモダンなHTTPリクエストを構築でき、req_proxy() 関数でHTTP/SOCKS5プロキシを簡単に設定できます。従来の httr パッケージと異なり、リクエストオブジェクトを段階的に組み立てる設計のため、プロキシ設定、リトライ、スロットリングを宣言的に追加できます。
httr2でreq_proxy()を設定する基本パターン
ProxyHatのゲートウェイは gate.proxyhat.com で提供され、HTTPプロキシはポート 8080、SOCKS5プロキシはポート 1080 で接続します。ユーザー名にジオターゲティングやセッション指定のフラグを埋め込むのが基本パターンです。
HTTPプロキシの基本例
library(httr2)
library(rvest)
# ProxyHat接続情報
proxy_host <- "gate.proxyhat.com"
proxy_port <- 8080
proxy_user <- "user-country-US"
proxy_pass <- "your_password"
# リクエストの構築
req <- request("https://httpbin.org/ip") |>
req_proxy(proxy_host, proxy_port, username = proxy_user, password = proxy_pass) |>
req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") |>
req_timeout(seconds = 30) |>
req_retry(max_tries = 3, backoff = ~ 2^x)
resp <- req |> req_perform()
status <- resp_status(resp)
body <- resp |> resp_body_json()
print(body)
# $origin
# [1] "198.51.100.42" # プロキシの出口IP
req_proxy() は接続先ホスト、ポート、ユーザー名、パスワードを引数に取ります。この例では req_retry() で最大3回のリトライと指数バックオフを設定し、req_timeout() で30秒のタイムアウトを指定しています。
地理ターゲティングとスティッキーセッション
ProxyHatでは、ユーザー名フィールドにフラグを追加することで、出口IPの国・都市を指定できます。また、セッションIDを固定することで、一連のリクエストで同じIPを使い続ける「スティッキーセッション」が実現できます。
# イギリス・ロンドンのIPを指定
proxy_user_gb <- "user-country-GB-city-london"
# セッションIDを固定(スティッキーセッション)
session_id <- paste0("sess-", as.integer(Sys.time()))
proxy_user_sticky <- paste0("user-country-GB-session-", session_id)
req <- request("https://httpbin.org/headers") |>
req_proxy(proxy_host, proxy_port,
username = proxy_user_sticky,
password = proxy_pass) |>
req_user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)")
resp <- req |> req_perform()
json <- resp |> resp_body_json()
print(json$headers)
スティッキーセッションは、ログイン後のページ遷移や、ページネーションを跨ぐスクレイピングで特に重要です。セッションIDが同じ限り、同じ出口IPが割り当てられ、サーバー側でセッションが切断されるのを防ぎます。
SOCKS5プロキシの使用
SOCKS5プロキシはポート 1080 で利用できます。SOCKS5はトランスポート層で動作するため、HTTPヘッダーの改変がなく、より低いレイヤーでのプロキシ通信が可能です。
# SOCKS5プロキシの設定
req_socks <- request("https://httpbin.org/ip") |>
req_proxy("gate.proxyhat.com", 1080,
username = "user-country-DE",
password = proxy_pass)
resp <- req_socks |> req_perform()
print(resp |> resp_body_json())
レジデンシャル vs データセンタープロキシ: 実用的な比較
プロキシの選択は、スクレイピングの成功率に直結します。以下の表は、主要なプロキシタイプの特徴を比較したものです。
| 特徴 | レジデンシャルプロキシ | データセンタープロキシ | モバイルプロキシ |
|---|---|---|---|
| IPソース | ISP(一般家庭) | クラウドプロバイダー | 携帯キャリア |
| 検知難易度 | 高(検知困難) | 低(容易に検知) | 非常に高 |
| 成功率(参考) | 90%以上 | 40〜60% | 95%以上 |
| レイテンシ | 200〜800ms | 50〜150ms | 300〜1200ms |
| 価格帯 | 中〜高 | 低 | 高 |
データセンタープロキシは高速で低コストですが、多くのWebサイトがプロキシ検知技術で容易に識別できます。レジデンシャルプロキシは、実在するISPのIPアドレスを使用するため、通常のユーザートラフィックと区別がつきにくく、ブロックされる確率が大幅に下がります。
実践例: ページネーション付きテーブルをtidyデータフレームに変換
ここでは、複数ページにわたるHTMLテーブルをスクレイピングし、tidyなデータフレームにまとめる実践的な例を紹介します。purrr::map でページを反復処理し、各ページで異なるセッションIDを使ってIPローテーションを行います。
library(httr2)
library(rvest)
library(purrr)
library(dplyr)
library(tibble)
# ProxyHat設定
proxy_host <- "gate.proxyhat.com"
proxy_port <- 8080
proxy_pass <- "your_password"
base_user <- "user-country-US"
# 1ページ分のテーブルを取得する関数
fetch_table_page <- function(page_num) {
session_id <- paste0("p", page_num, "-", as.integer(Sys.time()))
proxy_user <- paste0(base_user, "-session-", session_id)
url <- sprintf("https://example.com/table?page=%d", page_num)
req <- request(url) |>
req_proxy(proxy_host, proxy_port,
username = proxy_user,
password = proxy_pass) |>
req_user_agent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
) |>
req_headers("Accept" = "text/html,application/xhtml+xml",
"Accept-Language" = "en-US,en;q=0.9") |>
req_timeout(seconds = 30) |>
req_retry(max_tries = 3, backoff = ~ 2^x) |>
req_throttle(1 / 2) # 2秒に1リクエスト
tryCatch({
resp <- req |> req_perform()
html <- resp |> resp_body_string() |> read_html()
table_df <- html |>
html_elements("table.data-table") |>
html_table()
if (length(table_df) > 0) {
table_df[[1]] <- table_df[[1]] |>
mutate(source_page = page_num)
return(table_df[[1]])
}
return(NULL)
}, error = function(e) {
message(sprintf("Page %d failed: %s", page_num, e$message))
return(NULL)
})
}
# 10ページ分を取得して結合
pages <- 1:10
results <- map(pages, fetch_table_page)
# NULLを除外して結合
combined <- results |>
keep(~ !is.null(.x)) |>
bind_rows()
print(sprintf("取得行数: %d", nrow(combined)))
glimpse(combined)
この例のポイントは以下の通りです:
- セッションIDのページ別ローテーション: 各ページで異なるセッションIDを生成し、IPをローテーション
req_retry(): 最大3回のリトライと指数バックオフで一時的な障害に対応req_throttle(1 / 2): 2秒に1リクエストのレート制限で対象サーバーに配慮tryCatch(): エラーハンドリングで一部ページの失敗が全体を止めないよう設計mutate(source_page = page_num): 出所ページ番号を列として保持し、トレーサビリティを確保
curlを使った同等のプロキシ設定
R以外のツールとの連携やデバッグ用途では、curlコマンドで同じプロキシ設定を検証できます。
# HTTPプロキシ経由でアクセス
curl -x http://user-country-US-session-test01:your_password@gate.proxyhat.com:8080 \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)" \
-H "Accept: text/html" \
"https://httpbin.org/ip"
# SOCKS5プロキシ経由
curl -x socks5://user-country-DE:your_password@gate.proxyhat.com:1080 \
"https://httpbin.org/ip"
JSレンダリングページへの対応: read_html_live()とchromote
多くのモダンなWebサイトは、JavaScriptでコンテンツを動的にレンダリングします。rvest の read_html() は静的HTMLのみを解析するため、JSレンダリングが必要なページでは read_html_live() を使用します。この関数は内部で chromote パッケージを利用し、ヘッドレスChromeを駆動します。
library(rvest)
library(httr2)
# read_html_live()でJSレンダリングページを取得
# プロキシはChromeの起動引数で指定
live_html <- read_html_live(
"https://example.com/dynamic-content",
chrome_args = c(
"--proxy-server=http://gate.proxyhat.com:8080",
"--proxy-auth=user-country-GB:your_password",
"--no-sandbox",
"--disable-gpu"
)
)
# 動的にレンダリングされた要素を抽出
dynamic_text <- live_html |>
html_elements(".dynamic-content") |>
html_text2()
print(dynamic_text)
read_html_live() は、Chromeの --proxy-server 起動引数でプロキシを指定します。HTTPプロキシの場合は http://gate.proxyhat.com:8080、SOCKS5の場合は socks5://gate.proxyhat.com:1080 を指定します。ただし、Chromeのプロキシ認証は --proxy-auth フラグでは直接サポートされていない場合があるため、ProxyHatのIP許可リスト機能を併用するか、拡張機能経由で認証ヘッダーを注入するアプローチが実用的です。
現実的なUser-Agentとヘッダーの設定
プロキシを使用する際、User-AgentやHTTPヘッダーがプロキシの出口IPの地理的位置と整合していることが重要です。例えば、イギリスのIPを使用しているのに日本語のAccept-Languageを送信すると、ボット検知システムにフラグが立つ可能性があります。
# 地理的に整合したヘッダーを設定
req_uk <- request("https://example.co.uk/data") |>
req_proxy("gate.proxyhat.com", 8080,
username = "user-country-GB-city-london",
password = "your_password") |>
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-GB,en;q=0.9",
"Accept" = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Sec-Ch-Ua" = '"Not_A Brand";v="8", "Chromium";v="120"'
) |>
req_timeout(seconds = 30) |>
req_retry(max_tries = 3, backoff = ~ 2^x)
resp <- req_uk |> req_perform()
エラー処理とプロダクション運用のベストプラクティス
スクレイピングを本番環境で運用する場合、以下の対策が重要です。
サーキットブレーカーパターンの実装
library(httr2)
library(purrr)
# サーキットブレーカー: 連続失敗が閾値を超えたら停止
circuit_breaker_fetch <- function(urls, max_consecutive_failures = 5) {
failures <- 0
results <- list()
for (i in seq_along(urls)) {
if (failures >= max_consecutive_failures) {
message(sprintf("Circuit breaker tripped at URL %d", i))
break
}
session_id <- paste0("cb-", i, "-", as.integer(Sys.time()))
req <- request(urls[i]) |>
req_proxy("gate.proxyhat.com", 8080,
username = paste0("user-country-US-session-", session_id),
password = "your_password") |>
req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)") |>
req_timeout(seconds = 20) |>
req_retry(max_tries = 2, backoff = ~ 1.5^x)
result <- tryCatch({
resp <- req |> req_perform()
if (resp_status(resp) == 200) {
failures <<- 0
resp |> resp_body_string() |> read_html()
} else {
failures <<- failures + 1
NULL
}
}, error = function(e) {
failures <<- failures + 1
message(sprintf("Error on URL %d: %s", i, e$message))
NULL
})
results[[i]] <- result
Sys.sleep(1.5) # リクエスト間隔を確保
}
results
}
ログ記録とモニタリング
library(httr2)
# ログ付きリクエスト関数
logged_fetch <- function(url, proxy_user, proxy_pass, log_file = "scrape_log.csv") {
start_time <- Sys.time()
req <- request(url) |>
req_proxy("gate.proxyhat.com", 8080,
username = proxy_user,
password = proxy_pass) |>
req_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)") |>
req_timeout(seconds = 30)
result <- tryCatch({
resp <- req |> req_perform()
elapsed <- as.numeric(difftime(Sys.time(), start_time, units = "secs"))
status <- resp_status(resp)
log_entry <- data.frame(
timestamp = start_time,
url = url,
status = status,
elapsed_sec = round(elapsed, 2),
success = status == 200,
stringsAsFactors = FALSE
)
write.table(log_entry, log_file, append = TRUE,
sep = ",", row.names = FALSE, col.names = FALSE)
resp
}, error = function(e) {
elapsed <- as.numeric(difftime(Sys.time(), start_time, units = "secs"))
log_entry <- data.frame(
timestamp = start_time,
url = url,
status = 0,
elapsed_sec = round(elapsed, 2),
success = FALSE,
stringsAsFactors = FALSE
)
write.table(log_entry, log_file, append = TRUE,
sep = ",", row.names = FALSE, col.names = FALSE)
stop(e)
})
result
}
倫理とコンプライアンス
スクレイピングを行う際は、以下の原則を遵守することが重要です:
- 公開データのみを対象とする: ログイン背後のデータや、明示的に公開されていないデータは収集しない
- robots.txtの尊重: RFC 9309で標準化されたrobots.txtの指示に従う
- 利用規約の確認: 対象サイトのTerms of Serviceを確認し、スクレイピングが禁止されていないか確認する
- GDPR対応: EUのデータ主体に関する個人データを収集する場合、GDPRの要件に準拠する
- 公式APIを優先: 公式APIが提供されている場合は、スクレイピングよりAPIを使用する
- 適切なレート制限: 対象サーバーに過度な負荷をかけないリクエスト間隔を設定する
Rでプロキシを使用する場合でも、これらの原則は技術的な実装と同様に重要です。ProxyHatの利用規約も併せて確認し、公式ドキュメントを参照してください。
ProxyHatの設定とSDK連携
ProxyHatのレジデンシャルプロキシは、100以上の国と地域で利用可能で、都市レベルのジオターゲティングに対応しています。詳細は料金ページで確認できます。
RパイプラインとPython/Node.jsパイプラインを混在させる場合、ProxyHatのSDKが同じ接続パターンを提供するため、プロキシ設定の一貫性が保たれます。例えば、Pythonの requests ライブラリでも同じゲートウェイとユーザー名フォーマットを使用します:
# Python側でも同じパターン
import requests
proxies = {
"http": "http://user-country-US-session-abc123:your_password@gate.proxyhat.com:8080",
"https": "http://user-country-US-session-abc123:your_password@gate.proxyhat.com:8080"
}
resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=30)
print(resp.json())
この一貫性により、Rで前処理を行いPythonで機械学習モデルに投入するパイプラインでも、同じプロキシインフラをシームレスに利用できます。WebスクレイピングのユースケースやSERPトラッキングの詳細も参照してください。
Key Takeaways
Rでのプロキシ活用の要点:
httr2::req_proxy()でProxyHatのゲートウェイgate.proxyhat.com:8080(HTTP)または:1080(SOCKS5)を設定- ユーザー名に
country-US、city-london、session-xxxフラグを埋め込んでジオターゲティングとスティッキーセッションを制御req_retry()+req_throttle()でリトライとレート制限を宣言的に設定purrr::mapでページ反復処理、各ページでセッションIDをローテーション- JSレンダリングページは
read_html_live()+ Chrome起動引数でプロキシ指定- User-AgentとAccept-Languageを出口IPの地理と整合させる
- 公開データ、robots.txt、GDPR、利用規約を遵守し、公式APIを優先
Rでプロキシを使用することは、データ収集の信頼性を飛躍的に向上させます。httr2のパイプベースAPIとrvestの直感的なパーサーを組み合わせれば、プロダクション品質のスクレイピングパイプラインをRで構築できます。まずは少規模でテストし、成功率とレイテンシを測定した上で、本格的なデータ収集に移行してください。






