在 Ruby 中,Net::HTTP 和 Mechanize 允许在创建对象或建立连接时直接配置代理主机、端口和认证凭据,从而方便地使用代理。这些库使 Ruby 应用能够将网络请求通过中间代理服务器转发,支持 IP 轮换、地理定位以及绕过频率限制等使用场景。
Net::HTTP 代理配置
Net::HTTP 是 Ruby 用于发起 HTTP 请求的标准库,可直接控制包括代理设置在内的连接参数。
直接传入代理参数
要为 Net::HTTP 请求配置代理,请在创建 Net::HTTP 对象或启动连接时指定代理主机、端口、用户名和密码。
require 'net/http'
require 'uri'
# 代理信息
proxy_host = 'your_proxy_host.com'
proxy_port = 8080
proxy_user = 'proxy_username'
proxy_pass = 'proxy_password'
# 目标 URL
uri = URI('http://example.com/data')
# 方法 1:在 Net::HTTP.new 中指定代理参数
http = Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port, proxy_user, proxy_pass)
http.use_ssl = (uri.scheme == 'https') # HTTPS 必需
request = Net::HTTP::Get.new(uri.request_uri)
begin
response = http.request(request)
puts "Method 1 Response Status: #{response.code}"
# puts response.body
rescue Net::HTTPClientException => e
puts "HTTP Error: #{e.message}"
rescue Net::ReadTimeout, Net::OpenTimeout => e
puts "Timeout Error: #{e.message}"
rescue StandardError => e
puts "An error occurred: #{e.message}"
ensure
http.finish if http.started? # 确保连接被关闭
end
# 方法 2:在 Net::HTTP.start 块中指定代理参数
uri_https = URI('https://httpbin.org/ip') # 用于检查 IP 的简单端点
Net::HTTP.start(uri_https.host, uri_https.port,
proxy_host, proxy_port, proxy_user, proxy_pass,
use_ssl: uri_https.scheme == 'https') do |http_with_proxy|
request_https = Net::HTTP::Get.new(uri_https.request_uri)
response_https = http_with_proxy.request(request_https)
puts "Method 2 Response Status: #{response_https.code}"
puts "External IP via proxy: #{response_https.body}" # 应显示代理的 IP
rescue Net::HTTPClientException => e
puts "HTTP Error: #{e.message}"
rescue Net::ReadTimeout, Net::OpenTimeout => e
puts "Timeout Error: #{e.message}"
rescue StandardError => e
puts "An error occurred: #{e.message}"
end
环境变量
Net::HTTP 可以自动从环境变量中读取代理设置。这种方式适合在不修改代码的情况下进行系统级或应用级的代理配置。
http_proxy:用于 HTTP 请求(例如http://user:[email protected]:8080)https_proxy:用于 HTTPS 请求(例如https://user:[email protected]:8080)no_proxy:以逗号分隔的主机名列表,这些主机将绕过代理。
require 'net/http'
require 'uri'
# 设置环境变量(示例,通常在脚本之外完成)
# ENV['http_proxy'] = 'http://proxy_username:proxy_password@your_proxy_host.com:8080'
# ENV['https_proxy'] = 'http://proxy_username:proxy_password@your_proxy_host.com:8080' # 注意:https_proxy 也可以是 http 代理
# ENV['no_proxy'] = 'localhost,127.0.0.1'
uri = URI('http://example.com/data')
http = Net::HTTP.new(uri.host, uri.port) # 未显式传入代理参数
request = Net::HTTP::Get.new(uri.request_uri)
# 若设置了 http_proxy/https_proxy,Net::HTTP 会自动使用它们。
begin
response = http.request(request)
puts "Env Var Response Status: #{response.code}"
rescue StandardError => e
puts "An error occurred: #{e.message}"
end
# 如果是通过代码设置的,用完后请清除环境变量
# ENV.delete('http_proxy')
# ENV.delete('https_proxy')
Mechanize 代理配置
Mechanize 是一个 Ruby gem,通过模拟浏览器简化网页抓取。它构建在 Net::HTTP 之上,并提供了更高层的代理处理 API。
直接传入代理参数
Mechanize 支持在初始化 agent 时设置代理信息,也可以调用 set_proxy 方法设置。
require 'mechanize'
# 代理信息
proxy_host = 'your_proxy_host.com'
proxy_port = 8080
proxy_user = 'proxy_username'
proxy_pass = 'proxy_password'
# 方法 1:使用代理参数初始化 Mechanize
agent = Mechanize.new do |a|
a.set_proxy(proxy_host, proxy_port, proxy_user, proxy_pass)
a.user_agent_alias = 'Mac Safari' # 网页抓取推荐设置
end
begin
page = agent.get('http://example.com/')
puts "Method 1 Mechanize Page Title: #{page.title}"
rescue Mechanize::ResponseCodeError => e
puts "Mechanize HTTP Error: #{e.response_code} - #{e.page.uri}"
rescue Mechanize::Error => e
puts "Mechanize Error: #{e.message}"
rescue StandardError => e
puts "An error occurred: #{e.message}"
end
# 方法 2:把代理参数直接传给 Mechanize.new(一次性使用更简单)
agent_direct = Mechanize.new(proxy_addr: proxy_host,
proxy_port: proxy_port,
proxy_user: proxy_user,
proxy_pass: proxy_pass)
agent_direct.user_agent_alias = 'Linux Firefox'
begin
page_direct = agent_direct.get('https://httpbin.org/ip')
puts "Method 2 Mechanize External IP via proxy: #{page_direct.body}"
rescue Mechanize::ResponseCodeError => e
puts "Mechanize HTTP Error: #{e.response_code} - #{e.page.uri}"
rescue Mechanize::Error => e
puts "Mechanize Error: #{e.message}"
rescue StandardError => e
puts "An error occurred: #{e.message}"
end
使用 Mechanize 进行代理轮换
在需要频繁更换 IP 的场景中,例如大规模数据采集,代理轮换必不可少。Mechanize 的 set_proxy 方法可以多次调用,从而在 agent 的生命周期内切换代理。
require 'mechanize'
proxies = [
{ host: 'proxy1.example.com', port: 8080, user: 'user1', pass: 'pass1' },
{ host: 'proxy2.example.com', port: 8080, user: 'user2', pass: 'pass2' },
# ... 更多代理
]
agent = Mechanize.new
agent.user_agent_alias = 'Windows Chrome'
proxies.each_with_index do |p, i|
puts "Using proxy #{i+1}: #{p[:host]}"
agent.set_proxy(p[:host], p[:port], p[:user], p[:pass])
begin
page = agent.get('https://httpbin.org/ip')
puts "Current IP: #{page.body.strip}"
sleep(2) # 暂停,避免对目标站点造成过大压力
rescue Mechanize::ResponseCodeError => e
puts "Error with proxy #{p[:host]}: #{e.response_code}"
rescue Mechanize::Error, StandardError => e
puts "Connection error with proxy #{p[:host]}: #{e.message}"
end
end
对比:代理场景下的 Net::HTTP 与 Mechanize
| 项目 | Net::HTTP | Mechanize |
|---|---|---|
| 抽象层级 | 低层 HTTP 客户端,直接控制 socket。 | 高层网页抓取库,模拟浏览器。 |
| 代理配置 | 构造函数参数或 Net::HTTP.start。 |
set_proxy 方法或 Mechanize.new 选项。 |
| 易用性 | 复杂任务下代码较冗长。 | 站点导航、表单提交更简单。 |
| 自动化功能 | 除基础 HTTP 外没有。 | Cookie 处理、重定向、JavaScript 解析(有限)、user-agent 管理。 |
| 错误处理 | Net::HTTP 异常(如 Net::OpenTimeout)。 |
Mechanize::Error、Mechanize::ResponseCodeError。 |
| 最适合 | 简单 API 调用、特定的 HTTP/S 请求。 | 网页抓取、浏览器自动化、复杂导航。 |
| 依赖 | 标准库。 | Gem(mechanize)。 |
代理类型与限制
Net::HTTP(以及基于它的 Mechanize)主要支持 HTTP 和 HTTPS 类型的代理,这类代理转发 HTTP/HTTPS 请求。
- HTTP 代理:用于未加密的 HTTP 流量。
- HTTPS 代理(CONNECT):用于加密的 HTTPS 流量。
Net::HTTP会通过代理向目标主机建立CONNECT隧道。 - SOCKS 代理:
Net::HTTP原生不支持 SOCKS 代理(SOCKS4、SOCKS5)。要在 Ruby 中使用 SOCKS 代理,需要socks-ruby这类外部 gem。该 gem 通过重写 socket 创建过程与Net::HTTP集成。
# 使用 socks-ruby 的 SOCKS 代理示例(需要 'socks-ruby' gem)
# gem install socks-ruby
require 'net/http'
require 'socks-ruby'
require 'uri'
socks_proxy_host = 'your_socks_proxy.com'
socks_proxy_port = 1080
socks_proxy_user = 'socks_user'
socks_proxy_pass = 'socks_pass'
uri = URI('https://httpbin.org/ip')
# 重写 Net::HTTP 的 socket 创建过程
Net::HTTP.class_eval do
def connect
if proxy_address
# 已有的 HTTP/HTTPS 代理逻辑
super
elsif ENV['SOCKS_PROXY'] # 用于 SOCKS 的自定义环境变量
socks_uri = URI(ENV['SOCKS_PROXY'])
socks_socket = Socks::HTTP.new(socks_uri.host, socks_uri.port,
socks_uri.user, socks_uri.password)
@socket = socks_socket.connect(@address, @port)
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
if use_ssl?
@socket = Net::HTTP::SSL_SOCKET_CLASS.new(@socket, read_timeout: @read_timeout)
@socket.sync_close = true
@socket.connect
end
else
super
end
end
end
# 为 SOCKS 代理设置自定义环境变量
ENV['SOCKS_PROXY'] = "socks5://#{socks_proxy_user}:#{socks_proxy_pass}@#{socks_proxy_host}:#{socks_proxy_port}"
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true # HTTPS 必需
http.verify_mode = OpenSSL::SSL::VERIFY_PEER # 出于安全考虑推荐
request = Net::HTTP::Get.new(uri.request_uri)
begin
response = http.request(request)
puts "SOCKS Proxy Response Status: #{response.code}"
puts "External IP via SOCKS proxy: #{response.body}"
rescue StandardError => e
puts "SOCKS Proxy Error: #{e.message}"
end
ENV.delete('SOCKS_PROXY') # 清理
错误处理与超时
使用代理时,网络问题、代理配置错误或代理服务器故障都很常见,因此健壮的错误处理至关重要。
常见错误
Net::OpenTimeout:与代理或目标服务器的连接在建立之前超时。Net::ReadTimeout:服务器(代理或目标)未在指定的超时时间内返回数据。Errno::ECONNREFUSED:代理服务器主动拒绝了连接。Net::HTTPBadResponse:代理服务器或目标返回了无效的 HTTP 响应。Mechanize::ResponseCodeError:Mechanize 特有的错误,当目标服务器返回非 2xx 的 HTTP 状态码时触发(例如 403 Forbidden、404 Not Found、500 Internal Server Error)。
超时配置
设置合适的超时可以避免脚本无限期挂起。
require 'net/http'
require 'uri'
uri = URI('http://slow-api.example.com')
proxy_host = 'your_proxy_host.com'
proxy_port = 8080
http = Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port)
http.open_timeout = 5 # 建立连接的超时时间(秒)
http.read_timeout = 10 # 从连接读取数据的超时时间(秒)
http.use_ssl = (uri.scheme == 'https')
request = Net::HTTP::Get.new(uri.request_uri)
begin
response = http.request(request)
puts "Response Status: #{response.code}"
rescue Net::OpenTimeout
puts "Connection to proxy or target timed out."
rescue Net::ReadTimeout
puts "Reading data from proxy or target timed out."
rescue Errno::ECONNREFUSED
puts "Connection refused by proxy or target server."
rescue StandardError => e
puts "An unexpected error occurred: #{e.message}"
end
# Mechanize 同样支持超时设置
# agent = Mechanize.new
# agent.set_proxy(proxy_host, proxy_port)
# agent.open_timeout = 5
# agent.read_timeout = 10
最佳实践
- User-Agent 字符串:抓取时务必设置真实的
User-Agent请求头。许多网站会拦截没有该请求头或使用通用值的请求。Mechanize 的user_agent_alias让这一步更简单。 - Referer 请求头:在部分站点上,需要有效的
Referer请求头来模拟正常浏览器行为。 - Cookie 管理:Mechanize 会自动处理 cookie,这对通过代理维持会话非常关键。使用
Net::HTTP时则需要手动管理 cookie。 - 错误处理与重试:针对临时性网络错误或代理问题,实现带指数退避的重试逻辑。
- 代理健康检查:使用某个代理前,可先对已知端点(例如
https://api.ipify.org)做一次快速检查,确认其可用性和 IP 地址。 - 资源管理:确保
Net::HTTP连接被正确关闭,尤其是在不使用start块时,以免资源泄漏。Mechanize 会在内部管理连接。
