خانه/مقالات/۵ کتابخانهٔ برتر HTML Parser در Node.js برای وب اسکریپینگ
برنامه نویسی
وب اسکریپینگ
برگشت به صفحه مقاله ها
۵ کتابخانهٔ برتر HTML Parser در Node.js برای وب اسکریپینگ

۵ کتابخانهٔ برتر HTML Parser در Node.js برای وب اسکریپینگ

این مقاله پنج کتابخانهٔ محبوب Node.js برای پارس HTML (Cheerio، JSDOM، Parse5، htmlparser2 و xml2js) را با مثال‌های عملی، معادل‌های پایتونی، مزایا و معایب و نکات عملکردی و امنیتی مقایسه می‌کند تا به توسعه‌دهندگان پایتون کمک کند مناسب‌ترین ابزار را برای سناریوی وب اسکریپینگ خود انتخاب کنند.
امیر حسین حسینیان
امیر حسین حسینیان
1404-09-21

مقدمه

در وب اسکریپینگ، انتخاب یک HTML parser مناسب می‌تواند تفاوت بزرگی در سرعت، مصرف حافظه و سهولت توسعه ایجاد کند. این مقاله بر اساس مقایسهٔ پنج کتابخانهٔ محبوب Node.js — Cheerio، jsdom، parse5، htmlparser2 و xml2js — نوشته شده و برای توسعه‌دهندگان پایتون سطح متوسط طراحی شده تا با مفاهیم، معادل‌های پایتونی و نکات عملی آشنا شوند. در پایان خواهید دانست هر کتابخانه برای چه سناریویی مناسب است، چگونه نمونهٔ پایه‌ای را پیاده‌سازی کنید و چه نکات عملکردی و امنیتی را رعایت کنید.

مروری کلی و معیارهای انتخاب

وقتی parser را انتخاب می‌کنیم، چند معیار کلیدی را در نظر بگیرید:

  • پشتیبانی از اجرای JavaScript (برای صفحات داینامیک)
  • سرعت و مصرف حافظه (برای صفحات بزرگ یا حجم بالا)
  • سهولت استفاده و API (CSS selector vs event-based)
  • پشتیبانی از استریمینگ و پردازش بخش‌به‌بخش
  • سازگاری با استانداردهای HTML/W3C

در ادامه هر کتابخانه را توضیح می‌دهیم، مثال‌های Node.js را بازنویسی می‌کنیم و برای هر مورد معادل یا روش‌های معمول در پایتون را نشان می‌دهیم.

Cheerio

ایدهٔ کلی: Cheerio یک کتابخانهٔ سبک و سریع است که سینتکس آن شبیه jQuery است؛ مناسب برای استخراج سریع داده‌ها از HTML ایستا (بدون نیاز به اجرای JavaScript).

نمونهٔ Node.js (پاک‌شده و ساده):

const rp = require('request-promise');
const cheerio = require('cheerio');

rp('https://quotes.toscrape.com/')
  .then(html => {
    const $ = cheerio.load(html);
    $('.quote .text').each((i, el) => {
      const quote = $(el).text().trim();
      console.log(`Quote ${i+1}: ${quote}`);
    });
  })
  .catch(err => console.error(err));

توضیح:

  • ورودی: URL (یا HTML خام)
  • خروجی: لیست متن عناصر انتخاب‌شده
  • نقش هر بخش: request-promise HTML را می‌گیرد؛ cheerio.load آن را پارس می‌کند؛ انتخابگر CSS عناصر هدف را برمی‌گرداند.
  • خط‌به‌خط: درخواست HTTP → بارگذاری در Cheerio → پیدا کردن المان‌ها با انتخابگر → خواندن متن و trim → چاپ.

معادل پایتون (requests + BeautifulSoup):

import requests
from bs4 import BeautifulSoup

