

در وب اسکریپینگ، انتخاب یک HTML parser مناسب میتواند تفاوت بزرگی در سرعت، مصرف حافظه و سهولت توسعه ایجاد کند. این مقاله بر اساس مقایسهٔ پنج کتابخانهٔ محبوب Node.js — Cheerio، jsdom، parse5، htmlparser2 و xml2js — نوشته شده و برای توسعهدهندگان پایتون سطح متوسط طراحی شده تا با مفاهیم، معادلهای پایتونی و نکات عملی آشنا شوند. در پایان خواهید دانست هر کتابخانه برای چه سناریویی مناسب است، چگونه نمونهٔ پایهای را پیادهسازی کنید و چه نکات عملکردی و امنیتی را رعایت کنید.
وقتی parser را انتخاب میکنیم، چند معیار کلیدی را در نظر بگیرید:
در ادامه هر کتابخانه را توضیح میدهیم، مثالهای Node.js را بازنویسی میکنیم و برای هر مورد معادل یا روشهای معمول در پایتون را نشان میدهیم.
ایدهٔ کلی: 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));توضیح:
معادل پایتون (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)}")نکات عملی و محدودیتها:
ایدهٔ کلی: 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));توضیح:
معادل پایتون برای محتوای داینامیک: معمولاً از ابزارهایی مانند 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()نکات عملی و محدودیتها:
ایدهٔ کلی: 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));توضیح:
معادل پایتون: برای پیروی از استاندارد 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()}")نکات عملی:
ایدهٔ کلی: 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));توضیح:
معادل پایتون برای استریمینگ و پردازش تدریجی:
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() # آزاد کردن حافظهنکات عملی:
ایدهٔ کلی: 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}")نکات عملی:
خلاصهٔ نقاط قوت هر ابزار:
راهنمای تصمیمگیری سریع:
نکات مهم هنگام اسکریپ کردن (وب اسکریپینگ):
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 مناسب ابتدا سناریوی خود را مشخص کنید: آیا صفحه داینامیک است؟ حجم صفحات چقدر است؟ آیا نیاز به استریم دارید؟ در عمل:
در نهایت، ترکیبی از ابزارها معمولاً بهترین نتیجه را میدهد: رندر با headless در صورت نیاز، سپس پارس و استخراج با ابزار سبکتر برای افزایش کارایی. همیشه نکات مربوط به retry، محدودسازی نرخ، و مدیریت حافظه را در پیادهسازی تولیدی رعایت کنید.

