Se stai scrivendo un parser, integrando un'API esterna o automatizzando la raccolta di dati in Ruby — prima o poi ti imbatterai in blocchi per IP. Wildberries, Ozon, Instagram e decine di altri servizi limitano le richieste da un singolo indirizzo dopo alcune centinaia di accessi. La soluzione è una sola — il proxy. In questo articolo vedremo come collegare un proxy a un progetto Ruby tramite il standard Net::HTTP e il popolare client HTTP Faraday, configurare la rotazione IP e gestire correttamente gli errori — sia in un'applicazione Rails che in semplici script.
Perché uno sviluppatore Ruby ha bisogno di un proxy: scenari principali
Ruby è ampiamente utilizzato per compiti in cui non si può fare a meno dei proxy. Prima di passare al codice, è utile capire in quali situazioni sono necessari — questo aiuterà a scegliere il tipo e l'architettura giusta della soluzione.
Parsing e raccolta dati. Questo è lo scenario più comune. Stai scrivendo uno script in Ruby che naviga tra le pagine di Wildberries, Ozon o Avito, raccogliendo prezzi e caratteristiche dei prodotti. Senza proxy, uno script del genere verrà bloccato per IP dopo pochi minuti. I siti vedono centinaia di richieste da un singolo indirizzo e attivano automaticamente la protezione — CAPTCHA, ban temporaneo o completo rifiuto del servizio.
Integrazione con API geograficamente dipendenti. Alcuni servizi esterni restituiscono contenuti diversi a seconda del paese della richiesta. Se la tua applicazione Rails si rivolge a un'API del genere, un proxy consente di simulare richieste dalla regione desiderata. Ad esempio, controllare i risultati dei motori di ricerca per diversi paesi o ottenere prezzi aggiornati per un mercato specifico.
Testing e QA. Gli sviluppatori utilizzano i proxy per testare il comportamento dell'applicazione con richieste provenienti da diversi indirizzi IP e paesi. Questo è particolarmente rilevante per i servizi con geo-blocco o limitazioni di velocità per IP.
Automazione del marketing. Gli script Ruby sono spesso utilizzati per monitorare campagne pubblicitarie, controllare le posizioni nei risultati o tracciare l'attività dei concorrenti. Qui i proxy sono necessari per distribuire il carico e aggirare le restrizioni.
Gestione di più account. Se il tuo script gestisce più account su una piattaforma, ogni account deve funzionare tramite un IP separato — altrimenti la piattaforma può facilmente rilevare il collegamento e bloccare tutto in una volta.
Quale tipo di proxy scegliere per le attività Ruby
Non tutti i proxy sono adatti per diversi compiti. La scelta del tipo influisce direttamente sul successo del funzionamento del tuo script. Esaminiamo le principali opzioni relative allo sviluppo Ruby.
| Tipo di proxy | Migliori compiti in Ruby | Velocità | Bypass delle protezioni |
|---|---|---|---|
| Proxy dei data center | Parsing senza protezioni rigorose, richieste API, testing | Alta | Media |
| Proxy residenziali | Parsing di siti protetti, lavoro con social media, marketplace | Media | Alta |
| Proxy mobili | Facebook, Instagram, TikTok API, gestione degli account | Media | Massimo |
Proxy dei data center — l'opzione più veloce ed economica. Adatti per il parsing di siti senza protezioni aggressive, richieste API di massa e testing del carico. Tuttavia, grandi piattaforme (Facebook, Google, Cloudflare) li riconoscono facilmente per ASN del data center.
Proxy residenziali utilizzano IP di veri utenti domestici. Per gli script Ruby che parsano Wildberries, Ozon o lavorano con siti protetti — questa è la scelta ottimale. I siti vedono un utente normale, non un server.
Proxy mobili funzionano tramite IP di operatori mobili (4G/5G). Questi sono gli indirizzi più affidabili dal punto di vista delle piattaforme — un singolo IP mobile può essere utilizzato contemporaneamente da centinaia di utenti reali, quindi i blocchi su di esso sono estremamente rari. Se il tuo script Ruby lavora con social media o piattaforme pubblicitarie — i proxy mobili ridurranno al minimo il rischio di blocchi.
Proxy in Net::HTTP: configurazione di base
Net::HTTP — la libreria standard di Ruby per le richieste HTTP, inclusa nella distribuzione standard del linguaggio. Supporta i proxy "out of the box" tramite un metodo speciale Net::HTTP::Proxy.
Il modo più semplice è creare una classe proxy e utilizzarla al posto del normale Net::HTTP:
require 'net/http'
require 'uri'
# Dati del proxy
proxy_host = 'proxy.example.com'
proxy_port = 8080
# Creiamo la classe proxy
proxy_class = Net::HTTP::Proxy(proxy_host, proxy_port)
# Eseguiamo la richiesta tramite il 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
Il metodo Net::HTTP::Proxy accetta quattro parametri: host del proxy, porta, nome utente e password. I primi due sono obbligatori, gli ultimi due sono opzionali per proxy senza autenticazione.
Un modo alternativo è passare i parametri del proxy direttamente al metodo 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 se senza autenticazione)
nil # proxy_pass
)
http.use_ssl = true
response = http.get(uri.path)
puts response.body
Questo approccio è comodo quando hai bisogno di riutilizzare lo stesso oggetto HTTP per più richieste a un singolo host. Nota: i parametri del proxy vengono passati come terzo e quarto argomento, e non tramite un metodo separato.
Autenticazione e HTTPS tramite Net::HTTP
La maggior parte dei proxy commerciali richiede autenticazione — nome utente e password. Inoltre, quando si lavora con siti HTTPS è necessaria una configurazione SSL aggiuntiva. Esaminiamo entrambi i casi.
Proxy con nome utente e password:
require 'net/http'
require 'uri'
proxy_host = 'proxy.example.com'
proxy_port = 8080
proxy_user = 'your_username'
proxy_pass = 'your_password'
# Creiamo la classe proxy con autenticazione
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|
# Disabilitiamo la verifica SSL (solo per test!)
# 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: quando si utilizza un proxy HTTPS (tunneling tramite CONNECT) Net::HTTP stabilisce automaticamente una connessione SSL con il server di destinazione tramite il tunnel proxy. Questo funziona correttamente per la maggior parte dei server proxy.
Configurazione tramite variabili d'ambiente. È una buona pratica non hardcodare i dati del proxy nel codice, ma leggerli dalle variabili d'ambiente:
require 'net/http'
require 'uri'
# Leggiamo le impostazioni del proxy da 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 variabile d'ambiente può essere impostata nel formato HTTP_PROXY=http://user:[email protected]:8080. Questo approccio consente di cambiare il proxy senza modificare il codice — basta aggiornare la variabile nel file .env o nelle impostazioni del server.
Collegamento del proxy tramite Faraday
Faraday è uno dei client HTTP più popolari nell'ecosistema Ruby. Viene utilizzato in molte applicazioni Rails grazie all'approccio middleware conveniente e al supporto di diversi adattatori (Net::HTTP, HTTParty, Typhoeus e altri). La configurazione del proxy in Faraday è leggermente diversa a seconda dell'adattatore utilizzato.
Configurazione di base del proxy in Faraday:
require 'faraday'
# Creiamo una connessione Faraday con il 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}"
Il parametro faraday.proxy accetta un hash con le chiavi uri, user e password. Puoi anche passare direttamente una stringa URI se il proxy non richiede autenticazione.
Faraday con adattatore Net::HTTP e impostazioni avanzate:
require 'faraday'
conn = Faraday.new(url: 'https://httpbin.org') do |faraday|
faraday.proxy = 'http://user:[email protected]:8080'
# Impostazioni dei timeout
faraday.options.timeout = 30
faraday.options.open_timeout = 10
# Middleware per il logging (comodo per il debug)
faraday.response :logger
# Retry in caso di errori di connessione
faraday.request :retry, max: 3, interval: 1
# Adattatore
faraday.adapter :net_http
end
begin
response = conn.get('/ip')
puts response.body
rescue Faraday::ConnectionFailed => e
puts "Errore di connessione: #{e.message}"
rescue Faraday::TimeoutError => e
puts "Timeout: #{e.message}"
end
Il middleware :retry è particolarmente utile quando si lavora con i proxy — se un indirizzo è temporaneamente non disponibile, Faraday ripeterà automaticamente la richiesta. Questo è critico per il funzionamento affidabile dello script in produzione.
Faraday con adattatore Typhoeus (per richieste parallele):
# 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
# Richieste parallele tramite 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 }
Rotazione del proxy: cambio automatico di IP
Un singolo indirizzo proxy, anche residenziale, prima o poi sarà soggetto a limitazioni durante un parsing intensivo. La soluzione è la rotazione: cambio automatico di IP dopo ogni richiesta o a intervalli specifici. Implementiamo un semplice rotatore per gli script Ruby.
Semplice rotatore basato su un array di proxy:
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 di proxy nel 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)
# Parsiamo la lista di URL tramite diversi proxy
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 "Errore con il proxy #{proxy[0]}: #{e.message}"
# Passiamo automaticamente al prossimo proxy
end
sleep(rand(0.5..2.0)) # Ritardo casuale tra le richieste
end
Il ritardo casuale tra le richieste (sleep(rand(0.5..2.0))) è un elemento importante. Il pattern di richieste con intervalli fissi è facilmente rilevabile dai sistemi anti-bot. Ritardi casuali imitano il comportamento di un utente reale.
Rotazione tramite 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)
# Selezioniamo un proxy casuale per ogni richiesta
proxy_uri = URI(PROXIES.sample)
env[:request][:proxy] = {
uri: proxy_uri,
user: proxy_uri.user,
password: proxy_uri.password
}
@app.call(env)
end
end
# Registriamo il 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
Integrazione del proxy in un'applicazione Rails
In un'applicazione Rails, i proxy sono spesso necessari per richieste HTTP esterne: integrazione con API, attività di parsing in background o webhook. Esaminiamo alcuni pattern pratici.
Pattern 1: Oggetto servizio con Faraday. Creiamo una classe di servizio di base che esegue tutte le richieste HTTP esterne tramite proxy:
# app/services/http_client.rb
class HttpClient
def self.connection(base_url)
Faraday.new(url: base_url) do |faraday|
# Proxy dalle variabili d'ambiente di 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
# Utilizzo nel controller o nel servizio:
# client = HttpClient.connection('https://api.example.com')
# response = client.get('/products', { category: 'electronics' })
Pattern 2: Configurazione tramite credenziali di Rails. Per memorizzare i dati del proxy utilizza le credenziali di Rails criptate:
# config/credentials.yml.enc (modificato tramite 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
Pattern 3: Attività in background con Sidekiq. Se il parsing o le richieste API vengono eseguite in worker in background, la configurazione del proxy rimane la stessa, ma è importante considerare la concorrenza:
# app/workers/price_parser_worker.rb
class PriceParserWorker
include Sidekiq::Worker
sidekiq_options retry: 3, queue: :parsers
def perform(product_url)
# Creiamo una nuova connessione per ogni worker
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 "Status inaspettato #{response.code} per #{product_url}"
end
end
rescue Net::OpenTimeout, Net::ReadTimeout => e
Rails.logger.error "Timeout per #{product_url}: #{e.message}"
raise # Sidekiq ripeterà il task
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)
# Logica di parsing...
end
end
Gestione degli errori e timeout
Lavorare tramite proxy aggiunge uno strato aggiuntivo che può generare errori specifici. Una corretta gestione delle eccezioni fa la differenza tra uno script che si interrompe dopo 5 minuti e uno che funziona per ore senza intervento.
Tipi principali di errori durante l'uso dei proxy in Ruby:
| Eccezione | Causa | Cosa fare |
|---|---|---|
Net::OpenTimeout |
Il proxy non risponde | Cambia proxy, ripeti |
Net::ReadTimeout |
Proxy lento o il sito non risponde | Aumenta il timeout o cambia proxy |
Errno::ECONNREFUSED |
Il server proxy ha rifiutato la connessione | Controlla i dati del proxy |
OpenSSL::SSL::SSLError |
Problema con SSL tramite proxy | Controlla le impostazioni SSL |
| HTTP 407 | Nome utente/password del proxy errati | Controlla le credenziali |
| HTTP 403/429 | IP bloccato dal sito | Cambia proxy, aggiungi pausa |
Gestione complessiva degli errori con cambio automatico di 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 "Autenticazione proxy fallita per #{proxy[0]}"
when 403, 429
rotate_proxy!
raise "IP bloccato (#{response.code}), rotazione del proxy"
else
raise "Status HTTP inaspettato: #{response.code}"
end
end
rescue *RETRIABLE_ERRORS => e
Rails.logger.warn "Errore di connessione (tentativo #{attempt}/#{retries}): #{e.message}"
rotate_proxy!
retry if attempt < retries
raise "Fallito dopo #{retries} tentativi: #{e.message}"
rescue RuntimeError => e
Rails.logger.warn "Errore HTTP (tentativo #{attempt}/#{retries}): #{e.message}"
retry if attempt < retries
raise
end
end
private
def current_proxy
@proxies.first || raise("Nessun proxy disponibile!")
end
def rotate_proxy!
failed = @proxies.shift
@failed_proxies << failed
Rails.logger.info "Proxy ruotato. Rimanenti: #{@proxies.length}"
end
end
Proxy SOCKS5 in Ruby: configurazione tramite socksify
Il standard Net::HTTP non supporta nativamente SOCKS5 — solo proxy HTTP/HTTPS. Per lavorare con SOCKS5 è necessaria una libreria aggiuntiva. L'opzione più popolare è la gemma socksify.
Perché SOCKS5 potrebbe essere preferibile ai proxy HTTP? Il protocollo SOCKS5 funziona a un livello più basso — proxy qualsiasi traffico TCP, non solo HTTP. Inoltre, SOCKS5 supporta l'autenticazione e trasmette meno metadati sulla richiesta, rendendolo meno rilevabile dai sistemi di rilevamento dei bot.
# Gemfile
# gem 'socksify'
require 'socksify'
require 'socksify/http'
require 'net/http'
require 'uri'
# Impostazione globale del proxy SOCKS5
TCPSocket::socks_server = 'proxy.example.com'
TCPSocket::socks_port = 1080
# Opzionale: autenticazione (SOCKS5 username/password)
# TCPSocket::socks_username = 'your_user'
# TCPSocket::socks_password = 'your_pass'
# Ora tutte le richieste Net::HTTP passano tramite SOCKS5
uri = URI('https://httpbin.org/ip')
response = Net::HTTP.get_response(uri)
puts response.body
Nota: quando si utilizza socksify, la configurazione viene applicata globalmente a tutte le connessioni TCP nel processo. Se hai bisogno di una configurazione più flessibile — utilizza Net::HTTP.SOCKSProxy dalla stessa gemma:
require 'socksify/http'
# Creiamo una classe proxy SOCKS5 (analogamente a Net::HTTP::Proxy per 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
Per Faraday con SOCKS5 sarà necessario un adattatore che supporti questo protocollo. Una buona opzione è faraday-net_http in combinazione con socksify, oppure utilizzare l'adattatore basato su curl faraday-typhoeus, che supporta nativamente SOCKS5 tramite libcurl.
💡 Consiglio sulla scelta del protocollo
Per la maggior parte dei compiti di parsing e lavoro con API, i proxy HTTP/HTTPS sono sufficienti — sono più facili da configurare e supportati nativamente. SOCKS5 dovrebbe essere utilizzato quando è necessario proxy non solo il traffico HTTP, o quando i proxy HTTP sono facilmente rilevabili dal servizio di destinazione. I proxy mobili e residenziali sono disponibili in entrambi i protocolli.
Conclusione e raccomandazioni
La configurazione dei proxy in Ruby non richiede librerie complesse — il standard Net::HTTP supporta i proxy HTTP "out of the box", e Faraday rende l'integrazione ancora più comoda grazie all'architettura middleware. Ecco i punti chiave di questa guida:
- Per Net::HTTP utilizza
Net::HTTP::Proxy— è un modo integrato e affidabile. - Per Faraday, il parametro
faraday.proxyaccetta una stringa URI o un hash con le credenziali. - Memorizza i dati del proxy in variabili d'ambiente o credenziali di Rails — non hardcodare nel codice.
- Implementa rotazione dei proxy e gestione degli errori per un funzionamento affidabile in produzione.
- Aggiungi ritardi casuali e rotazione dell'User-Agent per imitare il comportamento umano.
- Per SOCKS5 utilizza la gemma
socksify.
La scelta del tipo di proxy dipende dal compito. Se stai scrivendo un parser per Wildberries o Ozon — i proxy residenziali offriranno il miglior equilibrio tra velocità e bypass delle protezioni: gli IP reali degli utenti domestici raramente finiscono nelle blacklist. Per compiti ad alta intensità con un grande volume di richieste a fonti meno protette, i proxy dei data center sono più veloci e più economici per richiesta. Se il tuo script Ruby lavora con social media o API pubblicitarie — dai un'occhiata ai proxy mobili: i loro indirizzi IP hanno il massimo livello di fiducia sulla maggior parte delle piattaforme.
```