Si vous écrivez un parseur, intégrez une API externe ou automatisez la collecte de données en Ruby — tôt ou tard, vous serez confronté à des blocages par IP. Wildberries, Ozon, Instagram et des dizaines d'autres services limitent les requêtes d'une seule adresse après quelques centaines d'appels. La solution est simple — un proxy. Dans cet article, nous allons examiner comment connecter un proxy à un projet Ruby via le standard Net::HTTP et le client HTTP populaire Faraday, configurer la rotation IP et gérer correctement les erreurs — tant dans une application Rails que dans des scripts simples.
Pourquoi un développeur Ruby a-t-il besoin d'un proxy : scénarios principaux
Ruby est largement utilisé pour des tâches où un proxy est indispensable. Avant de passer au code, il est important de comprendre dans quelles situations ils sont nécessaires — cela aidera à choisir le bon type et l'architecture de la solution.
Parsing et collecte de données. C'est le scénario le plus courant. Vous écrivez un script en Ruby qui parcourt les pages de Wildberries, Ozon ou Avito, collectant les prix et les caractéristiques des produits. Sans proxy, un tel script sera bloqué par IP en quelques minutes. Les sites détectent des centaines de requêtes provenant d'une seule adresse et activent automatiquement la protection — captcha, bannissement temporaire ou refus total de service.
Intégration avec des API dépendantes de la géolocalisation. Certains services externes renvoient un contenu différent selon le pays de la requête. Si votre application Rails interroge une telle API, un proxy permet de simuler des requêtes provenant de la région souhaitée. Par exemple, vérifier les résultats des moteurs de recherche pour différents pays ou obtenir des prix pertinents pour un marché spécifique.
Tests et QA. Les développeurs utilisent des proxies pour tester le comportement de l'application lors de requêtes provenant de différentes adresses IP et pays. Cela est particulièrement pertinent pour les services avec géo-blocage ou limitation de débit par IP.
Automatisation du marketing. Les scripts Ruby sont souvent utilisés pour surveiller les campagnes publicitaires, vérifier les positions dans les résultats ou suivre l'activité des concurrents. Ici, les proxies sont nécessaires pour répartir la charge et contourner les restrictions.
Gestion de plusieurs comptes. Si votre script gère plusieurs comptes sur une même plateforme, chaque compte doit fonctionner via une IP distincte — sinon, la plateforme peut facilement détecter le lien et bloquer tous les comptes simultanément.
Quel type de proxy choisir pour les tâches Ruby
Tous les proxies ne conviennent pas également à différentes tâches. Le choix du type influence directement le succès de votre script. Examinons les principales options applicables au développement Ruby.
| Type de proxy | Meilleures tâches en Ruby | Vitesse | Contournement des protections |
|---|---|---|---|
| Proxies de centre de données | Parsing sans protection stricte, requêtes API, tests | Élevée | Moyenne |
| Proxies résidentiels | Parsing de sites protégés, travail avec les réseaux sociaux, marketplaces | Moyenne | Élevée |
| Proxies mobiles | API Facebook, Instagram, TikTok, gestion de comptes | Moyenne | Maximale |
Proxies de centre de données — l'option la plus rapide et la moins chère. Ils conviennent pour le parsing de sites sans protection agressive, les requêtes API massives et les tests de charge. Cependant, les grandes plateformes (Facebook, Google, Cloudflare) peuvent facilement les reconnaître par l'ASN du centre de données.
Proxies résidentiels utilisent des IP d'utilisateurs domestiques réels. Pour les scripts Ruby qui parsèment Wildberries, Ozon ou travaillent avec des sites protégés — c'est le choix optimal. Les sites voient un utilisateur ordinaire, et non un serveur.
Proxies mobiles fonctionnent via les IP des opérateurs mobiles (4G/5G). Ce sont les adresses les plus fiables du point de vue des plateformes — une seule IP mobile peut être utilisée par des centaines d'utilisateurs réels en même temps, donc les blocages sont extrêmement rares. Si votre script Ruby travaille avec des réseaux sociaux ou des plateformes publicitaires — les proxies mobiles réduiront le risque de blocages au minimum.
Proxy dans Net::HTTP : configuration de base
Net::HTTP — la bibliothèque standard Ruby pour les requêtes HTTP, incluse dans la distribution standard du langage. Elle prend en charge les proxies « out of the box » via une méthode spéciale Net::HTTP::Proxy.
La façon la plus simple est de créer une classe proxy et de l'utiliser à la place de Net::HTTP :
require 'net/http'
require 'uri'
# Données du proxy
proxy_host = 'proxy.example.com'
proxy_port = 8080
# Créons une classe proxy
proxy_class = Net::HTTP::Proxy(proxy_host, proxy_port)
# Exécutons une requête via le 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
La méthode Net::HTTP::Proxy prend quatre paramètres : l'hôte du proxy, le port, le nom d'utilisateur et le mot de passe. Les deux premiers sont obligatoires, les deux derniers sont optionnels pour les proxies sans authentification.
Une autre méthode consiste à passer les paramètres du proxy directement dans la méthode 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 sans authentification)
nil # proxy_pass
)
http.use_ssl = true
response = http.get(uri.path)
puts response.body
Cette approche est pratique lorsque vous devez réutiliser le même objet HTTP pour plusieurs requêtes à un même hôte. Notez que les paramètres du proxy sont passés en tant que troisième et quatrième arguments, et non via une méthode distincte.
Authentification et HTTPS via Net::HTTP
La plupart des proxies commerciaux nécessitent une authentification — un nom d'utilisateur et un mot de passe. De plus, lors de l'utilisation de sites HTTPS, une configuration SSL supplémentaire est nécessaire. Examinons les deux cas.
Proxy avec nom d'utilisateur et mot de passe :
require 'net/http'
require 'uri'
proxy_host = 'proxy.example.com'
proxy_port = 8080
proxy_user = 'your_username'
proxy_pass = 'your_password'
# Créons une classe proxy avec authentification
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|
# Désactiver la vérification SSL (uniquement pour les tests !)
# 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 "Statut: #{response.code}"
end
Un point important : lors de l'utilisation de proxies HTTPS (tunneling via CONNECT) Net::HTTP établit automatiquement une connexion SSL avec le serveur cible via le tunnel proxy. Cela fonctionne correctement pour la plupart des serveurs proxy.
Configuration via des variables d'environnement. Une bonne pratique consiste à ne pas hardcoder les données du proxy dans le code, mais à les lire à partir des variables d'environnement :
require 'net/http'
require 'uri'
# Lire les paramètres du proxy à partir de 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 d'environnement peut être définie au format HTTP_PROXY=http://user:[email protected]:8080. Cette approche permet de changer le proxy sans modifier le code — il suffit de mettre à jour la variable dans le fichier .env ou dans les paramètres du serveur.
Connexion de proxy via Faraday
Faraday est l'un des clients HTTP les plus populaires dans l'écosystème Ruby. Il est utilisé dans de nombreuses applications Rails grâce à son approche middleware pratique et à son support de différents adaptateurs (Net::HTTP, HTTParty, Typhoeus, et d'autres). La configuration des proxies dans Faraday diffère légèrement selon l'adaptateur utilisé.
Configuration de base du proxy dans Faraday :
require 'faraday'
# Créons une connexion Faraday avec un 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 "Statut: #{response.status}"
Le paramètre faraday.proxy accepte un hash avec les clés uri, user et password. Vous pouvez également passer une chaîne URI directement si le proxy ne nécessite pas d'authentification.
Faraday avec l'adaptateur Net::HTTP et des paramètres avancés :
require 'faraday'
conn = Faraday.new(url: 'https://httpbin.org') do |faraday|
faraday.proxy = 'http://user:[email protected]:8080'
# Paramètres de délai d'attente
faraday.options.timeout = 30
faraday.options.open_timeout = 10
# Middleware pour la journalisation (pratique lors du débogage)
faraday.response :logger
# Réessayer en cas d'erreurs de connexion
faraday.request :retry, max: 3, interval: 1
# Adaptateur
faraday.adapter :net_http
end
begin
response = conn.get('/ip')
puts response.body
rescue Faraday::ConnectionFailed => e
puts "Erreur de connexion : #{e.message}"
rescue Faraday::TimeoutError => e
puts "Délai d'attente : #{e.message}"
end
Le middleware :retry est particulièrement utile lors de l'utilisation de proxies — si une adresse est temporairement indisponible, Faraday réessaiera automatiquement la requête. Cela est crucial pour le bon fonctionnement du script en production.
Faraday avec l'adaptateur Typhoeus (pour des requêtes parallèles) :
# 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
# Requêtes parallèles via le 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 }
Rotation de proxy : changement automatique d'IP
Une seule adresse proxy, même résidentielle, finira par être soumise à des restrictions lors d'un parsing intensif. La solution est la rotation : changement automatique d'IP après chaque requête ou à intervalles réguliers. Implémentons un simple rotateur pour les scripts Ruby.
Simple rotateur basé sur un tableau 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
# Liste des proxies au format [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)
# Parsage de la liste d'URL via différents 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} -> Statut: #{response.code}, Proxy: #{proxy[0]}"
end
rescue => e
puts "Erreur avec le proxy #{proxy[0]}: #{e.message}"
# Passer automatiquement au proxy suivant
end
sleep(rand(0.5..2.0)) # Délai aléatoire entre les requêtes
end
Un délai aléatoire entre les requêtes (sleep(rand(0.5..2.0))) est un élément important. Le modèle de requêtes à intervalles fixes est facilement détectable par les systèmes anti-bots. Des délais aléatoires imitent le comportement d'un utilisateur réel.
Rotation via Faraday avec 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)
# Choisir un proxy aléatoire pour chaque requête
proxy_uri = URI(PROXIES.sample)
env[:request][:proxy] = {
uri: proxy_uri,
user: proxy_uri.user,
password: proxy_uri.password
}
@app.call(env)
end
end
# Enregistrement du 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
Intégration de proxy dans une application Rails
Dans une application Rails, les proxies sont souvent nécessaires pour les requêtes HTTP externes : intégration avec des API, tâches de parsing en arrière-plan ou webhooks. Examinons quelques modèles pratiques.
Modèle 1 : Objet de service avec Faraday. Créons une classe de service de base qui effectue toutes les requêtes HTTP externes via un proxy :
# app/services/http_client.rb
class HttpClient
def self.connection(base_url)
Faraday.new(url: base_url) do |faraday|
# Proxy à partir des variables d'environnement 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
# Utilisation dans un contrôleur ou un service :
# client = HttpClient.connection('https://api.example.com')
# response = client.get('/products', { category: 'electronics' })
Modèle 2 : Configuration via les credentials Rails. Pour stocker les données du proxy, utilisez les credentials Rails chiffrés :
# config/credentials.yml.enc (édité via 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
Modèle 3 : Tâches en arrière-plan avec Sidekiq. Si le parsing ou les requêtes API sont effectués dans des workers en arrière-plan, la configuration du proxy reste la même, mais il est important de prendre en compte la concurrence :
# app/workers/price_parser_worker.rb
class PriceParserWorker
include Sidekiq::Worker
sidekiq_options retry: 3, queue: :parsers
def perform(product_url)
# Créons une nouvelle connexion pour chaque 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 "Statut inattendu #{response.code} pour #{product_url}"
end
end
rescue Net::OpenTimeout, Net::ReadTimeout => e
Rails.logger.error "Délai d'attente pour #{product_url}: #{e.message}"
raise # Sidekiq répétera la tâche
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)
# Logique de parsing...
end
end
Gestion des erreurs et des délais d'attente
Travailler via un proxy ajoute une couche supplémentaire qui peut générer des erreurs spécifiques. Une gestion appropriée des exceptions fait la différence entre un script qui échoue après 5 minutes et un script qui fonctionne pendant des heures sans intervention.
Principaux types d'erreurs lors de l'utilisation de proxies en Ruby :
| Exception | Cause | Que faire |
|---|---|---|
Net::OpenTimeout |
Le proxy ne répond pas | Changer de proxy, réessayer |
Net::ReadTimeout |
Proxy lent ou le site ne répond pas | Augmenter le délai d'attente ou changer de proxy |
Errno::ECONNREFUSED |
Le serveur proxy a refusé la connexion | Vérifier les données du proxy |
OpenSSL::SSL::SSLError |
Problème avec SSL via le proxy | Vérifier les paramètres SSL |
| HTTP 407 | Nom d'utilisateur/mot de passe du proxy incorrect | Vérifier les credentials |
| HTTP 403/429 | IP bloquée par le site | Changer de proxy, ajouter une pause |
Gestion complète des erreurs avec changement automatique 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 "Échec de l'authentification du proxy pour #{proxy[0]}"
when 403, 429
rotate_proxy!
raise "IP bloquée (#{response.code}), rotation du proxy"
else
raise "Statut HTTP inattendu : #{response.code}"
end
end
rescue *RETRIABLE_ERRORS => e
Rails.logger.warn "Erreur de connexion (tentative #{attempt}/#{retries}) : #{e.message}"
rotate_proxy!
retry if attempt < retries
raise "Échec après #{retries} tentatives : #{e.message}"
rescue RuntimeError => e
Rails.logger.warn "Erreur HTTP (tentative #{attempt}/#{retries}) : #{e.message}"
retry if attempt < retries
raise
end
end
private
def current_proxy
@proxies.first || raise("Aucun proxy disponible !")
end
def rotate_proxy!
failed = @proxies.shift
@failed_proxies << failed
Rails.logger.info "Proxy tourné. Restants : #{@proxies.length}"
end
end
Proxy SOCKS5 en Ruby : configuration via socksify
Le standard Net::HTTP ne prend pas en charge SOCKS5 nativement — uniquement les proxies HTTP/HTTPS. Pour travailler avec SOCKS5, une bibliothèque supplémentaire est nécessaire. L'option la plus populaire est la gem socksify.
Pourquoi SOCKS5 peut-il être préférable aux proxies HTTP ? Le protocole SOCKS5 fonctionne à un niveau plus bas — il proxy tout le trafic TCP, et pas seulement HTTP. De plus, SOCKS5 prend en charge l'authentification et transmet moins de métadonnées sur la requête, ce qui le rend moins détectable par les systèmes de détection de bots.
# Gemfile
# gem 'socksify'
require 'socksify'
require 'socksify/http'
require 'net/http'
require 'uri'
# Configuration globale du proxy SOCKS5
TCPSocket::socks_server = 'proxy.example.com'
TCPSocket::socks_port = 1080
# Optionnel : authentification (nom d'utilisateur/mot de passe SOCKS5)
# TCPSocket::socks_username = 'your_user'
# TCPSocket::socks_password = 'your_pass'
# Maintenant, toutes les requêtes Net::HTTP passent par SOCKS5
uri = URI('https://httpbin.org/ip')
response = Net::HTTP.get_response(uri)
puts response.body
Notez que lors de l'utilisation de socksify, la configuration s'applique globalement à toutes les connexions TCP dans le processus. Si vous avez besoin d'une configuration plus flexible — utilisez Net::HTTP.SOCKSProxy de la même gem :
require 'socksify/http'
# Créons une classe proxy SOCKS5 (similaire à Net::HTTP::Proxy pour 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
Pour Faraday avec SOCKS5, un adaptateur prenant en charge ce protocole est nécessaire. Une bonne option est faraday-net_http en combinaison avec socksify, ou l'utilisation d'un adaptateur basé sur curl faraday-typhoeus, qui prend en charge nativement SOCKS5 via libcurl.
💡 Conseil sur le choix du protocole
Pour la plupart des tâches de parsing et de travail avec des API, des proxies HTTP/HTTPS suffisent — ils sont plus simples à configurer et pris en charge nativement. SOCKS5 doit être utilisé lorsque vous devez proxy pas seulement le trafic HTTP, ou lorsque les proxies HTTP sont facilement détectables par le service cible. Les proxies mobiles et résidentiels sont disponibles dans les deux protocoles.
Conclusion et recommandations
La configuration de proxies en Ruby ne nécessite pas de bibliothèques complexes — le standard Net::HTTP prend en charge les proxies HTTP « out of the box », et Faraday rend l'intégration encore plus pratique grâce à son architecture middleware. Voici les points clés de ce guide :
- Pour Net::HTTP, utilisez
Net::HTTP::Proxy— c'est une méthode intégrée et fiable. - Pour Faraday, le paramètre
faraday.proxyaccepte une chaîne URI ou un hash avec des credentials. - Stockez les données du proxy dans des variables d'environnement ou dans les credentials Rails — ne les hardcodez pas dans le code.
- Implémentez la rotation des proxies et la gestion des erreurs pour un fonctionnement fiable en production.
- Ajoutez des délais aléatoires et une rotation de l'User-Agent pour imiter le comportement humain.
- Pour SOCKS5, utilisez la gem
socksify.
Le choix du type de proxy dépend de la tâche. Si vous écrivez un parseur pour Wildberries ou Ozon — les proxies résidentiels offriront le meilleur équilibre entre vitesse et contournement des protections : les IP réelles des utilisateurs domestiques sont très rarement ajoutées aux listes de blocage. Pour des tâches à forte charge avec un grand volume de requêtes vers des sources moins protégées, les proxies de centre de données seront plus rapides et moins chers par requête. Si votre script Ruby travaille avec des réseaux sociaux ou des API publicitaires — pensez aux proxies mobiles : leurs adresses IP ont le niveau de confiance maximal sur la plupart des plateformes.
```