دليل استخدام HTTP Proxy في Ruby: من Net::HTTP إلى ProxyHat SDK

دليل شامل لمطوري Ruby حول استخدام بروكسيات HTTP مع Net::HTTP وTyphoeus وProxyHat SDK. تعلم كيفية إدارة الاتصالات المتوازية والتناوب الجغرافي ومعالجة TLS/SSL في تطبيقاتك.

دليل استخدام HTTP Proxy في Ruby: من Net::HTTP إلى ProxyHat SDK

إذا كنت مطور Ruby وتبني تطبيقات تجميع بيانات أو خطوط معالجة آلية، فستواجه حتماً الحاجة لاستخدام بروكسيات HTTP. سواء كان ذلك لتجاوز حدود المعدل rate limits أو للوصول إلى محتوى مقيد جغرافياً أو ببساطة لحماية هوية خوادمك، فإن فهم كيفية دمج البروكسيات في كود Ruby مهارة أساسية.

في هذا الدليل، سنتعمق في ثلاثة أساليب رئيسية: مكتبة Net::HTTP المدمجة في Ruby، ومكتبة Typhoeus السريعة للطلبات المتوازية، وأخيراً ProxyHat Ruby SDK للتكامل السلس مع خدمة البروكسيات السكنية. كل مثال عملي وجاهز للتشغيل.

أساسيات Net::HTTP مع البروكسي

مكتبة Net::HTTP هي جزء من المكتبة القياسية في Ruby، مما يعني لا حاجة لتثبيت أي gem إضافي. هذه هي الطريقة الأكثر مباشرة لإجراء طلبات HTTP من خلال بروكسي.

الاتصال الأساسي بالبروكسي

للاتصال ببروكسي مع مصادقة، نستخدم Net::HTTP::Proxy أو نمرر معاملات البروكسي مباشرة:

require 'net/http'
require 'uri'

# تكوين بروكسي ProxyHat
PROXY_HOST = 'gate.proxyhat.com'
PROXY_PORT = 8080
PROXY_USER = 'user-country-US'
PROXY_PASS = 'your_password'

