

وبدیتا معمولاً آشفته، ناتمام و پر از استثناهاست. در این مقاله عملی و فنی برای یک توسعهدهنده پایتون سطح متوسط، روشهایی را برای ساختن اسپایدرهای Scrapy مقاوم و قابلاعتماد توضیح میدهیم. پس از مطالعه، شما خواهید توانست دادهها را با Items سازماندهی کنید، با Item Loaders پیشپردازش انجام دهید و با Item Pipelines پردازش و اعتبارسنجی پس از استخراج را انجام دهید؛ همچنین با استراتژیهای برخورد با حالات مرزی (edge cases) آشنا میشوید.
قبل از طراحی ساختار داده و کد اسپایدر، بهتر است چند الگوی معمول برای مواجهه با دادههای نامنظم را در نظر بگیریم:
هر روش مزایا و معایب خود را دارد؛ آشنایی با هر چهار رویکرد بالا به شما انتخاب بهینه را در پروژههای مختلف میدهد. در ادامه تمرکز ما روی ترکیب Items + Item Loaders + Item Pipelines است چون بیشترین کنترل و تفکیک مسئولیت را فراهم میکنند.
بهجای بازگرداندن دیکشنریهای آزاد، بهتر است ساختار دادهای مشخصی تعریف کنید. Items نقش اسکیمای سادهای را دارند که فیلدها را مشخص میکنند و خوانایی و ایمنی کد را افزایش میدهند.
مثال: فایل items.py که سه فیلد پایه دارد:
import scrapy
class ChocolateProduct(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
url = scrapy.Field()توضیح: ورودی این کلاس هیچ پردازشی انجام نمیدهد؛ فقط فیلدها را تعریف میکند. خروجی هر اسپایدر از نوع این Item خواهد بود که سپس توسط Item Loaders یا Pipelines خوانده میشود.
Item Loaders مکانیزمی تمیز برای جمعآوری، پاکسازی و نمایش اولیه دادهها هنگام استخراج فراهم میکنند. شما میتوانید برای هر فیلد processors تعریف کنید که هنگام اضافه شدن مقدار اجرا شوند.
نمونهای از itemsloaders.py که علامت پوند را حذف و URL نسبی را مطلق میکند:
from itemloaders.processors import TakeFirst, MapCompose
from scrapy.loader import ItemLoader
class ChocolateProductLoader(ItemLoader):
default_output_processor = TakeFirst()
# ورودی قیمت: تقسیم روی '£' و گرفتن آخرین بخش
price_in = MapCompose(lambda x: x.split("£")[-1])
# ورودی url: تبدیل نسبی به مطلق
url_in = MapCompose(lambda x: 'https://www.chocolate.co.uk' + x)توضیح پردازشگرها:
ادغام Item Loader با اسپایدر — قطعهای از کد parse که از Loader استفاده میکند:
import scrapy
from chocolatescraper.itemloaders import ChocolateProductLoader
from chocolatescraper.items import ChocolateProduct
class ChocolateSpider(scrapy.Spider):
name = 'chocolatespider'
start_urls = ['https://www.chocolate.co.uk/collections/all']
def parse(self, response):
products = response.css('div.product-item')
for product in products:
loader = ChocolateProductLoader(item=ChocolateProduct(), selector=product)
loader.add_css('name', 'a.product-item-meta__title::text')
loader.add_css('price', 'span.price', re='\s*(?:Sale price)?(.*)')
loader.add_css('url', 'div.product-item-meta a::attr(href)')
yield loader.load_item()
next_page = response.css('[rel="next"] ::attr(href)').get()
if next_page:
yield response.follow('https://www.chocolate.co.uk' + next_page, callback=self.parse)نکته درباره ورودیها/خروجیها:
پس از اینکه Item از اسپایدر خارج شد، وارد صف Pipeline میشود. هر کلاس Pipeline متد process_item را پیادهسازی میکند و میتواند آیتم را تغییر دهد یا با DropItem آن را حذف کند.
اولین Pipeline: تبدیل قیمت به float و اعمال نرخ تبدیل:
from itemadapter import ItemAdapter
from scrapy.exceptions import DropItem
class PriceToUSDPipeline:
gbpToUsdRate = 1.3
def process_item(self, item, spider):
adapter = ItemAdapter(item)
# اگر قیمت وجود دارد آن را به float تبدیل کن و نرخ تبدیل را اعمال کن
if adapter.get('price'):
float_price = float(adapter['price'])
adapter['price'] = float_price * self.gbpToUsdRate
return item
else:
# حذف آیتمهایی که قیمت ندارند (مثلاً محصول ناموجود)
raise DropItem(f"Missing price in {item}")توضیح عملکرد:
دومین Pipeline: حذف موارد تکراری بر اساس نام:
from itemadapter import ItemAdapter
from scrapy.exceptions import DropItem
class DuplicatesPipeline:
def __init__(self):
self.names_seen = set()
def process_item(self, item, spider):
adapter = ItemAdapter(item)
name = adapter.get('name')
if name in self.names_seen:
raise DropItem(f"Duplicate item found: {item!r}")
else:
self.names_seen.add(name)
return itemنکتهها درباره حذف تکراریها:
فعالسازی Pipelines در settings.py:
ITEM_PIPELINES = {
'chocolatescraper.pipelines.PriceToUSDPipeline': 100,
'chocolatescraper.pipelines.DuplicatesPipeline': 200,
}اعداد ترتیب اجرای Pipelineها را تعیین میکنند (از کوچک به بزرگ).
بعد از راهاندازی، خروجی دیباگ باید آیتمها را نشان دهد و ببینید که قیمتها به دلار تبدیل شده و موارد تکراری حذف شدهاند. نمونه لاگ:
DEBUG: Scraped from <200 https://www.chocolate.co.uk/collections/all>
{'name': 'Blonde Chocolate Honeycomb', 'price': 11.63, 'url': 'https://www.chocolate.co.uk/products/blonde-chocolate-honeycomb'}نکات مهم در تولید و امنیت:
چند ایده برای پروژههای بعدی یا مقیاسبندی:
با سازماندهی دادهها با Items، پاکسازی هنگام استخراج با Item Loaders و پردازش نهایی با Item Pipelines میتوانید اسپایدرهای Scrapy بسازید که هم خواناتر و هم مقاومتر در برابر حالات مرزی هستند. توصیه نهایی: پردازشها را تفکیک کنید، تست بنویسید و هنگام مقیاسبندی از ذخیرهسازی خارجی برای وضعیت و حذف تکراریها استفاده کنید. در قسمت بعدی (ذخیرهسازی) خواهید دید چگونه خروجی را به فایل، دیتابیس یا S3 بفرستید.


