مقدمه
در این مقاله قدمبهقدم یاد میگیریم چطور با فریمورک Scrapy یک اسکرپر تولیدی ساده برای یک فروشگاه اینترنتی بسازیم. اهداف آموزشی شامل راهاندازی محیط، ساخت پروژه و اسپایدر، استخراج داده با CSS Selector، پاکسازی سادهی داده، ناوبری بین صفحات (pagination)، و ذخیرهسازی خروجی است. فرض میکنیم خواننده توسعهدهنده پایتون با سطح متوسط است و به جزئیات فنی، توضیحات خطبهخط کد و نکات پایداری/امنیتی نیاز دارد.
چرا Scrapy برای وب اسکریپینگ؟
Scrapy یک فریمورک کامل پایتون برای اسکریپ کردن صفحات وب است که قابلیتهای همزمانی، مدیریت صف درخواستها، middlewareها، و سیستم pipeline برای پردازش و ذخیرهسازی داده را از ابتدا فراهم میکند. در مقابل ترکیب requests و BeautifulSoup یا ابزارهایی مثل Selenium، Scrapy مقیاسپذیری و امکانات production-ready بیشتری ارائه میدهد، هرچند یادگیری اولیه آن کمی طولانیتر است.
محیط توسعه و نصب
پیش از هر چیز یک virtual environment بسازید تا وابستگیها ایزوله بمانند. در لینوکس/مک:
$ sudo apt-get update
$ sudo apt install -y python3-venv python3-pip
$ cd /path/to/projects
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install --upgrade pip
$ pip install scrapyدر ویندوز:
C:\> pip install virtualenv
C:\> cd \path\to\projects
C:\> virtualenv venv
C:\> venv\Scripts\activate
(venv) C:\> pip install scrapyتوضیح: ورودی این بخش دستورات ترمینال هستند و خروجی انتظار میرود نصب موفق پکیجها و فعالسازی محیط مجازی باشد. در صورت مواجهه با خطاهای کامپايلر یا wheels، پکیجهای موردنیاز سیستمی (مثلاً build-essential یا libxml2-dev) را نصب کنید.
ساختار پروژه Scrapy
ایجاد پروژه با scrapy startproject <name> یک ساختار پایه میسازد. فایلهای کلیدی:
- settings.py: تنظیمات پروژه (تأخیر، همزمانی، middleware، pipelines).
- items.py: مدل دادهای برای آیتمهای استخراجشده.
- pipelines.py: تبدیل و ذخیرهسازی آیتمها (DB، CSV، JSON، S3).
- middlewares.py: تغییر درخواست/پاسخ، مدیریت پراکسی یا user-agent.
این تفکیک کمک میکند منطق استخراج از منطق پردازش داده و ذخیرهسازی جدا باقی بماند؛ بهترینروش برای پروژههای پایدار و قابل نگهداری است.
ایجاد اسپایدر اولیه
برای تولید یک اسپایدر از دستور scrapy genspider <name> <domain> استفاده کنید. اسپایدر قالبی چیزی شبیه این خواهد داشت:
import scrapy
class ChocolateSpider(scrapy.Spider):
name = 'chocolatespider'
allowed_domains = ['chocolate.co.uk']
start_urls = ['https://www.chocolate.co.uk/collections/all']
def parse(self, response):
passتوضیح: ورودی اولیه این کلاس، درخواست به URLهای موجود در start_urls است. خروجی تابع parse معمولاً آیتمها (به شکل dict یا scrapy.Item) و/یا درخواستهای بعدی (برای pagination یا دنبالکردن لینکها) است.
استفاده از Scrapy Shell برای یافتن سلکتورها
قبل از نوشتن کد اسپایدر، سلکتورهای CSS/XPath را با scrapy shell تست کنید. نمونه دستورات:
$ scrapy shell
scrapy> fetch('https://www.chocolate.co.uk/collections/all')
scrapy> response # نمایش response
scrapy> response.css('product-item') # گرفتن نودهای محصولمثالهای استخراج در shell (این دستورات در محیط shell اجرا میشوند):
# گرفتن اولین محصول
product = response.css('product-item').get() # یا .get() برای HTML خام
# اگر بخواهیم به عنوان selector کار کنیم:
products = response.css('product-item')
len(products) # تعداد آیتمها
# استخراج نام
products[0].css('a.product-item-meta__title::text').get()
# استخراج URL نسبی
products[0].css('div.product-item-meta a').attrib['href']
# استخراج متن قیمت معمولاً بهتر با ::text یا join کردن تمام متن داخلی
''.join(products[0].css('span.price ::text').getall()).strip()نکته: ترجیح دهید از ::text یا getall() و سپس پاکسازی متن استفاده کنید تا از دستکاری HTML خام با replace اجتناب شود.
مثال اسپایدر کاملتر (استخراج و پاکسازی)
import scrapy
class ChocolateSpider(scrapy.Spider):
name = 'chocolatespider'
start_urls = ['https://www.chocolate.co.uk/collections/all']
def parse(self, response):
products = response.css('product-item')
for p in products:
name = p.css('a.product-item-meta__title::text').get()
# جمعآوری تمام قطعات متن قیمت و پاکسازی
raw_price_parts = p.css('span.price ::text').getall()
price = ''.join(raw_price_parts).replace('Sale price', '').strip()
url = p.css('div.product-item-meta a').attrib.get('href')
yield {
'name': name.strip() if name else None,
'price': price,
'url': response.urljoin(url) if url else None,
}
# pagination: اگر صفحه بعدی وجود دارد، از response.follow استفاده کنید
next_page = response.css('[rel="next"] ::attr(href)').get()
if next_page:
yield response.follow(next_page, callback=self.parse)توضیح کد (ورودی/خروجی و نقش هر بخش):
- ورودی: تابع parse یک response از Scrapy دریافت میکند که شامل HTML صفحه است.
- محاسبه products: یک لیست از سلکتورهای مربوط به هر محصول.
- داخل حلقه: نام، قیمت و URL هر محصول استخراج و پاکسازی میشود؛ سپس یک دیکشنری (آیتم) yield میشود تا توسط pipeline یا feed exporter ضبط شود.
- pagination: اگر لینک صفحه بعدی وجود دارد، با response.follow آن را درخواست میکنیم تا Scrapy مدیریت کوکیها، ریدایرکت و صفبندی را انجام دهد.
ذخیرهسازی خروجی و اجرای اسپایدر
برای اجرای اسپایدر و ذخیره خروجی در JSON یا CSV از گزینه -O استفاده کنید:
$ scrapy crawl chocolatespider -O products.json
# یا
$ scrapy crawl chocolatespider -O products.csvتوضیح: این دستور اسپایدر را اجرا میکند، آیتمهای yield شده را به فایل تعیینشده میریزد و آمار run را در لاگ نمایش میدهد.
استفاده از Items، ItemLoader و Pipelines (چشمانداز)
برای پروژههای بزرگتر بهتر است ساختار آیتمها را در items.py تعریف کنید و پردازش پاکسازی و اعتبارسنجی را در Item Pipeline منتقل کنید. مثال ساده:
# items.py
import scrapy
class ProductItem(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
url = scrapy.Field()
# در pipelines.py میتوانید قیمت را به عدد تبدیل کنید، فیلترهای duplicate بزنید یا ذخیره در دیتابیس انجام دهید.مزیت: جدا کردن وظایف، تستپذیری بهتر و قابلیت استفاده در چند اسپایدر.
نکات پایداری، عملکرد و امنیت
مواردی که در تولید باید رعایت شود:
- احترام به robots.txt و قوانین سایت؛ قبل از اسکریپ کردن بررسی حقوقی/اخلاقی انجام دهید.
- کاهش فشار روی سرور با تنظیم DOWNLOAD_DELAY و CONCURRENT_REQUESTS در settings.py و در صورت نیاز استفاده از backoff الگوریتمی.
- مدیریت retries و خطاها: RETRY_TIMES و لاگگیری؛ در pipeline خطاهای parsing را لاگ کنید تا بتوانید ریکاوری کنید.
- استفاده از پراکسی و چرخش user-agent در middlewareها برای جلوگیری از بلاک شدن، اما مراقب سیاستها و هزینهها باشید.
- حفظ اطلاعات حساس: هرگز کلیدها یا credentials را در رپوزیتوری عمومی نگه ندارید—از متغیرهای محیطی یا secret manager استفاده کنید.
- در مقیاس بزرگ از صف پیام (مثل RabbitMQ یا Kafka) و معماری microservices برای تفکیک دانلود از پردازش استفاده کنید.
نمونه تنظیمات پایه برای محدودسازی سرعت (در settings.py):
# settings.py
DOWNLOAD_DELAY = 1 # یک ثانیه بین درخواستها
CONCURRENT_REQUESTS = 8
RETRY_TIMES = 3
USER_AGENT = 'my-scraper/1.0 (+contact@example.com)'
ROBOTSTXT_OBEY = Trueنکات عملی و بهترین روشها
- قبل از استخراج واقعی در Scrapy Shell سلکتورها را تست کنید تا از تغییرات DOM مطلع شوید.
- برای دادههای عددی و تاریخها، تبدیل و اعتبارسنجی را در Pipeline انجام دهید.
- هرگاه ممکن است از response.follow برای لینکهای نسبی استفاده کنید تا URL کامل تولید شود.
- لاگها و آمار Scrapy را بررسی کنید: تعداد آیتمها، زمان اجرا، نرخ خطاها؛ اینها برای debug و scaling ضروریاند.
- برای نگهداری طولانیمدت، خروجی را به دیتابیس یا فضای ابری (S3، دیتابیس رابطهای یا NoSQL) منتقل کنید.
جمعبندی
در این راهنما با مراحل عملی ساخت یک اسکرپر پایه در Scrapy آشنا شدید: از راهاندازی محیط، تست سلکتورها با Scrapy Shell، نوشتن اسپایدر، پاکسازی دادهها، تا پیمایش صفحات و ذخیرهسازی خروجی. برای آمادهسازی اسپایدر برای محیط تولید، به تنظیمات concurrency، مدیریت retries، استفاده از pipelines، و برنامهریزی اجرا (scheduling) نیاز دارید. قدم بعدی میتواند کار روی Item Pipelines برای پاکسازی بهتر، ذخیرهسازی در دیتابیس، و افزودن middleware برای پراکسی و چرخش user-agent باشد.





