تیم شما از قبل با Node.js کار میکند و کسی عنوان، قیمت و تعداد موجودی همه کتابهای یک دسته از یک کاتالوگ را میخواهد. کد منبع صفحه در مرورگر نشان میدهد که دادهها داخل تگهای ساده <article> قرار دارند، پس برای خواندن آنها به مرورگر نیازی ندارید. آنچه لازم دارید راهی است برای دانلود HTML، انتخاب عنصرهای درست، دنبال کردن پیوند «next» و جلوگیری از اینکه اسکریپت سایت را زیر بار ببرد یا با اولین مهلت زمانی (timeout) از کار بیفتد. مسیر کلی از صفحه تا فایل در چگونه از یک وبسایت داده استخراج کنیم آمده است؛ این آموزش همان کار را در JavaScript با Cheerio انجام میدهد.
در این نوشته میبینیم Cheerio چیست و چه نیست، بارگذاری HTML با load و fromURL، انتخابگرها و فهرستها، متد جدیدتر extract، صفحهبندی، محدودیت همزمانی، تلاش مجدد با تأخیر فزاینده (backoff)، نوشتن JSON و ارسال درخواستها از طریق پروکسی با ProxyAgent کتابخانه undici. بخش آخر توضیح میدهد چه زمانی Cheerio ابزار مناسبی نیست. همه نمونهها در 28 سپتامبر 2026 با cheerio 1.2.0، undici 8.11.2 و Node.js 24.11.1 روی books.toscrape.com اجرا شدند؛ سایتی آزمایشی که برای تمرین وب اسکرپینگ ساخته شده است.
Cheerio چیست؟
Cheerio یک تجزیهگر HTML و XML برای Node.js است که API آن از jQuery الگو گرفته است. شما کد نشانهگذاری را به آن میدهید، درخت سند را میسازد و شما آن درخت را با $("css selector") جستوجو میکنید. سرعتش از این جهت است که هر کاری را که مرورگر پس از تجزیه انجام میدهد کنار میگذارد: چیدمان صفحه محاسبه نمیشود، CSS اعمال نمیشود، تصویری بارگذاری نمیشود و اسکریپتی اجرا نمیشود.
نسخه فعلی 1.2.0 است (cheerio در npm) و به Node.js 20.18.1 یا بالاتر نیاز دارد. نسخه 1.0 که در اوت 2024 منتشر شد، به دوره نسخههای نامزد انتشار (release candidate) پایان داد که از 2017 آغاز شده بود. این بسته خروجی پیشفرض ندارد، پس باید بنویسید import * as cheerio from "cheerio". آموزشهایی که require("cheerio").default را صدا میزنند برای نسخههای قدیمی نوشته شدهاند.
Cheerio فقط HTMLی را میبیند که سرور فرستاده است. اگر فهرست محصولات بعداً با JavaScript پر شود، دادهها در آن HTML نیستند و هیچ انتخابگری آنها را پیدا نمیکند. صفحههای ایستا و پویا نشان میدهد چگونه پیش از نوشتن هر کدی نوع صفحه را بررسی کنید.
اسکرپر مبتنی بر Cheerio چگونه کار میکند؟
اسکرپری که بر پایه Cheerio ساخته شده، برای هر صفحه همین پنج گام را تکرار میکند:
- دانلود. یک کلاینت HTTP (در اینجا
fetch) نشانی را درخواست میکند و HTML را بهصورت متن دریافت میکند. - تجزیه.
cheerio.load(html)درخت را میسازد و تابع$متصل به همان سند را برمیگرداند. - انتخاب.
$("article.product_pod")همه عنصرهای منطبق را برمیگرداند؛.find()و.text()و.attr()درون آنها را میخوانند. - دنبال کردن. اسکرپر نشانی بعدی را از صفحه میخواند (پیوند صفحهبندی یا پیوند صفحه جزئیات) و آن را نسبت به نشانی فعلی کامل میکند.
- ذخیره. ردیفها در حافظه جمع میشوند و در پایان در فایل یا پایگاه داده نوشته میشوند.
گامهای 2 و 3 هرگز به شبکه دست نمیزنند. این جدایی در اشکالزدایی مهم است: اگر انتخابگری چیزی برنگرداند، HTML را در فایلی ذخیره کنید و انتخابگر را روی همان فایل امتحان کنید، بیآنکه درخواست دیگری بفرستید.
مقایسه Cheerio، jsdom و Playwright
سه ابزاری که برای اسکرپینگ در Node.js بیش از همه با هم مقایسه میشوند، کارهای متفاوتی انجام میدهند:
| ابزار | چه میکند | اجرای JavaScript صفحه | هزینه هر صفحه | کاربرد مناسب |
|---|---|---|---|---|
| Cheerio | تجزیه HTML، پرسوجو به سبک jQuery | خیر | کمترین: فقط تجزیه | HTML رندرشده در سرور، تعداد صفحه زیاد |
| jsdom | ساخت DOM شبیه مرورگر در Node.js | اختیاری، محدود | بیشتر از Cheerio | کدی که document و APIهای DOM را انتظار دارد |
| Playwright | کنترل یک Chromium، Firefox یا WebKit واقعی | بله | بیشترین: مرورگر کامل | صفحههایی که محتوا را با JavaScript میسازند، کلیک، ورود به حساب |
یک چیدمان رایج از هر دو سر استفاده میکند: Playwright برای معدود صفحههایی که به مرورگر نیاز دارند و Cheerio برای بقیه. انتخاب زبان پرسش جداگانهای است که در وب اسکرپینگ: JavaScript یا Python؟ به آن پرداخته شده است.
نصب Cheerio و بارگذاری نخستین صفحه
یک پروژه بسازید و بسته را نصب کنید. افزودن "type": "module" به شما امکان میدهد از import و await در سطح بالا استفاده کنید:
mkdir book-scraper && cd book-scraper
npm init -y
npm pkg set type=module
npm install cheerioNode.js 18 و نسخههای بعدی fetch را همراه دارند، پس نخستین اسکریپت به چیز دیگری نیاز ندارد:
import * as cheerio from "cheerio";
const url = "https://books.toscrape.com/";
const response = await fetch(url, {
headers: { "user-agent": "book-research/1.0 (+mailto:you@example.com)" },
});
if (!response.ok) throw new Error(`HTTP ${response.status} for ${url}`);
const $ = cheerio.load(await response.text());
console.log($("title").text().trim());
console.log($("article.product_pod").length, "books on this page");
$("article.product_pod").slice(0, 3).each((i, el) => {
const card = $(el);
const title = card.find("h3 a").attr("title");
const price = card.find(".price_color").text();
console.log(i + 1, title, price);
});All products | Books to Scrape - Sandbox
20 books on this page
1 A Light in the Attic £51.77
2 Tipping the Velvet £53.74
3 Soumission £50.10عنوان از ویژگی title پیوند میآید، نه از متن آن: در این سایت متن نمایان پیوند کوتاه شده است («In a Dark, Dark ...») در حالی که ویژگی، عنوان کامل را نگه میدارد. پیش از انتخاب، هر دو را در کد منبع بررسی کنید. سرآیند user-agent اسکریپت شما را معرفی میکند و به مالک سایت راهی برای تماس با شما میدهد.
روشهای بارگذاری
Cheerio 1.x پنج راه برای بارگذاری سند دارد (مستندات بارگذاری Cheerio):
| متد | ورودی | چه زمانی استفاده شود |
|---|---|---|
load(html) | یک رشته | صفحه را خودتان دانلود کردهاید (حالت معمول) |
loadBuffer(buffer) | بایتهای خام | کدگذاری نامعلوم است؛ Cheerio آن را تشخیص میدهد |
stringStream(options, cb) | جریان متن رمزگشاییشده | فایلهای بزرگ با کدگذاری معلوم |
decodeStream(options, cb) | جریان بایت خام | فایلهای بزرگ با کدگذاری نامعلوم |
fromURL(url, options) | یک نشانی | اسکریپتهای سریع؛ Cheerio خودش صفحه را دانلود میکند |
fromURL راحت است، اما برای مبدأ صفحه کلاینت undici جداگانهای باز میکند. در آزمایش ما، این تابع dispatcher ارسالشده در requestOptions را نادیده گرفت و مستقیم وصل شد، حتی وقتی آن dispatcher به پروکسیای اشاره میکرد که همه درخواستها را رد میکرد. برای هر کاری که به پروکسی، تلاش مجدد یا مهلت زمانی نیاز دارد، با fetch دانلود کنید و از load استفاده کنید.
انتخاب عنصرها و خواندن مقدارها
بیشتر کدهای اسکرپینگ فقط بخش کوچکی از API را به کار میبرند:
$(selector)در کل سند انتخاب میکند؛el.find(selector)درون یک عنصر جستوجو میکند..text()متن ترکیبی انتخاب را برمیگرداند؛.attr("href")یک ویژگی از نخستین عنصر را برمیگرداند..each((i, el) => …)حلقه میزند؛.map((i, el) => value).get()انتخاب را به یک آرایه ساده تبدیل میکند..first()و.eq(n)و.slice(a, b)انتخاب را محدودتر میکنند.
انتخابگری که با هیچ عنصری منطبق نشود خطا نمیدهد. .text() رشته خالی و .attr() مقدار undefined برمیگرداند، پس تغییر نام یک کلاس به جای خطا، فیلدهای خالی تولید میکند. ردیفهایی را که جمع میکنید اعتبارسنجی کنید (جزئیات بیشتر در فهرست اشتباهات پایینتر). نحو انتخابگرها و دلیل نداشتن XPath در Cheerio در انتخابگر CSS یا XPath آمده است.
متد extract
Cheerio 1.0 متد $.extract() را اضافه کرد که کل رکورد را بهصورت یک شیء توصیف میکند (مستندات extract در Cheerio). یک رشته متن نخستین تطابق را برمیگرداند، کروشه همه تطابقها را جمع میکند و { selector, value } یک ویژگی را میخواند یا تابعی را اجرا میکند:
import * as cheerio from "cheerio";
const $ = await cheerio.fromURL("https://books.toscrape.com/");
const data = $.extract({
heading: "h1",
books: [
{
selector: "article.product_pod",
value: {
title: { selector: "h3 a", value: "title" },
price: ".price_color",
link: { selector: "h3 a", value: "href" },
rating: {
selector: "p.star-rating",
value: (el) => $(el).attr("class").replace("star-rating", "").trim(),
},
},
},
],
});
console.log(data.heading, data.books.length);
console.log(data.books[0]);All products 20
{
title: 'A Light in the Attic',
price: '£51.77',
link: 'catalogue/a-light-in-the-attic_1000/index.html',
rating: 'Three'
}انتخابگرهای درون value نسبت به هر article اجرا میشوند و همین باعث میشود فیلدهای یک کتاب کنار هم بمانند. پیوند نسبی باقی میماند، پس پیش از درخواست آن را با new URL(link, pageUrl) کامل کنید.
یک اسکرپر کامل: صفحهبندی، همزمانی، تلاش مجدد و JSON
اسکریپت زیر همه کتابهای دسته Mystery را جمع میکند. با دنبال کردن پیوند «next» صفحههای فهرست را میپیماید، صفحه جزئیات هر کتاب را با حداکثر چهار درخواست همزمان باز میکند، خطاهای شبکه، مهلتهای زمانی و پاسخهای 429 و 5xx را با backoff نمایی دوباره امتحان میکند و فایل books.json را مینویسد. این اسکریپت از fetch کتابخانه undici استفاده میکند تا پروکسی اختیاری بخش بعد بدون تغییر کار کند. آن را با npm install cheerio undici نصب کنید (undici 8 به Node.js 22.19 یا بالاتر نیاز دارد).
import * as cheerio from "cheerio";
import { fetch, ProxyAgent } from "undici";
import { writeFile } from "node:fs/promises";
const START_URL =
"https://books.toscrape.com/catalogue/category/books/mystery_3/index.html";
const CONCURRENCY = 4; // detail pages fetched at the same time
const MAX_RETRIES = 3; // extra attempts after the first one
const HEADERS = { "user-agent": "book-research/1.0 (+mailto:you@example.com)" };
// Optional proxy: PROXY_URL=http://user:pass@pr.proxynet.io:8000
const dispatcher = process.env.PROXY_URL
? new ProxyAgent(process.env.PROXY_URL)
: undefined;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const backoff = (attempt) => 1000 * 2 ** (attempt - 1) + Math.random() * 250;
class HttpError extends Error {
constructor(status, url) {
super(`HTTP ${status} for ${url}`);
this.status = status;
}
}
async function fetchHtml(url) {
for (let attempt = 1; ; attempt++) {
let wait;
try {
const res = await fetch(url, {
headers: HEADERS,
dispatcher,
signal: AbortSignal.timeout(15_000),
});
if (res.ok) return await res.text();
const retryable = res.status === 429 || res.status >= 500;
if (!retryable || attempt > MAX_RETRIES) throw new HttpError(res.status, url);
const retryAfter = Number(res.headers.get("retry-after"));
wait = retryAfter > 0 ? retryAfter * 1000 : backoff(attempt);
} catch (err) {
if (err instanceof HttpError || attempt > MAX_RETRIES) throw err;
wait = backoff(attempt); // network error or timeout
}
console.warn(`retry ${attempt}/${MAX_RETRIES} in ${Math.round(wait)} ms: ${url}`);
await sleep(wait);
}
}
// Run fn over items with at most `limit` calls in flight.
async function mapLimit(items, limit, fn) {
const results = new Array(items.length);
let next = 0;
async function worker() {
while (next < items.length) {
const i = next++;
results[i] = await fn(items[i], i);
}
}
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
return results;
}
function parseListPage(html, pageUrl) {
const $ = cheerio.load(html);
const books = $("article.product_pod")
.map((_, el) => {
const card = $(el);
const link = card.find("h3 a");
return {
title: link.attr("title"),
price: Number(card.find(".price_color").text().replace(/[^0-9.]/g, "")),
rating: card.find("p.star-rating").attr("class").split(" ").pop(),
url: new URL(link.attr("href"), pageUrl).href,
};
})
.get();
const nextHref = $("li.next a").attr("href");
return { books, nextUrl: nextHref ? new URL(nextHref, pageUrl).href : null };
}
function parseDetailPage(html) {
const $ = cheerio.load(html);
const info = {};
$("table.table-striped tr").each((_, row) => {
info[$(row).find("th").text().trim()] = $(row).find("td").text().trim();
});
const stock = info["Availability"]?.match(/\((\d+) available\)/);
return {
upc: info["UPC"],
inStock: stock ? Number(stock[1]) : 0,
description: $("#product_description + p").text().trim(),
};
}
// 1. Walk the listing pages by following the "next" link.
const listed = [];
for (let url = START_URL; url; ) {
const { books, nextUrl } = parseListPage(await fetchHtml(url), url);
listed.push(...books);
console.log(`${url} -> ${books.length} books`);
url = nextUrl;
}
// 2. Open every detail page, four at a time.
const books = await mapLimit(listed, CONCURRENCY, async (book) => {
try {
return { ...book, ...parseDetailPage(await fetchHtml(book.url)) };
} catch (err) {
console.error(`skipped ${book.url}: ${err.message}`);
return { ...book, error: err.message };
}
});
// 3. Save the result.
await writeFile(
"books.json",
JSON.stringify({ scrapedAt: new Date().toISOString(), count: books.length, books }, null, 2),
);
console.log(`saved ${books.length} books to books.json`);https://books.toscrape.com/catalogue/category/books/mystery_3/index.html -> 20 books
https://books.toscrape.com/catalogue/category/books/mystery_3/page-2.html -> 12 books
saved 32 books to books.jsonیک رکورد از books.json (توضیح کوتاهشده):
{
"title": "Sharp Objects",
"price": 47.82,
"rating": "Four",
"url": "https://books.toscrape.com/catalogue/sharp-objects_997/index.html",
"upc": "e00eb4fd7b871a48",
"inStock": 20,
"description": "…"
}کار هر بخش:
- صفحهبندی. حلقه وقتی متوقف میشود که صفحه
li.next aنداشته باشد. پیوند صفحه 1 برابرpage-2.htmlو نسبت به پوشه دسته است؛ به همین دلیل هر نشانی ازnew URL(href, pageUrl)میگذرد. الگوهای دیگر (شماره صفحه در query string، کرسرها، APIهای «بارگذاری بیشتر») در صفحهبندی در وب اسکرپینگ آمدهاند. - محدودیت همزمانی.
mapLimitچهار worker راه میاندازد که هر کدام مورد بعدی را از یک شمارنده مشترک برمیدارند.Promise.allروی هر 32 نشانی، 32 درخواست را یکجا میفرستد؛ با 1,000 نشانی این کار برای سرور مثل یک هجوم ناگهانی دیده میشود. عدد چهار برای یک سایت کوچک نقطه شروعی محترمانه است. - تلاش مجدد. فقط خطاهایی دوباره امتحان میشوند که ممکن است خودبهخود برطرف شوند: خطای شبکه، مهلت زمانی 15 ثانیهای، 429 و 5xx. پاسخ 404 بلافاصله شکست میخورد. سرآیند عددی
Retry-Afterبر تأخیر محاسبهشده مقدم است؛ backoff از حدود یک ثانیه دوبرابر میشود و یک مقدار تصادفی (jitter) به آن اضافه میشود تا workerهای موازی همزمان تلاش مجدد نکنند. علت بروز 429 و نحوه خواندن این سرآیند در HTTP 429 Too Many Requests توضیح داده شده است. - شکست جزئی. صفحه جزئیاتی که پس از سه تلاش مجدد هنوز شکست میخورد، به جای متوقف کردن اجرا به ردیفی با فیلد
errorتبدیل میشود. بعداً میتوانید فقط همین ردیفها را دوباره اجرا کنید. - JSON. فایل شامل
scrapedAtوcountاست که در مقایسه اجراها کمک میکند. برای CSV، JSON Lines یا SQLite با upsert به ذخیره دادههای اسکرپینگ در CSV، JSON و SQLite مراجعه کنید.
استفاده از پروکسی با Cheerio (undici ProxyAgent)
در این اسکریپت Cheerio هرگز اتصالی باز نمیکند، پس پروکسی به کلاینت HTTP مربوط است. با undici یک ProxyAgent میسازید و آن را بهعنوان dispatcher به fetch میدهید. اسکریپت کامل بالا وقتی PROXY_URL تنظیم شده باشد همین کار را انجام میدهد:
PROXY_URL=http://user:pass@pr.proxynet.io:8000 node scrape-books.mjsundici سرآیند Proxy-Authorization را از نام کاربری و گذرواژه درون نشانی میسازد و پیش از آن، آنها را از حالت URL-encoded رمزگشایی میکند؛ بنابراین نویسههای خاص در گذرواژه باید percent-encoded شوند (مستندات ProxyAgent در undici). برای مقصدهای HTTPS، agent یک تونل CONNECT باز میکند و TLS تا سایت درون همین تونل برقرار میشود.
ما اسکریپت را از طریق یک پروکسی محلی کوچک اجرا کردیم که user:pass را الزامی میکرد و هر تونل را ثبت میکرد. هر 34 درخواست (دو صفحه فهرست و 32 صفحه جزئیات) از طریق یک CONNECT books.toscrape.com:443 رسیدند: agent تونل را باز نگه داشت و دوباره از آن استفاده کرد. با گذرواژه اشتباه، پروکسی پاسخ 407 داد و undici پیام Proxy response (407) !== 200 when HTTP Tunneling را گزارش کرد. اسکریپت پیش از تسلیم شدن سه بار دوباره تلاش کرد؛ گذرواژه اشتباه هیچوقت خودبهخود درست نمیشود، پس به جای بالا بردن تعداد تلاشها، اطلاعات ورود را بررسی کنید.
اگر نمیخواهید undici را به وابستگیها اضافه کنید، Node.js 24.5 و 22.21 پشتیبانی داخلی از پروکسی را اضافه کردهاند که با تنظیم NODE_USE_ENV_PROXY=1 متغیرهای HTTP_PROXY و HTTPS_PROXY و NO_PROXY را میخواند (پشتیبانی داخلی Node.js از پروکسی). مستندات این قابلیت را در حال توسعه فعال معرفی میکند. در آزمایش ما روی Node.js 24.11.1، همان fetch سراسری معمولی با این تنظیم از پروکسی محلی عبور کرد:
NODE_USE_ENV_PROXY=1 HTTPS_PROXY=http://user:pass@pr.proxynet.io:8000 node first-page.mjsAxios و node-fetch به جای dispatcher از agent استفاده میکنند؛ استفاده از پروکسی در Node.js هر دو را پوشش میدهد. برای چرخش IP خروجی در هر درخواست یا نشستهای ثابتی که یک IP را برای مدتی نگه میدارند، پروکسی مسکونی و پروکسی چرخشی همان نشانی user:pass@host:port را میپذیرند.
وقتی Cheerio کافی نیست
Cheerio نمیتواند کلیک کند، صفحه را پیمایش کند یا منتظر درخواستی بماند که صفحه پس از بارگذاری میفرستد. نشانههایی که میگویند به مرورگر نیاز دارید:
- کد منبع صفحه (Ctrl+U) دادهای را که صفحه رندرشده نشان میدهد ندارد.
- HTML یک ظرف خالی مثل
<div id="root"></div>و یک بسته اسکریپت بزرگ دارد. - دادهها فقط پس از فرم ورود، بنر کوکی یا «پیمایش بیپایان» ظاهر میشوند.
پیش از راهاندازی مرورگر، زبانه Network را در ابزارهای توسعهدهنده باز کنید. بسیاری از صفحههای «پویا» دادههایشان را از یک endpoint از نوع JSON بارگذاری میکنند و درخواست مستقیم آن endpoint با fetch سبکتر از رندر کردن صفحه است. اگر واقعاً به مرورگر نیاز دارید، Playwright میتواند صفحه را رندر کند و HTML نهایی را با cheerio.load(await page.content()) به Cheerio بدهد، و کد تجزیه شما همان میماند.
کاربردهای اسکرپرهای Cheerio
- پیگیری قیمت: خواندن زمانبندیشده قیمتها از صفحههای محصولی که در سرور رندر میشوند (پایش قیمت).
- دادههای کاتالوگ و بازار: جمعآوری سبد محصولات و سطح موجودی در چند فروشگاه (تحقیقات بازار).
- دیدهشدن در جستوجو: بررسی عنوانها، متاتگها و سرفصلهای صفحههای خودتان (پروکسی SEO).
- خط لوله داده: رساندن ردیفهای تجزیهشده به یک خزنده یا فرایند ETL بزرگتر (استخراج داده، خزنده وب).
- تجزیه HTML ذخیرهشده: تبدیل صفحههای بایگانیشده به رکوردهای ساختاریافته؛ بخش تجزیه در تجزیه داده (parsing) چیست؟ توضیح داده شده است.
اشتباهات رایج و روش تشخیص آنها
- رشتههای خالی همهجا. انتخابگر با هیچ عنصری منطبق نشده یا دادهها را JavaScript اضافه میکند. HTML را با
writeFile("page.html", html)ذخیره کنید و در آن دنبال مقداری بگردید که در مرورگر میبینید. require(...).default is not a functionیاdoes not provide an export named 'default'. شیوه وارد کردن قدیمی است. ازimport * as cheerio from "cheerio"استفاده کنید.TypeError: fetch failedهمراه باinvalid onRequestStart method. یکProxyAgentاز بسته undici در npm را بهfetchسراسری Node.js دادهاید. Node 24.11.1 نسخه undici 7.16.0 را درون خود دارد و این دو نسخه رابط dispatcher یکسانی ندارند.fetchوProxyAgentرا از یک بسته وارد کنید.- پروکسیای که «هیچ کاری نمیکند». agent را به
cheerio.fromURLدادهاید که کلاینت خودش را به کار میبرد. باfetchدانلود کنید وcheerio.loadرا صدا بزنید. - پیوندهای نسبی شکست میخورند.
fetch("catalogue/…")خطایFailed to parse URLمیدهد. باnew URL(href, pageUrl)نشانی را کامل کنید. - درخواستهای بیش از حد بهطور همزمان.
Promise.all(urls.map(fetch))همه را موازی میفرستد و پاسخهای 429 را به دنبال دارد. از محدودیتی مثلmapLimitاستفاده کنید. - جابهجایی بیصدای داده. سایت نام یک کلاس را عوض میکند و قیمتها
NaNمیشوند. هر اجرا را بررسی کنید: ردیفها و قیمتهایNaNرا بشمارید و اگر اعداد ناگهان افت کردند، اجرا را متوقف کنید.
پیش از بزرگتر کردن مقیاس، robots.txt و شرایط استفاده سایت را بخوانید، اگر API رسمی وجود دارد آن را ترجیح دهید و نرخ درخواستها را معتدل نگه دارید. توضیح robots.txt و آیا وب اسکرپینگ قانونی است؟ قواعد را پوشش میدهند؛ وب اسکرپینگ بدون مسدود شدن به خزش محترمانه میپردازد.
راهنمای انتخاب
| نیاز | پیشنهاد |
|---|---|
| داده در کد منبع صفحه هست | fetch + cheerio.load |
| اسکریپت یکباره، بدون پروکسی | cheerio.fromURL |
| رکوردهای زیاد با ساختار یکسان | $.extract با توصیفگر آرایهای |
| صدها صفحه | محدودیت همزمانی 2 تا 5 بهعلاوه تلاش مجدد با backoff |
| درخواست از طریق پروکسی | fetch کتابخانه undici + ProxyAgent، یا NODE_USE_ENV_PROXY=1 روی Node.js 24.5+ |
| داده فقط پس از اجرای JavaScript ظاهر میشود | اول endpoint مربوط به JSON را پیدا کنید، وگرنه Playwright + Cheerio |
کد به DOM کامل (document، رویدادها) نیاز دارد | jsdom |
پرسشهای متداول
آیا Cheerio در 2026 هنوز نگهداری میشود؟
بله. رجیستری npm نسخه 1.2.0 را که در ژانویه 2026 منتشر شد بهعنوان آخرین نسخه نشان میدهد و سایت مستندات API نسخه 1.x را با extract و fromURL پوشش میدهد.
آیا Cheerio کد JavaScript اجرا میکند؟
خیر. فقط رشته HTMLی را که به آن میدهید تجزیه میکند و کار دیگری انجام نمیدهد. اسکریپتهای صفحه مثل متن در نظر گرفته میشوند. برای صفحههایی که محتوایشان را در مرورگر میسازند، از Playwright استفاده کنید یا endpoint دادهای را که صفحه صدا میزند پیدا کنید.
آیا کنار Cheerio به Axios نیاز دارم؟
خیر. Node.js 18 و نسخههای بعدی fetch را دارند که نیاز بیشتر اسکرپرها را برآورده میکند. Axios انتخابی سلیقهای است؛ اگر از آن استفاده میکنید، بدنه پاسخ (response.data) را به cheerio.load بدهید.
چگونه با Cheerio چند صفحه را اسکرپ کنم؟
پیوند صفحه بعد را از هر صفحه بخوانید، آن را نسبت به نشانی فعلی کامل کنید و تا وقتی پیوند وجود دارد حلقه را ادامه دهید، همانطور که در اسکریپت کامل بالا آمده است. وقتی تعداد صفحهها معلوم است، میتوانید فهرست نشانیها را از پیش بسازید و آن را با محدودیت همزمانی اجرا کنید.
چگونه با Cheerio از پروکسی استفاده کنم؟
پروکسی را روی کلاینت HTTP تنظیم کنید، نه روی Cheerio. با undici: new ProxyAgent("http://user:pass@pr.proxynet.io:8000") را بهعنوان dispatcher به fetch همین کتابخانه بدهید. روی Node.js 24.5 یا بالاتر میتوانید به جای آن NODE_USE_ENV_PROXY=1 و HTTPS_PROXY را تنظیم کنید.
آیا Cheerio از Puppeteer یا Playwright سریعتر است؟
برای صفحههایی که دادهشان در HTML است، بله؛ چون فقط متن را تجزیه میکند، در حالی که مرورگر علاوه بر آن منابع را دانلود میکند، اسکریپتها را اجرا میکند و چیدمان صفحه را میسازد. ما این تفاوت را بنچمارک نکردیم و به صفحه بستگی دارد، پس اگر اعداد برایتان مهم است، روی سایتهای هدف خودتان اندازه بگیرید.
خلاصه
Cheerio کد HTML دانلودشده را به درختی تبدیل میکند که میتوانید با انتخابگرهای CSS جستوجو کنید و در نسخه 1.x متدهای fromURL و extract را هم اضافه کرده است. یک اسکرپر قابل اتکا کار شبکه را بیرون از Cheerio نگه میدارد: fetch با مهلت زمانی، تلاش مجدد برای 429، 5xx و خطاهای شبکه، محدودیت همزمانی کوچک و یک فایل JSON با برچسب زمانی. وقتی درخواستها باید از IP یا کشور دیگری خارج شوند، یک ProxyAgent از undici را بهعنوان dispatcher بدهید و آن را به یک پروکسی Proxynet متصل کنید.