resp = requests.get('https://quotes.toscrape.com/')
soup = BeautifulSoup(resp.text, 'html.parser')
for i, el in enumerate(soup.select('.quote .text')):
    print(f"Quote {i+1}: {el.get_text(strip=True)}")

نکات عملی و محدودیت‌ها:

  • مزایا: سبک، سریع برای HTML ایستا، API ساده بر پایهٔ CSS selector.
  • معایب: اجرای JavaScript را ندارد؛ مناسب نیست برای محتوای داینامیک که توسط JS ساخته می‌شود.
  • Best practice: قبل از پارس، HTML را با ابزارهای حذفِ whitespace و نرمالایز کردن کاراکترها آماده کنید تا نتایج selector پایدار باشند.

JSDOM

ایدهٔ کلی: jsdom محیطی شبیه مرورگر ایجاد می‌کند که DOM کامل و APIهای مرورگر را فراهم می‌کند و می‌تواند برای سناریوهایی که نیاز به اجرای JavaScript یا تعامل DOM پیچیده دارند، مفید باشد.

نمونهٔ Node.js:

const { JSDOM } = require('jsdom');
const rp = require('request-promise');

rp('https://quotes.toscrape.com/')
  .then(html => {
    const dom = new JSDOM(html);
    const document = dom.window.document;
    const quotes = document.querySelectorAll('.quote .text');
    quotes.forEach((q, i) => console.log(`Quote ${i+1}: ${q.textContent.trim()}`));
  })
  .catch(err => console.error(err));

توضیح:

  • ورودی: HTML یا URL
  • خروجی: دسترسی DOM (document) با متدهای استاندارد مانند querySelectorAll
  • مزیت اصلی: شبیه‌سازی رفتار مرورگر در سطح DOM، مناسب برای تست، SSR یا صفحات با نیاز به تعامل DOM.

معادل پایتون برای محتوای داینامیک: معمولاً از ابزارهایی مانند Playwright یا Selenium استفاده می‌کنیم تا صفحه را رندر و سپس HTML را خوانده و با BeautifulSoup/ lxml پارس کنیم. مثال کوتاه با Playwright (پایتون):

from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto('https://quotes.toscrape.com/')
    html = page.content()
    soup = BeautifulSoup(html, 'html.parser')
    for i, el in enumerate(soup.select('.quote .text')):
        print(f"Quote {i+1}: {el.get_text(strip=True)}")
    browser.close()

نکات عملی و محدودیت‌ها:

  • مزایا: امکان اجرای JS و دسترسی به APIهای window/document.
  • معایب: مصرف منابع بیشتر و پیچیدگی نصب/پیکربندی، کندتر از پارسرهای سبک.
  • رفتن سمت jsdom وقتی منطقی است که تعامل با DOM یا اجرای اسکریپت‌ها ضروری باشد؛ در غیر این صورت Cheerio یا Parse5 سریع‌تر و بهینه‌ترند.

Parse5

ایدهٔ کلی: parse5 یک پارسر سطح پایین و W3C-compliant است که برای کارایی و حافظه بهینه طراحی شده و برای پردازش‌های بزرگ یا نیاز به تطابق با استانداردها مناسب است.

نمونهٔ Node.js (استراتژی: parse + traverse):

const rp = require('request-promise');
const parse5 = require('parse5');

rp('https://quotes.toscrape.com/')
  .then(html => {
    const document = parse5.parse(html);
    const quotes = [];
    function walk(node) {
      if (node.tagName === 'span' && node.attrs && node.attrs.find(a => a.name === 'class' && a.value === 'text')) {
        const textNode = node.childNodes && node.childNodes[0];
        if (textNode && textNode.value) quotes.push(textNode.value.trim());
      }
      if (node.childNodes) node.childNodes.forEach(walk);
    }
    walk(document);
    quotes.forEach((q, i) => console.log(`Quote ${i+1}: ${q}`));
  })
  .catch(err => console.error(err));

