مقدمه
در این راهنما گامبهگام دربارهٔ اسکریپینگ صفحات اسکرول بیپایان با پایتون توضیح میدهیم. ابتدا مفهوم و چالشها را مرور میکنیم، سپس سه روش عملی را با مثالهای کد و توضیحات خطبهخط نشان میدهیم: استفاده از یک مرورگر هدلس (Selenium)، شبیهسازی درخواستهای پسزمینه با requests، و نمونهای از استفاده از پروکسی/هدلس-سرویس (مثل ScrapeOps). در پایان نکات عملی برای پایداری، امنیت و بهینهسازی را جمعبندی میکنیم. مخاطب این مطلب برنامهنویس پایتون در سطح متوسط است که دنبال جزئیات اجرایی است.
صفحات اسکرول بیپایان چیستند؟
صفحات اسکرول بیپایان صفحاتی هستند که با رسیدن کاربر به پایین، با اجرای جاوااسکریپت درخواستهای جدیدی به سرور میزنند و محتوا را به DOM اضافه میکنند. این چرخه میتواند تا نامحدود ادامه یابد و برخلاف صفحههای صفحهبندی شده، همهٔ محتوا در یک URL و یک DOM تدریجی بارگذاری میشود.
فرآیند کلی به صورت خلاصه:
- کاربر صفحه را باز میکند
- به پایین اسکرول میکند
- جاوااسکریپت متوجه میشود که به پایین رسیدیم
- درخواست API یا AJAX برای محتوا ارسال میشود
- پاسخ دریافت و به صورت HTML یا JSON پردازش میشود
- محتوا به DOM اضافه میشود و حلقه تکرار میگردد
چالشها در اسکریپینگ صفحات بیپایان
این صفحات چند مشکل مهم برای اسکریپرها ایجاد میکنند:
- محتوای داینامیک: محتوای بعدی معمولاً با JS بارگذاری میشود و در پاسخ اولیه HTML نیست.
- بارگذاری ناهمزمان: چندین درخواست پسزمینه ممکن است همزمان ارسال شوند و ترتیب بارگذاری و دیتای نهایی را پیچیده کنند.
- سلکتورهای داینامیک: آیدیها یا کلاسها ممکن است تولیدی باشند و پیدا کردن المانها مشکل شود.
- محدودیت نرخ و محافظتها: تعداد زیاد درخواستها میتواند منجر به بلاک یا throttling شود.
- تحلیل دیداری: اگر هدف گرفتن تصاویر یا نمای صفحه باشد باید DOM و منابع رسانهای را هم درنظر گرفت.
نتیجهٔ مهم: کلید موفقیت این است که بفهمیم محتوا چگونه بارگذاری میشود (توسط API، AJAX، یا مستقیم توسط DOM) و بر اساس آن ابزار مناسب را انتخاب کنیم.
روش 1: اسکریپینگ بینهایت با Selenium
ایدهٔ کلی: با یک مرورگر واقعی (یا هدلس) رفتار کاربر را شبیهسازی میکنیم—اسکرول میکنیم، منتظر بارگذاری میمانیم و سپس محتوای جدید را میخوانیم یا ذخیره میکنیم. این روش برای سایتهایی که نیاز به اجرای JS دارند مناسبترین راه است.
مثال پایتون (Selenium): این اسکریپت یک مرورگر کروم هدلس باز میکند، چند بار به پایین اسکرول میکند و هر بار یک اسکرینشات میگیرد. متغیرهایی مثل تعداد تکرار و زمان انتظار را باید بر اساس سایت تنظیم کنید.
from selenium import webdriver
from time import sleep
options = webdriver.ChromeOptions()
# اجرای هدلس برای صرفهجویی در منابع
options.add_argument('--headless')
# نمونهسازی درایور
driver = webdriver.Chrome(options=options)
# آدرس هدف را به عنوان یک رشته قرار دهید (نمونه: 'TARGET_PAGE_URL')
target_url = 'TARGET_PAGE_URL'
driver.get(target_url)
# تعداد دفعات اسکرول و زمان انتظار میان هر اسکرول
iterations = 5
wait_seconds = 0.3
for i in range(iterations):
# اسکرول به پایین صفحه با جاوااسکریپت
driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
# گرفتن اسکرینشات برای بررسی نتیجه (یا استخراج DOM)
driver.save_screenshot(f'selenium-shot{i}.png')
# صبر کوتاه تا محتوا لود شود
sleep(wait_seconds)
driver.quit()
ورودیها و خروجیها:
- ورودی: target_url، تعداد تکرار و زمان انتظار.
- خروجی: اسکرینشاتها یا محتوای DOM که میتوانید با driver.page_source بخوانید.
توضیح خطبهخط:
- ساخت ChromeOptions و اضافهکردن '--headless' برای اجرا بدون رابط گرافیکی.
- باز کردن صفحه با driver.get.
- در حلقه: اجرا کردن JS برای اسکرول، گرفتن اسکرینشات، و توقف کوتاه برای بارگذاری.
- در نهایت بستن درایور با quit().
نکات و بهترین شیوهها:
- به جای زمان ثابت، بهتر است از مکانیزمهای منتظر شدن مبتنی بر وجود یک عنصر جدید استفاده کنید (مثل WebDriverWait)، ولی در صفحات بیپایان ممکن است عنصر مشخصی برای انتظار نباشد؛ در این حالت ترکیب استراتژیهای منتظر شدن و بررسی تغییر تعداد المانها مفید است.
- برای جلوگیری از بلاک شدن از پراکسی، ریتلیمیت و هدرهای واقعی مرورگر استفاده کنید.
- برای سایتهای سنگین از headful با مدیریت تبها و پاکسازی حافظه استفاده کنید.
روش 2: شبیهسازی درخواستهای پسزمینه با Requests
ایده: بسیاری از صفحات بیپایان محتوای اضافی را از طریق APIهای مشخص (مثلاً /api/quotes?page=) دریافت میکنند. اگر بتوانیم این الگو را در ابزار توسعهٔ مرورگر پیدا کنیم، میتوانیم مستقیم به API درخواست بزنیم و JSON را بخوانیم — بسیار سریعتر و پایدارتر از رندر کردن کامل صفحه.
مراحل عمومی:
- با DevTools تب شبکه را باز کنید و فیلتر XHR/Fetch را انتخاب کنید.
- الگوی endpointها و پارامترها (page, offset, cursor) را پیدا کنید.
- درخواستها را با requests تقلید کنید، دادهها را پارس کنید و ذخیره کنید.
مثال پایتون برای پیمایش صفحات API:
import requests
page = 1
for i in range(5):
# endpoint الگو را به جای TARGET_API_ENDPOINT قرار دهید، مثلاً '/api/quotes?page={}'
endpoint = f'TARGET_API_ENDPOINT?page={page}'
resp = requests.get(endpoint, timeout=10)
resp.raise_for_status()
data = resp.json() # معمولاً JSON برمیگردد
# پردازش داده: ذخیره در فایل، دیتابیس یا تبدیل به CSV
print(f'گرفتم صفحه {page} - آیتمها: {len(data.get("results", []))}')
page += 1
ورودی/خروجی و نقش توابع:
- requests.get: ارسال درخواست HTTP و گرفتن پاسخ.
- resp.json(): تبدیل محتوای JSON به دیکشنری پایتون.
- خروجی عملی: دادههای ساختیافتهای که میتوانند فایل یا رکورد دیتابیس شوند.
نکات عملی:
- از هدرهای واقعگرایانه مانند User-Agent و زمانبندی مناسب استفاده کنید.
- برای موفقیت بیشتر از مدیریت خطا و روال retry با backoff (مثل سه تلاش با افزایش تأخیر) استفاده کنید.
- این روش منابع تصویری یا HTML رندر شده را ندارد؛ اما برای دادههای ساختیافته بسیار سریع و کمهزینه است.
روش 3: استفاده از پروکسی هدلس/سرور هدلس (مثلاً ScrapeOps)
ایده: ترکیب مزایای Selenium (رندر JS) و Requests (سهولت کار) با استفاده از سرویسهایی که از طرف شما یک مرورگر هدلس اجرا میکنند یا درخواستها را از طریق پراکسی هدلس هدایت میکنند. این روش برای زمانی مناسب است که شما نمیخواهید یا نمیتوانید خودتان زیرساخت مرورگر را مدیریت کنید.
نمونهٔ ساختار پارامترها و حلقهای که به سرویس دستور اسکرول میدهد:
import requests
from bs4 import BeautifulSoup
proxy_api = 'PROXY_API_ENDPOINT'
params = {
'api_key': 'YOUR_API_KEY',
'url': 'TARGET_PAGE_URL',
'render_js': True,
'instructions': [
{'scroll_y': 5000},
{'wait': 3000}
]
}
# تکرار برای انجام چند اسکرول متوالی
for i in range(5):
resp = requests.get(proxy_api, params=params, timeout=120)
resp.raise_for_status()
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
# پاکسازی اسکریپتها اگر نیاز به تولید HTML تمیز دارید
for tag in soup(['script', 'link']):
tag.decompose()
clean_html = str(soup)
# ذخیره یا تبدیل به تصویر بسته به نیاز
with open(f'proxy-shot{i}.html', 'w', encoding='utf-8') as f:
f.write(clean_html)
# افزودن یک گام اسکرول و انتظار به دستورها تا در درخواست بعدی ادامه یابد
params['instructions'].append({'scroll_y': 5000})
params['instructions'].append({'wait': 3000})
چه زمانی از این روش استفاده کنیم:
- وقتی نمیخواهیم مدیریت مرورگرها را برعهده بگیریم ولی نیاز به رندر JS داریم.
- وقتی سرویس پروکسی قابلیت اجرای مجموعهای از دستورها را دارد (اسکرول، کلیک، پرکردن فرم).
محدودیتها:
- بعضی سرویسها ممکن است محدودیت در حفظ وضعیت جلسه یا حافظهٔ DOM داشته باشند و نیاز به طراحی مجدد داشته باشند.
- هزینه و تاخیر شبکه ممکن است بالاتر از اجرای محلی باشد.
مطالعه موردی: Scraping Behance
Behance نمونهٔ واقعی از صفحهٔ بیپایان است که ترکیبی از کارتهای پروژه و تصاویر را بارگذاری میکند. در ادامه سه رویکرد را با نکات اجرایی میآوریم.
Behance — Selenium
رویکرد Selenium برای گرفتن DOM کامل و مشاهدهٔ نحوهٔ اضافهشدن کارتها مفید است. نکتهٔ عملی: تصاویر ممکن است lazy-load باشند؛ بنابراین بعد از اسکرول باید صبر یا بررسی کنید که تگهای تصویر src کامل گرفتهاند.
پیشنهاد عملی: بعد از هر اسکرول با BeautifulSoup یا مستقیم با Selenium تعداد کارتها را بشمارید و زمانی که عدد تکراری شد، احتمالاً به ته صفحه رسیدهاید یا هیچ دادهٔ جدیدی لود نمیشود.
Behance — Requests (دانلود تصاویر)
اگر از DevTools بفهمید تصاویر یا CDNها مستقیم GET میشوند، میتوانید URLهای تصاویر را جمعآوری و با requests دانلود کنید—این سریع، هدفمند و کارا است چون نیازی به رندر کامل صفحه ندارید.
import requests
image_urls = [
'IMAGE_URL_1',
'IMAGE_URL_2',
# ...
]
for idx, url in enumerate(image_urls):
r = requests.get(url, timeout=15)
r.raise_for_status()
with open(f'behance-img-{idx}.jpg', 'wb') as f:
f.write(r.content)
print(f'downloaded {idx}')
نکته: نام فایل، فرمت و بررسی هدر Content-Type را فراموش نکنید. برای حجم بالا از چندنخی یا صف کاری استفاده کنید اما مراقب ریتلیمیت سرور باشید.
Behance — ScrapeOps / پروکسی هدلس
اگر سرویس پروکسی شما اجرای اسکریپت و اسکرول را پشتیبانی کند، میتوانید مانند نمونهٔ کلی پارامتر ارسال کنید و نتیجهٔ رندر شده را دریافت کنید. اما در عمل ممکن است محدودیتهایی در حفظ وضعیت طولانیمدت اسکرول وجود داشته باشد؛ بنابراین همیشه خروجی را با Selenium مقایسه کنید تا از درست بودن آن اطمینان حاصل کنید.
نکات عملی، امنیت و عملکرد
- مدیریت خطا و Retry: از مکانیزم retry با backoff نمایی استفاده کنید تا از بلاک شدن و خطاهای موقتی جلوگیری شود.
- هدرها و پراکسی: از هدرهای واقعی مرورگر، تاخیرهای تصادفی و پراکسی برای کاهش شانس بلاک شدن استفاده کنید.
- پایداری حافظه: در اجرای طولانیمدت مرورگرها حافظه و leak مدیریت کنید: تبها را ببندید، garbage collection را تحت نظر داشته باشید یا مرورگر را دورهای ریست کنید.
- احترام به قوانین: قوانین سایت (robots.txt و Terms of Service) را بررسی کنید و حقوق مالکیت محتوا را رعایت کنید.
- بهینهسازی I/O: برای دانلود فایلها از استریم باینری و برای ذخیره دادهها از batched writes یا دیتابیس استفاده کنید تا حافظه مصرفی کنترل شود.
جمعبندی
در انتها: اگر نیاز به اجرای جاوااسکریپت و دستکاری DOM دارید از Selenium یا مرورگر هدلس استفاده کنید. برای دادههای ساختیافته و API داخلی، Requests بسیار سریعتر و مقیاسپذیرتر است. سرویسهای پروکسی/هدلس میتوانند میانبر مناسبی باشند ولی همیشه خروجی آنها را کنترل و اعتبارسنجی کنید. کلید موفقیت در اسکریپینگ صفحات بیپایان ترکیبی از فهم الگوی بارگذاری داده، مدیریت نرخ درخواستها، و پیادهسازی مکانیزمهای مقاوم و قابلاعتماد است.