def fetch_with_proxy(url_string)
  uri = URI.parse(url_string)
  
  # إنشاء اتصال عبر البروكسي
  proxy = Net::HTTP::Proxy(PROXY_HOST, PROXY_PORT, PROXY_USER, PROXY_PASS)
  
  http = proxy.new(uri.host, uri.port)
  http.use_ssl = (uri.scheme == 'https')
  http.open_timeout = 15
  http.read_timeout = 30
  
  begin
    request = Net::HTTP::Get.new(uri.request_uri)
    request['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    request['Accept'] = 'text/html,application/xhtml+xml,application/xml'
    
    response = http.request(request)
    
    case response
    when Net::HTTPSuccess
      {
        status: response.code.to_i,
        headers: response.each_header.to_h,
        body: response.body,
        success: true
      }
    when Net::HTTPRedirection
      {
        status: response.code.to_i,
        location: response['location'],
        success: false,
        error: 'Redirect detected'
      }
    else
      {
        status: response.code.to_i,
        success: false,
        error: response.message
      }
    end
  rescue Net::OpenTimeout => e
    { success: false, error: "Connection timeout: #{e.message}" }
  rescue Net::ReadTimeout => e
    { success: false, error: "Read timeout: #{e.message}" }
  rescue SocketError => e
    { success: false, error: "DNS/Network error: #{e.message}" }
  rescue StandardError => e
    { success: false, error: "Unexpected error: #{e.message}" }
  ensure
    http.finish if http.started?
  end
end

# مثال الاستخدام
result = fetch_with_proxy('https://httpbin.org/ip')
puts "Status: #{result[:status]}"
puts "Body: #{result[:body]}" if result[:success]
puts "Error: #{result[:error]}" unless result[:success]

إعادة المحاولة مع التناوب

عند استخدام بروكسيات سكنية، من الشائع أن تفشل بعض الطلبات. إليك نمط قوي لإعادة المحاولة مع تبديل البروكسي:

require 'net/http'
require 'uri'

class ProxyAwareClient
  attr_reader :proxy_configs
  
  def initialize(proxy_configs)
    @proxy_configs = proxy_configs
    @current_index = 0
  end
  
  def rotate_proxy!
    @current_index = (@current_index + 1) % proxy_configs.length
  end
  
  def current_proxy
    proxy_configs[@current_index]
  end
  
  def fetch(url_string, max_retries: 3)
    uri = URI.parse(url_string)
    attempts = 0
    last_error = nil
    
    while attempts < max_retries
      proxy = current_proxy
      http_class = Net::HTTP::Proxy(
        proxy[:host],
        proxy[:port],
        proxy[:user],
        proxy[:pass]
      )
      
      http = http_class.new(uri.host, uri.port)
      http.use_ssl = (uri.scheme == 'https')
      http.open_timeout = 10
      http.read_timeout = 20
      
      begin
        attempts += 1
        response = http.request(Net::HTTP::Get.new(uri.request_uri))
        
        if response.is_a?(Net::HTTPSuccess)
          return { success: true, body: response.body, proxy_used: proxy }
        elsif response.code.to_i == 429
          # Rate limited - rotate and retry
          puts "Rate limited, rotating proxy..."
          rotate_proxy!
          sleep(2 ** attempts) # Exponential backoff
        elsif response.code.to_i >= 500
          # Server error - retry with same proxy
          puts "Server error #{response.code}, retrying..."
          sleep(1)
        else
          return { success: false, status: response.code, error: response.message }
        end
      rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNRESET => e
        last_error = e
        puts "Connection error with #{proxy[:user]}: #{e.message}"
        rotate_proxy!
        sleep(1)
      ensure
        http.finish if http.started?
      end
    end
    
    { success: false, error: "Max retries exceeded", last_error: last_error }
  end
end

# تكوين عدة بروكسيات
proxy_configs = [
  { host: 'gate.proxyhat.com', port: 8080, user: 'user-country-US', pass: 'password' },
  { host: 'gate.proxyhat.com', port: 8080, user: 'user-country-DE', pass: 'password' },
  { host: 'gate.proxyhat.com', port: 8080, user: 'user-country-GB', pass: 'password' }
]

client = ProxyAwareClient.new(proxy_configs)
result = client.fetch('https://example.com/data.json', max_retries: 5)

Typhoeus: الطلبات المتوازية مع Hydra

عندما تحتاج لأداء عالٍ مع مئات الطلبات المتوازية، فإن Typhoeus هو الخيار الأمثل. هذه المكتبة مبنية على libcurl وتوفر واجهة Hydra لإدارة الطلبات المتوازية بكفاءة.

التثبيت والإعداد

# Gemfile
gem 'typhoeus', '~> 1.4'

# أو مباشرة
gem install typhoeus

الطلبات المتوازية مع البروكسي

require 'typhoeus'

# تكوين البروكسي لـ ProxyHat
PROXY_CONFIG = {
  host: 'gate.proxyhat.com',
  port: 8080,
  username: 'user-country-US',
  password: 'your_password'
}

class ParallelScraper
  def initialize(concurrency: 50, timeout: 30)
    @concurrency = concurrency
    @timeout = timeout
    @hydra = Typhoeus::Hydra.new(max_concurrency: concurrency)
  end
  
  def fetch_urls(urls, proxy: PROXY_CONFIG)
    results = {}
    mutex = Mutex.new
    
    urls.each do |url|
      request = Typhoeus::Request.new(
        url,
        method: :get,
        proxy: "http://#{proxy[:username]}:#{proxy[:password]}@#{proxy[:host]}:#{proxy[:port]}",
        timeout: @timeout,
        followlocation: true,
        headers: {
          'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
          'Accept' => 'text/html,application/xhtml+xml'
        }
      )
      
      request.on_complete do |response|
        mutex.synchronize do
          results[url] = {
            status: response.code,
            success: response.success?,
            body: response.body,
            total_time: response.total_time,
            error: response.failure? ? response.return_message : nil
          }
        end
      end
      
      @hydra.queue(request)
    end
    
    @hydra.run
    results
  end
  
  def fetch_with_rotation(urls, proxy_configs:)
    results = {}
    mutex = Mutex.new
    proxy_count = proxy_configs.length
    
    urls.each_with_index do |(url, index)|
      proxy = proxy_configs[index % proxy_count]
      
      request = Typhoeus::Request.new(
        url,
        method: :get,
        proxy: "http://#{proxy[:user]}:#{proxy[:pass]}@#{proxy[:host]}:#{proxy[:port]}",
        timeout: @timeout,
        headers: { 'User-Agent' => random_user_agent }
      )
      
      request.on_complete do |response|
        mutex.synchronize do
          results[url] = {
            status: response.code,
            success: response.success?,
            body: response.body
          }
        end
      end
      
      @hydra.queue(request)
    end
    
    @hydra.run
    results
  end
  
  private
  
  def random_user_agent
    agents = [
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
      'Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0'
    ]
    agents.sample
  end
end

# مثال الاستخدام
scraper = ParallelScraper.new(concurrency: 100)

urls = (1..100).map { |i| "https://httpbin.org/delay/#{rand(1..3)}?id=#{i}" }

puts "Fetching #{urls.length} URLs..."
start_time = Time.now
results = scraper.fetch_urls(urls)
elapsed = Time.now - start_time

successful = results.count { |_, r| r[:success] }
puts "Completed: #{successful}/#{urls.length} in #{elapsed.round(2)}s"
puts "Requests/sec: #{(urls.length / elapsed).round(2)}"

ProxyHat Ruby SDK: التناوب والاستهداف الجغرافي

ProxyHat يوفر SDK مخصص لـ Ruby يبسط إدارة التناوب والاستهداف الجغرافي. هذا يتيح لك التركيز على منطق التطبيق بدلاً من إدارة اتصالات البروكسي.

# Gemfile
gem 'proxyhat', '~> 1.0'

# أو مباشرة
gem install proxyhat

# lib/proxyhat_client.rb
require 'proxyhat'

class ProxyHatClient
  attr_reader :client, :config
  
  def initialize(username:, password:, default_country: nil)
    @config = {
      gateway: 'gate.proxyhat.com',
      http_port: 8080,
      socks_port: 1080,
      username: username,
      password: password,
      default_country: default_country
    }
    
    @client = ProxyHat::Client.new(
      host: @config[:gateway],
      port: @config[:http_port],
      username: @config[:username],
      password: @config[:password]
    )
  end
  
  # بروكسي سكني مع تناوب تلقائي
  def residential_proxy(country: nil, city: nil, session_id: nil)
    user_parts = [@config[:username]]
    user_parts << "country-#{country}" if country
    user_parts << "city-#{city}" if city
    user_parts << "session-#{session_id}" if session_id
    
    proxy_user = user_parts.join('-')
    
    {
      host: @config[:gateway],
      port: @config[:http_port],
      user: proxy_user,
      pass: @config[:password],
      http_url: "http://#{proxy_user}:#{@config[:password]}@#{@config[:gateway]}:#{@config[:http_port]}",
      socks_url: "socks5://#{proxy_user}:#{@config[:password]}@#{@config[:gateway]}:#{@config[:socks_port]}"
    }
  end
  
  # جلسة ثابتة (sticky session)
  def sticky_session(country:, duration: 30, session_id: nil)
    session_id ||= SecureRandom.hex(8)
    
    proxy = residential_proxy(
      country: country,
      session_id: session_id
    )
    
    {
      proxy: proxy,
      session_id: session_id,
      expires_at: Time.now + (duration * 60)
    }
  end
  
  # التحقق من صحة البروكسي
  def test_proxy(proxy_config)
    uri = URI.parse('https://httpbin.org/ip')
    
    http = Net::HTTP::Proxy(
      proxy_config[:host],
      proxy_config[:port],
      proxy_config[:user],
      proxy_config[:pass]
    ).new(uri.host, uri.port)
    
    http.use_ssl = true
    http.open_timeout = 10
    
    begin
      response = http.request(Net::HTTP::Get.new(uri.request_uri))
      if response.is_a?(Net::HTTPSuccess)
        ip_info = JSON.parse(response.body)
        { success: true, ip: ip_info['origin'], proxy: proxy_config }
      else
        { success: false, error: "HTTP #{response.code}" }
      end
    rescue => e
      { success: false, error: e.message }
    end
  end
end

# مثال الاستخدام
client = ProxyHatClient.new(
  username: 'your_username',
  password: 'your_password',
  default_country: 'US'
)

# بروكسي من الولايات المتحدة
us_proxy = client.residential_proxy(country: 'US')
puts "US Proxy URL: #{us_proxy[:http_url]}"

# بروكسي من مدينة برلين
berlin_proxy = client.residential_proxy(country: 'DE', city: 'berlin')

# جلسة ثابتة لمدة 30 دقيقة
session = client.sticky_session(country: 'GB', duration: 30)
puts "Session ID: #{session[:session_id]}"
puts "Expires at: #{session[:expires_at]}"

مثال عملي: تجميع 1000 رابط بشكل متوازي

إليك مثال حقيقي لتجميع 1000 رابط باستخدام بروكسيات سكنية متناوبة مع Typhoeus وProxyHat:

require 'typhoeus'
require 'json'
require 'concurrent' # gem install concurrent-ruby

class HighVolumeScraper
  attr_reader :config
  
  def initialize(username:, password:, concurrency: 100)
    @config = {
      gateway: 'gate.proxyhat.com',
      port: 8080,
      username: username,
      password: password
    }
    @concurrency = concurrency
    @stats = {
      total: 0,
      success: 0,
      failed: 0,
      retries: 0
    }
    @stats_mutex = Mutex.new
  end
  
  def scrape_urls(urls, countries: ['US', 'DE', 'GB', 'FR', 'JP'])
    start_time = Time.now
    results = Concurrent::Hash.new
    hydra = Typhoeus::Hydra.new(max_concurrency: @concurrency)
    
    urls.each_with_index do |url, index|
      # تناوب البروكسي حسب الدولة
      country = countries[index % countries.length]
      proxy_user = "#{@config[:username]}-country-#{country}"
      proxy_url = "http://#{proxy_user}:#{@config[:password]}@#{@config[:gateway]}:#{@config[:port]}"
      
      request = build_request(url, proxy_url, index)
      
      request.on_complete do |response|
        handle_response(response, url, results)
      end
      
      hydra.queue(request)
    end
    
    # تنفيذ الطلبات
    hydra.run
    
    elapsed = Time.now - start_time
    print_summary(results, elapsed)
    
    results
  end
  
  def scrape_with_retry(urls, max_retries: 3)
    results = {}
    retry_queue = []
    
    # الجولة الأولى
    urls.each_slice(@concurrency) do |batch|
      batch_results = scrape_batch(batch)
      results.merge!(batch_results[:success])
      retry_queue.concat(batch_results[:failed].keys)
    end
    
    # إعادة المحاولة للطلبات الفاشلة
    retry_count = 0
    while !retry_queue.empty? && retry_count < max_retries
      retry_count += 1
      puts "Retry round #{retry_count}: #{retry_queue.length} URLs"
      
      retry_results = scrape_batch(retry_queue)
      results.merge!(retry_results[:success])
      retry_queue = retry_results[:failed].keys
      
      sleep(2 ** retry_count) # Exponential backoff
    end
    
    results
  end
  
  private
  
  def build_request(url, proxy_url, index)
    Typhoeus::Request.new(
      url,
      method: :get,
      proxy: proxy_url,
      timeout: 30,
      connecttimeout: 15,
      followlocation: true,
      ssl_verifypeer: false, # للبروكسيات التي قد تعترض
      headers: {
        'User-Agent' => random_user_agent,
        'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language' => 'en-US,en;q=0.9',
        'Accept-Encoding' => 'gzip, deflate',
        'X-Request-ID' => SecureRandom.uuid
      }
    )
  end
  
  def handle_response(response, url, results)
    @stats_mutex.synchronize do
      @stats[:total] += 1
    end
    
    if response.success? && response.code == 200
      @stats_mutex.synchronize { @stats[:success] += 1 }
      results[url] = {
        status: :success,
        code: response.code,
        body: response.body,
        time: response.total_time
      }
    elsif response.code == 429
      # Rate limited
      @stats_mutex.synchronize { @stats[:retries] += 1 }
      results[url] = { status: :rate_limited, code: response.code }
    else
      @stats_mutex.synchronize { @stats[:failed] += 1 }
      results[url] = {
        status: :failed,
        code: response.code,
        error: response.return_message
      }
    end
  end
  
  def scrape_batch(urls)
    results = { success: {}, failed: {} }
    hydra = Typhoeus::Hydra.new(max_concurrency: @concurrency)
    mutex = Mutex.new
    
    urls.each do |url|
      proxy_user = "#{@config[:username]}-country-#{['US', 'DE', 'GB'].sample}"
      proxy_url = "http://#{proxy_user}:#{@config[:password]}@#{@config[:gateway]}:#{@config[:port]}"
      
      request = Typhoeus::Request.new(
        url,
        method: :get,
        proxy: proxy_url,
        timeout: 30
      )
      
      request.on_complete do |response|
        mutex.synchronize do
          if response.success?
            results[:success][url] = { body: response.body, code: response.code }
          else
            results[:failed][url] = { error: response.return_message, code: response.code }
          end
        end
      end
      
      hydra.queue(request)
    end
    
    hydra.run
    results
  end
  
  def print_summary(results, elapsed)
    total = results.length
    successful = results.count { |_, r| r[:status] == :success }
    failed = results.count { |_, r| r[:status] == :failed }
    
    puts "\n" + "="*50
    puts "Scraping Summary"
    puts "="*50
    puts "Total URLs: #{total}"
    puts "Successful: #{successful} (#{(successful.to_f / total * 100).round(1)}%)"
    puts "Failed: #{failed}"
    puts "Time elapsed: #{elapsed.round(2)}s"
    puts "Requests/sec: #{(total / elapsed).round(2)}"
    puts "="*50
  end
  
  def random_user_agent
    [
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
      'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
    ].sample
  end
end

# تشغيل المثال
scraper = HighVolumeScraper.new(
  username: ENV['PROXYHAT_USER'],
  password: ENV['PROXYHAT_PASS'],
  concurrency: 100
)

# توليد قائمة URLs للتجربة
test_urls = (1..1000).map do |i|
  "https://httpbin.org/get?id=#{i}&seed=#{rand(1000)}"
end

puts "Starting to scrape #{test_urls.length} URLs..."
results = scraper.scrape_urls(test_urls)

TLS/SSL: التعامل مع الشهادات وإعدادات الأمان

عند استخدام البروكسيات مع HTTPS، هناك اعتبارات خاصة بـ TLS/SSL. إليك كيفية التعامل معها:

require 'net/http'
require 'openssl'

class SecureProxyClient
  # التعامل مع شهادات SSL
  def self.create_ssl_context(verify_mode: OpenSSL::SSL::VERIFY_PEER)
    ssl_context = OpenSSL::SSL::SSLContext.new
    ssl_context.verify_mode = verify_mode
    
    # استخدام متجر الشهادات الافتراضي للنظام
    if defined?(OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT)
      ssl_context.cert_store = OpenSSL::X509::Store.new
      ssl_context.cert_store.set_default_paths
    end
    
    ssl_context
  end
  
  # للتعامل مع شهادات self-signed (في بيئة التطوير فقط)
  def self.create_insecure_ssl_context
    ssl_context = OpenSSL::SSL::SSLContext.new
    ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
    ssl_context
  end
  
  def initialize(proxy_host:, proxy_port:, proxy_user:, proxy_pass:, verify_ssl: true)
    @proxy_host = proxy_host
    @proxy_port = proxy_port
    @proxy_user = proxy_user
    @proxy_pass = proxy_pass
    @ssl_context = verify_ssl ? self.class.create_ssl_context : self.class.create_insecure_ssl_context
  end
  
  def fetch(url_string)
    uri = URI.parse(url_string)
    
    proxy_class = Net::HTTP::Proxy(@proxy_host, @proxy_port, @proxy_user, @proxy_pass)
    http = proxy_class.new(uri.host, uri.port)
    
    if uri.scheme == 'https'
      http.use_ssl = true
      http.ssl_context = @ssl_context
      
      # إعدادات SNI (Server Name Indication)
      http.sni_hostname = uri.host
    end
    
    http.open_timeout = 15
    http.read_timeout = 30
    
    begin
      request = Net::HTTP::Get.new(uri.request_uri)
      request['Host'] = uri.host # مهم لـ SNI عبر البروكسي
      
      response = http.request(request)
      
      {
        success: response.is_a?(Net::HTTPSuccess),
        status: response.code.to_i,
        body: response.body,
        headers: response.each_header.to_h
      }
    rescue OpenSSL::SSL::SSLError => e
      { success: false, error: "SSL Error: #{e.message}" }
    rescue => e
      { success: false, error: e.message }
    ensure
      http.finish if http.started?
    end
  end
  
  # التحقق من شهادة SSL للهدف
  def self.verify_target_cert(hostname, port = 443)
    tcp_socket = TCPSocket.new(hostname, port)
    ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket)
    ssl_socket.hostname = hostname # SNI
    ssl_socket.connect
    
    cert = ssl_socket.peer_cert
    
    result = {
      subject: cert.subject.to_s,
      issuer: cert.issuer.to_s,
      not_before: cert.not_before,
      not_after: cert.not_after,
      serial: cert.serial.to_s
    }
    
    ssl_socket.close
    tcp_socket.close
    
    result
  rescue => e
    { error: e.message }
  end
