如果您正在编写解析器,集成外部API或在Ruby中自动化数据收集 - 迟早您会遇到IP封锁。Wildberries、Ozon、Instagram和其他数十个服务在几百次请求后就会切断来自同一地址的请求。解决方案只有一个 - 代理。在本文中,我们将讨论如何通过标准的 Net::HTTP 和流行的HTTP客户端Faraday将代理连接到Ruby项目,设置IP轮换并正确处理错误 - 无论是在Rails应用程序中还是在简单的脚本中。
为什么Ruby开发者需要代理:主要场景
Ruby被广泛用于需要代理的任务。在进入代码之前,了解在什么情况下需要代理是很重要的 - 这将有助于选择正确的类型和解决方案架构。
解析和数据收集。 这是最常见的场景。您编写一个Ruby脚本,遍历Wildberries、Ozon或Avito的页面,收集商品的价格和特性。没有代理,这样的脚本在几分钟内就会被封锁。网站会看到来自同一地址的数百个请求,并自动启用保护 - 验证码、临时封禁或完全拒绝服务。
与地理相关的API集成。 一些外部服务根据请求的国家返回不同的内容。如果您的Rails应用程序调用这样的API,代理可以模拟来自所需地区的请求。例如,检查不同国家的搜索引擎结果或获取特定市场的最新价格。
测试和质量保证。 开发人员使用代理测试应用程序在来自不同IP地址和国家的请求下的行为。这对于具有地理封锁或基于IP的速率限制的服务尤其重要。
营销自动化。 Ruby脚本经常用于监控广告活动、检查搜索结果中的位置或跟踪竞争对手的活动。在这里,代理用于分配负载和绕过限制。
多个账户的操作。 如果您的脚本管理一个平台上的多个账户,每个账户必须通过单独的IP工作 - 否则平台很容易发现关联并同时封锁所有账户。
为Ruby任务选择哪种类型的代理
并非所有代理都适合不同的任务。选择代理类型直接影响您脚本的成功率。我们将讨论与Ruby开发相关的主要选项。
| 代理类型 | 在Ruby中的最佳任务 | 速度 | 绕过保护 |
|---|---|---|---|
| 数据中心代理 | 没有严格保护的解析、API请求、测试 | 高 | 中等 |
| 住宅代理 | 解析受保护的网站、社交媒体、市场 | 中等 | 高 |
| 移动代理 | Facebook、Instagram、TikTok API、账户操作 | 中等 | 最大 |
数据中心代理 是最快和最便宜的选择。适用于没有激进保护的网站解析、大规模API请求和压力测试。然而,大型平台(Facebook、Google、Cloudflare)很容易通过数据中心的ASN识别它们。
住宅代理 使用真实家庭用户的IP。对于解析Wildberries、Ozon或处理受保护网站的Ruby脚本,这是最佳选择。网站看到的是普通用户,而不是服务器。
移动代理 通过移动运营商的IP(4G/5G)工作。这是从平台的角度来看最可信的地址 - 一个移动IP可以被数百个真实用户同时使用,因此很少会被封锁。如果您的Ruby脚本与社交网络或广告平台合作,移动代理将把封锁风险降到最低。
Net::HTTP中的代理:基本设置
Net::HTTP 是Ruby的标准库,用于HTTP请求,包含在语言的标准发行版中。它通过特殊方法 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 接受四个参数:代理主机、端口、用户名和密码。前两个是必需的,后两个是可选的,适用于无身份验证的代理。
另一种方法是将代理参数直接传递给 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对象进行多个请求时很方便。请注意:代理参数作为第三和第四个参数传递,而不是通过单独的方法。
通过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 "状态: #{response.code}"
end
重要的一点:使用HTTPS代理(通过CONNECT进行隧道)时,Net::HTTP 会自动通过代理隧道与目标服务器建立SSL连接。这对于大多数代理服务器来说是正确的。
通过环境变量进行设置。 一个好的做法是不要在代码中硬编码代理数据,而是从环境变量中读取它们:
require 'net/http'
require 'uri'
# 从ENV读取代理设置
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 "状态: #{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} -> 状态: #{response.code}, 代理: #{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凭证进行设置。 使用加密的Rails凭证存储代理数据:
# 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} 对于 #{product_url}"
end
end
rescue Net::OpenTimeout, Net::ReadTimeout => e
Rails.logger.error "超时对于 #{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协议在更低的层次上工作 - 它代理任何TCP流量,而不仅仅是HTTP。此外,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连接。如果您需要更灵活的设置 - 请使用 Net::HTTP.SOCKSProxy,来自同一gem:
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
对于Faraday使用SOCKS5,您需要一个支持该协议的适配器。一个不错的选择是将 faraday-net_http 与 socksify 结合使用,或者使用基于curl的适配器 faraday-typhoeus,该适配器通过libcurl原生支持SOCKS5。
💡 协议选择建议
对于大多数解析和API工作,HTTP/HTTPS代理就足够了 - 它们更易于设置并且原生支持。SOCKS5应在您需要代理的不仅仅是HTTP流量,或者当HTTP代理容易被目标服务检测到时使用。移动和住宅代理在这两种协议中均可用。
结论和建议
在Ruby中设置代理不需要复杂的库 - 标准的 Net::HTTP 原生支持HTTP代理,而Faraday通过中间件架构使集成更加方便。本指南的关键结论:
- 对于 Net::HTTP,使用
Net::HTTP::Proxy- 这是内置且可靠的方法。 - 对于 Faraday,参数
faraday.proxy接受URI字符串或包含凭证的哈希。 - 将代理数据存储在 环境变量 或Rails凭证中 - 不要在代码中硬编码。
- 实现 代理轮换 和错误处理,以确保在生产环境中的可靠运行。
- 添加 随机延迟 和User-Agent轮换,以模拟人类行为。
- 对于SOCKS5,使用
socksifygem。
代理类型的选择取决于任务。如果您正在为Wildberries或Ozon编写解析器 - 住宅代理 将提供速度和绕过保护之间的最佳平衡:真实家庭用户的IP极少进入黑名单。对于高负载任务,针对较少保护的来源进行大量请求,数据中心代理 是更快和更便宜的选择。如果您的Ruby脚本与社交网络或广告API合作,请注意 移动代理:它们的IP地址在大多数平台上具有最高的信任级别。
```