توضیح:

  • Parse5 مستقیماً درختی تولید می‌کند که باید با آن پیمایش (traverse) شود؛ لذا API پایین‌تری نسبت به Cheerio دارد.
  • مناسب برای ابزارهایی که نیاز به اعتبارسنجی/تطابق با استاندارد دارند یا پردازش حجم بالای HTML.

معادل پایتون: برای پیروی از استاندارد HTML5 می‌توان از html5lib همراه با lxml یا BeautifulSoup استفاده کرد؛ برای پردازش‌های سطح پایین‌تر از lxml.etree استفاده می‌شود. مثال با html5lib+lxml:

from lxml import html
import requests

resp = requests.get('https://quotes.toscrape.com/')
doc = html.fromstring(resp.content)  # lxml سازگار با استانداردها و سریع
quotes = doc.cssselect('.quote .text')
for i, el in enumerate(quotes):
    print(f"Quote {i+1}: {el.text_content().strip()}")

نکات عملی:

  • Parse5 برای زمانی مناسب است که می‌خواهید دقیقاً رفتار پارسینگ مرورگر را تقلید کنید و مصرف حافظه برایتان مهم است.
  • وقتی نیاز به selector ساده دارید، استفاده از Cheerio یا سطح بالاتری راحت‌تر است.

htmlparser2

ایدهٔ کلی: htmlparser2 یک پارسر مبتنی بر SAX/event است که برای پردازش استریم‌ها و فایل‌های بزرگ مناسب است؛ به جای ساختن کل درخت DOM، رویدادها را هنگام خواندن HTML منتشر می‌کند.

نمونهٔ Node.js (event-driven):

const rp = require('request-promise');
const { Parser } = require('htmlparser2');

rp('https://quotes.toscrape.com/')
  .then(html => {
    const quotes = [];
    const parser = new Parser({
      onopentag(name, attrs) {
        if (name === 'span' && attrs.class === 'text') this._collect = '';
      },
      ontext(text) {
        if (this._collect !== undefined) this._collect += text.trim();
      },
      onclosetag(name) {
        if (name === 'span' && this._collect !== undefined) {
          quotes.push(this._collect);
          this._collect = undefined;
        }
      }
    });
    parser.write(html);
    parser.end();
    quotes.forEach((q, i) => console.log(`Quote ${i+1}: ${q}`));
  })
  .catch(err => console.error(err));

توضیح:

  • استفاده از callbackها برای پردازش شروع/متن/پایان تگ‌ها
  • مزیت اصلی: کم‌حافظه بودن و مناسب برای استریمینگ و پردازش تدریجی

معادل پایتون برای استریمینگ و پردازش تدریجی:

import requests
from lxml import etree

# مثال: استفاده از iterparse برای فایل HTML بزرگ (باید HTML به XML تبدیل یا پاک‌سازی شود)
resp = requests.get('https://example.com/large.html', stream=True)
context = etree.iterparse(resp.raw, html=True, events=('end',), tag='span')
for event, elem in context:
    if 'text' in (elem.get('class') or ''):
        print(elem.text_content().strip())
        elem.clear()  # آزاد کردن حافظه

نکات عملی:

  • htmlparser2 برای استخراجِ خط‌به‌خط یا لاگ‌مانند مفید است؛ در پایتون iterparse یا SAX-based parsers همان نقش را دارند.
  • اگر نیاز به پردازش هزاران صفحه یا اسناد بسیار بزرگ دارید، از مدل استریمینگ استفاده کنید تا مصرف حافظه محدود بماند.

xml2js

ایدهٔ کلی: xml2js کتابخانه‌ای برای تبدیل XML به اشیاء جاوااسکریپت است؛ برای HTML معمولی توصیه نمی‌شود مگر اینکه HTML ورودی دقیقاً XML-شکل (XHTML) باشد یا با APIهای XML کار می‌کنید.

نمونهٔ Node.js:

const rp = require('request-promise');
const { parseString } = require('xml2js');