end

# مثال الاستخدام
client = SecureProxyClient.new(
  proxy_host: 'gate.proxyhat.com',
  proxy_port: 8080,
  proxy_user: 'user-country-US',
  proxy_pass: 'your_password',
  verify_ssl: true
)

# طلب HTTPS عبر البروكسي
result = client.fetch('https://example.com')

# التحقق من شهادة SSL
# cert_info = SecureProxyClient.verify_target_cert('example.com')

التكامل مع Ruby on Rails

عند استخدام البروكسيات في تطبيق Rails، إليك أفضل الممارسات للتكامل:

Faraday Middleware للبروكسي

# Gemfile
gem 'faraday', '~> 2.0'
gem 'faraday-retry'

# lib/middleware/proxy_middleware.rb
require 'faraday'

class ProxyMiddleware < Faraday::Middleware
  def initialize(app, proxy_config:)
    super(app)
    @proxy_config = proxy_config
  end
  
  def call(env)
    # تدوير البروكسي لكل طلب
    proxy = @proxy_config.call(env)
    
    env[:proxy] = {
      uri: "http://#{proxy[:user]}:#{proxy[:pass]}@#{proxy[:host]}:#{proxy[:port]}",
      user: proxy[:user],
      password: proxy[:pass]
    }
    
    @app.call(env)
  end
