مقدمه
در این مقاله مقایسهای عملی و فنی بین دو رویکرد رایج در اسکریپینگ پایتون داریم: فریمورک مبتنی بر HTTP و همزمانی بالا (Scrapy) و کنترل مرورگر واقعی برای صفحات سنگین JavaScript (Pyppeteer). در پایان این نوشته شما میدانید هر ابزار برای چه سناریویی مناسب است، چگونه آنها را راهاندازی و بهرهبرداری کنید، و نمونههای کد عملی برای هر کدام خواهید داشت.
چیست Pyppeteer؟
Pyppeteer پورت پایتونی کتابخانه Puppeteer است که از طریق پروتکل DevTools مرورگر Chrome/Chromium را کنترل میکند. نکته کلیدی: Pyppeteer یک محیط مرورگر کامل را شبیهسازی میکند و برای صفحات با رندر سمت مشتری (SPA، AJAX، WebSocket) مناسب است.
مزایا و معایب Pyppeteer
- مزایا:
- پردازش کامل JavaScript و دریافت محتوای رندرشده.
- اتماتیکسازی تعاملات: کلیک، فرمها، اسکرول و گرفتن اسکرینشات/PDF.
- پشتیبانی از سشن کامل مرورگر (کوکیها، LocalStorage) و امکان فعالسازی حالت stealth برای کاهش شناسایی توسط سایتها.
- معایب:
- مصرف بالای منابع و کندتر بودن نسبت به درخواستهای خالص HTTP.
- پیچیدگی بیشتر در مدیریت همزمانی و فرآیندهای مرورگر.
- در زمان نگارش این متن پروژه پیپیتیر ممکن است بهروزرسانی فعال نداشته باشد؛ برای پروژههای تولیدی بررسی وضعیت نگهداری ضروری است.
راهاندازی و مثال پایه Pyppeteer
نصب با pip ساده است:
pip install pyppeteerمثال ساده برای باز کردن صفحه و بستن آن:
import asyncio
from pyppeteer import launch
async def main():
browser = await launch(headless=True)
page = await browser.newPage()
await page.goto('https://example.com')
await browser.close()
asyncio.run(main())توضیح: ورودی این اسکریپ هیچ ورودی خارجی نیاز ندارد؛ خروجی هم صرفاً عمل مرور و بستن مرورگر است. مرحلهبندی:
- ایجاد و راهاندازی مرورگر با launch.
- ایجاد یک صفحه با newPage.
- رفتن به URL با goto و سپس بستن مرورگر.
نکات عملی: از setRequestInterception برای فیلتر کردن منابع سنگین (مثل تصاویر) و از Semaphore برای محدود کردن تعداد تبهای همزمان استفاده کنید تا منابع کنترل شوند.
چیست Scrapy؟
Scrapy یک فریمورک کامل برای خزش و استخراج داده است که بهصورت غیرهمزمان بر پایه Twisted کار میکند. مناسب پروژههای بزرگ، مقیاسپذیر و وقتی سرعت و مدیریت همزمانی مهم است.
مزایا و معایب Scrapy
- مزایا:
- عملکرد بالا با همزمانی داخلی، مدیریت نرخ درخواست و صفبندی.
- معماری ساختیافته: spiders، items، pipelines و middlewares.
- خروجیها و ذخیرهسازی (JSON/CSV/XML/DB) و جامعهٔ بزرگ و منابع آموزشی فراوان.
- معایب:
- اجرای JavaScript بهصورت پیشفرض ندارد (برای صفحات JS محور نیاز به ادغام با headless browser یا Splash/Playwright دارید).
- پیچیدگی اولیه برای آشنایان تازهکار نسبت به اسکریپتهای ساده.
راهاندازی و مثال پایه Scrapy
نصب:
pip install scrapyایجاد پروژه و ساختار پایه:
scrapy startproject myprojectنمونه کوتاه برای تعریف Item و یک Spider که نقلقولها را از یک سایت نمونه استخراج میکند:
# items.py
import scrapy
class QuoteItem(scrapy.Item):
text = scrapy.Field()
author = scrapy.Field()
tags = scrapy.Field()
# spiders/quotes_spider.py
import scrapy
from myproject.items import QuoteItem
class QuotesSpider(scrapy.Spider):
name = 'quotes'
start_urls = ['http://quotes.toscrape.com/page/1/']
def parse(self, response):
for quote in response.css('div.quote'):
item = QuoteItem()
item['text'] = quote.css('span.text::text').get()
item['author'] = quote.css('small.author::text').get()
item['tags'] = quote.css('div.tags a.tag::text').getall()
yield item
next_page = response.css('li.next a::attr(href)').get()
if next_page:
yield response.follow(next_page, self.parse)توضیح: ورودی این اسپایدر URLهای شروع است؛ خروجی مجموعهٔ آیتمهایی با فیلدهای تعریفشده. Scrapy بهصورت خودکار صفحات را بهصورت همزمان میگیرد، آیتمها را به pipeline میفرستد و میتوانید خروجی را با دستور scrapy crawl quotes ذخیره کنید.
مقایسه فنی و نکات عملی
- اجرا و JavaScript: اگر صفحه به JS وابسته است از Pyppeteer (یا ادغام Scrapy با headless browser) استفاده کنید؛ برای صفحات استاتیک Scrapy سریعتر و سبکتر است.
- همزمانی: Scrapy (Twisted) برای هزاران درخواست موازی بهتر ساخته شده؛ Pyppeteer با asyncio قابل موازیسازی است اما هر تب/مرورگر منابع قابلتوجهی مصرف میکند.
- ذخیرهسازی داده: Scrapy خروجیها و pipelineها را دارد؛ در Pyppeteer باید خودتان ذخیرهسازی را پیادهسازی کنید (مثلاً نوشتن JSON، ارسال به DB یا صف پیام).
- پراکسی و مدیریت هویت: هر دو از پراکسی پشتیبانی میکنند؛ Scrapy ادغام پراکسی و روتیشن را سادهتر میکند. در Pyppeteer معمولاً پراکسی را در آرگومانهای راهاندازی مرورگر یا با پارامتر --proxy-server تنظیم میکنید.
- پایداری و نگهداری: Scrapy جامعه بزرگتری دارد؛ در انتخاب Pyppeteer به وضعیت نگهداری پروژه توجه کنید و در صورت نیاز از کپیهای فعالتر (مثل Playwright پایتون) بررسی کنید.
- امنیت/قوانین: همیشه فایل robots.txt را بررسی کرده و سیاستهای سایت و قوانین محلی را رعایت کنید. اسکریپینگ خودکار میتواند باعث بار اضافی سرورها شود؛ نرخ درخواستها را محدود و از هدرهای صحیح (User-Agent و Referer) استفاده کنید.
مطالعه موردی: اسکریپینگ محصولات آمازون — مقایسه پیادهسازی
ایده کلی: جستجوی نتایج، استخراج لینک محصولات، بازدید صفحه محصول و جمعآوری نام و قیمت. در ادامه دو پیادهسازی نمونه و نکات کلیدی آورده شده است.
نمونه مختصر با Scrapy
# spider مختصر برای آمازون (نمونه آموزشی)
import scrapy
from urllib.parse import urljoin
class AmazonSpider(scrapy.Spider):
name = 'amazon_sample'
start_urls = ['https://www.amazon.com/s?k=jeans']
custom_settings = {
'CONCURRENT_REQUESTS': 3,
'DOWNLOAD_DELAY': 1,
'ROBOTSTXT_OBEY': True,
}
def parse(self, response):
product_links = response.css('h2 a::attr(href)').getall()
for rel in product_links:
url = urljoin('https://www.amazon.com/', rel.split('?')[0])
yield response.follow(url, self.parse_product)
next_page = response.css('li.a-last a::attr(href)').get()
if next_page:
yield response.follow(next_page, self.parse)و تابع استخراج محصول:
def parse_product(self, response):
name = response.css('span#productTitle::text').get()
price = response.css('.a-price-whole::text').get() or response.css('#priceblock_dealprice::text').get()
yield {'product_name': name.strip() if name else 'not available',
'product_price': price.strip() if price else 'not available'}توضیح: در Scrapy شما از CSS/XPath برای استخراج استفاده میکنید؛ پیادهسازی pagination و follow ساده و مقرونبهصرفه و مدیریت concurrency داخلی باعث کارایی بالا میشود.
نمونه مختصر با Pyppeteer
import asyncio
from pyppeteer import launch
async def extract_product_details(browser, url, sem):
async with sem:
page = await browser.newPage()
await page.setRequestInterception(True)
page.on('request', lambda req: asyncio.create_task(req.continue_({}) if req.resourceType != 'image' else req.abort()))
try:
await page.goto(url, waitUntil='domcontentloaded', timeout=120000)
await page.waitForSelector('span#productTitle', timeout=60000)
name = await page.evaluate('() => document.querySelector("span#productTitle").innerText')
price = await page.evaluate('() => (document.querySelector(".a-price-whole")||{}).innerText')
return {'name': name.strip(), 'price': price.strip()}
except Exception as e:
print('Error:', e)
return {'name': 'not available', 'price': 'not available'}
finally:
await page.close()
async def main():
browser = await launch(headless=True)
page = await browser.newPage()
all_urls = []
for p in range(1, 3):
await page.goto(f'https://www.amazon.com/s?k=jeans&page={p}')
links = await page.querySelectorAll('h2 a.a-link-normal')
urls = [await page.evaluate('(el)=>el.href', el) for el in links]
all_urls.extend(urls)
await page.close()
sem = asyncio.Semaphore(3)
tasks = [extract_product_details(browser, u, sem) for u in all_urls]
products = await asyncio.gather(*tasks)
print(products)
await browser.close()
asyncio.run(main())توضیح: در این رویکرد شما مرورگر را کنترل میکنید، از interception برای بلاک کردن تصاویر استفاده میشود تا مصرف پهنای باند کاهش یابد، و با Semaphore همزمانی محدود میشود. خروجی لیست دیکشنریها است که باید بعداً ذخیرهسازی کنید.
مقایسه سریع در عمل
- سرعت: Scrapy معمولاً سریعتر است (خالص HTTP، بدون رندر کامل).
- دقت محتوای JS: Pyppeteer بهتر است چون محتوای رندرشده را میبیند.
- مقیاسپذیری: Scrapy برای پروژههای بزرگ آمادهتر است؛ Pyppeteer برای مقیاس بزرگ هزینهٔ محاسباتی و زیرساختی بالایی میطلبد.
- ذخیرهسازی و pipeline: Scrapy دارای ابزارهای آماده است؛ Pyppeteer نیاز به پیادهسازی دستی دارد.
نکات امنیتی، قانونی و بهترین روشها
- قوانین و حقوق: پیش از اسکریپینگ قوانین سایت، فایل robots.txt و شرایط خدمات را بررسی کنید.
- محدودیت نرخ درخواست: نرخ و تاخیر را تنظیم کنید تا از بلوک شدن جلوگیری شود.
- پردازش خطا: هم در Scrapy و هم در Pyppeteer از retry، timeouts و بلوک try/except استفاده کنید تا منابع آزاد شوند.
- پراکسی و هدرها: برای جلوگیری از بلاک شدن از روتویشن پراکسی و هدرهای معتبر استفاده کنید، اما مراقب قوانین استفاده از پراکسیها باشید.
- مدیریت منابع: در Pyppeteer حتماً صفحات و مرورگرها را در finally ببندید و از temp userDataDir استفادهشده را پاک کنید.
جمعبندی و توصیههای عملی
اگر هدف شما جمعآوری مقدار زیادی داده از صفحات نسبتاً استاتیک یا زمانی که سرعت و مقیاسپذیری مهم است، Scrapy معمولاً انتخاب بهتر است. اگر با SPAها یا صفحات سنگین JavaScript سروکار دارید و نیاز به تعامل کاربر-مانند (کلیک، فرم، اسکرینشات) دارید، Pyppeteer (یا راهحلهای مشابه مثل Playwright) گزینه مناسبی است.
توصیه نهایی:
- برای پروژههای تولیدی و مقیاسپذیر: Scrapy و استفاده از middlewares و pipelines.
- برای تعامل با وباپهای پیچیده یا گرفتن رندر کامل: Pyppeteer با مدیریت دقیق همزمانی و منابع.
- همیشه جوانب قانونی و بار سرور را در نظر بگیرید و از روشهای مودبانهٔ اسکریپینگ استفاده کنید.