rp('https://quotes-api.example.com/api/quotes')
  .then(xml => {
    parseString(xml, (err, result) => {
      if (err) return console.error(err);
      const quotes = result.quotes.quote || [];
      quotes.forEach((q, i) => console.log(`Quote ${i+1}: ${q}`));
    });
  })
  .catch(err => console.error(err));

معادل پایتون: از xml.etree.ElementTree یا xmltodict استفاده می‌شود:

import requests
import xmltodict

resp = requests.get('https://quotes-api.example.com/api/quotes')
data = xmltodict.parse(resp.text)
quotes = data.get('quotes', {}).get('quote', [])
for i, q in enumerate(quotes):
    print(f"Quote {i+1}: {q}")

نکات عملی:

  • xml2js برای APIهای مبتنی بر XML عالی است؛ برای HTML معمولی احتمالاً ابزارهای HTML-specific بهتر عمل می‌کنند.
  • اگر منبع شما XHTML یا XML-styled است، xml2js و xmltodict پردازش را بسیار ساده می‌کنند.

مقایسهٔ جامع و موارد تصمیم‌گیری

خلاصهٔ نقاط قوت هر ابزار:

  • Cheerio: سریع، کم‌حجم، API شبیه jQuery — مناسب استخراج سریع از HTML ایستا.
  • JSDOM: محیط مرورگر-مانند، اجرای JS — مناسب صفحات داینامیک و تست.
  • Parse5: پارسینگ W3C-compliant، کارایی بالا — مناسب نیازهای استاندارد محور و حجم بالا.
  • htmlparser2: مدل event-based و استریمینگ — مناسب پردازش اسناد بزرگ یا جریان‌های HTML.
  • xml2js: تبدیل XML به شیء — مناسب APIهای XML/XHTML.

راهنمای تصمیم‌گیری سریع:

  1. صفحه ایستا و نیاز به سرعت: Cheerio (یا در پایتون BeautifulSoup/lxml)
  2. صفحه داینامیک که JS تولید محتوا می‌کند: JSDOM یا ابزار headless (Playwright/Selenium) و سپس پارس
  3. پردازش صفحات بسیار بزرگ یا استریم: htmlparser2 یا رویکردهای SAX / iterparse در پایتون
  4. لزوم تطابق با استاندارد HTML5: Parse5 یا html5lib در پایتون
  5. منابع XML: xml2js یا xmltodict/xml.etree

بهینه‌سازی، امنیت و پایداری

نکات مهم هنگام اسکریپ کردن (وب اسکریپینگ):

  • مدیریت خطا: همیشه پاسخ‌های غیر 200، timeout و محتوای ناقص را مدیریت کنید. در Node.js از try/catch یا .catch و در پایتون از استثناء‌ها استفاده کنید.
  • retry و backoff: برای درخواست‌ها از مکانیزم retry با exponential backoff استفاده کنید تا از بلاک شدن یا spike جلوگیری شود.
  • استفاده از جلسات (sessions): در پایتون از requests.Session و در Node.js از pool های HTTP برای نگه داشتن کانکشن‌ها استفاده کنید تا overhead کاهش یابد.
  • همزمانی/موازی‌سازی: برای مقیاس‌پذیری از concurrency کنترل‌شده استفاده کنید (asyncio/aiohttp در پایتون یا Promise.all محدودشده در Node.js). جلوگیری از ارسال تعداد زیاد درخواست همزمان که منجر به блок شدن می‌شود.
  • پروکسی و ریت‌لیمیت: در صورت نیاز از پروکسی و محدودیت سرعت (rate limiting) استفاده کنید و headerهای معقول ارسال کنید (User-Agent). رعایت قوانین سایت و نرخ مجاز درخواست‌ها.
  • امنیت: محتویات HTML ممکن است شامل اسکریپت یا payload مخرب باشد؛ هر دادهٔ ورودی را قبل از پردازش یا ذخیره نهایی validate و sanitize کنید.

