Если вы пишете парсер, интегрируете внешний API или автоматизируете сбор данных на Ruby — рано или поздно вы столкнётесь с блокировками по IP. Wildberries, Ozon, Instagram и десятки других сервисов режут запросы с одного адреса уже после нескольких сотен обращений. Решение одно — прокси. В этой статье разберём, как подключить прокси к Ruby-проекту через стандартный Net::HTTP и популярный HTTP-клиент Faraday, настроить ротацию IP и корректно обработать ошибки — как в Rails-приложении, так и в простых скриптах.
Зачем Ruby-разработчику прокси: основные сценарии
Ruby активно используется для задач, где без прокси просто не обойтись. Прежде чем переходить к коду, стоит понять, в каких именно ситуациях они нужны — это поможет выбрать правильный тип и архитектуру решения.
Парсинг и сбор данных. Это самый распространённый сценарий. Вы пишете скрипт на Ruby, который обходит страницы Wildberries, Ozon или Авито, собирает цены и характеристики товаров. Без прокси такой скрипт заблокируют по IP уже через несколько минут. Сайты видят сотни запросов с одного адреса и автоматически включают защиту — капчу, временный бан или полный отказ в обслуживании.
Интеграция с гео-зависимыми API. Некоторые внешние сервисы возвращают разный контент в зависимости от страны запроса. Если ваше Rails-приложение обращается к такому API, прокси позволяет имитировать запросы из нужного региона. Например, проверять выдачу поисковиков для разных стран или получать цены, актуальные для конкретного рынка.
Тестирование и QA. Разработчики используют прокси для тестирования поведения приложения при запросах из разных IP-адресов и стран. Это особенно актуально для сервисов с гео-блокировкой или rate-limiting по IP.
Автоматизация маркетинга. Ruby-скрипты нередко используются для мониторинга рекламных кампаний, проверки позиций в выдаче или отслеживания активности конкурентов. Здесь прокси нужны для распределения нагрузки и обхода ограничений.
Работа с несколькими аккаунтами. Если ваш скрипт управляет несколькими аккаунтами на одной платформе, каждый аккаунт должен работать через отдельный IP — иначе платформа легко обнаружит связь и заблокирует всё разом.
Какой тип прокси выбрать для Ruby-задач
Не все прокси одинаково хорошо подходят для разных задач. Выбор типа напрямую влияет на успешность работы вашего скрипта. Разберём основные варианты применительно к Ruby-разработке.
| Тип прокси | Лучшие задачи в Ruby | Скорость | Обход защит |
|---|---|---|---|
| Прокси дата-центров | Парсинг без строгой защиты, API-запросы, тестирование | Высокая | Средний |
| Резидентные прокси | Парсинг защищённых сайтов, работа с соцсетями, маркетплейсы | Средняя | Высокий |
| Мобильные прокси | Facebook, Instagram, TikTok API, работа с аккаунтами | Средняя | Максимальный |
Прокси дата-центров — самый быстрый и дешёвый вариант. Подходят для парсинга сайтов без агрессивной защиты, массовых API-запросов и нагрузочного тестирования. Однако крупные платформы (Facebook, Google, Cloudflare) легко их распознают по ASN дата-центра.
Резидентные прокси используют IP реальных домашних пользователей. Для Ruby-скриптов, которые парсят Wildberries, Ozon или работают с защищёнными сайтами — это оптимальный выбор. Сайты видят обычного пользователя, а не сервер.
Мобильные прокси работают через 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-объект для нескольких запросов к одному хосту. Обратите внимание: параметры прокси передаются третьим и четвёртым аргументом, а не через отдельный метод.
Аутентификация и HTTPS через Net::HTTP
Большинство коммерческих прокси требуют аутентификации — логина и пароля. Кроме того, при работе с 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'
# Читаем настройки прокси из 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 — один из самых популярных HTTP-клиентов в экосистеме Ruby. Он используется во многих Rails-приложениях благодаря удобному middleware-подходу и поддержке разных адаптеров (Net::HTTP, HTTParty, Typhoeus и другие). Настройка прокси в 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 напрямую, если прокси не требует аутентификации.
Faraday с адаптером Net::HTTP и расширенными настройками:
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
# Middleware для логирования (удобно при отладке)
faraday.response :logger
# Retry при ошибках соединения
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
Middleware :retry особенно полезен при работе с прокси — если один адрес временно недоступен, Faraday автоматически повторит запрос. Это критично для надёжной работы скрипта в продакшне.
Faraday с адаптером Typhoeus (для параллельных запросов):
# 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 с middleware:
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
# Регистрируем middleware
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, фоновых задач парсинга или вебхуков. Рассмотрим несколько практических паттернов.
Паттерн 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. Для хранения данных прокси используйте зашифрованные credentials 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 "Unexpected status #{response.code} for #{product_url}"
end
end
rescue Net::OpenTimeout, Net::ReadTimeout => e
Rails.logger.error "Timeout 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 | Неверный логин/пароль прокси | Проверить credentials |
| 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 authentication failed for #{proxy[0]}"
when 403, 429
rotate_proxy!
raise "IP blocked (#{response.code}), rotating proxy"
else
raise "Unexpected HTTP status: #{response.code}"
end
end
rescue *RETRIABLE_ERRORS => e
Rails.logger.warn "Connection error (attempt #{attempt}/#{retries}): #{e.message}"
rotate_proxy!
retry if attempt < retries
raise "Failed after #{retries} attempts: #{e.message}"
rescue RuntimeError => e
Rails.logger.warn "HTTP error (attempt #{attempt}/#{retries}): #{e.message}"
retry if attempt < retries
raise
end
end
private
def current_proxy
@proxies.first || raise("No available proxies!")
end
def rotate_proxy!
failed = @proxies.shift
@failed_proxies << failed
Rails.logger.info "Rotated proxy. Remaining: #{@proxies.length}"
end
end
SOCKS5 прокси в Ruby: настройка через socksify
Стандартный Net::HTTP не поддерживает SOCKS5 нативно — только HTTP/HTTPS прокси. Для работы с SOCKS5 нужна дополнительная библиотека. Самый популярный вариант — гем socksify.
Почему 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 username/password)
# 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 из того же гема:
require 'socksify/http'
# Создаём SOCKS5 прокси-класс (аналогично Net::HTTP::Proxy для HTTP)
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-based адаптера faraday-typhoeus, который нативно поддерживает SOCKS5 через libcurl.
💡 Совет по выбору протокола
Для большинства задач парсинга и работы с API достаточно HTTP/HTTPS прокси — они проще в настройке и поддерживаются нативно. SOCKS5 стоит использовать, когда вам нужно проксировать не только HTTP-трафик, или когда HTTP-прокси легко детектируются целевым сервисом. Мобильные и резидентные прокси доступны в обоих протоколах.
Заключение и рекомендации
Настройка прокси в Ruby не требует сложных библиотек — стандартный Net::HTTP поддерживает HTTP-прокси «из коробки», а Faraday делает интеграцию ещё удобнее благодаря middleware-архитектуре. Ключевые выводы из этого руководства:
- Для Net::HTTP используйте
Net::HTTP::Proxy— это встроенный и надёжный способ. - Для Faraday параметр
faraday.proxyпринимает URI строку или хэш с credentials. - Храните данные прокси в переменных окружения или Rails credentials — не хардкодьте в коде.
- Реализуйте ротацию прокси и обработку ошибок для надёжной работы в продакшне.
- Добавляйте случайные задержки и ротацию User-Agent для имитации человеческого поведения.
- Для SOCKS5 используйте гем
socksify.
Выбор типа прокси зависит от задачи. Если вы пишете парсер для Wildberries или Ozon — резидентные прокси дадут наилучший баланс между скоростью и обходом защит: реальные IP домашних пользователей крайне редко попадают в блоклисты. Для высоконагруженных задач с большим объёмом запросов к менее защищённым источникам подойдут прокси дата-центров — они быстрее и дешевле в расчёте на запрос. Если же ваш Ruby-скрипт работает с социальными сетями или рекламными API — обратите внимание на мобильные прокси: их IP-адреса имеют максимальный уровень доверия на большинстве платформ.