🤖

本文抽出ライブラリの readability.js を DOM 非依存に書き直す(WIP)

に公開

現状、最低限動いてはいるけどガバガバ

Readability.js とは

HTML から本文を抽出するライブラリ。

https://github.com/mozilla/readability

元々はArc90によって開発され、現在はMozillaのFirefox Reader View に使われている。

AIモデルではなくルールベースだが、かなり精度がでる。

自分はこれを AI に食わせるサマリを作るのに愛用していたのだが...

DOM API 依存と Cloudflare でJSDOMが動かない問題

DOM API に依存しているので、 jsdom に依存してしまう。

import { JSDOM } from "jsdom";
import { Readability } from "@mozilla/readability";
import html2md from "html-to-md";

function getExtractContent(htmlContent: string) {
  const doc = new JSDOM(htmlContent);
  const article = new Readability(doc.window.document).parse();
  if (!article) {
    throw new Error("Article not found");
  }
  return html2md(article!.content);
}

普段は気にしないのだが、これによって cloudflare workers にデプロイできなかった。

mastra の MCP サーバーに載せて cloudflare deployer を使おうとしたが、駄目。

https://github.com/mastra-ai/mastra

書き直した

とりあえずコアロジックだけDOM非依存して、使えるようにした。一旦は htmlparser2 を使っている。

https://github.com/mizchi/readability

npm install --save @mizchi/readability htmlparser2 html-to-md
import { toHTML, extract } from "@mizchi/readability";
import html2md from "html-to-md";

const url = "https://zenn.dev/mizchi/articles/ts-using-sampling-logger";
const html = await fetch(url).then((res) => res.text());
const extracted = extract(html, { charThreshold: 100 });
// 結果を表示
console.log(`Title: ${extracted.title}`);
console.log(`Author: ${extracted.byline}`);
if (!extracted.root) {
  process.exit(1);
}
const htmlContent = toHTML(extracted.root);
const md = html2md(htmlContent);
console.log(md);

API が違うが、テキスト密度のコアロジックを移植したつもりではある。

出力例

$ pnpm tsx examples/run.ts
Title: TS の using でプリントデバッグを確率的にサンプリングして出力する
Author: null
pageType: article
[![](https://storage.googleapis.com/zenn-user-upload/topics/23eef6d9d7.png)

AI](/topics/ai)[![](https://storage.googleapis.com/zenn-user-upload/topics/f13e758fdb.png)

TypeScript](/topics/typescript)[![](https://static.zenn.studio/images/drawing/tech-icon.svg)

tech](/tech-or-idea)

スコープ単位でログをサンプリングする。

```
{
  using log = createSampleLog<string>(5);
  log("a");
  log("b");
  // スコープを抜けるときに最大5件サンプリングされて表示される
}
```

見てわかるが html-to-md のせいか自分のせいか不明だがリンクが壊れている。まあAI向けには多少壊れてても使えなくはないのだが...

書き直した手順

Gemini 2.5 に食わせて、コアロジックを解釈させた。

自分で htmlparser2 で VDOM 的な構造体を決めて、そこにつなぎ込むのを前提に、AIに元実装でコードを読ませながら翻訳させた。

元コードが3600行ぐらいだが、Gemini 2.5 のコンテキストウィンドウが大きくすんなり言った。

とはいえ実際読んでみると、2006 年のベースらしく HTML5 依然のコードが対象になってるので、いらない仕様を自分で判断して捨てた。

もう少しちゃんとやる。

Discussion