مقدمه
در این مقاله یاد میگیرید چگونه با استفاده از Scrapy و افزونهٔ scrapy-playwright صفحات سنگین از نظر جاوااسکریپت را رندر و اسکریپ کنید. هدف این راهنما ارائهٔ توضیحات فنی، نمونههای پایتون قابلاجرا و نکات عملی برای مدیریت رندرینگ، تعامل با صفحه، پیمایش بین صفحات، اسکرول بینهایت، گرفتن تصاویر و استفاده از پراکسیها است. خواننده فرضی این مطلب توسعهدهندهٔ پایتون در سطح متوسط است که نیاز دارد نمونههای عملی و توضیحات خطبهخط ببیند.
نصب و پیکربندی اولیه
نصب scrapy-playwright و Playwright ساده است اما چند نکتهٔ مهم وجود دارد:
- در محیط مجازی پایتون نصب کنید تا تداخل بستهها مدیریت شود.
- کامندهای زیر را اجرا کنید:
pip install scrapy-playwright
playwright installسپس در تنظیمات پروژهٔ Scrapy باید دانلود هندلر را به کلاس مربوطه تغییر دهید و از یک reactor سازگار با asyncio استفاده کنید. مثال در settings.py:
DOWNLOAD_HANDLERS = {
"http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
"https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"چرا؟ چون ScrapyPlaywrightDownloadHandler درخواستها را از طریق Playwright عبور میدهد؛ مگر آنکه به صراحت در هر Request این گزینه فعال نشده باشد (meta).
استفادهٔ ساده در اسپایدرها
برای رندر کردن یک صفحهٔ JS-محور کافی است در متای درخواست، کلید playwright را فعال کنید. مثال سادهٔ اسپایدر:
import scrapy
from myproject.items import QuoteItem
class QuotesSpider(scrapy.Spider):
name = 'quotes'
def start_requests(self):
url = 'https://quotes.toscrape.com/js/'
yield scrapy.Request(url, meta={'playwright': True})
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توضیح: ورودی این اسپایدر URL است؛ خروجی آیتمهای استخراجشده. وقتی meta شامل 'playwright': True باشد، پاسخ حاوی HTML رندرشده از دید مرورگر خواهد بود.
دسترسی به شیء Page و PageMethods
برای انجام تعاملات پیچیدهتر (کلیک، اسکرول، اجرای اسکریپت و...) باید شیء Playwright Page را بگیرید. برای این کار در متا علاوه بر playwright، باید playwright_include_page را نیز True کنید و callback های شما باید async باشند تا بتوانید از await استفاده کنید.
from scrapy_playwright.page import PageMethod
class QuotesSpider(scrapy.Spider):
name = 'quotes'
def start_requests(self):
url = 'https://quotes.toscrape.com/js/'
yield scrapy.Request(
url,
meta=dict(
playwright=True,
playwright_include_page=True,
playwright_page_methods=[PageMethod('wait_for_selector', 'div.quote')],
),
)
async def parse(self, response):
page = response.meta['playwright_page']
# در پایان حتما صفحه را ببندید
await page.close()
for quote in response.css('div.quote'):
# استخراج دادهها همانند قبل
...نکتهٔ عملی: وقتی playwright_include_page را True میکنید، در صورت وقوع خطا باید صفحه را در errback ببندید تا منابع آزاد شوند. اگر این کلید False باشد، صفحات در مواجهه با خطا خودکار بسته میشوند.
انتظار برای المانها قبل از بازگرداندن پاسخ
در بسیاری از صفحات JS، لازم است تا منتظر بارگذاری یک انتخابگر مشخص باشیم. برای این کار از PageMethod('wait_for_selector', selector) استفاده کنید. مثال بالا همین کار را انجام میدهد و باعث میشود Playwright رندر را تا زمانی که div.quote ظاهر شود ادامه دهد.
نکات:
- میتوانید timeoutهای سفارشی به PageMethod اضافه کنید تا از بلوکه شدن جلوگیری کنید.
- در مواقعی که المنت جلوی نمایش دادهشدن رخ میدهد، از wait_for_selector با گزینهٔ visible=True استفاده کنید.
اسکریپینگ چند صفحهای (Pagination)
الگوی معمول: عناصر را استخراج کنید، لینک صفحهٔ بعد را بخوانید و با همان متا دوباره درخواست بزنید تا رندر نیز انجام شود. نمونهٔ ترکیب pagination با PageMethod:
# داخل async def parse(self, response):
for quote in response.css('div.quote'):
# استخراج
...
next_page = response.css('.next > a::attr(href)').get()
if next_page:
next_page_url = response.urljoin(next_page)
yield scrapy.Request(
next_page_url,
meta=dict(
playwright=True,
playwright_include_page=True,
playwright_page_methods=[PageMethod('wait_for_selector', 'div.quote')],
errback=self.errback,
),
)توضیح: از response.urljoin برای ساخت URL مطمئن استفاده کنید و همیشه errback تعریف کنید تا صفحات باز در صورت خطا بسته شوند.
اسکرول صفحات با بارگذاری بینهایت (Infinite Scroll)
برای سایتهایی که با اسکرول داده بارگذاری میکنند، ترکیبی از evaluate (اجرای جاوااسکریپت در صفحه) و wait_for_selector کارآمد است. در مثال زیر Playwright اسکرول میکند تا عنصر شمارهٔ 11 بارگذاری شود:
playwright_page_methods = [
PageMethod('wait_for_selector', 'div.quote'),
PageMethod('evaluate', 'window.scrollBy(0, document.body.scrollHeight)'),
PageMethod('wait_for_selector', 'div.quote:nth-child(11)'),
]
# سپس این لیست را در meta قرار دهید (playwright_page_methods=playwright_page_methods)
نکته: برای اسکرول پیچیدهتر میتوانید حلقهای از evaluate + انتظار با timeout معقول بسازید و تعداد آیتمهای دریافتشده را بررسی کنید تا بدانید چه زمانی اسکرول را متوقف کنید.
گرفتن اسکرینشات و خروجی باینری
Playwright امکان گرفتن اسکرینشات صفحه و بازگشت بایتهای تصویر را به شما میدهد. نمونه:
screenshot = await page.screenshot(path='example.png', full_page=True)
# screenshot شامل بایتهای تصویر است
await page.close()نکته: اگر میخواهید تصویر را در حافظه پردازش کنید به جای نوشتن در فایل، از بایت دریافتی استفاده کنید (مثلا برای آپلود یا ذخیره در دیتابیس).
استفاده از پراکسی با Scrapy Playwright
پراکسیها معمولاً در زمان اجرای مرورگر تنظیم میشوند. میتوانید آنها را در تنظیمات اسپایدر یا پروژه مشخص کنید. نمونهٔ custom_settings برای تنظیم پراکسی هنگام لانچ Playwright:
class ProxySpider(scrapy.Spider):
name = 'quotes'
custom_settings = {
"PLAYWRIGHT_LAUNCH_OPTIONS": {
"proxy": {
"server": "http://proxy.example:5353",
"username": "user",
"password": "pass",
}
}
}
def start_requests(self):
yield scrapy.Request(
'https://quotes.toscrape.com/js/',
meta=dict(
playwright=True,
playwright_context_kwargs={"ignore_https_errors": True},
),
)نکتهٔ امنیتی: هنگام استفاده از پراکسی با احراز هویت، کلیدها و توکنها را در متغیرهای محیطی نگهدارید و آنها را در سیستم کنترل نسخه ذخیره نکنید.
عملکرد، خطاها و بهترین شیوهها
- مدیریت منابع: حتماً page.close() را بعد از پایان کار فراخوانی کنید یا از errback برای بستن صفحات استفاده کنید.
- تایماوتها: وقتهایی که انتظار برای المنت طولانی میشود، از timeout معقول استفاده و در صورت نیاز retry کنید.
- مقیاسپذیری: رانکردن زیاد instanceِ مرورگر، حافظه و CPU بالایی مصرف میکند؛ برای کراول بزرگ از صفبندی درخواستها و محدودیت concurrency در Scrapy استفاده کنید.
- قوانین سایت و رباتها: قبل از اسکریپینگ احترام به قوانین مالکیت داده و فایل robots.txt را بررسی کنید و رفتار ریتلیمیت را رعایت کنید.
- Windows: در زمان نگارش برخی پیادهسازیها نیاز به WSL برای اجرای Playwright داشتند؛ در صورت مواجهه با خطاهای پلتفرم، اجرای در لینوکس یا کانتینرها راهحل است.
خطای متداول و راهحل سریع
خطایی از نوع "No module named 'scrapy_playwright'" معمولاً به خاطر فعال نبودن virtualenv یا نصب در محیط دیگر است. راهحل: محیط را deactivate و دوباره فعال کنید و مطمئن شوید بسته در همان venv نصب شده است.
جمعبندی
با ترکیب قدرت Scrapy برای استخراج و Playwright برای رندرینگ میتوانید صفحات مبتنی بر جاوااسکریپت را با دقت بالا استخراج کنید. مهمترین نکات عملی: نصب صحیح، فعالسازی دانلود هندلر، مدیریت صفحهها (بستن و errback)، استفاده از PageMethod برای انتظار، اسکرول و اسکرینشات، و رعایت نکات امنیتی و مقیاسپذیری. پس از این راهنما شما توانایی پیادهسازی اسپایدرهای رندرشده، مدیریت pagination و اسکرول بینهایت و کار با پراکسیها را دارید؛ برای موارد پیچیدهتر به توسعهٔ استراتژی retry و مدیریت منابع توجه کنید.