end

# app/services/proxy_http_client.rb
class ProxyHttpClient
  attr_reader :connection
  
  def initialize(username:, password:, gateway: 'gate.proxyhat.com', port: 8080)
    @username = username
    @password = password
    @gateway = gateway
    @port = port
    @current_country = 'US'
    @countries = ['US', 'DE', 'GB', 'FR', 'JP', 'BR']
  end
  
  def connection(country: nil, session_id: nil)
    proxy_user = build_proxy_user(country, session_id)
    
    Faraday.new do |builder|
      builder.adapter :net_http
      builder.options.timeout = 30
      builder.options.open_timeout = 15
      
      # تكوين البروكسي
      builder.proxy = "http://#{proxy_user}:#{@password}@#{@gateway}:#{@port}"
      
      # إعادة المحاولة
      builder.request :retry, {
        max: 3,
        interval: 1,
        backoff_factor: 2,
        retry_statuses: [429, 500, 502, 503, 504]
      }
      
      # Logging في بيئة التطوير
      if Rails.env.development?
        builder.response :logger, Rails.logger, bodies: true
      end
    end
  end
  
  def get(url, country: nil, session_id: nil)
    conn = connection(country: country, session_id: session_id)
    conn.get(url)
  rescue Faraday::Error => e
    Rails.logger.error("Proxy request failed: #{e.message}")
    raise
  end
  
  def post(url, body:, country: nil)
    conn = connection(country: country)
    conn.post(url, body.to_json, { 'Content-Type' => 'application/json' })
  end
  
  private
  
  def build_proxy_user(country, session_id)
    parts = [@username]
    parts << "country-#{country || rotate_country}"
    parts << "session-#{session_id}" if session_id
    parts.join('-')
  end
  
  def rotate_country
    @current_country = @countries[(@countries.index(@current_country) + 1) % @countries.length]
  end