نمونهٔ الگوی کار (پایتون) با retry و async برای استخراج مقیاس‌پذیر

import asyncio
import aiohttp
from lxml import html
from tenacity import AsyncRetrying, stop_after_attempt, wait_exponential

async def fetch(session, url):
    async for attempt in AsyncRetrying(stop=stop_after_attempt(3), wait=wait_exponential()):
        with attempt:
            async with session.get(url, timeout=10) as resp:
                resp.raise_for_status()
                return await resp.text()

async def parse_quotes(html_text):
    doc = html.fromstring(html_text)
    return [el.text_content().strip() for el in doc.cssselect('.quote .text')]

async def worker(url):
    async with aiohttp.ClientSession() as session:
        html_text = await fetch(session, url)
        quotes = await asyncio.get_event_loop().run_in_executor(None, parse_quotes, html_text)
        for q in quotes:
            print(q)

asyncio.run(worker('https://quotes.toscrape.com/'))

این الگو نشان می‌دهد چطور از async برای concurrency کنترل‌شده، از tenacity برای retry و از lxml برای پردازش سریع HTML استفاده کنید.

جمع‌بندی

برای انتخاب parser مناسب ابتدا سناریوی خود را مشخص کنید: آیا صفحه داینامیک است؟ حجم صفحات چقدر است؟ آیا نیاز به استریم دارید؟ در عمل:

  • اگر به سرعت و سادگی نیاز دارید و محتوا ایستا است، Cheerio (Node.js) یا BeautifulSoup/lxml (پایتون) را انتخاب کنید.
  • برای صفحات داینامیک یا نیاز به اجرای JS از JSDOM یا ابزارهای headless مانند Playwright/Selenium استفاده کنید.
  • برای پردازش اسناد بزرگ یا استریمینگ سمت htmlparser2 یا رویکردهای SAX/iterparse در پایتون بروید.

در نهایت، ترکیبی از ابزارها معمولاً بهترین نتیجه را می‌دهد: رندر با headless در صورت نیاز، سپس پارس و استخراج با ابزار سبک‌تر برای افزایش کارایی. همیشه نکات مربوط به retry، محدودسازی نرخ، و مدیریت حافظه را در پیاده‌سازی تولیدی رعایت کنید.

مقاله‌های مرتبط
ابزارها و فریم‌ورک‌ها (Scrapy, Puppeteer و …)
1404-09-19
راهنمای انتخاب و کاربرد بهترین Headless Browserهای Node.js برای وب اسکریپینگ
در این راهنمای فارسی به معرفی و مقایسهٔ بهترین headless browserهای Node.js برای وب اسکریپینگ پرداخته شده؛ با مثال‌های کد، نکات پیکربندی، مدیریت منابع، مقابله با ضدربات و توصیه‌های عملی برای اجرای پایدار و مقیاس‌پذیر اسکریپ‌ها. پس از مطالعهٔ مقاله می‌توانید ابزار مناسب (Puppeteer یا Playwright) را انتخاب و یک pipeline عملی برای جمع‌آوری داده پیاده‌سازی کنید.
ابزارها و فریم‌ورک‌ها (Scrapy, Puppeteer و …)
1404-09-11
مقایسه و راهنمای عملی ۶ پارسر HTML برای C#/.NET
این مقاله شش پارسر HTML محبوب در اکوسیستم C#/.NET را معرفی و مقایسه می‌کند، همراه با مثال‌های کد، توضیح ورودی/خروجی و نکات عملی در مورد عملکرد، امنیت و بهترین‌روش‌ها برای وب اسکریپینگ. با خواندن راهنما می‌توانید بر اساس نیاز (سازگاری با HTML خراب، پشتیبانی CSS، سرعت یا مصرف حافظه) مناسب‌ترین ابزار را انتخاب و آن را به‌صورت ایمن و پایدار در پروژه‌تان به‌کار بگیرید.