もしあなたがRubyでパーサーを書いたり、外部APIを統合したり、データ収集を自動化したりしているなら、遅かれ早かれIPによるブロックに直面することになります。Wildberries、Ozon、Instagram、その他の多くのサービスは、数百回のリクエストの後に同じアドレスからのリクエストを制限します。解決策は一つ、プロキシです。この記事では、標準のNet::HTTPと人気のHTTPクライアントFaradayを通じてRubyプロジェクトにプロキシを接続する方法、IPのローテーションを設定し、エラーを適切に処理する方法を解説します。Railsアプリケーションでも、シンプルなスクリプトでも同様です。
Ruby開発者にとってのプロキシ: 主なシナリオ
Rubyは、プロキシなしでは解決できないタスクに広く使用されています。コードに進む前に、プロキシが必要な具体的な状況を理解することが重要です。これにより、適切なタイプとアーキテクチャのソリューションを選択するのに役立ちます。
データのパースと収集。 これは最も一般的なシナリオです。あなたはRubyでWildberries、Ozon、またはAvitoのページをクロールし、商品の価格や仕様を収集するスクリプトを書いています。プロキシなしでは、そのようなスクリプトは数分でIPによってブロックされます。サイトは同じアドレスからの数百のリクエストを検知し、自動的に保護を有効にします— CAPTCHA、一定期間の禁止、または完全なサービス拒否です。
地理的に依存するAPIとの統合。 一部の外部サービスは、リクエストの国に応じて異なるコンテンツを返します。あなたのRailsアプリケーションがそのようなAPIにアクセスする場合、プロキシを使用することで必要な地域からのリクエストを模倣できます。たとえば、異なる国の検索エンジンの結果を確認したり、特定の市場に適した価格を取得したりすることができます。
テストとQA。 開発者は、異なるIPアドレスや国からのリクエストに対するアプリケーションの動作をテストするためにプロキシを使用します。これは、地理的なブロックやIPによるレート制限があるサービスに特に関連しています。
マーケティングの自動化。 Rubyスクリプトは、広告キャンペーンの監視、検索結果のポジション確認、または競合の活動追跡に頻繁に使用されます。ここでプロキシは、負荷を分散し、制限を回避するために必要です。
複数アカウントの管理。 あなたのスクリプトが同じプラットフォーム上で複数のアカウントを管理する場合、各アカウントは別々のIPを介して動作する必要があります—さもなければ、プラットフォームは簡単に関連性を検出し、一度にすべてをブロックします。
Rubyタスクに最適なプロキシの種類
すべてのプロキシが異なるタスクに同じように適しているわけではありません。タイプの選択は、スクリプトの成功に直接影響します。Ruby開発に関連する主要なオプションを見ていきましょう。
| プロキシの種類 | Rubyでの最適なタスク | 速度 | 保護の回避 |
|---|---|---|---|
| データセンターのプロキシ | 厳しい保護のないパース、APIリクエスト、テスト | 高速 | 中程度 |
| レジデンシャルプロキシ | 保護されたサイトのパース、ソーシャルメディアとの連携、マーケットプレイス | 中程度 | 高い |
| モバイルプロキシ | Facebook、Instagram、TikTok API、アカウント管理 | 中程度 | 最大 |
データセンターのプロキシは最も速く、最も安価なオプションです。攻撃的な保護のないサイトのパース、大量のAPIリクエスト、負荷テストに適しています。しかし、大規模なプラットフォーム(Facebook、Google、Cloudflare)は、データセンターのASNによって簡単にそれらを認識します。
レジデンシャルプロキシは、実際の家庭ユーザーのIPを使用します。Wildberries、OzonをパースするRubyスクリプトや保護されたサイトで作業する場合、これは最適な選択です。サイトはサーバーではなく、通常のユーザーを認識します。
モバイルプロキシは、モバイルオペレーター(4G/5G)のIPを介して動作します。プラットフォームから見て最も信頼できるアドレスです—1つのモバイルIPは、同時に数百の実際のユーザーによって使用される可能性があるため、そのIPによるブロックは非常にまれです。あなたのRubyスクリプトがソーシャルメディアや広告プラットフォームで動作する場合、モバイルプロキシはブロックのリスクを最小限に抑えます。
Net::HTTPでのプロキシ: 基本設定
Net::HTTPは、HTTPリクエストのためのRubyの標準ライブラリで、言語の標準配布に含まれています。特別なメソッドNet::HTTP::Proxyを通じて、プロキシを「そのまま」サポートしています。
最も簡単な方法は、プロキシクラスを作成し、通常のNet::HTTPの代わりに使用することです:
require 'net/http'
require 'uri'
# プロキシのデータ
proxy_host = 'proxy.example.com'
proxy_port = 8080
# プロキシクラスを作成
proxy_class = Net::HTTP::Proxy(proxy_host, proxy_port)
# プロキシを通じてリクエストを実行
uri = URI('https://httpbin.org/ip')
proxy_class.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
request = Net::HTTP::Get.new(uri)
response = http.request(request)
puts response.body
end
メソッドNet::HTTP::Proxyは、プロキシのホスト、ポート、ユーザー名、およびパスワードの4つのパラメータを受け取ります。最初の2つは必須で、最後の2つは認証なしのプロキシの場合はオプションです。
代替方法は、プロキシのパラメータを直接Net::HTTP.newメソッドに渡すことです:
require 'net/http'
uri = URI('https://httpbin.org/ip')
http = Net::HTTP.new(
uri.host,
uri.port,
'proxy.example.com', # proxy_addr
8080, # proxy_port
nil, # proxy_user (認証なしの場合はnil)
nil # proxy_pass
)
http.use_ssl = true
response = http.get(uri.path)
puts response.body
このアプローチは、同じHTTPオブジェクトを複数のリクエストで再利用する必要がある場合に便利です。注意してください: プロキシのパラメータは、3番目と4番目の引数として渡され、別のメソッドを介してではありません。
Net::HTTPを通じた認証とHTTPS
ほとんどの商用プロキシは認証—ユーザー名とパスワードを必要とします。さらに、HTTPSサイトで作業する場合は、追加のSSL設定が必要です。両方のケースを見ていきましょう。
ユーザー名とパスワードを持つプロキシ:
require 'net/http'
require 'uri'
proxy_host = 'proxy.example.com'
proxy_port = 8080
proxy_user = 'your_username'
proxy_pass = 'your_password'
# 認証付きプロキシクラスを作成
proxy_class = Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass)
uri = URI('https://httpbin.org/ip')
proxy_class.start(uri.host, uri.port, use_ssl: true) do |http|
# SSLの検証を無効にする(テスト用のみ!)
# http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Get.new(uri)
request['User-Agent'] = 'Mozilla/5.0 (compatible; MyBot/1.0)'
response = http.request(request)
puts "IP: #{response.body}"
puts "Status: #{response.code}"
end
重要な点: HTTPSプロキシを使用する場合(CONNECTを介したトンネリング)、Net::HTTPはプロキシトンネルを介してターゲットサーバーとのSSL接続を自動的に確立します。これは、ほとんどのプロキシサーバーで正しく機能します。
環境変数を通じた設定。 プロキシのデータをコードにハードコーディングするのではなく、環境変数から読み取るのが良いプラクティスです:
require 'net/http'
require 'uri'
# 環境からプロキシの設定を読み取る
proxy_uri = URI(ENV.fetch('HTTP_PROXY', 'http://proxy.example.com:8080'))
proxy_class = Net::HTTP::Proxy(
proxy_uri.host,
proxy_uri.port,
proxy_uri.user,
proxy_uri.password
)
target_uri = URI('https://httpbin.org/ip')
proxy_class.start(target_uri.host, target_uri.port, use_ssl: true) do |http|
response = http.get(target_uri.path)
puts response.body
end
環境変数は、HTTP_PROXY=http://user:[email protected]:8080の形式で設定できます。このアプローチにより、コードを変更せずにプロキシを変更することができます—環境変数を.envファイルやサーバー設定で更新するだけです。
Faradayを通じたプロキシの接続
Faradayは、Rubyエコシステムで最も人気のあるHTTPクライアントの一つです。便利なミドルウェアアプローチとさまざまなアダプタ(Net::HTTP、HTTParty、Typhoeusなど)をサポートしているため、多くのRailsアプリケーションで使用されています。Faradayでのプロキシ設定は、使用するアダプタによって少し異なります。
Faradayでのプロキシの基本設定:
require 'faraday'
# プロキシを使用してFaraday接続を作成
conn = Faraday.new(url: 'https://httpbin.org') do |faraday|
faraday.proxy = {
uri: 'http://proxy.example.com:8080',
user: 'your_username',
password: 'your_password'
}
faraday.headers['User-Agent'] = 'Mozilla/5.0 (compatible; MyBot/1.0)'
faraday.adapter Faraday.default_adapter
end
response = conn.get('/ip')
puts response.body
puts "Status: #{response.status}"
パラメータfaraday.proxyは、uri、user、およびpasswordを持つハッシュを受け取ります。認証を必要としない場合は、URI文字列を直接渡すこともできます。
Net::HTTPアダプタを使用したFaradayと拡張設定:
require 'faraday'
conn = Faraday.new(url: 'https://httpbin.org') do |faraday|
faraday.proxy = 'http://user:[email protected]:8080'
# タイムアウトの設定
faraday.options.timeout = 30
faraday.options.open_timeout = 10
# ロギング用のミドルウェア(デバッグ時に便利)
faraday.response :logger
# 接続エラー時のリトライ
faraday.request :retry, max: 3, interval: 1
# アダプタ
faraday.adapter :net_http
end
begin
response = conn.get('/ip')
puts response.body
rescue Faraday::ConnectionFailed => e
puts "接続エラー: #{e.message}"
rescue Faraday::TimeoutError => e
puts "タイムアウト: #{e.message}"
end
ミドルウェア:retryは、プロキシを使用する際に特に便利です—もし一つのアドレスが一時的に利用できない場合、Faradayは自動的にリクエストを再試行します。これは、プロダクション環境でのスクリプトの信頼性にとって重要です。
Typhoeusアダプタを使用したFaraday(並列リクエスト用):
# Gemfile: gem 'typhoeus'
require 'faraday'
require 'typhoeus/adapters/faraday'
conn = Faraday.new(url: 'https://httpbin.org') do |faraday|
faraday.proxy = 'http://user:[email protected]:8080'
faraday.adapter :typhoeus
end
# プロキシを介した並列リクエスト
responses = []
conn.in_parallel do
responses << conn.get('/ip')
responses << conn.get('/headers')
responses << conn.get('/user-agent')
end
responses.each { |r| puts r.body }
プロキシのローテーション: 自動IP変更
一つのプロキシアドレスは、たとえレジデンシャルであっても、集中的なパース作業を行うと制限に達することがあります。解決策はローテーションです:各リクエストの後、または特定の間隔で自動的にIPを変更します。Rubyスクリプト用のシンプルなロテーターを実装します。
プロキシの配列に基づくシンプルなロテーター:
require 'net/http'
require 'uri'
class ProxyRotator
def initialize(proxies)
@proxies = proxies
@index = 0
@mutex = Mutex.new
end
def next_proxy
@mutex.synchronize do
proxy = @proxies[@index % @proxies.length]
@index += 1
proxy
end
end
def random_proxy
@proxies.sample
end
end
# [host, port, user, pass]形式のプロキシリスト
proxies = [
['proxy1.example.com', 8080, 'user1', 'pass1'],
['proxy2.example.com', 8080, 'user2', 'pass2'],
['proxy3.example.com', 8080, 'user3', 'pass3'],
]
rotator = ProxyRotator.new(proxies)
# 異なるプロキシを介してURLリストをパース
urls = [
'https://httpbin.org/ip',
'https://httpbin.org/headers',
'https://httpbin.org/user-agent'
]
urls.each do |url|
proxy = rotator.next_proxy
proxy_class = Net::HTTP::Proxy(proxy[0], proxy[1], proxy[2], proxy[3])
uri = URI(url)
begin
proxy_class.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.read_timeout = 15
response = http.get(uri.path)
puts "#{url} -> Status: #{response.code}, Proxy: #{proxy[0]}"
end
rescue => e
puts "プロキシ#{proxy[0]}に関するエラー: #{e.message}"
# 自動的に次のプロキシに移行
end
sleep(rand(0.5..2.0)) # リクエスト間のランダムな遅延
end
リクエスト間のランダムな遅延(sleep(rand(0.5..2.0)))は重要な要素です。固定間隔でのリクエストパターンは、アンチボットシステムによって簡単に検出されます。ランダムな遅延は、実際のユーザーの行動を模倣します。
ミドルウェアを使用したFaradayによるローテーション:
require 'faraday'
class ProxyMiddleware < Faraday::Middleware
PROXIES = [
'http://user1:[email protected]:8080',
'http://user2:[email protected]:8080',
'http://user3:[email protected]:8080',
].freeze
def call(env)
# 各リクエストに対してランダムなプロキシを選択
proxy_uri = URI(PROXIES.sample)
env[:request][:proxy] = {
uri: proxy_uri,
user: proxy_uri.user,
password: proxy_uri.password
}
@app.call(env)
end
end
# ミドルウェアを登録
Faraday::Middleware.register_middleware proxy_rotator: ProxyMiddleware
conn = Faraday.new(url: 'https://httpbin.org') do |faraday|
faraday.use :proxy_rotator
faraday.adapter :net_http
end
5.times do
response = conn.get('/ip')
puts response.body
end
Railsアプリケーションへのプロキシ統合
Railsアプリケーションでは、プロキシは主に外部HTTPリクエスト、APIとの統合、パースのバックグラウンドタスク、またはWebhookに必要です。いくつかの実用的なパターンを見ていきましょう。
パターン1: Faradayを使用したサービスオブジェクト。 外部HTTPリクエストをすべてプロキシを介して行う基本的なサービスクラスを作成します:
# app/services/http_client.rb
class HttpClient
def self.connection(base_url)
Faraday.new(url: base_url) do |faraday|
# Railsの環境変数からプロキシを取得
if Rails.env.production? && ENV['PROXY_URL'].present?
faraday.proxy = ENV['PROXY_URL']
end
faraday.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
faraday.options.timeout = 30
faraday.options.open_timeout = 10
faraday.request :retry, max: 3, interval: 2,
exceptions: [Faraday::ConnectionFailed, Faraday::TimeoutError]
faraday.response :raise_error
faraday.adapter :net_http
end
end
end
# コントローラやサービスでの使用:
# client = HttpClient.connection('https://api.example.com')
# response = client.get('/products', { category: 'electronics' })
パターン2: Railsのcredentialsを通じた設定。 プロキシのデータを保存するために、暗号化されたRailsのcredentialsを使用します:
# config/credentials.yml.enc (rails credentials:editで編集)
# proxy:
# host: proxy.example.com
# port: 8080
# username: your_user
# password: your_pass
# app/services/proxy_service.rb
class ProxyService
def self.faraday_proxy_config
creds = Rails.application.credentials.proxy
return nil unless creds
{
uri: "http://#{creds[:host]}:#{creds[:port]}",
user: creds[:username],
password: creds[:password]
}
end
def self.net_http_proxy
creds = Rails.application.credentials.proxy
return Net::HTTP::Proxy(nil, nil) unless creds
Net::HTTP::Proxy(
creds[:host],
creds[:port],
creds[:username],
creds[:password]
)
end
end
パターン3: Sidekiqを使用したバックグラウンドタスク。 パースやAPIリクエストがバックグラウンドワーカーで実行される場合、プロキシの設定は同じですが、競合を考慮することが重要です:
# app/workers/price_parser_worker.rb
class PriceParserWorker
include Sidekiq::Worker
sidekiq_options retry: 3, queue: :parsers
def perform(product_url)
# 各ワーカーのために新しい接続を作成
proxy = ProxyService.net_http_proxy
uri = URI(product_url)
proxy.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.read_timeout = 20
request = Net::HTTP::Get.new(uri)
request['User-Agent'] = random_user_agent
response = http.request(request)
if response.code == '200'
parse_and_save(response.body, product_url)
else
Rails.logger.warn "予期しないステータス #{response.code} for #{product_url}"
end
end
rescue Net::OpenTimeout, Net::ReadTimeout => e
Rails.logger.error "タイムアウト for #{product_url}: #{e.message}"
raise # Sidekiqはタスクを再試行します
end
private
def random_user_agent
agents = [
'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',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
]
agents.sample
end
def parse_and_save(html, url)
# パースのロジック...
end
end
エラー処理とタイムアウト
プロキシを介して作業することは、特定のエラーを生成する追加のレイヤーを追加します。適切な例外処理は、5分でクラッシュするスクリプトと、数時間介入なしで動作するスクリプトの違いです。
Rubyでのプロキシ作業時の主なエラータイプ:
| 例外 | 原因 | 対処法 |
|---|---|---|
Net::OpenTimeout |
プロキシが応答しない | プロキシを変更し、再試行する |
Net::ReadTimeout |
プロキシが遅いか、サイトが応答しない | タイムアウトを延長するか、プロキシを変更する |
Errno::ECONNREFUSED |
プロキシサーバーが接続を拒否した | プロキシのデータを確認する |
OpenSSL::SSL::SSLError |
プロキシを介したSSLの問題 | SSL設定を確認する |
| HTTP 407 | プロキシの無効なユーザー名/パスワード | 資格情報を確認する |
| HTTP 403/429 | IPがサイトによってブロックされている | プロキシを変更し、待機時間を追加する |
自動プロキシ変更を伴う包括的なエラー処理:
require 'net/http'
require 'uri'
class RobustHttpClient
MAX_RETRIES = 3
RETRIABLE_ERRORS = [
Net::OpenTimeout,
Net::ReadTimeout,
Errno::ECONNREFUSED,
Errno::ECONNRESET,
OpenSSL::SSL::SSLError
].freeze
def initialize(proxies)
@proxies = proxies.dup
@failed_proxies = []
end
def get(url, retries: MAX_RETRIES)
uri = URI(url)
attempt = 0
begin
attempt += 1
proxy = current_proxy
proxy_class = Net::HTTP::Proxy(*proxy)
proxy_class.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.open_timeout = 10
http.read_timeout = 20
request = Net::HTTP::Get.new(uri)
request['User-Agent'] = 'Mozilla/5.0 (compatible; RubyBot/1.0)'
response = http.request(request)
case response.code.to_i
when 200..299
response
when 407
raise "プロキシ認証に失敗しました: #{proxy[0]}"
when 403, 429
rotate_proxy!
raise "IPがブロックされました (#{response.code}), プロキシをローテーションします"
else
raise "予期しないHTTPステータス: #{response.code}"
end
end
rescue *RETRIABLE_ERRORS => e
Rails.logger.warn "接続エラー (試行 #{attempt}/#{retries}): #{e.message}"
rotate_proxy!
retry if attempt < retries
raise "試行回数 #{retries} の後に失敗しました: #{e.message}"
rescue RuntimeError => e
Rails.logger.warn "HTTPエラー (試行 #{attempt}/#{retries}): #{e.message}"
retry if attempt < retries
raise
end
end
private
def current_proxy
@proxies.first || raise("利用可能なプロキシがありません!")
end
def rotate_proxy!
failed = @proxies.shift
@failed_proxies << failed
Rails.logger.info "プロキシをローテーションしました。残り: #{@proxies.length}"
end
end
RubyにおけるSOCKS5プロキシ: socksifyを通じた設定
標準のNet::HTTPは、SOCKS5をネイティブにサポートしていません—HTTP/HTTPSプロキシのみです。SOCKS5を使用するには、追加のライブラリが必要です。最も人気のあるオプションは、socksifyというgemです。
なぜSOCKS5がHTTPプロキシよりも好まれることがあるのでしょうか?SOCKS5プロトコルは、より低いレベルで動作し、HTTPだけでなく任意のTCPトラフィックをプロキシします。さらに、SOCKS5は認証をサポートし、リクエストに関するメタデータを少なく送信するため、ボット検出システムに対して目立たなくなります。
# Gemfile
# gem 'socksify'
require 'socksify'
require 'socksify/http'
require 'net/http'
require 'uri'
# SOCKS5プロキシのグローバル設定
TCPSocket::socks_server = 'proxy.example.com'
TCPSocket::socks_port = 1080
# オプション: 認証 (SOCKS5のユーザー名/パスワード)
# TCPSocket::socks_username = 'your_user'
# TCPSocket::socks_password = 'your_pass'
# これで全てのNet::HTTPリクエストがSOCKS5を通じて行われます
uri = URI('https://httpbin.org/ip')
response = Net::HTTP.get_response(uri)
puts response.body
注意してください: socksifyを使用する場合、設定はプロセス内のすべてのTCP接続にグローバルに適用されます。より柔軟な設定が必要な場合は、同じgemのNet::HTTP.SOCKSProxyを使用してください:
require 'socksify/http'
# SOCKS5プロキシクラスを作成(HTTP用のNet::HTTP::Proxyと同様)
socks_proxy = Net::HTTP.SOCKSProxy('proxy.example.com', 1080)
uri = URI('https://httpbin.org/ip')
socks_proxy.start(uri.host, uri.port, use_ssl: true) do |http|
response = http.get(uri.path)
puts response.body
end
SOCKS5を使用するためにFaradayには、このプロトコルをサポートするアダプタが必要です。良いオプションは、faraday-net_httpをfaraday-typhoeusを使用することです。
💡 プロトコル選択のアドバイス
ほとんどのパースおよびAPI作業にはHTTP/HTTPSプロキシで十分です—設定が簡単で、ネイティブにサポートされています。HTTPプロキシがターゲットサービスによって簡単に検出される場合や、HTTPトラフィックだけでなくプロキシする必要がある場合はSOCKS5を使用してください。モバイルおよびレジデンシャルプロキシは、両方のプロトコルで利用可能です。
結論と推奨事項
Rubyでのプロキシ設定は、複雑なライブラリを必要としません—標準のNet::HTTPはHTTPプロキシを「そのまま」サポートし、Faradayはミドルウェアアーキテクチャのおかげで統合をさらに便利にします。このガイドからの重要なポイントは次のとおりです:
- Net::HTTPには
Net::HTTP::Proxyを使用してください—これは組み込みで信頼できる方法です。 - Faradayのパラメータ
faraday.proxyは、URI文字列または資格情報を持つハッシュを受け取ります。 - プロキシのデータは環境変数またはRailsのcredentialsに保存してください—コードにハードコーディングしないでください。
- プロキシのローテーションとエラー処理を実装して、プロダクションでの信頼性を確保してください。
- ランダムな遅延とUser-Agentのローテーションを追加して、人間の行動を模倣してください。
- SOCKS5には
socksifyを使用してください。
プロキシの種類の選択は、タスクによって異なります。WildberriesやOzon用のパーサーを書く場合は、レジデンシャルプロキシが速度と保護回避の最適なバランスを提供します: 実際の家庭ユーザーのIPは、ブロックリストに載ることが非常に少ないです。高負荷のタスクで、あまり保護されていないソースに大量のリクエストを行う場合は、データセンターのプロキシがより速く、リクエストあたりのコストが低くなります。もしあなたのRubyスクリプトがソーシャルメディアや広告APIで動作する場合は、モバイルプロキシに注目してください: それらのIPアドレスは、ほとんどのプラットフォームで最大の信頼レベルを持っています。
```