end

# config/initializers/proxy_client.rb
Rails.application.config.to_prepare do
  PROXY_CLIENT = ProxyHttpClient.new(
    username: ENV['PROXYHAT_USERNAME'],
    password: ENV['PROXYHAT_PASSWORD']
  )
end

# app/jobs/scraping_job.rb
class ScrapingJob < ApplicationJob
  queue_as :scraping
  
  retry_on Faraday::Error, wait: :exponentially_longer, attempts: 5
  
  def perform(url, country: 'US')
    response = PROXY_CLIENT.get(url, country: country)
    
    if response.success?
      process_response(response.body)
    else
      Rails.logger.warn("Scraping failed: #{response.status}")
      raise Faraday::Error, "HTTP #{response.status}"
    end
  end
  
  private
  
  def process_response(body)
    # معالجة البيانات
    data = JSON.parse(body)
    # ...
  end
end

# app/jobs/batch_scraping_job.rb
class BatchScrapingJob < ApplicationJob
  queue_as :scraping
  
  def perform(urls, country: 'US')
    results = []
    
    urls.each_slice(50) do |batch| # معالجة 50 رابط في كل مرة
      batch_results = Parallel.map(batch, in_threads: 10) do |url|
        begin
          response = PROXY_CLIENT.get(url, country: country)
          { url: url, status: :success, data: response.body }
        rescue => e
          { url: url, status: :error, error: e.message }
        end
      end
      
      results.concat(batch_results)
      
      # حفظ النتائج جزئياً
      save_partial_results(batch_results)
    end
    
    results
  end
  
  def save_partial_results(results)
    # حفظ في قاعدة البيانات أو Redis
  end
