cheerio、domino、jsdom、parse5 は、Node.js 環境で HTML を処理するための代表的なライブラリです。これらはサーバーサイドでのスクレイピング、テスト、HTML 変換などの用途に使われますが、内部の実装アプローチと提供される機能には大きな違いがあります。jsdom はブラウザの DOM 仕様をほぼ完全に実装しており、ブラウザ依存のコードをテストする際に最適です。cheerio は jQuery に似た API を提供し、HTML の抽出や操作を軽量かつ高速に行えます。domino は DOM 実装に特化しており、ウィンドウオブジェクトを持たない軽量な環境を提供します。parse5 は HTML パーサーそのものであり、他のライブラリの基盤としても使われる低レベルなツールです。
Node.js で HTML を扱う場合、ブラウザと同じように DOM を操作できるか、それとも軽量なパーシングだけで十分かによって選ぶべきツールが変わります。cheerio、domino、jsdom、parse5 はそれぞれ異なる設計思想を持っており、用途によって明確な使い分けが必要です。ここでは、実開発で直面する具体的なシナリオに基づいて、これら 4 つのライブラリを技術的に比較します。
jsdom は、W3C の DOM 仕様を可能な限り忠実に実装しようとする「フル機能」のライブラリです。
window や document オブジェクトをエミュレートします。// jsdom: 完全な DOM 環境
const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><p>Hello</p>`);
console.log(dom.window.document.querySelector("p").textContent); // "Hello"
cheerio は、DOM 仕様の実装ではなく、サーバーサイドでの HTML 操作に特化したライブラリです。
parse5 をパーサーとして使用していますが、完全な DOM ツリーは構築しません。// cheerio: jQuery ライクな API
const cheerio = require("cheerio");
const $ = cheerio.load(`<p>Hello</p>`);
console.log($("p").text()); // "Hello"
domino は、DOM レベル 4 仕様に基づいた軽量な DOM 実装です。
window オブジェクトを持たず、document のみを提供します。// domino: 軽量な DOM 実装
const domino = require("domino");
const doc = domino.createDocument(`<p>Hello</p>`);
console.log(doc.querySelector("p").textContent); // "Hello"
parse5 は、HTML パーサーおよびシリアライザーそのものです。
// parse5: 純粋なパーサー
const parse5 = require("parse5");
const ast = parse5.parse(`<p>Hello</p>`);
console.log(ast.childNodes[0].tagName); // "p"
処理速度とメモリ使用量は、大規模な HTML を扱う際に重要な要素です。
cheerio は、不要なブラウザ機能を捨てることで最も高速に動作する傾向があります。
// cheerio: 高速な操作
const $ = cheerio.load(largeHtmlString);
const titles = $("h1").map((i, el) => $(el).text()).get();
jsdom は機能性が最も高い分、初期化コストとメモリ使用量が大きいです。
// jsdom: 高機能だが重め
const { JSDOM } = require("jsdom");
const dom = new JSDOM(largeHtmlString);
const titles = Array.from(dom.window.document.querySelectorAll("h1")).map(el => el.textContent);
domino は jsdom よりも軽量ですが、cheerio ほど最適化されていません。
// domino: 中程度の重さ
const doc = domino.createDocument(largeHtmlString);
const titles = Array.from(doc.querySelectorAll("h1")).map(el => el.textContent);
parse5 はパーシングのみを行うため、単体では比較対象が異なりますが、基盤として非常に高速です。
// parse5: 最速のパーシングだが手動作業が必要
const ast = parse5.parse(largeHtmlString);
// ここから自分で AST を走査して h1 タグを探す必要がある
フロントエンドのテスト自動化において、どのライブラリを選ぶかはテストの目的によります。
jsdom は、ブラウザの挙動を再現する必要があるテストに最適です。
jest や vitest などのテストランナーでデフォルト環境としてよく使われます。click() や dispatchEvent() などのイベント操作も可能です。// jsdom: イベント処理のテスト
const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<button id="btn">Click</button>`);
const btn = dom.window.document.getElementById("btn");
btn.click(); // イベントが発火する
cheerio は、HTML の出力内容を確認するスナップショットテストや、スクレイピングロジックのテストに向いています。
// cheerio: 出力内容の検証
const $ = cheerio.load(renderedHtml);
expect($(".error-message").length).toBe(1);
domino は、DOM 構造の検証には使えますが、イベントシミュレーション機能は限定的です。
jsdom ほどテストエコシステムとの統合は進んでいません。// domino: 構造検証
const doc = domino.createDocument(renderedHtml);
expect(doc.querySelectorAll(".item").length).toBe(5);
parse5 は、HTML パーサー自体のテストや、AST 変換ツールの開発に使われます。
// parse5: AST 構造の検証
const ast = parse5.parse(html);
// AST のノード構造が正しいか検証する
開発者がどれだけ早くコードを書けるかも重要な選定基準です。
cheerio は、jQuery を知っている開発者にとって最も親しみやすいです。
// cheerio: 直感的なチェーン操作
$("ul").find("li").addClass("active").text();
jsdom は、標準の Web API に準拠しているため、ブラウザでの開発経験がそのまま活かせます。
querySelector や addEventListener など、普段使う API が使えます。// jsdom: 標準 Web API
document.querySelector("ul").classList.add("active");
domino も標準 DOM API に準拠していますが、window がないため一部のコードは修正が必要です。
// domino: 標準 API だが window なし
doc.querySelector("ul").classList.add("active");
parse5 は、AST を直接扱うため、他の 3 つに比べて学習コストが高いです。
// parse5: AST 直接操作
// ノードを再帰的に走査する関数を書く必要がある
function traverse(node) { /*...*/ }
大量のページから特定のデータを抽出したい。
cheerioconst $ = cheerio.load(html);
const price = $(".price").text();
React や Vue コンポーネントが正しくレンダリングされるか確認したい。
jsdomconst { JSDOM } = require("jsdom");
global.window = new JSDOM().window;
// ここでコンポーネントをマウントしてテスト
HTML 構造を安全に書き換えたり、別の形式に変換したい。
parse5const ast = parse5.parse(html);
// AST を加工して
const html = parse5.serialize(ast);
DOM 操作は必要だが、イベントやウィンドウ機能は不要。
dominojsdom より軽量で、標準 DOM API が使えるため。const doc = domino.createDocument(html);
doc.querySelector("div").remove();
| 特徴 | cheerio | jsdom | domino | parse5 |
|---|---|---|---|---|
| 主な用途 | スクレイピング、HTML 操作 | テスト、ブラウザエミュレーション | 軽量 DOM 操作 | パーシング基盤 |
| API スタイル | jQuery ライク | 標準 Web API | 標準 DOM API | AST 操作 |
| DOM 実装 | 独自(簡易) | 完全準拠 | 準拠(window なし) | なし(AST) |
| JavaScript 実行 | ❌ | ✅ | ❌ | ❌ |
| イベント処理 | ❌ | ✅ | ⚠️ 限定的 | ❌ |
| パフォーマンス | 🚀 非常に高速 | 🐢 重い | 🏃 中程度 | 🚀 高速(パーシングのみ) |
| メンテナンス | ✅ 活発 | ✅ 活発 | ⚠️ 低調 | ✅ 活発 |
プロジェクトの要件に応じて、以下のように選択するのが賢明です。
cheerio を選択。開発速度と実行速度のバランスが最も優れています。jsdom を選択。環境の再現性が最も重要です。domino を検討。ただしメンテナンス状況を注視してください。parse5 を選択。最も低レベルで正確な制御が可能です。これら 4 つのライブラリは互いに排他的なものではなく、状況に応じて使い分けることで、Node.js での HTML 処理を効率的かつ確実に行うことができます。
HTML パーシングやシリアライズそのものを行いたい場合、あるいは独自の DOM 実装の上にパーサーを構築したい場合に使用します。DOM 操作 API は提供しないため、他のライブラリと組み合わせて使うことが一般的です。
jQuery に似た構文で HTML を操作したい場合や、スクレイピングで特定の要素を素早く抽出したい場合に選択します。ブラウザの DOM 仕様全体を必要とせず、パフォーマンスと記述のしやすさを重視するプロジェクトに適しています。
ブラウザのレイアウトや描画機能を必要とせず、純粋な DOM ツリー操作だけを行いたい場合に適しています。jsdom よりも軽量ですが、メンテナンス頻度は低いため、長期プロジェクトでは注意が必要です。
ブラウザ環境をシミュレートして JavaScript を実行する必要がある場合や、React や Vue などのフレームワークをサーバーサイドでテストする場合に選択します。DOM 仕様のサポートが最も充実していますが、リソース消費は大きめです。
npm install --save parse5
📖 Documentation 📖