خانه/مقالات/شخصی‌سازی Scrapy برای وب اسکریپینگ حرفه‌ای
وب اسکریپینگ
اتوماسیون
ضد بلاک (Anti-bot)
برگشت به صفحه مقاله ها
شخصی‌سازی Scrapy برای وب اسکریپینگ حرفه‌ای

شخصی‌سازی Scrapy برای وب اسکریپینگ حرفه‌ای

راهنمایی عملی برای شخصی‌سازی Scrapy در وب اسکریپینگ: افزونه‌ها برای مانیتورینگ، میدل‌ویرها برای کنترل درخواست/پاسخ و پایپلاین‌ها برای پردازش و ذخیره داده. شامل مثال‌های کد (افزونه لاگر، middleware برای retry و pipeline برای sqlite)، توضیحات مرحله‌به‌مرحله و نکات بهینه‌سازی و امنیت.
امیر حسین حسینیان
امیر حسین حسینیان
1404-11-26

مقدمه

این مقاله یک راهنمای عملی برای شخصی‌سازی Scrapy در پروژه‌های وب اسکریپینگ است. هدف این راهنما این است که پس از خواندن آن شما بدانید چه زمانی باید از extensions، middlewares و pipelines استفاده کنید، چگونه نمونه‌های عملی بنویسید و آن‌ها را در پروژه‌تان فعال و تست کنید. مثال‌ها به زبان Python و با کد قابل‌اجرا ارائه شده‌اند تا بتوانید سریع شروع به اسکریپ کردن کنید.

چرا Scrapy را شخصی‌سازی کنیم؟

Scrapy به‌صورت پیش‌فرض بسیار قدرتمند و غیرهمزمان است، اما هر هدف اسکریپ متفاوت است. با شخصی‌سازی می‌توانید رفتار کلی پروژه، جریان درخواست/پاسخ و پردازش آیتم‌ها را دقیقاً مطابق نیازتان تغییر دهید:

  • افزونه‌ها (Extensions) برای تغییر رفتار سطح پروژه و مدیریت رخدادها مناسب‌اند.
  • میان‌افزارها (Middlewares) برای دستکاری درخواست‌ها و پاسخ‌ها در مسیر بین engine و spider کاربرد دارند.
  • پایپلاین‌ها (Pipelines) برای پاک‌سازی، اعتبارسنجی و ذخیره‌سازی داده‌ها بعد از استخراج مناسب‌اند.

در ادامه هر بخش را با مثال واقعی توضیح می‌دهیم: یک افزونه برای لاگ‌گیری زمان اجرای اسپایدر، یک downloader middleware برای منطق retry سفارشی و یک pipeline برای ذخیره در sqlite.

Extensions — مفهوم و مثال عملی

افزونه‌ها برای hook زدن به سیگنال‌های سطح پروژه استفاده می‌شوند؛ مثلاً زمانی که یک اسپایدر باز یا بسته می‌شود یا وقتی یک آیتم اسکریپ شد. آن‌ها برای مانیتورینگ، ارسال هشدار و جمع‌آوری آمار کلی بسیار مناسب‌اند. مزیت اصلی: تغییر رفتار در یک نقطه مرکزی؛ ایراد بالقوه: باگ در افزونه می‌تواند تمام اسپایدرها را تحت‌تأثیر قرار دهد.

مثال: یک افزونه ساده که زمان شروع/پایان اسپایدر و وضعیت پاسخ‌ها را در یک فایل لاگ ذخیره می‌کند.