end

# مثال الاستخدام في Controller
# app/controllers/api/scrape_controller.rb
class Api::ScrapeController < ApplicationController
  def create
    urls = params[:urls]
    country = params[:country] || 'US'
    
    # تشغيل Job في الخلفية
    job_id = BatchScrapingJob.perform_later(urls, country: country)
    
    render json: { 
      status: 'queued', 
      job_id: job_id.job_id 
    }
  end
  
  def show
    status = Sidekiq::Status.get(params[:id])
    render json: status
  end
end

جدولة المهام مع Sidekiq/Pekko

# app/workers/proxy_scraper_worker.rb
require 'sidekiq'

class ProxyScraperWorker
  include Sidekiq::Worker
  sidekiq_options queue: 'scraping', retry: 5, backtrace: true
  
  def perform(urls, options = {})
    @options = options
    @results = []
    
    # تقسيم العمل على عدة عمليات
    urls.each_slice(concurrency) do |batch|
      results = process_batch(batch)
      @results.concat(results)
    end
    
    # حفظ النتائج
    save_results(@results)
  end
  
  private
  
  def process_batch(urls)
    require 'typhoeus'
    
    hydra = Typhoeus::Hydra.new(max_concurrency: concurrency)
    results = []
    mutex = Mutex.new
    
    urls.each do |url|
      proxy_url = build_proxy_url
      
      request = Typhoeus::Request.new(
        url,
        method: :get,
        proxy: proxy_url,
        timeout: timeout,
        headers: request_headers
      )
      
      request.on_complete do |response|
        mutex.synchronize do
          results << {
            url: url,
            status: response.code,
            success: response.success?,
            body: response.body,
            time: response.total_time
          }
        end
      end
      
      hydra.queue(request)
    end
    
    hydra.run
    results
  end
  
  def build_proxy_url
    country = @options[:country] || 'US'
    session = @options[:sticky_session] ? "-session-#{SecureRandom.hex(8)}" : ''
    
    "http://#{username}-country-#{country}#{session}:#{password}@gate.proxyhat.com:8080"
  end
  
  def concurrency
    @options[:concurrency] || 50
  end
  
  def timeout
    @options[:timeout] || 30
  end
  
  def username
    ENV['PROXYHAT_USERNAME']
  end
  
  def password
    ENV['PROXYHAT_PASSWORD']
  end
  
  def request_headers
    {
      'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
      'Accept' => 'text/html,application/xhtml+xml'
    }
  end
  
  def save_results(results)
    # حفظ في قاعدة البيانات
    ScrapeResult.insert_all(
      results.map do |r|
        {
          url: r[:url],
          status_code: r[:status],
          body: r[:body],
          created_at: Time.current
        }
      end
    )
  end
