مقدمه
در این مقالهِ عملی، با Pyppeteer — یک wrapper غیررسمی پایتون برای Puppeteer — آشنا میشویم. هدف این است که بتوانید صفحات جاوااسکریپتدار را رندر، تعامل و استخراج (اسکریپینگ) کنید. پس از خواندن این مطلب یک اسکریپت پایه برای باز کردن صفحه، صبر کردن تا بارگذاری کامل، کلیک، اسکرول، گرفتن اسکرینشات و استفاده از پراکسی خواهید داشت. همچنین نکات مربوط به خطا، بهینهسازی و امنیت را میبینید.
نصب و آمادهسازی
نصب pyppeteer ساده است. از pip استفاده کنید یا اگر به روزترین نسخهٔ توسعه را میخواهید، آن را از مخزن گیت نصب کنید. همچنین اولین اجرای Pyppeteer معمولاً دانلود Chromium را انجام میدهد (حدود 150 مگابایت) که میتوانید با دستور نصب از پیش آن را دانلود کنید تا زمان اجرای اسکریپتها کوتاهتر شود.
pip install pyppeteer
# یا برای نسخهٔ dev
pip install -U git+https://github.com/pyppeteer/pyppeteer@dev
# در صورت تمایل برای دانلود Chromium پیش از اجرا
pyppeteer-install
نکته: اگر در محیط CI یا سرور کار میکنید، Chromium را از قبل نصب کنید و مسیر آن را به launch بدهید تا از دانلود زمانبر جلوگیری شود.
نمونهٔ پایه: باز کردن صفحه و گرفتن اسکرینشات
# demo.py
import asyncio
from pyppeteer import launch
async def main():
browser = await launch()
page = await browser.newPage()
await page.goto('https://quotes.toscrape.com/')
await page.screenshot({'path': 'screenshot.png'})
await browser.close()
if __name__ == '__main__':
asyncio.run(main())
شرح اجزای کد:
- launch(): مرورگر Chromium را اجرا میکند. میتوان پارامترهایی مانند headless، آرگومانهای خط فرمان و مسیر باینری را به آن داد.
- newPage(): یک تب/صفحهٔ جدید در مرورگر میسازد.
- page.goto(url): صفحهٔ هدف را بارگذاری میکند و معمولاً تا زمان دریافت اولین پاسخ صبر میکند مگر اینکه timeout تنظیم شده باشد.
- page.screenshot(): اسکرینشات میگیرد؛ مسیر فایل را با کلید path مشخص میکنیم.
- در پایان browser.close() منابع را آزاد میکند.
اسکریپ کردن صفحات: دو روش معمول
برای استخراج داده از صفحات جاوااسکریپتدار دو رویکرد رایج وجود دارد:
- با استفاده از API DOM داخل صفحه (querySelector و getProperty)
- دریافت HTML کامل با page.content() و پارس با کتابخانههایی مثل BeautifulSoup
# روش اول: استخراج مستقیم از DOM
import asyncio
from pyppeteer import launch
async def main():
browser = await launch()
page = await browser.newPage()
await page.goto('https://quotes.toscrape.com/')
# انتخاب عنصر h1 و دریافت محتوای متنی آن
title_el = await page.querySelector('h1')
title_prop = await title_el.getProperty('textContent')
title = await title_prop.jsonValue()
print('title:', title)
await browser.close()
asyncio.run(main())
نکات خط به خط: querySelector یک ElementHandle برمیگرداند؛ getProperty('textContent') یک JSHandle برمیگرداند که برای خواندن مقدار نهایی از jsonValue() استفاده میکنیم.
# روش دوم: گرفتن HTML و استفاده از BeautifulSoup
import asyncio
from pyppeteer import launch
from bs4 import BeautifulSoup
async def fetch_html():
browser = await launch()
page = await browser.newPage()
await page.goto('https://quotes.toscrape.com/')
html = await page.content()
await browser.close()
return html
html_response = asyncio.run(fetch_html())
soup = BeautifulSoup(html_response, 'html.parser')
print('title:', soup.find('h1').text)
مزیت روش دوم این است که از ابزارهای آشنا برای پردازش HTML استفاده میکنید؛ معایب: ممکن است بعضی دادههای تولیدشدهٔ جاوااسکریپت در حالت استریم نباشند مگر صبر کنید یا اسکرول کنید.
صبر برای بارگذاری: استراتژیها
دو روش اصلی برای منتظر ماندن وجود دارد:
- منتظر زمان مشخص شدن (sleep)
- منتظر ظاهر شدن یک selector مشخص
# انتظار ثابت (میتواند غیرقابلاعتماد باشد)
await page.waitFor(5000) # میلیثانیه
این رویکرد ساده است اما قابل اعتماد نیست: زمان لازم ممکن است بسته به شبکه یا بار سرور تغییر کند.
# انتظار برای یک selector تا قابل رؤیت شود
await page.waitForSelector('h1', {'visible': True})
این روش دقیقتر و قابلاطمینانتر است؛ معمولاً ترکیبِ timeout مناسب و بررسی خطا بهترین نتیجه را میدهد.
کلیک کردن روی عناصر
# کلیک روی یک عنصر
link = await page.querySelector('h1')
await link.click()
نکات عملی: گاهی لازم است قبل از کلیک اسکرول یا صبر کنید تا عنصر قابل کلیک شود. در صفحههایی با listenerهای پیچیده جاوااسکریپت ممکن است لازم شود از page.evaluate برای اجرای JS سفارشی استفاده کنید.
اسکرول کردن و بارگذاری تدریجی (Infinite Scroll)
# نمونهٔ سادهٔ اسکرول تا انتها
import asyncio
from pyppeteer import launch
async def scroll_page(url):
browser = await launch()
page = await browser.newPage()
await page.goto(url)
# اجرای JS برای اسکرول کردن تا پایین صفحه
await page.evaluate('''() => { window.scrollBy(0, document.body.scrollHeight); }''')
# در صفحات infinite scroll معمولاً باید چند بار این کار را تکرار و بین هر بار صبر کرد
await browser.close()
asyncio.run(scroll_page('https://quotes.toscrape.com/scroll'))
برای صفحات پیچیده معمولاً الگوریتمی با حلقه و بررسی تغییر طول DOM یا تعداد آیتمها لازم است تا وقتی دیگر محتوای جدید بارگذاری نشد، توقف کنید.
اسکرینشات و تنظیم نمای صفحه
# تغییر اندازهٔ viewport و گرفتن اسکرینشات
await page.setViewport({'width': 1600, 'height': 900})
await page.screenshot({'path': 'screenshot.png', 'fullPage': False})
گزینهٔ fullPage برای گرفتن اسکرینشات از کل صفحه مفید است؛ دقت کنید که سایز بزرگ میتواند حافظه بیشتری مصرف کند.
استفاده از پراکسی و احراز هویت پراکسی
# راهاندازی مرورگر با پراکسی
browser = await launch({
'args': ['--proxy-server=ip:port'],
'headless': False,
})
# اگر پراکسی نیاز به احراز هویت داشت
await page.authenticate({'username': 'user', 'password': 'passw'})
نکتهٔ عملی: پراکسی را قبل از page.goto تنظیم کنید و اگر از pool پراکسی استفاده میکنید، مدیریت خطا و retry را برای هر درخواست لحاظ کنید.
سایر قابلیتهای مهم و تنظیمات
- تنظیم User-Agent با page.setUserAgent() برای کاهش احتمال شناسایی توسط سایت.
- اجرای در حالت headless یا headful با پارامتر headless در launch().
- مسدود کردن بارگذاری منابع سنگین (مثل تصاویر یا ویدیو) با request interception برای افزایش سرعت.
- استفاده از page.evaluate() برای اجرای کد JS سفارشی در کانتکست صفحه (مثلاً گرفتن دادههای ساختیافته یا اجرای توابع موجود در سایت).
نکات عملی، محدودیتها و بهترین روشها
- مدیریت خطا: همیشه try/except دور کدهای async بیندازید، timeout معقول تنظیم کنید و در صورت خطا منابع را ببندید.
- retry و backoff: برای درخواستهای حساس از استراتژی retry با افزایش تدریجی فاصله استفاده کنید.
- پایداری: در اسکریپهای طولانی تعداد تبها و حافظه را کنترل کنید؛ بعد از هر تعدادی عملیات، مرورگر را ریاستارت کنید تا نشتی حافظه کاهش یابد.
- همزمانی: از asyncio.gather برای اجرای همزمان چند تب استفاده کنید، اما مراقب محدودیتهای شبکه و سرور هدف باشید.
- اجتناب از شناسایی: تغییر user-agent، تنظیم viewport، تاخیرهای متغیر انسانینما و استفاده از پراکسی شفاف به کاهش ریسک بلاک شدن کمک میکند؛ اما هیچ تضمینی وجود ندارد.
- قوانین و اخلاق: همیشه قوانین سایت (robots.txt) و مقررات مربوط به مالکیت داده و استفادهٔ منصفانه را لحاظ کنید و از بارگذاری بیمورد سرور جلوگیری کنید.
جمعبندی
Pyppeteer ابزار قدرتمندی برای اسکریپینگ صفحات دینامیک در پایتون است؛ با ترکیب تواناییهای رندر مرورگر، اجرای JS داخل صفحه و امکاناتی مثل پروکسی و گرفتن اسکرینشات میتوانید اکثر نیازهای استخراج داده را پوشش دهید. بهترین رویکردها شامل استفاده از waitForSelector بهجای sleep ثابت، مدیریت خطا و منابع، و رعایت اخلاق و قوانین سایت است.





