بازگشت به وبلاگ

تنظیم پروکسی در فریم‌ورک اسکریپی: راهنمای کامل با مثال‌های کد

راهنمای کامل ادغام پروکسی در Scrapy: از تنظیمات پایه تا روش‌های پیشرفته چرخش آدرس‌های IP با مثال‌های کد کارا.

📅۲۵ بهمن ۱۴۰۴
```html

Scrapy یکی از قدرتمندترین فریم‌ورک‌های Python برای وب‌اسکرپینگ است، اما بدون تنظیمات صحیح پروکسی، پارسرهای شما تنها پس از چند دقیقه کار مسدود خواهند شد. در این راهنما، من تمام روش‌های ادغام پروکسی در Scrapy را نشان می‌دهم: از ساده‌ترین تنظیمات تا روش‌های پیشرفته چرخش آدرس‌های IP با پردازش خودکار خطاها.

این محتوا بر اساس تجربه واقعی پارس کردن وب‌سایت‌های بزرگ تجارت الکترونیک و سایت‌های محافظت‌شده است. شما مثال‌های آماده کدی را دریافت خواهید کرد که می‌توانید به راحتی در پروژه‌های خود استفاده کنید.

چرا Scrapy بدون پروکسی مسدود می‌شود

وب‌سایت‌های مدرن از حفاظت چندلایه‌ای در برابر پارس کردن استفاده می‌کنند. حتی اگر User-Agent و تأخیر بین درخواست‌ها را تنظیم کرده باشید، آدرس IP شما به چندین نشانه، اتوماسیون را نشان می‌دهد:

  • فرکانس درخواست‌ها: یک IP بیش از 100 درخواست در دقیقه ارسال می‌کند — نشانه واضحی از ربات است
  • الگوهای رفتاری: مرور متوالی صفحات بدون انتقال‌های تصادفی
  • عدم وجود JavaScript: Scrapy JS را اجرا نمی‌کند، که به راحتی قابل شناسایی است
  • موقعیت جغرافیایی: دسترسی از مرکز داده به جای شبکه خانگی

نتیجه — مسدودیت IP به مدت چند ساعت یا چند روز. به ویژه، بازارهای آنلاین (Amazon، Wildberries، Ozon)، شبکه‌های اجتماعی و وب‌سایت‌های با Cloudflare از حفاظت بسیار شدید استفاده می‌کنند. پروکسی این مشکل را با توزیع درخواست‌ها بین چندین آدرس IP حل می‌کند.

مهم: حتی با پروکسی باید از محدودیت‌های نرخ پیروی کنید. سرعت توصیه‌شده: 1-3 درخواست در ثانیه برای هر IP. برای پارس کردن با سرعت بالا، از مجموعه‌ای از 50+ پروکسی با چرخش استفاده کنید.

تنظیمات پایه پروکسی در Scrapy

ساده‌ترین روش — مشخص کردن پروکسی به طور مستقیم در تنظیمات عنکبوت. این روش برای آزمایش یا پارس کردن حجم‌های کوچک داده‌ها با یک سرور پروکسی مناسب است.

روش 1: از طریق meta در Request

import scrapy

class MySpider(scrapy.Spider):
    name = 'example'
    start_urls = ['https://example.com']
    
    def start_requests(self):
        proxy = 'http://username:password@proxy.example.com:8080'
        
        for url in self.start_urls:
            yield scrapy.Request(
                url=url,
                callback=self.parse,
                meta={'proxy': proxy}
            )
    
    def parse(self, response):
        # منطق پارس کردن شما
        self.log(f'Scraped {response.url} via {response.meta["proxy"]}')

فرمت پروکسی بستگی به پروتکل و روش احراز هویت دارد:

  • http://proxy.example.com:8080 — بدون احراز هویت
  • http://user:pass@proxy.example.com:8080 — با نام کاربری/رمز عبور
  • socks5://user:pass@proxy.example.com:1080 — پروکسی SOCKS5

روش 2: تنظیمات جهانی در settings.py

# settings.py

# پروکسی HTTP برای تمام درخواست‌ها
HTTPPROXY_ENABLED = True
HTTPPROXY_AUTH_ENCODING = 'utf-8'

# تنظیمات از طریق متغیرهای محیطی
HTTP_PROXY = 'http://username:password@proxy.example.com:8080'
HTTPS_PROXY = 'http://username:password@proxy.example.com:8080'

این روش برای آزمایش سریع مناسب است، اما برای تولید مناسب نیست: چرخش IP وجود ندارد، در صورت سقوط پروکسی، کل پارسر متوقف می‌شود و نمی‌توان از پروکسی‌های مختلف برای وب‌سایت‌های مختلف استفاده کرد.

ایجاد Middleware پروکسی سفارشی

برای پارس کردن در تولید، به یک middleware سفارشی نیاز است که مدیریت مجموعه پروکسی، پردازش خطاها و چرخش خودکار IP را انجام دهد. در اینجا یک پیاده‌سازی پایه‌ای آورده شده است:

# middlewares.py

import random
from scrapy import signals
from scrapy.exceptions import NotConfigured

class RandomProxyMiddleware:
    def __init__(self, proxy_list):
        self.proxy_list = proxy_list
    
    @classmethod
    def from_crawler(cls, crawler):
        # بارگذاری لیست پروکسی از تنظیمات
        proxy_list = crawler.settings.getlist('PROXY_LIST')
        
        if not proxy_list:
            raise NotConfigured('PROXY_LIST not configured')
        
        return cls(proxy_list)
    
    def process_request(self, request, spider):
        # انتخاب یک پروکسی تصادفی از مجموعه
        proxy = random.choice(self.proxy_list)
        request.meta['proxy'] = proxy
        
        spider.logger.info(f'Using proxy: {proxy}')
    
    def process_exception(self, request, exception, spider):
        # در صورت بروز خطا، پروکسی دیگری را امتحان می‌کنیم
        proxy = random.choice(self.proxy_list)
        request.meta['proxy'] = proxy
        
        spider.logger.warning(
            f'Proxy error, switching to: {proxy}'
        )
        
        return request

حالا استفاده از middleware را در settings.py تنظیم می‌کنیم:

# settings.py

# لیست پروکسی (می‌توان از فایل یا API بارگذاری کرد)
PROXY_LIST = [
    'http://user1:pass1@proxy1.example.com:8080',
    'http://user2:pass2@proxy2.example.com:8080',
    'http://user3:pass3@proxy3.example.com:8080',
    # ... 50+ پروکسی برای چرخش مؤثر اضافه کنید
]

# اتصال middleware
DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.RandomProxyMiddleware': 350,
    'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 400,
}

# تلاش‌های مجدد در صورت بروز خطا
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429]

چرخش پروکسی: سه روش عملی

انتخاب تصادفی پروکسی (مانند مثال بالا) ساده‌ترین، اما مؤثرترین روش نیست. بیایید سه استراتژی چرخش را برای سناریوهای مختلف بررسی کنیم.

روش 1: Round-robin (چرخش متوالی)

پروکسی‌ها به صورت گردشی انتخاب می‌شوند. این روش برای توزیع بار به طور یکنواخت مناسب است:

class RoundRobinProxyMiddleware:
    def __init__(self, proxy_list):
        self.proxy_list = proxy_list
        self.current_index = 0
    
    @classmethod
    def from_crawler(cls, crawler):
        proxy_list = crawler.settings.getlist('PROXY_LIST')
        return cls(proxy_list)
    
    def process_request(self, request, spider):
        # انتخاب پروکسی بعدی به صورت گردشی
        proxy = self.proxy_list[self.current_index]
        self.current_index = (self.current_index + 1) % len(self.proxy_list)
        
        request.meta['proxy'] = proxy

روش 2: چرخش هوشمند با لیست سیاه

پروکسی‌های مشکل‌دار را پیگیری کرده و به طور موقت از چرخش خارج می‌کنیم:

import time
from collections import defaultdict

class SmartProxyMiddleware:
    def __init__(self, proxy_list):
        self.proxy_list = proxy_list
        self.proxy_errors = defaultdict(int)
        self.blacklist = set()
        self.blacklist_timeout = 300  # 5 دقیقه
        self.blacklist_time = {}
    
    @classmethod
    def from_crawler(cls, crawler):
        proxy_list = crawler.settings.getlist('PROXY_LIST')
        return cls(proxy_list)
    
    def get_working_proxies(self):
        # حذف پروکسی‌هایی که زمان لیست سیاه آنها منقضی شده است
        current_time = time.time()
        expired = [
            proxy for proxy, ban_time in self.blacklist_time.items()
            if current_time - ban_time > self.blacklist_timeout
        ]
        
        for proxy in expired:
            self.blacklist.discard(proxy)
            self.proxy_errors[proxy] = 0
        
        # بازگشت پروکسی‌های کارآمد
        return [p for p in self.proxy_list if p not in self.blacklist]
    
    def process_request(self, request, spider):
        working_proxies = self.get_working_proxies()
        
        if not working_proxies:
            spider.logger.error('All proxies are blacklisted!')
            return
        
        proxy = random.choice(working_proxies)
        request.meta['proxy'] = proxy
    
    def process_response(self, request, response, spider):
        # اگر مسدودیت دریافت کردیم — به لیست سیاه اضافه می‌کنیم
        if response.status in [403, 429, 503]:
            proxy = request.meta.get('proxy')
            self.proxy_errors[proxy] += 1
            
            if self.proxy_errors[proxy] >= 3:
                self.blacklist.add(proxy)
                self.blacklist_time[proxy] = time.time()
                spider.logger.warning(
                    f'Proxy {proxy} blacklisted for {self.blacklist_timeout}s'
                )
        
        return response

روش 3: چرخش از طریق API ارائه‌دهنده

بسیاری از ارائه‌دهندگان پروکسی (از جمله پروکسی‌های مسکونی) یک endpoint چرخشی ارائه می‌دهند — یک URL که به طور خودکار IP را در هر درخواست تغییر می‌دهد:

# settings.py

# یک endpoint واحد با چرخش خودکار
ROTATING_PROXY = 'http://username:password@rotating.proxy.com:8080'

# middleware ساده
class RotatingProxyMiddleware:
    def __init__(self, proxy):
        self.proxy = proxy
    
    @classmethod
    def from_crawler(cls, crawler):
        proxy = crawler.settings.get('ROTATING_PROXY')
        return cls(proxy)
    
    def process_request(self, request, spider):
        # یک URL، اما هر درخواست با IP جدید انجام می‌شود
        request.meta['proxy'] = self.proxy

این روش برای تولید بسیار راحت است: نیازی به مدیریت مجموعه پروکسی نیست، ارائه‌دهنده خود کیفیت IP را زیر نظر دارد و پروکسی‌های مشکل‌دار را جایگزین می‌کند. به ویژه با پروکسی‌های مسکونی که مجموعه IP می‌تواند به میلیون‌ها آدرس برسد، به طور مؤثر کار می‌کند.

احراز هویت: نام کاربری/رمز عبور در مقابل لیست سفید IP

ارائه‌دهندگان پروکسی دو روش احراز هویت را ارائه می‌دهند. انتخاب بر سرعت اتصال و راحتی تنظیم تأثیر می‌گذارد.

احراز هویت User:Pass

نام کاربری و رمز عبور در URL پروکسی منتقل می‌شوند. Scrapy به طور خودکار آنها را به هدر HTTP Proxy-Authorization تبدیل می‌کند:

proxy = 'http://username:password@proxy.example.com:8080'
request.meta['proxy'] = proxy

# Scrapy به طور خودکار هدر را اضافه می‌کند:
# Proxy-Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

مزایا: از هر IP کار می‌کند، تغییر پروکسی در کد آسان است.
معایب: کمی overhead در هر درخواست (~50-100ms)، اطلاعات کاربری به صورت واضح در کد.

احراز هویت IP Whitelist

شما IP سرور خود را به لیست سفید در ارائه‌دهنده اضافه می‌کنید، احراز هویت لازم نیست:

proxy = 'http://proxy.example.com:8080'  # بدون نام کاربری/رمز عبور
request.meta['proxy'] = proxy

مزایا: سریع‌تر به میزان 50-100ms، ایمن‌تر (هیچ اطلاعات کاربری در کد وجود ندارد).
معایب: فقط از IP‌های مشخص کار می‌کند، نیاز به به‌روزرسانی لیست سفید در صورت تغییر سرور دارد.

توصیه برای تولید:

از لیست سفید IP برای پارس کردن از سرورهای اختصاصی (AWS، Google Cloud، Hetzner) استفاده کنید. برای توسعه و آزمایش از ماشین محلی — احراز هویت user:pass استفاده کنید.

مدیریت خطاها و تغییر خودکار IP

حتی با پروکسی‌های باکیفیت نیز خطاهایی وجود خواهد داشت: زمان‌های تایم‌اوت، رد درخواست‌ها، مسدودیت‌ها. مدیریت صحیح خطاها برای عملکرد پایدار پارسر حیاتی است.

مدیریت وضعیت‌های HTTP

class ProxyMiddleware:
    def process_response(self, request, response, spider):
        # کدهایی که باید پروکسی را تغییر داده و دوباره تلاش کنیم
        ban_codes = [403, 407, 429, 503]
        
        if response.status in ban_codes:
            proxy = request.meta.get('proxy')
            spider.logger.warning(
                f'Got {response.status} from {proxy}, retrying...'
            )
            
            # علامت‌گذاری برای تلاش مجدد با پروکسی جدید
            request.meta['dont_retry'] = False
            request.meta['proxy'] = self.get_new_proxy()
            
            return request
        
        return response

مدیریت استثناهای شبکه

from twisted.internet.error import TimeoutError, ConnectionRefusedError
from scrapy.exceptions import IgnoreRequest

class ProxyMiddleware:
    def process_exception(self, request, exception, spider):
        # خطاهای اتصال به پروکسی
        proxy_errors = (
            TimeoutError,
            ConnectionRefusedError,
            ConnectionLost,
        )
        
        if isinstance(exception, proxy_errors):
            proxy = request.meta.get('proxy')
            spider.logger.error(
                f'Proxy {proxy} connection failed: {exception}'
            )
            
            # تغییر پروکسی و تلاش دوباره
            request.meta['proxy'] = self.get_new_proxy()
            return request
        
        # برای سایر خطاها از مدیریت استاندارد استفاده می‌کنیم
        return None

شناسایی مسدودیت‌ها بر اساس محتوا

برخی وب‌سایت‌ها HTTP 200 را بازمی‌گردانند، اما کد کپچا یا صفحه مسدودیت را نشان می‌دهند:

class ProxyMiddleware:
    def process_response(self, request, response, spider):
        # نشانه‌های مسدودیت در محتوا
        ban_indicators = [
            'captcha',
            'access denied',
            'blocked',
            'unusual traffic',
            'robot check',
        ]
        
        body_text = response.text.lower()
        
        if any(indicator in body_text for indicator in ban_indicators):
            spider.logger.warning(
                f'Ban page detected from {request.meta.get("proxy")}'
            )
            
            # تغییر پروکسی و تکرار
            request.meta['proxy'] = self.get_new_proxy()
            return request
        
        return response

کدام نوع پروکسی را برای Scrapy انتخاب کنیم

انتخاب نوع پروکسی بستگی به وب‌سایت هدف، بودجه و سرعت مورد نیاز پارس کردن دارد. در اینجا مقایسه‌ای از گزینه‌های اصلی آورده شده است:

نوع پروکسی سرعت هزینه چه زمانی استفاده کنیم
پروکسی مرکز داده بالا (50-200ms) پایین ($1-3/IP) وب‌سایت‌های ساده بدون حفاظت، API، ابزارهای داخلی
پروکسی‌های مسکونی متوسط (300-800ms) متوسط ($5-15/GB) تجارت الکترونیک، شبکه‌های اجتماعی، وب‌سایت‌های با Cloudflare، هدف‌گذاری جغرافیایی
پروکسی‌های موبایل پایین (500-1500ms) بالا ($50-150/IP) برنامه‌های موبایل، Instagram، TikTok، حداکثر حفاظت

توصیه‌ها برای انتخاب

برای پارس کردن بازارهای آنلاین (Amazon، Wildberries، Ozon، AliExpress) — فقط پروکسی‌های مسکونی. این وب‌سایت‌ها به شدت مرکز داده‌ها را مسدود می‌کنند. نیاز به چرخش و هدف‌گذاری جغرافیایی (به عنوان مثال، IP‌های روسی برای Wildberries) دارید.

برای پارس کردن وب‌سایت‌های خبری، وبلاگ‌ها، فروم‌ها — پروکسی‌های مرکز داده مناسب هستند. حفاظت حداقلی است و سرعت و هزینه پایین ترافیک مهم است.

برای پارس کردن وب‌سایت‌های با Cloudflare — پروکسی‌های مسکونی الزامی هستند. مرکز داده‌های Cloudflare تقریباً به سرعت شناسایی می‌شوند. کتابخانه cloudscraper را به Scrapy اضافه کنید تا از چالش‌های JS عبور کنید.

برای پارس کردن Google Search، ابزارهای SEO — پروکسی‌های مسکونی با هدف‌گذاری جغرافیایی. Google نتایج مختلفی را برای کشورهای مختلف و شهرها نشان می‌دهد.

نکته: با مجموعه‌ای از 10 پروکسی مسکونی برای آزمایش شروع کنید. اگر مسدودیت دریافت کردید — مجموعه را به 50-100 IP افزایش دهید. برای پارس کردن با سرعت بالا (1000+ درخواست در دقیقه) از endpoint چرخشی با مجموعه‌ای از 10,000+ IP استفاده کنید.

تکنیک‌های پیشرفته: سشن‌ها و IP ثابت

هنگام پارس کردن برخی وب‌سایت‌ها، باید یک IP را در طول کل سشن حفظ کنید (احراز هویت، سبد خرید، فرم‌های چند مرحله‌ای). در اینجا نحوه پیاده‌سازی سشن‌های ثابت در Scrapy آورده شده است.

IP ثابت برای یک دامنه

from urllib.parse import urlparse

class StickyProxyMiddleware:
    def __init__(self, proxy_list):
        self.proxy_list = proxy_list
        # دیکشنری: دامنه -> پروکسی
        self.domain_proxy_map = {}
    
    @classmethod
    def from_crawler(cls, crawler):
        proxy_list = crawler.settings.getlist('PROXY_LIST')
        return cls(proxy_list)
    
    def process_request(self, request, spider):
        # استخراج دامنه از URL
        domain = urlparse(request.url).netloc
        
        # اگر برای این دامنه پروکسی وجود دارد — از آن استفاده کنید
        if domain in self.domain_proxy_map:
            proxy = self.domain_proxy_map[domain]
        else:
            # در غیر این صورت یک پروکسی جدید انتخاب کرده و ذخیره کنید
            proxy = random.choice(self.proxy_list)
            self.domain_proxy_map[domain] = proxy
            spider.logger.info(f'Assigned {proxy} to {domain}')
        
        request.meta['proxy'] = proxy

IP ثابت با زمان تایم‌اوت سشن

یک گزینه پیشرفته‌تر: پروکسی به دامنه برای مدت معین (به عنوان مثال، 10 دقیقه) متصل می‌شود و سپس تغییر می‌کند:

import time
from urllib.parse import urlparse

class SessionProxyMiddleware:
    def __init__(self, proxy_list, session_timeout=600):
        self.proxy_list = proxy_list
        self.session_timeout = session_timeout  # 10 دقیقه
        # دیکشنری: دامنه -> (پروکسی، زمان ایجاد)
        self.sessions = {}
    
    @classmethod
    def from_crawler(cls, crawler):
        proxy_list = crawler.settings.getlist('PROXY_LIST')
        timeout = crawler.settings.getint('PROXY_SESSION_TIMEOUT', 600)
        return cls(proxy_list, timeout)
    
    def get_proxy_for_domain(self, domain):
        current_time = time.time()
        
        # بررسی اینکه آیا سشن فعالی وجود دارد
        if domain in self.sessions:
            proxy, created_at = self.sessions[domain]
            
            # اگر سشن منقضی نشده — از همان پروکسی استفاده کنید
            if current_time - created_at < self.session_timeout:
                return proxy
        
        # ایجاد یک سشن جدید با پروکسی جدید
        new_proxy = random.choice(self.proxy_list)
        self.sessions[domain] = (new_proxy, current_time)
        
        return new_proxy
    
    def process_request(self, request, spider):
        domain = urlparse(request.url).netloc
        proxy = self.get_proxy_for_domain(domain)
        request.meta['proxy'] = proxy

ادغام با Cookie Middleware

برای سشن‌های کامل، باید پروکسی و کوکی‌ها را همگام‌سازی کنید. Scrapy کوکی‌ها را به طور جداگانه برای هر دامنه ذخیره می‌کند، اما هنگام تغییر پروکسی باید کوکی‌ها را پاک کنید:

# settings.py

# فعال کردن middleware کوکی
COOKIES_ENABLED = True
COOKIES_DEBUG = False

# Middleware برای همگام‌سازی پروکسی و کوکی‌ها
class ProxyCookieMiddleware:
    def process_request(self, request, spider):
        # دریافت پروکسی فعلی
        current_proxy = request.meta.get('proxy')
        
        # اگر پروکسی تغییر کرده — کوکی‌ها را پاک کنید
        previous_proxy = request.meta.get('previous_proxy')
        
        if previous_proxy and previous_proxy != current_proxy:
            # پاک کردن کوکی‌ها برای این دامنه
            jar = spider.crawler.engine.downloader.middleware.middlewares[0].jars
            domain = urlparse(request.url).netloc
            
            if domain in jar:
                jar[domain].clear()
                spider.logger.info(f'Cleared cookies for {domain}')
        
        request.meta['previous_proxy'] = current_proxy

نتیجه‌گیری

تنظیم صحیح پروکسی در Scrapy اساس پارس پایدار بدون مسدودیت است. ما تمام جنبه‌های کلیدی را بررسی کردیم: از ادغام پایه‌ای تا تکنیک‌های پیشرفته چرخش و مدیریت سشن‌ها.

نکات اصلی:

  • برای تولید از middleware سفارشی با چرخش هوشمند و لیست سیاه IP‌های مشکل‌دار استفاده کنید
  • تمام انواع خطاها را مدیریت کنید: وضعیت‌های HTTP، استثناهای شبکه، مسدودیت‌ها بر اساس محتوا
  • نوع پروکسی را بر اساس نیاز انتخاب کنید: مرکز داده‌ها برای وب‌سایت‌های ساده، پروکسی‌های مسکونی برای وب‌سایت‌های محافظت‌شده
  • برای وب‌سایت‌های با احراز هویت از سشن‌های ثابت با اتصال پروکسی به دامنه استفاده کنید
  • با مجموعه‌ای از 10-50 پروکسی شروع کنید و در صورت افزایش بار، مقیاس را افزایش دهید

اگر قصد دارید وب‌سایت‌های محافظت‌شده (بازارهای آنلاین، شبکه‌های اجتماعی، وب‌سایت‌های با Cloudflare) را پارس کنید، توصیه می‌کنم از پروکسی‌های مسکونی استفاده کنید — آنها حداکثر ناشناسی و حداقل خطر مسدودیت را فراهم می‌کنند. برای پارس کردن با سرعت بالا، ارائه‌دهندگانی با endpoint چرخشی و مجموعه‌ای از 10,000 آدرس IP انتخاب کنید.

تمام مثال‌های کد این مقاله بر روی Scrapy 2.x آزمایش شده و آماده استفاده در تولید هستند. آنها را بر اساس نیازهای خود تنظیم کرده و با رشد پروژه مقیاس دهید.

```