مقدمه
در این مقاله تمرکز روی «اسکریپ کردن» صفحات وب با Node.js و چالش شناختهشدهٔ بارگذاری محتوا بعد از اسکرول است. خواهیم دید چگونه با Puppeteer و Playwright محتوای خارج از ویوپورت را بارگیری و استخراج کنیم، چه زمانی بهتر است از اسکرول ملایم استفاده کنیم و چه زمانی از پرش به انتهای صفحه. در انتها نمونههای کد، روشهای پیشرفته، جایگزین با استفاده از API داخلی و نکات عیبیابی و بهینهسازی را خواهید داشت.
خلاصهٔ سریع (TL;DR)
ایدهٔ کلی در هر دو کتابخانه یکسان است: مرورگر را لانچ کنید، صفحهای باز کنید، به URL بروید و با page.evaluate جاوااسکریپت صفحه را برای اسکرول اجرا کنید. مثال سادهٔ اسکرول 500px:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
await page.evaluate(() => window.scrollBy(0, 500)); // اسکرول 500px
await browser.close();
})();همین شیوه با Playwright:
const { chromium } = require("playwright");
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
await page.evaluate(() => window.scrollBy(0, 500));
await browser.close();
})();
درک انواع اسکرول و زمان استفاده
قبل از پیادهسازی باید بدانید صفحه شما چه مکانیزمی دارد:
- Infinite scrolling: دادهها در پسزمینه با اسکرول بارگذاری میشوند.
- Pagination: محتوا در صفحات مجزا با پارامترهای URL یا لینکها تقسیم شده.
- Lazy loading: تصاویر/بخشها فقط وقتی وارد ویو میشوند بارگذاری میشوند.
دو الگوی رایج اسکرول:
- Smooth (ملایم): با window.scrollBy در قدمهای کوچک حرکت کنید تا lazy-loading فعال شود.
- Jump (پرشی): با window.scrollTo(document.body.scrollHeight) مستقیم به انتها بروید؛ مناسب infinite scroll که رسیدن به انتها تریگر لود جدید است.
راهاندازی محیط
مطمئن شوید Node.js نصب است و سپس بستهها را با npm نصب کنید: puppeteer و playwright. همچنین برای درخواست مستقیم API از axios یا node-fetch استفاده کنید.
اسکرول با Puppeteer — پایه
Puppeteer روی Chromium کار میکند و برای کنترل صفحه از page.evaluate استفاده میکنیم تا جاوااسکریپت داخل مرورگر اجرا شود.
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto("https://example.com", { waitUntil: "load" });
// اسکرول ساده: 1000px پایین
await page.evaluate(() => window.scrollTo(0, 1000));
await browser.close();
})();
توضیح:
- ورودیها: URL صفحه؛ گزینههای launch و waitUntil برای کنترل رفتار بارگذاری.
- خروجی: صفحه در حالت اسکرولشده آمادهٔ استخراج داده.
- هر خط: puppeteer.launch مرورگر را میسازد، newPage صفحه جدید میسازد، page.goto به URL میرود و page.evaluate کد جاوا را در کانتکست مرورگر اجرا میکند.
اسکرول با Puppeteer — تا دیدن المان مشخص
این سناریو زمانی کاربرد دارد که میخواهید تا وقتی المان موردنظر ظاهر نشده، اسکرول را ادامه دهید.
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto("https://example.com/long-list");
// تا زمانی که سلکتور ظاهر نشده، هر بار 200px اسکرول کن
while ((await page.$("div.item:nth-child(100)")) == null) {
await page.evaluate(() => window.scrollBy(0, 200));
await new Promise(resolve => setTimeout(resolve, 100)); // تا لود کامل
}
// وقتی المان دید شد، میتوانید دادهها را استخراج کنید
await browser.close();
})();
نکات عملی:
- چک کردن page.$ در هر حلقه هزینهٔ IO دارد؛ برای صفحات خیلی طولانی از محدودکننده تعداد دفعات استفاده کنید.
- تأخیر کوتاه بین اسکرولها (مثلاً 100ـ300ms) به مرورگر و سرور زمان میدهد و از spike شدن CPU جلوگیری میکند.
اسکرول با Puppeteer — تا توقف شبکه (network idle)
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto("https://example.com/infinite", { waitUntil: "domcontentloaded" });
const isBottom = async () => {
return await page.evaluate(() => (window.innerHeight + window.scrollY) >= document.body.offsetHeight);
};
while (!(await isBottom())) {
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForNetworkIdle({ idleTime: 500 });
}
await browser.close();
})();
این روش مناسب infinite scroll است که بارگذاری با درخواستهای پسزمینه انجام میشود. مزیت: نیازی نیست سلکتور خاصی را بشناسید؛ معایب: اگر سایت از وبسوکت یا استریم استفاده کند، تشخیص network idle ممکن است مشکلساز شود.
اسکرول با Playwright — پایه
Playwright مشابه Puppeteer است اما از مرورگرهای بیشتری (Chromium، Firefox، WebKit) پشتیبانی میکند. سینتکس نزدیک است اما متدهای کمکی مخصوص خود را دارد.
const { chromium } = require("playwright");
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
await page.evaluate(() => window.scrollBy(0, 500));
await browser.close();
})();
توضیح: همان روند Puppeteer است؛ در Playwright میتوانید از page.locator و waitForLoadState برای هماهنگی بهتر استفاده کنید.
اسکرول در Playwright — تا المان یا تا networkidle
const { chromium } = require("playwright");
(async () => {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto("https://example.com/long-list");
while ((await page.locator("div.item:nth-child(100)").count()) < 1) {
await page.evaluate(() => window.scrollBy(0, 200));
await page.waitForTimeout(100);
}
await browser.close();
})();
مزیت locator.count(): کار با Playwright پایدارتر است و برای بررسی تعداد المانها مفید است.
پیمایش با تغییر URL (Pagination)
اگر سایت از پارامتر صفحه در URL استفاده میکند، نیازی به اسکرول نیست؛ کافیست الگوی URL را پیدا و درخواستهای صفحهای بزنید. مثال ساده با Playwright:
const { chromium } = require("playwright");
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
const maxPage = 10;
for (let i = 1; i <= maxPage; i++) {
await page.goto(`https://example.com?page=${i}`);
// استخراج داده از صفحه
}
await browser.close();
})();
این روش سریعتر و کمهزینهتر است چون نیاز به رندر کامل اسکرول ندارد؛ در عین حال باید الگوی پارامترها را از network inspector استخراج کنید.
استفاده از API داخلی به جای اسکرول
بسیاری از صفحات infinite scroll در نهایت از یک API داخلی برای آوردن دادهها استفاده میکنند. اگر بتوانید آن endpoint و پارامترها را مهندسی معکوس کنید، مستقیماً با axios یا fetch دادهها را بگیرید و از مرورگر صرفنظر کنید.
const axios = require("axios");
async function fetchDataFromAPI(endpoint, payload) {
const response = await axios.post(endpoint, payload);
return response.data; // پردازش پاسخ تابع
}
(async () => {
const data = await fetchDataFromAPI('https://api.example.com/load', { page: 2, limit: 20 });
console.log(data);
})();
توضیح: ورودیها endpoint و payload هستند که از بررسی شبکه استخراج میشوند؛ خروجی دادهٔ JSON قابل پردازش برای استخراج تکبهتک آیتمها است.
مشکلات شایع و راهحلها
- اسکرول عمل نمیکند: معمولاً المانهای JS نیاز به تعامل کاربر (مثلاً eventهای لمسی یا کلیک) دارند؛ امتحان کنید page.mouse.wheel یا شبیهسازی تعامل کاربر را انجام دهید.
- لودینگ خیلی طول میکشد: زمان تأخیر بین اسکرولها را افزایش دهید یا صبر کنید تا المانهای هدف حاضر شوند (waitForSelector).
- محافظت ضد ربات: بعضی سایتها رفتار اسکریپ را تشخیص میدهند؛ از پراکسی، ریتلیمیت و تغییر User-Agent استفاده کنید و همیشه قوانین سایت را رعایت نمایید.
بهترینروشها
- تا حد لازم صبر کنید: بین اسکرولها تاخیر معقول قرار دهید تا سرویس هدف متحمل بار نشود.
- چکهای پایانی: برای جلوگیری از حلقهٔ بینهایت، محدودیت تعداد اسکرول یا زمان کلی تعیین کنید.
- انتخاب ابزار مناسب: اگر فقط داده از یک API بارگذاری میشود، استفاده از HTTP مستقیم سریعتر و اقتصادیتر است تا رندر کردن کل صفحه.
- امنیت و احترام: سیاستهای سایت (robots.txt) و قوانین سرویس دهنده را رعایت کنید و دادههایی که اجازهٔ جمعآوری ندارند را استخراج نکنید.
جمعبندی
اسکرول کردن در وب اسکریپینگ یک نیاز رایج است و Puppeteer و Playwright ابزارهای قدرتمندی برای آن فراهم میکنند. برای هر پروژه ابتدا ساختار بارگذاری سایت را تحلیل کنید: آیا pagination URL محور است؟ از API داخلی استفاده میکند؟ یا infinite scroll با lazy loading دارد؟ سپس مناسبترین روش (اسکرول ملایم، پرشی، یا فراخوانی API) را پیاده کنید و همیشه محدودیتها، تاخیرها و مسائل اخلاقی را مد نظر نگه دارید.
با ترکیب مثالهای بالا میتوانید یک استراتژی پایدار برای صفحات طولانی بسازید و بهینهسازی برای سرعت و پایداری انجام دهید.




