

در این مقاله تمرکز روی «اسکریپ کردن» صفحات وب با Node.js و چالش شناختهشدهٔ بارگذاری محتوا بعد از اسکرول است. خواهیم دید چگونه با Puppeteer و Playwright محتوای خارج از ویوپورت را بارگیری و استخراج کنیم، چه زمانی بهتر است از اسکرول ملایم استفاده کنیم و چه زمانی از پرش به انتهای صفحه. در انتها نمونههای کد، روشهای پیشرفته، جایگزین با استفاده از API داخلی و نکات عیبیابی و بهینهسازی را خواهید داشت.
ایدهٔ کلی در هر دو کتابخانه یکسان است: مرورگر را لانچ کنید، صفحهای باز کنید، به 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();
})();
قبل از پیادهسازی باید بدانید صفحه شما چه مکانیزمی دارد:
دو الگوی رایج اسکرول:
مطمئن شوید Node.js نصب است و سپس بستهها را با npm نصب کنید: puppeteer و playwright. همچنین برای درخواست مستقیم API از axios یا node-fetch استفاده کنید.
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();
})();
توضیح:
این سناریو زمانی کاربرد دارد که میخواهید تا وقتی المان موردنظر ظاهر نشده، اسکرول را ادامه دهید.
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();
})();
نکات عملی:
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 مشابه 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 برای هماهنگی بهتر استفاده کنید.
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 استفاده میکند، نیازی به اسکرول نیست؛ کافیست الگوی 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 استخراج کنید.
بسیاری از صفحات 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 قابل پردازش برای استخراج تکبهتک آیتمها است.
اسکرول کردن در وب اسکریپینگ یک نیاز رایج است و Puppeteer و Playwright ابزارهای قدرتمندی برای آن فراهم میکنند. برای هر پروژه ابتدا ساختار بارگذاری سایت را تحلیل کنید: آیا pagination URL محور است؟ از API داخلی استفاده میکند؟ یا infinite scroll با lazy loading دارد؟ سپس مناسبترین روش (اسکرول ملایم، پرشی، یا فراخوانی API) را پیاده کنید و همیشه محدودیتها، تاخیرها و مسائل اخلاقی را مد نظر نگه دارید.
با ترکیب مثالهای بالا میتوانید یک استراتژی پایدار برای صفحات طولانی بسازید و بهینهسازی برای سرعت و پایداری انجام دهید.