end

# جدولة المهمة
ProxyScraperWorker.perform_async(
  ['https://example.com/1', 'https://example.com/2'],
  { country: 'US', concurrency: 100 }
)

مقارنة الأساليب المختلفة

اختيار المكتبة المناسبة يعتمد على متطلبات مشروعك:

المعيار Net::HTTP Typhoeus ProxyHat SDK
التوازي محدود (Threads) ممتاز (Hydra) ممتاز (مدمج)
سهولة الاستخدام متوسطة متوسطة عالية
إدارة البروكسي يدوية يدوية آلية
التناوب الجغرافي يدوي يدوي مدمج
الأداء (طلب/ثانية) 10-50 100-500+ 100-500+
التبعيات لا شيء (stdlib) libcurl net/http أو typhoeus
الحالة المناسبة طلبات بسيطة تجريد مكثف إنتاج معقد
نصيحة: ابدأ بـ Net::HTTP للنماذج الأولية، ثم انتقل إلى Typhoeus أو ProxyHat SDK عندما تحتاج لأداء عالٍ. استخدم ProxyHat SDK دائماً في الإنتاج لإدارة التناوب والاستهداف الجغرافي تلقائياً.

أفضل الممارسات

  • استخدم Connection Pooling: أعد استخدام الاتصالات عندما أمكن لتقليل overhead.
  • حدد Timeouts مناسبة: لا تترك الطلبات بدون timeout. 15-30 ثانية مناسبة لمعظم الحالات.
  • نفذ Circuit Breaker: أوقف الطلبات مؤقتاً عند فشل عدة طلبات متتالية.
  • سجل الأخطاء: احتفظ بسجلات مفصلة للفشل مع معلومات البروكسي المستخدم.
  • احترم robots.txt: تحقق من ملف robots.txt ولا تتجاوز حدود المعدل.
  • استخدم User-Agent متنوع: قم بتدوير User-Agent لتجنب الكشف.

