Si estás escribiendo un parser, integrando una API externa o automatizando la recolección de datos en Ruby, tarde o temprano te encontrarás con bloqueos por IP. Wildberries, Ozon, Instagram y decenas de otros servicios limitan las solicitudes desde una sola dirección después de unas pocas centenas de accesos. La solución es una — proxies. En este artículo, analizaremos cómo conectar un proxy a un proyecto Ruby a través de la biblioteca estándar Net::HTTP y el popular cliente HTTP Faraday, configurar la rotación de IP y manejar errores correctamente — tanto en una aplicación Rails como en scripts simples.
¿Por qué necesita un proxy un desarrollador de Ruby: escenarios principales?
Ruby se utiliza activamente para tareas donde no se puede prescindir de proxies. Antes de pasar al código, es importante entender en qué situaciones son necesarios — esto ayudará a elegir el tipo y la arquitectura de la solución correcta.
Parsing y recolección de datos. Este es el escenario más común. Estás escribiendo un script en Ruby que navega por las páginas de Wildberries, Ozon o Avito, recolectando precios y características de productos. Sin un proxy, dicho script será bloqueado por IP en cuestión de minutos. Los sitios ven cientos de solicitudes desde una sola dirección y activan automáticamente la protección — captcha, baneo temporal o un rechazo total del servicio.
Integración con APIs geográficamente dependientes. Algunos servicios externos devuelven contenido diferente dependiendo del país de la solicitud. Si tu aplicación Rails se conecta a tal API, un proxy permite simular solicitudes desde la región deseada. Por ejemplo, verificar los resultados de los motores de búsqueda para diferentes países o recibir precios relevantes para un mercado específico.
Pruebas y QA. Los desarrolladores utilizan proxies para probar el comportamiento de la aplicación con solicitudes desde diferentes direcciones IP y países. Esto es especialmente relevante para servicios con geobloqueo o limitación de tasa por IP.
Automatización del marketing. Los scripts de Ruby se utilizan a menudo para monitorear campañas publicitarias, verificar posiciones en los resultados o rastrear la actividad de competidores. Aquí, los proxies son necesarios para distribuir la carga y eludir restricciones.
Trabajo con múltiples cuentas. Si tu script gestiona varias cuentas en una misma plataforma, cada cuenta debe operar a través de una IP separada — de lo contrario, la plataforma puede detectar fácilmente la conexión y bloquear todo de una vez.
Qué tipo de proxy elegir para tareas en Ruby
No todos los proxies son igualmente adecuados para diferentes tareas. La elección del tipo influye directamente en el éxito de tu script. Analicemos las opciones principales en relación con el desarrollo en Ruby.
| Tipo de proxy | Mejores tareas en Ruby | Velocidad | Elusión de protecciones |
|---|---|---|---|
| Proxies de centros de datos | Parsing sin protección estricta, solicitudes API, pruebas | Alta | Media |
| Proxies residenciales | Parsing de sitios protegidos, trabajo con redes sociales, marketplaces | Media | Alta |
| Proxies móviles | Facebook, Instagram, TikTok API, trabajo con cuentas | Media | Máxima |
Proxies de centros de datos — la opción más rápida y económica. Son adecuados para el parsing de sitios sin protección agresiva, solicitudes masivas de API y pruebas de carga. Sin embargo, plataformas grandes (Facebook, Google, Cloudflare) pueden reconocerlas fácilmente por el ASN del centro de datos.
Proxies residenciales utilizan IP de usuarios domésticos reales. Para scripts de Ruby que parsean Wildberries, Ozon o trabajan con sitios protegidos, esta es la opción óptima. Los sitios ven a un usuario normal, no a un servidor.
Proxies móviles funcionan a través de IP de operadores móviles (4G/5G). Estas son las direcciones más confiables desde el punto de vista de las plataformas — una IP móvil puede ser utilizada por cientos de usuarios reales al mismo tiempo, por lo que los bloqueos son extremadamente raros. Si tu script de Ruby trabaja con redes sociales o plataformas publicitarias, los proxies móviles reducirán el riesgo de bloqueos al mínimo.
Proxy en Net::HTTP: configuración básica
Net::HTTP — la biblioteca estándar de Ruby para solicitudes HTTP, incluida en la distribución estándar del lenguaje. Soporta proxies "out of the box" a través de un método especial Net::HTTP::Proxy.
La forma más sencilla es crear una clase de proxy y usarla en lugar de Net::HTTP:
require 'net/http'
require 'uri'
# Datos del proxy
proxy_host = 'proxy.example.com'
proxy_port = 8080
# Creamos la clase de proxy
proxy_class = Net::HTTP::Proxy(proxy_host, proxy_port)
# Ejecutamos la solicitud a través del proxy
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
El método Net::HTTP::Proxy acepta cuatro parámetros: el host del proxy, el puerto, el nombre de usuario y la contraseña. Los primeros dos son obligatorios, los últimos dos son opcionales para proxies sin autenticación.
Otra forma alternativa es pasar los parámetros del proxy directamente al método 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 si no hay autenticación)
nil # proxy_pass
)
http.use_ssl = true
response = http.get(uri.path)
puts response.body
Este enfoque es conveniente cuando necesitas reutilizar el mismo objeto HTTP para múltiples solicitudes a un mismo host. Ten en cuenta que los parámetros del proxy se pasan como el tercer y cuarto argumento, no a través de un método separado.
Autenticación y HTTPS a través de Net::HTTP
La mayoría de los proxies comerciales requieren autenticación — nombre de usuario y contraseña. Además, al trabajar con sitios HTTPS, se necesita una configuración adicional de SSL. Analicemos ambos casos.
Proxy con nombre de usuario y contraseña:
require 'net/http'
require 'uri'
proxy_host = 'proxy.example.com'
proxy_port = 8080
proxy_user = 'your_username'
proxy_pass = 'your_password'
# Creamos la clase de proxy con autenticación
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|
# Desactivamos la verificación de SSL (¡solo para pruebas!)
# 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
Un punto importante: al usar proxies HTTPS (túnel a través de CONNECT) Net::HTTP establece automáticamente una conexión SSL con el servidor de destino a través del túnel proxy. Esto funciona correctamente para la mayoría de los servidores proxy.
Configuración a través de variables de entorno. Una buena práctica es no hardcodear los datos del proxy en el código, sino leerlos de las variables de entorno:
require 'net/http'
require 'uri'
# Leemos la configuración del proxy desde 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
La variable de entorno se puede establecer en el formato HTTP_PROXY=http://user:[email protected]:8080. Este enfoque permite cambiar el proxy sin modificar el código — solo es necesario actualizar la variable en el archivo .env o en la configuración del servidor.
Conexión de proxy a través de Faraday
Faraday es uno de los clientes HTTP más populares en el ecosistema Ruby. Se utiliza en muchas aplicaciones Rails gracias a su enfoque conveniente de middleware y soporte para diferentes adaptadores (Net::HTTP, HTTParty, Typhoeus y otros). La configuración de proxies en Faraday es un poco diferente dependiendo del adaptador utilizado.
Configuración básica de proxy en Faraday:
require 'faraday'
# Creamos una conexión Faraday con proxy
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}"
El parámetro faraday.proxy acepta un hash con las claves uri, user y password. También se puede pasar una cadena URI directamente, si el proxy no requiere autenticación.
Faraday con adaptador Net::HTTP y configuraciones avanzadas:
require 'faraday'
conn = Faraday.new(url: 'https://httpbin.org') do |faraday|
faraday.proxy = 'http://user:[email protected]:8080'
# Configuración de timeouts
faraday.options.timeout = 30
faraday.options.open_timeout = 10
# Middleware para logging (útil para depuración)
faraday.response :logger
# Retry en caso de errores de conexión
faraday.request :retry, max: 3, interval: 1
# Adaptador
faraday.adapter :net_http
end
begin
response = conn.get('/ip')
puts response.body
rescue Faraday::ConnectionFailed => e
puts "Error de conexión: #{e.message}"
rescue Faraday::TimeoutError => e
puts "Timeout: #{e.message}"
end
El middleware :retry es especialmente útil al trabajar con proxies — si una dirección está temporalmente no disponible, Faraday repetirá automáticamente la solicitud. Esto es crítico para el funcionamiento confiable del script en producción.
Faraday con adaptador Typhoeus (para solicitudes paralelas):
# 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
# Solicitudes paralelas a través de proxy
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 }
Rotación de proxies: cambio automático de IP
Una sola dirección de proxy, incluso residencial, tarde o temprano caerá bajo restricciones durante un parsing intensivo. La solución es la rotación: cambio automático de IP después de cada solicitud o a intervalos determinados. Implementemos un simple rotador para scripts de Ruby.
Un simple rotador basado en un array de proxies:
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
# Lista de proxies en formato [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)
# Parseamos una lista de URL a través de diferentes proxies
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 "Error con el proxy #{proxy[0]}: #{e.message}"
# Pasamos automáticamente al siguiente proxy
end
sleep(rand(0.5..2.0)) # Retraso aleatorio entre solicitudes
end
El retraso aleatorio entre solicitudes (sleep(rand(0.5..2.0))) es un elemento importante. El patrón de solicitudes con intervalos fijos es fácilmente detectado por sistemas anti-bot. Los retrasos aleatorios imitan el comportamiento de un usuario real.
Rotación a través de Faraday con 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)
# Elegimos un proxy aleatorio para cada solicitud
proxy_uri = URI(PROXIES.sample)
env[:request][:proxy] = {
uri: proxy_uri,
user: proxy_uri.user,
password: proxy_uri.password
}
@app.call(env)
end
end
# Registramos el 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
Integración de proxies en una aplicación Rails
En una aplicación Rails, los proxies son más necesarios para solicitudes HTTP externas: integración con APIs, tareas en segundo plano de parsing o webhooks. Analicemos algunos patrones prácticos.
Patrón 1: Objeto de servicio con Faraday. Creamos una clase de servicio básica que realiza todas las solicitudes HTTP externas a través de un proxy:
# app/services/http_client.rb
class HttpClient
def self.connection(base_url)
Faraday.new(url: base_url) do |faraday|
# Proxies desde variables de entorno de 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
# Uso en un controlador o servicio:
# client = HttpClient.connection('https://api.example.com')
# response = client.get('/products', { category: 'electronics' })
Patrón 2: Configuración a través de credenciales de Rails. Para almacenar datos del proxy, utiliza credenciales de Rails encriptadas:
# config/credentials.yml.enc (editado a través de 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
Patrón 3: Tareas en segundo plano con Sidekiq. Si el parsing o las solicitudes API se realizan en trabajadores en segundo plano, la configuración del proxy permanece igual, pero es importante considerar la concurrencia:
# app/workers/price_parser_worker.rb
class PriceParserWorker
include Sidekiq::Worker
sidekiq_options retry: 3, queue: :parsers
def perform(product_url)
# Creamos una nueva conexión para cada trabajador
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 "Estado inesperado #{response.code} para #{product_url}"
end
end
rescue Net::OpenTimeout, Net::ReadTimeout => e
Rails.logger.error "Timeout para #{product_url}: #{e.message}"
raise # Sidekiq repetirá la tarea
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)
# Lógica de parsing...
end
end
Manejo de errores y timeouts
Trabajar a través de proxies añade una capa adicional que puede generar errores específicos. Un manejo adecuado de excepciones es la diferencia entre un script que se cae en 5 minutos y uno que funciona durante horas sin intervención.
Tipos principales de errores al trabajar con proxies en Ruby:
| Excepción | Causa | Qué hacer |
|---|---|---|
Net::OpenTimeout |
El proxy no responde | Cambiar proxy, repetir |
Net::ReadTimeout |
Proxy lento o el sitio no responde | Aumentar timeout o cambiar proxy |
Errno::ECONNREFUSED |
El servidor proxy rechazó la conexión | Verificar datos del proxy |
OpenSSL::SSL::SSLError |
Problema con SSL a través del proxy | Verificar configuración de SSL |
| HTTP 407 | Nombre de usuario/contraseña incorrectos del proxy | Verificar credenciales |
| HTTP 403/429 | IP bloqueada por el sitio | Cambiar proxy, añadir pausa |
Manejo integral de errores con cambio automático de proxy:
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 "La autenticación del proxy falló para #{proxy[0]}"
when 403, 429
rotate_proxy!
raise "IP bloqueada (#{response.code}), rotando proxy"
else
raise "Estado HTTP inesperado: #{response.code}"
end
end
rescue *RETRIABLE_ERRORS => e
Rails.logger.warn "Error de conexión (intento #{attempt}/#{retries}): #{e.message}"
rotate_proxy!
retry if attempt < retries
raise "Falló después de #{retries} intentos: #{e.message}"
rescue RuntimeError => e
Rails.logger.warn "Error HTTP (intento #{attempt}/#{retries}): #{e.message}"
retry if attempt < retries
raise
end
end
private
def current_proxy
@proxies.first || raise("¡No hay proxies disponibles!")
end
def rotate_proxy!
failed = @proxies.shift
@failed_proxies << failed
Rails.logger.info "Proxy rotado. Restantes: #{@proxies.length}"
end
end
Proxy SOCKS5 en Ruby: configuración a través de socksify
La Net::HTTP estándar no soporta SOCKS5 de forma nativa — solo proxies HTTP/HTTPS. Para trabajar con SOCKS5 se necesita una biblioteca adicional. La opción más popular es la gema socksify.
¿Por qué SOCKS5 puede ser preferible a un proxy HTTP? El protocolo SOCKS5 opera a un nivel más bajo — proxy cualquier tráfico TCP, no solo HTTP. Además, SOCKS5 soporta autenticación y transmite menos metadatos sobre la solicitud, lo que lo hace menos detectable para los sistemas de detección de bots.
# Gemfile
# gem 'socksify'
require 'socksify'
require 'socksify/http'
require 'net/http'
require 'uri'
# Configuración global de proxy SOCKS5
TCPSocket::socks_server = 'proxy.example.com'
TCPSocket::socks_port = 1080
# Opcional: autenticación (SOCKS5 username/password)
# TCPSocket::socks_username = 'your_user'
# TCPSocket::socks_password = 'your_pass'
# Ahora todas las solicitudes Net::HTTP van a través de SOCKS5
uri = URI('https://httpbin.org/ip')
response = Net::HTTP.get_response(uri)
puts response.body
Ten en cuenta: al usar socksify, la configuración se aplica globalmente a todas las conexiones TCP en el proceso. Si necesitas una configuración más flexible, utiliza Net::HTTP.SOCKSProxy de la misma gema:
require 'socksify/http'
# Creamos una clase de proxy SOCKS5 (similar a Net::HTTP::Proxy para 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
Para Faraday con SOCKS5 se necesitará un adaptador que soporte este protocolo. Una buena opción es faraday-net_http en combinación con socksify, o el uso de un adaptador basado en curl faraday-typhoeus, que soporta nativamente SOCKS5 a través de libcurl.
💡 Consejo sobre la elección del protocolo
Para la mayoría de las tareas de parsing y trabajo con APIs, los proxies HTTP/HTTPS son suficientes — son más fáciles de configurar y se soportan de forma nativa. SOCKS5 debe utilizarse cuando necesitas proxy no solo tráfico HTTP, o cuando los proxies HTTP son fácilmente detectados por el servicio objetivo. Los proxies móviles y residenciales están disponibles en ambos protocolos.
Conclusión y recomendaciones
Configurar proxies en Ruby no requiere bibliotecas complejas — la estándar Net::HTTP soporta proxies HTTP "out of the box", y Faraday hace la integración aún más conveniente gracias a su arquitectura de middleware. Las conclusiones clave de esta guía son:
- Para Net::HTTP, utiliza
Net::HTTP::Proxy— es un método incorporado y confiable. - Para Faraday, el parámetro
faraday.proxyacepta una cadena URI o un hash con credenciales. - Almacena los datos del proxy en variables de entorno o credenciales de Rails — no hardcodees en el código.
- Implementa rotación de proxies y manejo de errores para un funcionamiento confiable en producción.
- Agrega retrasos aleatorios y rotación de User-Agent para imitar el comportamiento humano.
- Para SOCKS5, utiliza la gema
socksify.
La elección del tipo de proxy depende de la tarea. Si estás escribiendo un parser para Wildberries o Ozon, los proxies residenciales ofrecerán el mejor equilibrio entre velocidad y elusión de protecciones: las IP reales de usuarios domésticos rara vez caen en listas de bloqueo. Para tareas de alta carga con un gran volumen de solicitudes a fuentes menos protegidas, los proxies de centros de datos son más rápidos y económicos por solicitud. Si tu script de Ruby trabaja con redes sociales o APIs publicitarias, considera los proxies móviles: sus direcciones IP tienen el nivel de confianza más alto en la mayoría de las plataformas.
```