返回博客

在Ruby中通过Net::HTTP和Faraday设置代理:Rails和独立脚本的完整指南

如何在Ruby项目中通过Net::HTTP和Faraday正确设置代理 - 包括Rails和独立脚本的示例、IP轮换和绕过封锁。

📅2026年8月13日
```html

如果您正在编写解析器,集成外部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 接受一个包含 uriuserpassword 的哈希。如果代理不需要身份验证,也可以直接传递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_httpsocksify 结合使用,或者使用基于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,使用 socksify gem。

代理类型的选择取决于任务。如果您正在为Wildberries或Ozon编写解析器 - 住宅代理 将提供速度和绕过保护之间的最佳平衡:真实家庭用户的IP极少进入黑名单。对于高负载任务,针对较少保护的来源进行大量请求,数据中心代理 是更快和更便宜的选择。如果您的Ruby脚本与社交网络或广告API合作,请注意 移动代理:它们的IP地址在大多数平台上具有最高的信任级别。

```