النقاط الرئيسية

  • Net::HTTP مناسبة للطلبات البسيطة وتتوفر في المكتبة القياسية بدون تبعيات إضافية.
  • Typhoeus مع Hydra يوفر أداءً عالياً للطلبات المتوازية مع إدارة فعالة للاتصالات.
  • ProxyHat SDK يبسط إدارة التناوب الجغرافي والجلسات الثابتة مع بروكسيات سكنية.
  • التكامل مع Rails عبر Faraday middleware وActiveJob يسهل بناء خطوط معالجة بيانات قابلة للتوسع.
  • إعدادات SSL/TLS مهمة عند التعامل مع HTTPS عبر البروكسي، خاصة SNI والتحقق من الشهادات.
  • التوازي يجب أن يكون مدروساً - ابدأ بتوازي منخفض (50-100) وزد تدريجياً حسب الاستجابة.

للمزيد من المعلومات حول أنواع البروكسيات المتاحة وحالات الاستخدام المختلفة، راجع دليل تجميع البيانات وخطط الأسعار. يمكنك أيضاً استكشاف مواقع البروكسيات المتاحة حول العالم.

¿Listo para empezar?

Accede a más de 50M de IPs residenciales en más de 148 países con filtrado impulsado por IA.

Ver preciosProxies residenciales
← Volver al Blog