

این مقاله یک راهنمای عملی برای شخصیسازی Scrapy در پروژههای وب اسکریپینگ است. هدف این راهنما این است که پس از خواندن آن شما بدانید چه زمانی باید از extensions، middlewares و pipelines استفاده کنید، چگونه نمونههای عملی بنویسید و آنها را در پروژهتان فعال و تست کنید. مثالها به زبان Python و با کد قابلاجرا ارائه شدهاند تا بتوانید سریع شروع به اسکریپ کردن کنید.
Scrapy بهصورت پیشفرض بسیار قدرتمند و غیرهمزمان است، اما هر هدف اسکریپ متفاوت است. با شخصیسازی میتوانید رفتار کلی پروژه، جریان درخواست/پاسخ و پردازش آیتمها را دقیقاً مطابق نیازتان تغییر دهید:
در ادامه هر بخش را با مثال واقعی توضیح میدهیم: یک افزونه برای لاگگیری زمان اجرای اسپایدر، یک downloader middleware برای منطق retry سفارشی و یک pipeline برای ذخیره در sqlite.
افزونهها برای 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}')
توضیح خط به خط (خلاصه):
میانافزارها اجازه میدهند رفتار 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:
پایپلاینها محل نهایی برای کارهای مربوط به آیتمها مثل پاکسازی، اعتبارسنجی، حذف تکراریها و ذخیرهسازی هستند. ترتیب اجرای پایپلاینها با مقدار عددی در تنظیمات مشخص میشود (اعداد کوچکتر زودتر اجرا میشوند).
مثال: ذخیره آیتمها در یک دیتابیس 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.')
نکات عملی:
برای فعال کردن افزونه، میدلویر و پایپلاین، مسیر آنها را در 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برای پروژههای جدی وب اسکریپینگ به این موارد توجه کنید:
شخصیسازی Scrapy با افزونهها، میانافزارها و پایپلاینها به شما کنترل دقیقی روی رفتار crawler میدهد. مراحل پیشنهادی برای شروع:
با رعایت نکات بالا و تست مداوم، میتوانید Scrapy را بهصورت قابلاعتماد و مقیاسپذیر برای پروژههای وب اسکریپینگ شخصیسازی کنید.