import logging
from datetime import datetime
from scrapy import signals
class CustomLoggerExtension:
    def __init__(self, stats, log_file):
        # ورودی‌ها: stats (آمار داخلی Scrapy)، log_file (مسیر فایل لاگ)
        self.stats = stats
        self.log_file = log_file
        self.start_time = None
        self.end_time = None
        self.logger = logging.getLogger('custom_logger')
        self.logger.setLevel(logging.INFO)
        handler = logging.FileHandler(self.log_file)
        handler.setLevel(logging.INFO)
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)
        self.logger.addHandler(handler)
    @classmethod
    def from_crawler(cls, crawler):
        # نقطه اتصال به runtime: تنظیمات را می‌خواند و سیگنال‌ها را متصل می‌کند
        log_file = crawler.settings.get('CUSTOM_LOG_FILE', 'custom_log.txt')
        ext = cls(crawler.stats, log_file)
        crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened)
        crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed)
        crawler.signals.connect(ext.response_received, signal=signals.response_received)
        return ext
    def spider_opened(self, spider):
        # اجرا هنگام باز شدن اسپایدر — خروجی: ثبت زمان شروع
        self.start_time = datetime.now()
        self.logger.info(f'Spider {spider.name} opened')
    def spider_closed(self, spider):
        # اجرا هنگام بسته شدن اسپایدر — خروجی: ثبت زمان پایان و مدت اجرا
        self.end_time = datetime.now()
        runtime = self.end_time - self.start_time
        self.logger.info(f'Spider {spider.name} closed. Time elapsed: {runtime}')
    def response_received(self, response, request, spider):
        # اجرا برای هر پاسخ دریافتی — خروجی: ثبت کد وضعیت و URL
        self.logger.info(f'Response received, status: {response.status} for {response.url}')

توضیح خط به خط (خلاصه):

  • __init__: منابع لازم را نگه می‌دارد و یک لاگر فایل تنظیم می‌کند.
  • from_crawler: تنظیمات را می‌خواند و متدهای افزونه را به سیگنال‌های مناسب متصل می‌کند.
  • spider_opened / spider_closed: زمان شروع/پایان را ثبت می‌کنند و به‌عنوان خروجی در فایل لاگ نوشته می‌شوند.
  • response_received: برای هر پاسخ فراخوانی می‌شود و اطلاعات مفید برای دیباگ را لاگ می‌کند.

Middlewares — کنترل جریان درخواست/پاسخ

میان‌افزارها اجازه می‌دهند رفتار HTTP شما (مثل user-agent، پراکسی، کوکی‌ها و retry) را وسط کار تغییر دهید. دو نوع اصلی وجود دارد: downloader middleware (کاری با request/response بین engine و downloader دارد) و spider middleware (قبل از رسیدن response به اسپایدر اجرا می‌شود).

در ادامه یک downloader middleware برای پیاده‌سازی منطق retry سفارشی می‌بینید.

from scrapy.exceptions import IgnoreRequest
from scrapy.utils.response import response_status_message
class CustomRetryMiddleware:
    def __init__(self, retry_times, retry_http_codes):
        # ورودی‌ها: حداکثر دفعات retry و لیست کدهای HTTP برای retry
        self.retry_times = retry_times
        self.retry_http_codes = set(retry_http_codes)
    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            retry_times=crawler.settings.getint('RETRY_TIMES', 3),
            retry_http_codes=crawler.settings.getlist('RETRY_HTTP_CODES', [500, 502, 503, 504, 522, 524, 408])
        )
    def process_request(self, request, spider):
        # ورودی: request؛ خروجی: معمولاً None (اجازه ادامه جریان)
        spider.logger.info(f'Modifying request: {request.url}')
        return None
    def process_response(self, request, response, spider):
        # منطق retry روی پاسخ‌ها انجام می‌شود
        retries = request.meta.get('retry_times', 0)
        if retries > 0:
            spider.logger.info(f'Retry response received: {response.status} for {response.url} (Retry {retries})')
        else:
            spider.logger.info(f'Processing response: {response.status} for {response.url}')
        if response.status in self.retry_http_codes:
            spider.logger.warning(f'Retrying {response.url} due to HTTP {response.status}')
            return self._retry(request, response_status_message(response.status), spider)
        return response
    def process_exception(self, request, exception, spider):
        # در صورت استثنا، سعی می‌کنیم مجدداً درخواست را اجرا کنیم
        spider.logger.warning(f'Exception encountered: {exception} for {request.url}')
        return self._retry(request, str(exception), spider)
    def _retry(self, request, reason, spider):
        retries = request.meta.get('retry_times', 0) + 1
        if retries <= self.retry_times:
            spider.logger.info(f'Retrying {request.url} ({retries}/{self.retry_times}) due to: {reason}')
            retry_req = request.copy()
            retry_req.meta['retry_times'] = retries
            retry_req.dont_filter = True
            return retry_req
        else:
            spider.logger.error(f'Gave up retrying {request.url} after {self.retry_times} attempts')
            raise IgnoreRequest(f'Request failed after retries: {request.url}')

