Defuddle
de·fud·dle — to remove unnecessary elements from a web page, and make it easily readable.
Defuddle は、ウェブページからメインコンテンツだけを抜き出し、コメント・サイドバー・ヘッダー・フッターなどを除いた「きれいな HTML」を返す TypeScript ライブラリ。Obsidian Web Clipper 向けに書かれており、Turndown などの HTML→Markdown 変換の前段として使いやすい出力を目指している。
Mozilla Readability の代替として使える。違いとしては「より寛容(不確実な要素を削りすぎない)」「脚注・数式・コードブロックを統一フォーマットに」「モバイル用 CSS からノイズ要素を推測」「schema.org を含むメタデータ抽出」がある。
メイン処理は Defuddle#parse()(同期的)と parseAsync()(ローカルで取れない場合に非同期エクストラクタへフォールバック)の 2 本。同期パースの大まかな流れは以下。
- メインコンテンツ検出:
ENTRY_POINT_ELEMENTS(#post,.entry-content,article,main,#content,bodyなど)を優先し、ContentScorerでスコアを付けて最良の要素を選ぶ。一覧ページでは「子の article が複数ある場合は親をコンテンツとする」などのヒューリスティックあり。 - 除去: 完全一致セレクタ(広告・ナビ・コメント等)、部分一致(class/id のキーワード)、
display:none/visibility:hidden/hiddenクラス、スコアの低いブロック、小さい img/SVG(33px 未満など)を削除。脚注コンテナは保護。 - リトライ: 語数が 200 未満のときは
removePartialSelectors: falseで再パース。50 未満のときはスコア除去と部分セレクタ除去の両方 off で再試行(一覧ページ対策)。 - schema.org フォールバック: 抽出テキストが schema.org の
text/articleBodyより短い場合、DOM から該当要素を探すか、schema の文字列をそのまま content に使う。
クローン時にシャドウルートの中身をフラット化してクローン側に取り込む。カスタム要素(ハイフン付きタグ)は div に置き換えて再初期化でシャドウが復活しないようにしている。
| バンドル | 用途 | 備考 |
|---|---|---|
defuddle | ブラウザ用コア | 依存なし。数式は扱うが MathML↔LaTeX のフォールバックはなし。 |
defuddle/full | 数式・Markdown 強化 | mathml-to-latex, temml で <math> 生成。Turndown で Markdown 変換。 |
defuddle/node | Node.js(JSDOM) | HTML 文字列や URL からパース。full 相当の数式・Markdown 対応。peer: jsdom。 |
返却オブジェクト(DefuddleResponse)
content(クリーンな HTML), title, author, description, domain, favicon, image, published, site, schemaOrgData, wordCount, parseTime, metaTags。オプションで contentMarkdown や debug(contentSelector / removals)を付与できる。
URL パターンに合致すると、汎用パイプラインの前にサイト専用の BaseExtractor が canExtract() / canExtractAsync() で判定し、extract() または extractAsync() で本文とメタデータを返す。同じドメインに複数登録されている場合は登録順で先にマッチしたものが使われる(例: X は XArticle → Twitter → XOembed の順)。
| エクストラクタ | パターン例 |
|---|---|
| XArticle | x.com, twitter.com |
| twitter.com, /x.com/.* | |
| XOembed | x.com, twitter.com(oembed 利用) |
| reddit.com, old.reddit.com, *.reddit.com | |
| Youtube | youtube.com, youtu.be |
| HackerNews | news.ycombinator.com/item?id=* |
| ChatGPT | chatgpt.com/(c|share)/* |
| Claude | claude.ai, claude.ai/(chat|share)/* |
| Grok | grok.com/(chat|share)* |
| Gemini | gemini.google.com/app/* |
| GitHub | github.com/* |
parseAsync() でローカルから本文が取れない場合(例: SPA で中身が空)、useAsync: true なら FxTwitter API など外部 API を叩くエクストラクタが使われる。無効にするには useAsync: false。
| オプション | デフォルト | 説明 |
|---|---|---|
debug | false | ログ増量 + 返却に debug.contentSelector / removals を付与 |
url | — | 相対 URL 解決のベース URL |
markdown | false | content を Markdown に変換 |
separateMarkdown | false | content は HTML のまま、contentMarkdown を別途返す |
removeExactSelectors | true | 広告・ナビ等の完全一致セレクタで除去 |
removePartialSelectors | true | class/id の部分一致で除去 |
removeHiddenElements | true | display:none 等で非表示の要素を除去 |
removeLowScoring | true | スコアの低いブロックを除去 |
removeSmallImages | true | 小さい img/SVG を除去 |
removeImages | false | 画像をすべて除去 |
standardize | true | 見出し・コード・脚注・数式の標準化 |
contentSelector | — | メインコンテンツの CSS セレクタ(指定時は自動検出をスキップ) |
useAsync | true | ローカルで取れないとき非同期エクストラクタを試す |
HTML 標準化の内容
- 見出し: タイトルと一致する最初の H1/H2 を削除。H1→H2 に変換。見出し内のアンカーリンクは削除。
- コードブロック: 行番号・シンタックス用クラスを整理し、言語を
data-langとclass="language-*"で保持。 - 脚注: インライン参照と脚注リストを統一フォーマット(
<sup id="fnref:*">,<div id="footnotes">内の<li class="footnote">)に変換。 - 数式: MathJax / KaTeX などを標準 MathML に変換。full バンドルでは LaTeX 往復や <math> 生成のフォールバックあり。
- GitHub: kepano/defuddle — 本体リポジトリ(TypeScript)
- Defuddle Playground — ブラウザで試せるデモ
- defuddle-cli — スタンドアロン CLI
- Mozilla Readability — 比較対象
- Obsidian Web Clipper — 想定利用先