إذا كنت مطور 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) وزد تدريجياً حسب الاستجابة.
للمزيد من المعلومات حول أنواع البروكسيات المتاحة وحالات الاستخدام المختلفة، راجع دليل تجميع البيانات وخطط الأسعار. يمكنك أيضاً استكشاف مواقع البروكسيات المتاحة حول العالم.