نکات و بهترین روش‌ها برای middleware:

  • برای جلوگیری از loop نگه‌داشتن شمارنده retries در request.meta لازم است.
  • برای حفظ کارآیی از پردازش سنگین در process_request خودداری کنید؛ معمولا کارهای I/O باید در لایه‌ای دیگر انجام شود.
  • در تنظیمات، اولویت (عدد) می‌تواند ترتیب فراخوانی میان‌افزارها را تغییر دهد.

Pipelines — پردازش و ذخیره داده‌ها

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

مثال: ذخیره آیتم‌ها در یک دیتابیس sqlite و حذف آیتم‌هایی که فیلدهای ضروری را ندارند.

import sqlite3
from scrapy.exceptions import DropItem
class SQLitePipeline:
    def open_spider(self, spider):
        # باز شدن اتصال دیتابیس هنگام شروع اسپایدر
        self.connection = sqlite3.connect('data.db')
        self.cursor = self.connection.cursor()
        spider.logger.info('SQLitePipeline: Database connection opened.')
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS quotes (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                title TEXT NOT NULL,
                author TEXT NOT NULL
            )
        ''')
        self.connection.commit()
    def process_item(self, item, spider):
        # پردازش هر آیتم: اعتبارسنجی و درج در دیتابیس
        if not item.get('title') or not item.get('author'):
            raise DropItem(f'Missing title or author in {item}')
        self.cursor.execute('INSERT INTO quotes (title, author) VALUES (?, ?)', (item['title'], item['author']))
        self.connection.commit()
        spider.logger.info(f'SQLitePipeline: Item stored in database: {item}')
        return item
    def close_spider(self, spider):
        # بستن اتصال دیتابیس
        self.connection.close()
        spider.logger.info('SQLitePipeline: Database connection closed.')

نکات عملی:

  • با حجم داده بالا از یک دیتابیس همزمان (async) مثل PostgreSQL یا MySQL همراه با لایه async استفاده کنید تا از گلوگاه I/O جلوگیری شود.
  • برای اطمینان از یکپارچگی، عملیات‌های حساس را در تراکنش قرار دهید یا از batch insert استفاده کنید.
  • برای حذف تکراری‌ها از یک شناسه یکتا استفاده کرده یا قبل از درج چک کنید.

فعال‌سازی اجزا و تنظیمات

برای فعال کردن افزونه، میدل‌ویر و پایپلاین، مسیر آن‌ها را در settings.py قرار دهید. ترتیب اجرای پایپلاین‌ها به مقدار عددی در ITEM_PIPELINES بستگی دارد.

# بخش تنظیمات نمونه
EXTENSIONS = {
    'my_custom_crawler.extensions.custom_logger.CustomLoggerExtension': 500,
}
DOWNLOADER_MIDDLEWARES = {
    'my_custom_crawler.middlewares.custom_request.CustomRetryMiddleware': 501,
}
ITEM_PIPELINES = {
    'my_custom_crawler.pipelines.sqlite_pipeline.SQLitePipeline': 300,
}

دستورات اولیه برای راه‌اندازی و تست:

scrapy startproject my_custom_crawler
scrapy crawl test_pipeline
scrapy fetch https://httpstat.us/500  # برای تست منطق retry

نکات پیشرفته، مقیاس‌پذیری و امنیت

برای پروژه‌های جدی وب اسکریپینگ به این موارد توجه کنید:

  • استفاده از سیگنال‌ها (signals) برای ساخت ابزارهای رفع خطای پیشرفته؛ مثلاً گوش دادن به spider_error یا item_dropped و ارسال هشدار.
  • برای صفحات جاوااسکریپتی از ابزارهایی مثل Scrapy-Splash یا راهکارهای headless استفاده کنید؛ آن‌ها را در یک downloader middleware cache کنید تا درخواست‌ها کاهش یابد.
  • برای توزیع بار و صف‌بندی از Scrapy-Redis یا سازوکار مشابه استفاده کنید تا چندین ماشین بتوانند همزمان کار کنند.
  • به قوانین سایت‌ها (robots.txt) و مسائل حقوقی/حریم خصوصی احترام بگذارید. نگهداری و پردازش داده‌های خصوصی نیازمند محافظت و رعایت قوانین است.
  • بهینه‌سازی: از AutoThrottle، تنظیمات concurrency و استفاده از دیتابیس‌های async برای افزایش throughput بهره بگیرید.

جمع‌بندی

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

  1. ابتدا هدف نهایی (ذخیره‌سازی/تحلیل) را مشخص کنید.
  2. با یک افزونه ساده مانیتورینگ بسازید تا وضعیت کلی را ببینید.
  3. میان‌افزارهای ضروری مثل retry، پراکسی و user-agent را پیاده کنید و تست کنید.
  4. پایپلاین‌ها را برای پاک‌سازی و ذخیره‌سازی داده‌ها سازمان‌دهی کنید و برای بار زیاد از دیتابیس مناسب بهره ببرید.

با رعایت نکات بالا و تست مداوم، می‌توانید Scrapy را به‌صورت قابل‌اعتماد و مقیاس‌پذیر برای پروژه‌های وب اسکریپینگ شخصی‌سازی کنید.

مقاله‌های مرتبط
ابزارها و فریم‌ورک‌ها (Scrapy, Puppeteer و …)
1404-12-01
نظارت اسپایدرهای Scrapy در وب اسکریپینگ
این راهنما چهار روش نظارت روی اسپایدرهای Scrapy را بررسی می‌کند: لاگ‌ها و آمار داخلی، ابزارهای اختصاصی مانیتورینگ، Spidermon برای تست‌های اعتبارسنجی و ابزارهای عمومی لاگینگ. با مثال‌های پایتون و تنظیمات عملی، توصیه‌های استقرار و یک چک‌لیست عملی برای تولید ارائه شده است.
ابزارها و فریم‌ورک‌ها (Scrapy, Puppeteer و …)
1404-11-28
مانیتورینگ Scrapy با Spidermon برای وب اسکریپینگ
این مقاله راهنمایی عملی برای راه‌اندازی Spidermon در پروژه‌های Scrapy ارائه می‌دهد: از نصب و تنظیمات پایه تا نوشتن Monitorها، MonitorSuiteها، اعتبارسنجی آیتم‌ها و ارسال نوتیفیکیشن (مثلاً اسلک). با مثال‌های کد پایتون و توضیحات خط به خط، می‌توانید ظرف چند دقیقه یک مانیتورینگ قابل اتکا برای وب اسکریپینگ خود بسازید.
ابزارها و فریم‌ورک‌ها (Scrapy, Puppeteer و …)
1404-11-27
Scrapyd؛ راهنمای عملی اسکریپ با Scrapy
این مقاله یک راهنمای عملی برای نصب، استقرار و مدیریت اسپایدرهای Scrapy با Scrapyd ارائه می‌دهد؛ شامل مثال‌های کد برای deploy، استفاده از API و کتابخانهٔ python-scrapyd-api، ادغام با ScrapeOps و نکات امنیتی و بهترین‌روش‌ها برای اجرا در محیط تولید.