Rubyプロジェクトでプロキシを使うべき理由
Webスクレイピングやデータパイプラインを構築する際、プロキシサーバーは単なるオプションではなく必須のインフラです。ターゲットサイトからのIPブロック回避、地理的制限の突破、レート制限の分散処理—allにおいてプロキシが解決策となります。
Rubyエコシステムには複数のHTTPクライアントが存在し、それぞれプロキシ対応のアプローチが異なります。Ruby proxy設定を正しく理解することで、本番環境で安定したスクレイピングシステムを構築できます。
本ガイドでは、標準ライブラリのNet::HTTPから、高機能なTyphoeus、そしてProxyHat SDKまで、実践的なコード例とともに解説します。
Net::HTTPでプロキシを使用する基本
Ruby標準ライブラリのNet::HTTPは、依存関係を追加せずにプロキシ経由でリクエストを送信できます。Net::HTTP proxy設定は、Proxyメソッドを使用してプロキシ接続を確立します。
基本的なプロキシ認証
以下はProxyHatのレジデンシャルプロキシを使用した基本的な例です:
require 'net/http'
require 'uri'
# ProxyHat接続パラメータ
PROXY_HOST = 'gate.proxyhat.com'
PROXY_PORT = 8080
PROXY_USER = 'your_username'
PROXY_PASS = 'your_password'
def fetch_with_proxy(url, proxy_user: PROXY_USER, proxy_pass: PROXY_PASS)
uri = URI.parse(url)
# プロキシ経由でHTTP接続を確立
proxy = Net::HTTP::Proxy(PROXY_HOST, PROXY_PORT, proxy_user, proxy_pass)
http = proxy.new(uri.host, uri.port)
# HTTPSの場合はSSLを有効化
http.use_ssl = uri.scheme == 'https'
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.open_timeout = 30
http.read_timeout = 60
request = Net::HTTP::Get.new(uri.request_uri)
request['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
request['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
response = http.request(request)
{
status: response.code.to_i,
headers: response.each_header.to_h,
body: response.body
}
end
# 使用例
result = fetch_with_proxy('https://httpbin.org/ip')
puts "Status: #{result[:status]}"
puts "Body: #{result[:body]}"
エラーハンドリングとリトライロジック
本番環境では、ネットワークエラーやタイムアウト、プロキシ接続失敗に対処する必要があります:
require 'net/http'
require 'uri'
class ProxyHTTPClient
class RetryExhausted < StandardError; end
class ProxyConnectionError < StandardError; end
MAX_RETRIES = 3
RETRY_DELAYS = [1, 2, 5] # 指数バックオフ
attr_reader :proxy_host, :proxy_port, :username, :password
def initialize(proxy_host: 'gate.proxyhat.com',
proxy_port: 8080,
username: ENV['PROXYHAT_USER'],
password: ENV['PROXYHAT_PASS'])
@proxy_host = proxy_host
@proxy_port = proxy_port
@username = username
@password = password
end
def get(url, headers: {})
retries = 0
loop do
begin
return perform_request(:get, url, headers: headers)
rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED => e
retries += 1
if retries >= MAX_RETRIES
raise RetryExhausted, "Failed after #{MAX_RETRIES} attempts: #{e.message}"
end
puts "Retry #{retries}/#{MAX_RETRIES} after #{RETRY_DELAYS[retries - 1]}s: #{e.class}"
sleep RETRY_DELAYS[retries - 1]
rescue OpenSSL::SSL::SSLError => e
raise ProxyConnectionError, "SSL error: #{e.message}"
end
end
end
private
def perform_request(method, url, headers: {})
uri = URI.parse(url)
proxy_class = Net::HTTP::Proxy(proxy_host, proxy_port, username, password)
http = proxy_class.new(uri.host, uri.port)
configure_ssl(http, uri)
http.open_timeout = 30
http.read_timeout = 60
request_class = case method
when :get then Net::HTTP::Get
when :post then Net::HTTP::Post
else raise ArgumentError, "Unsupported method: #{method}"
end
request = request_class.new(uri.request_uri)
headers.each { |k, v| request[k] = v }
request['User-Agent'] ||= 'Mozilla/5.0 (compatible; RubyBot/1.0)'
response = http.request(request)
case response
when Net::HTTPSuccess
{ success: true, status: response.code.to_i, body: response.body }
when Net::HTTPRedirection
{ success: true, status: response.code.to_i, body: response.body, location: response['location'] }
else
{ success: false, status: response.code.to_i, body: response.body }
end
end
def configure_ssl(http, uri)
return unless uri.scheme == 'https'
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.ca_file = OpenSSL::X509::DEFAULT_CERT_FILE
end
end
# 使用例
client = ProxyHTTPClient.new
result = client.get('https://httpbin.org/status/200')
puts result.inspect
Typhoeusで並列リクエストを処理する
Typhoeusはlibcurlバインディングを使用し、Hydraによる並列リクエスト処理を実現します。大量のURLを効率的にスクレイピングする場合、Typhoeusは必須のツールです。
単一リクエストとプロキシ設定
require 'typhoeus'
# Typhoeusでプロキシを使用する設定
def create_typhoeus_request(url, proxy_url: nil)
Typhoeus::Request.new(
url,
method: :get,
proxy: proxy_url,
headers: {
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language' => 'en-US,en;q=0.9'
},
timeout: 30,
connecttimeout: 15,
followlocation: true,
ssl_verifypeer: true,
ssl_verifyhost: 2
)
end
# ProxyHatプロキシURLを構築
def build_proxy_url(username:, password:, country: nil, city: nil, session: nil)
user_parts = [username]
user_parts << "country-#{country}" if country
user_parts << "city-#{city}" if city
user_parts << "session-#{session}" if session
"http://#{user_parts.join('-')}:#{password}@gate.proxyhat.com:8080"
end
# 使用例
proxy_url = build_proxy_url(
username: ENV['PROXYHAT_USER'],
password: ENV['PROXYHAT_PASS'],
country: 'US'
)
request = create_typhoeus_request('https://httpbin.org/ip', proxy_url: proxy_url)
response = request.run
if response.success?
puts "Success: #{response.code}"
puts "Body: #{response.body}"
elsif response.timed_out?
puts "Request timed out"
elsif response.code == 0
puts "Connection failed: #{response.return_message}"
else
puts "HTTP Error: #{response.code}"
end
Hydraで並列リクエストを実行
Hydraを使用すると、複数のリクエストを同時に実行し、ネットワーク待ち時間を最小化できます:
require 'typhoeus'
class ParallelScraper
CONCURRENCY = 50 # 同時接続数
attr_reader :proxy_config
def initialize(username:, password:, country: 'US')
@proxy_config = { username: username, password: password, country: country }
end
def fetch_all(urls)
hydra = Typhoeus::Hydra.new(max_concurrency: CONCURRENCY)
results = Concurrent::Array.new
mutex = Mutex.new
urls.each_with_index do |url, index|
# 各リクエストで異なるセッションを使用(IPローテーション)
session_id = "req_#{index}_#{SecureRandom.hex(4)}"
proxy_url = build_proxy_url(session: session_id)
request = Typhoeus::Request.new(
url,
method: :get,
proxy: proxy_url,
headers: default_headers,
timeout: 30,
followlocation: true
)
request.on_complete do |response|
mutex.synchronize do
results << {
url: url,
status: response.code,
success: response.success?,
body: response.body,
total_time: response.total_time,
ip: extract_ip_from_response(response.body)
}
end
end
request.on_failure do |response|
mutex.synchronize do
results << {
url: url,
status: response.code || 0,
success: false,
error: response.return_message
}
end
end
hydra.queue(request)
end
# 全リクエストを実行
hydra.run
results
end
private
def build_proxy_url(session:)
"http://#{@proxy_config[:username]}-country-#{@proxy_config[:country]}-session-#{session}:#{@proxy_config[:password]}@gate.proxyhat.com:8080"
end
def default_headers
{
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
}
end
def extract_ip_from_response(body)
JSON.parse(body)['origin'] rescue nil
end
end
# 使用例
require 'concurrent'
require 'securerandom'
scraper = ParallelScraper.new(
username: ENV['PROXYHAT_USER'],
password: ENV['PROXYHAT_PASS'],
country: 'US'
)
urls = 100.times.map { |i| "https://httpbin.org/delay/#{rand(1..3)}" }
results = scraper.fetch_all(urls)
puts "Completed: #{results.count} requests"
puts "Success rate: #{results.count { |r| r[:success] } / results.count.to_f * 100}%"
ProxyHat Ruby SDKでIPローテーションと地理ターゲティング
Ruby residential proxiesを活用する場合、IPローテーゲーションと地理的ターゲティングが重要です。ProxyHatのユーザー名ベースの設定により、これらを簡単に制御できます。
require 'typhoeus'
require 'securerandom'
# ProxyHat SDKクライアント
class ProxyHatClient
GATEWAY_HOST = 'gate.proxyhat.com'
HTTP_PORT = 8080
SOCKS5_PORT = 1080
attr_reader :username, :password
def initialize(username:, password:)
@username = username
@password = password
end
# ローテーティングプロキシ(リクエストごとに新しいIP)
def rotating_proxy_url(country: nil, city: nil)
session = SecureRandom.hex(8)
build_proxy_url(session: session, country: country, city: city)
end
# スティッキーセッション(固定IPを維持)
def sticky_proxy_url(session_id:, country: nil, city: nil, duration: nil)
build_proxy_url(session: session_id, country: country, city: city, duration: duration)
end
# 国別ターゲティング
def country_proxy(country_code)
rotating_proxy_url(country: country_code)
end
# 都市別ターゲティング
def city_proxy(country_code, city_name)
rotating_proxy_url(country: country_code, city: city_name.downcase.gsub(/\s+/, '-'))
end
# SOCKS5プロキシURL
def socks5_proxy_url(session: nil, country: nil)
user_parts = [username]
user_parts << "country-#{country}" if country
user_parts << "session-#{session}" if session
"socks5://#{user_parts.join('-')}:#{password}@#{GATEWAY_HOST}:#{SOCKS5_PORT}"
end
# リクエスト実行ヘルパー
def get(url, options = {})
proxy_url = if options[:sticky]
sticky_proxy_url(session_id: options[:session_id] || SecureRandom.hex(4))
else
rotating_proxy_url(country: options[:country], city: options[:city])
end
request = Typhoeus::Request.new(
url,
method: :get,
proxy: proxy_url,
headers: options[:headers] || default_headers,
timeout: options[:timeout] || 30
)
response = request.run
{
success: response.success?,
status: response.code,
body: response.body,
proxy_used: proxy_url.gsub(/:[^:@]+@/, ':****@') # パスワードを隠蔽
}
end
private
def build_proxy_url(session:, country: nil, city: nil, duration: nil)
user_parts = [username]
user_parts << "country-#{country}" if country
user_parts << "city-#{city}" if city
user_parts << "session-#{session}" if session
user_parts << "duration-#{duration}" if duration
"http://#{user_parts.join('-')}:#{password}@#{GATEWAY_HOST}:#{HTTP_PORT}"
end
def default_headers
{
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
}
end
end
# 使用例
client = ProxyHatClient.new(
username: ENV['PROXYHAT_USER'],
password: ENV['PROXYHAT_PASS']
)
# 米国IPでリクエスト
us_result = client.get('https://httpbin.org/ip', country: 'US')
puts "US IP: #{us_result[:body]}"
# ドイツ・ベルリンIPでリクエスト
de_result = client.get('https://httpbin.org/ip', country: 'DE', city: 'berlin')
puts "Berlin IP: #{de_result[:body]}"
# 固定セッション(同じIPを維持)
sticky_result = client.get('https://httpbin.org/ip', sticky: true, session_id: 'my_session_123')
puts "Sticky IP: #{sticky_result[:body]}"
実践例:1000URLを並列スクレイピング
実際の本番環境での使用例として、1000のURLをローテーティングレジデンシャルプロキシ経由で並列取得するシステムを構築します:
require 'typhoeus'
require 'concurrent'
require 'securerandom'
require 'logger'
class ProductionScraper
include Concurrent::Async
BATCH_SIZE = 100
MAX_CONCURRENCY = 50
RETRY_COUNT = 3
attr_reader :client, :logger, :metrics
def initialize(username:, password:)
@client = ProxyHatClient.new(username: username, password: password)
@logger = Logger.new(STDOUT)
@logger.level = Logger::INFO
@metrics = {
total: 0,
success: 0,
failed: 0,
retries: 0,
start_time: nil
}
@metrics_mutex = Mutex.new
end
def scrape_urls(urls, country: 'US')
@metrics[:start_time] = Time.now
@metrics[:total] = urls.count
logger.info "Starting scrape of #{urls.count} URLs with country=#{country}"
# URLをバッチに分割
batches = urls.each_slice(BATCH_SIZE).to_a
results = []
batches.each_with_index do |batch, batch_idx|
logger.info "Processing batch #{batch_idx + 1}/#{batches.count}"
batch_results = process_batch(batch, country)
results.concat(batch_results)
# バッチ間で少し待機(サーバー負荷軽減)
sleep 0.5 unless batch_idx == batches.count - 1
end
log_summary
results
end
private
def process_batch(urls, country)
hydra = Typhoeus::Hydra.new(max_concurrency: MAX_CONCURRENCY)
results = Concurrent::Array.new
urls.each_with_index do |url, idx|
session_id = "batch_#{SecureRandom.hex(4)}_#{idx}"
proxy_url = client.rotating_proxy_url(country: country)
request = build_request(url, proxy_url)
request.on_complete do |response|
result = handle_response(url, response)
update_metrics(result)
results << result
end
hydra.queue(request)
end
hydra.run
results.to_a
end
def build_request(url, proxy_url)
Typhoeus::Request.new(
url,
method: :get,
proxy: proxy_url,
headers: {
'User-Agent' => random_user_agent,
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language' => 'en-US,en;q=0.9',
'Accept-Encoding' => 'gzip, deflate'
},
timeout: 30,
connecttimeout: 15,
followlocation: true,
ssl_verifypeer: true
)
end
def handle_response(url, response)
{
url: url,
status: response.code,
success: response.success?,
body: response.success? ? response.body : nil,
time: response.total_time,
error: response.success? ? nil : response.return_message
}
end
def update_metrics(result)
@metrics_mutex.synchronize do
if result[:success]
@metrics[:success] += 1
else
@metrics[:failed] += 1
end
end
end
def log_summary
duration = Time.now - @metrics[:start_time]
rate = @metrics[:total] / duration
success_rate = (@metrics[:success].to_f / @metrics[:total] * 100).round(2)
logger.info "=" * 50
logger.info "Scraping Complete"
logger.info "Total URLs: #{@metrics[:total]}"
logger.info "Success: #{@metrics[:success]} (#{success_rate}%)"
logger.info "Failed: #{@metrics[:failed]}"
logger.info "Duration: #{duration.round(2)}s"
logger.info "Rate: #{rate.round(2)} req/s"
logger.info "=" * 50
end
def random_user_agent
agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'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',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15'
]
agents.sample
end
end
# 実行例
scraper = ProductionScraper.new(
username: ENV['PROXYHAT_USER'],
password: ENV['PROXYHAT_PASS']
)
# テスト用URLを生成
test_urls = 1000.times.map do |i|
"https://httpbin.org/delay/#{rand(1..2)}?id=#{i}"
end
results = scraper.scrape_urls(test_urls, country: 'US')
# 結果をCSVに保存
require 'csv'
CSV.open('scraping_results.csv', 'w') do |csv|
csv << ['url', 'status', 'success', 'time', 'error']
results.each do |r|
csv << [r[:url], r[:status], r[:success], r[:time]&.round(2), r[:error]]
end
end
TLS/SSL設定と証明書検証
プロキシ経由でHTTPSサイトにアクセスする際、SSL/TLS設定は重要です。自己署名証明書やSNI(Server Name Indication)の問題に対処する方法を解説します。
Net::HTTPでのSSL設定
require 'net/http'
require 'uri'
require 'openssl'
class SSLAwareProxyClient
attr_reader :proxy_host, :proxy_port, :username, :password
def initialize(proxy_host: 'gate.proxyhat.com', proxy_port: 8080, username:, password:)
@proxy_host = proxy_host
@proxy_port = proxy_port
@username = username
@password = password
end
# 標準的なSSL検証
def fetch_with_ssl_verification(url)
uri = URI.parse(url)
proxy = Net::HTTP::Proxy(proxy_host, proxy_port, username, password)
http = proxy.new(uri.host, uri.port)
if uri.scheme == 'https'
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
# システムのCA証明書ストアを使用
http.cert_store = OpenSSL::X509::Store.new.tap do |store|
store.set_default_paths
end
# SNIを有効化(デフォルトで有効だが明示的に設定)
http.enable_post_connection_check = true
end
request = Net::HTTP::Get.new(uri.request_uri)
http.request(request)
end
# 自己署名証明書を許可(開発環境のみ)
def fetch_with_insecure_ssl(url)
warn "WARNING: SSL verification disabled - use only in development!"
uri = URI.parse(url)
proxy = Net::HTTP::Proxy(proxy_host, proxy_port, username, password)
http = proxy.new(uri.host, uri.port)
if uri.scheme == 'https'
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
request = Net::HTTP::Get.new(uri.request_uri)
http.request(request)
end
# カスタムCA証明書を使用
def fetch_with_custom_ca(url, ca_cert_path)
uri = URI.parse(url)
proxy = Net::HTTP::Proxy(proxy_host, proxy_port, username, password)
http = proxy.new(uri.host, uri.port)
if uri.scheme == 'https'
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.cert_store = OpenSSL::X509::Store.new.tap do |store|
store.add_file(ca_cert_path)
end
end
request = Net::HTTP::Get.new(uri.request_uri)
http.request(request)
end
# クライアント証明書認証
def fetch_with_client_cert(url, cert_path, key_path)
uri = URI.parse(url)
proxy = Net::HTTP::Proxy(proxy_host, proxy_port, username, password)
http = proxy.new(uri.host, uri.port)
if uri.scheme == 'https'
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
http.key = OpenSSL::PKey::RSA.new(File.read(key_path))
http.cert_store = OpenSSL::X509::Store.new.tap(&:set_default_paths)
end
request = Net::HTTP::Get.new(uri.request_uri)
http.request(request)
end
end
# 使用例
client = SSLAwareProxyClient.new(
username: ENV['PROXYHAT_USER'],
password: ENV['PROXYHAT_PASS']
)
# 標準的なHTTPSリクエスト
response = client.fetch_with_ssl_verification('https://example.com')
puts "Status: #{response.code}"
TyphoeusでのSSL設定
require 'typhoeus'
# TyphoeusでのSSLオプション
request = Typhoeus::Request.new(
'https://example.com',
method: :get,
proxy: 'http://user:pass@gate.proxyhat.com:8080',
# SSL検証オプション
ssl_verifypeer: true, # ピア証明書を検証
ssl_verifyhost: 2, # ホスト名を検証(2=厳密、1=緩い、0=無効)
sslversion: :tlsv1_2, # TLS 1.2以上を強制
# カスタムCA証明書(オプション)
# cainfo: '/path/to/ca-bundle.crt',
# クライアント証明書(オプション)
# sslcert: '/path/to/client.crt',
# sslkey: '/path/to/client.key',
timeout: 30
)
response = request.run
puts "Status: #{response.code}"
Ruby on Railsでの統合
Railsアプリケーションでプロキシを使用する場合、FaradayミドルウェアやActiveJobとの統合が効果的です。
Faradayミドルウェアでのプロキシ設定
# config/initializers/proxy.rb
require 'faraday'
require 'faraday_middleware'
class ProxyMiddleware < Faraday::Middleware
def initialize(app, options = {})
super(app)
@options = options
end
def call(env)
# リクエストごとにローテーティングプロキシURLを生成
session = SecureRandom.hex(8)
country = @options[:country] || 'US'
proxy_url = build_proxy_url(session, country)
env[:proxy] = URI.parse(proxy_url)
@app.call(env)
end
private
def build_proxy_url(session, country)
username = ENV['PROXYHAT_USER']
password = ENV['PROXYHAT_PASS']
"http://#{username}-country-#{country}-session-#{session}:#{password}@gate.proxyhat.com:8080"
end
end
# Faradayクライアントの設定
class ApiClient
attr_reader :connection
def initialize(country: 'US')
@connection = Faraday.new do |builder|
builder.use ProxyMiddleware, country: country
builder.request :url_encoded
builder.request :retry, max: 3, interval: 1, backoff_factor: 2
builder.response :json, content_type: /\bjson$/
builder.adapter :typhoeus
end
end
def get(url, params: {})
response = connection.get(url, params)
{
status: response.status,
body: response.body,
success: response.success?
}
rescue Faraday::Error => e
{ status: 0, body: nil, success: false, error: e.message }
end
def post(url, body: {})
response = connection.post(url, body.to_json, 'Content-Type' => 'application/json')
{
status: response.status,
body: response.body,
success: response.success?
}
rescue Faraday::Error => e
{ status: 0, body: nil, success: false, error: e.message }
end
end
# app/services/web_scraper_service.rb
class WebScraperService
def initialize(country: 'US')
@client = ApiClient.new(country: country)
end
def scrape(url)
result = @client.get(url)
return nil unless result[:success]
# HTMLをパースしてデータを抽出
doc = Nokogiri::HTML(result[:body])
extract_data(doc)
end
private
def extract_data(doc)
{
title: doc.at('title')&.text,
h1: doc.at('h1')&.text,
meta_description: doc.at('meta[name="description"]')&.[]('content')
}
end
end
ActiveJobでの並列スクレイピング
# app/jobs/scraping_job.rb
class ScrapingJob < ApplicationJob
queue_as :scraping
# ジョブ失敗時のリトライ設定
retry_on ScrapingError, wait: :polynomially_longer, attempts: 3
discard_on ActiveJob::DeserializationError
def perform(url, country: 'US', session_id: nil)
@url = url
@country = country
@session_id = session_id || SecureRandom.hex(8)
result = fetch_with_proxy
if result[:success]
process_result(result[:body])
else
raise ScrapingError, "Failed to fetch #{@url}: #{result[:error]}"
end
end
private
def fetch_with_proxy
proxy_url = build_proxy_url
request = Typhoeus::Request.new(
@url,
method: :get,
proxy: proxy_url,
headers: {
'User-Agent' => random_user_agent,
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
},
timeout: 30,
followlocation: true
)
response = request.run
{
success: response.success?,
status: response.code,
body: response.body,
error: response.success? ? nil : response.return_message
}
end
def build_proxy_url
username = Rails.application.credentials.proxyhat[:username]
password = Rails.application.credentials.proxyhat[:password]
"http://#{username}-country-#{@country}-session-#{@session_id}:#{password}@gate.proxyhat.com:8080"
end
def process_result(html)
doc = Nokogiri::HTML(html)
# データを抽出して保存
data = {
url: @url,
title: doc.at('title')&.text,
scraped_at: Time.current
}
ScrapedPage.create!(data)
end
def random_user_agent
[
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
].sample
end
end
# app/jobs/batch_scraping_job.rb
class BatchScrapingJob < ApplicationJob
queue_as :scraping
def perform(urls, country: 'US')
# URLを小さなバッチに分割して並列処理
urls.each_slice(50).with_index do |batch, index|
batch.each do |url|
# 各URLを個別のジョブとしてエンキュー
ScrapingJob.perform_later(url, country, "batch_#{index}_#{SecureRandom.hex(4)}")
end
# バッチ間で少し待機
sleep 2 unless index == (urls.count / 50.0).ceil - 1
end
end
end
# 使用例
# 大量のURLをバッチでスクレイピング
urls = ['https://example1.com', 'https://example2.com', ...] # 1000 URLs
BatchScrapingJob.perform_later(urls, 'US')
パフォーマンス比較表
各HTTPクライアントとプロキシ構成のパフォーマンスを比較します:
| クライアント | 並列処理 | メモリ使用 | 推奨用途 |
|---|---|---|---|
| Net::HTTP (stdlib) | なし(順次) | 低 | 単純なリクエスト、スクリプト |
| Net::HTTP + Threads | 制限あり | 中 | 小規模並列処理 |
| Typhoeus | Hydra(高性能) | 中 | 大規模スクレイピング |
| Faraday + Typhoeus | Hydra | 中 | Rails統合、APIクライアント |
| ProxyHat + Typhoeus | Hydra + IPローテーション | 中 | 本番スクレイピング |
Key Takeaways
- Net::HTTPは標準ライブラリで依存関係不要、単純なリクエストに最適
- Typhoeus/Hydraは並列リクエスト処理のベストチョイス
- ProxyHatのユーザー名ベース設定でIPローテーションと地理ターゲティングを簡単に制御
- 本番環境では必ずリトライロジック、タイムアウト、エラーハンドリングを実装
- RailsではFaradayミドルウェアとActiveJobでスケーラブルなアーキテクチャを構築
- SSL/TLS検証を適切に設定し、セキュリティを維持
Rubyでのプロキシ活用についてさらに詳しく知りたい方は、WebスクレイピングのベストプラクティスやSERPトラッキングユースケースも参照してください。ProxyHatのプロキシネットワークは世界中のロケーションで利用可能です。






