html2text は 1つのパッケージ だけでインストールでき、容量は 0.2 MiB。最も近い Python 代替より 9 倍小さく、Node 系の代替より 40 倍も軽量です。変換スイートの 4 ページすべてを処理し、登録済みの本文プローブ 16 個もすべて保持しました。これらのプローブは、選んだ本文文字列が残るかどうかを確認するもので、階層構造、リストの入れ子、リンク先、重複コンテンツ、表の完全な忠実性までは評価しません。
また、このライブラリは GPL-3.0-or-later ライセンスです。この比較の中で、ベンチマークには一切表れず、それでいてライブラリ採用の可否を左右しかねない唯一の要素でもあります。
html2text とは
html2text は、HTML を Markdown 風のプレーンテキストへ変換する Python ライブラリです。系譜は Aaron Swartz のオリジナルにさかのぼり、現在メンテナンスされている系統の最新は 2025.4.15。2025年4月の日時バージョンで、GitHub のスター数は 2,168、未解決 issue は 95 件、最後の push は 2025年10月でした。
公式リファレンス: html2text の公式リポジトリ
import html2text
h = html2text.HTML2Text()
h.body_width = 0 # 後述。デフォルト設定には注意
md = h.handle(html)
pip install html2text で入るのは 1パッケージ、サイズは 0.2 MiB。コールドインポートは 0.077 秒 でした。依存関係はゼロです。コンテナイメージや Lambda レイヤーでは、markdownify の 1.8 MiB や turndown の 8.8 MiB との差はかなり大きいです。
すべての数値を変えてしまうデフォルト設定

body_width のデフォルトは 78 です。つまり、無効化しない限り、html2text は出力の全行を 78 文字で強制折り返しします。
ターミナル表示やメール向けの読みやすいプレーンテキストを作る、という元来の用途には妥当な設定です。しかし、モデル入力や diff 用途には不向きです。改行が増えるとトークン化が変わり、長い URL が途中で分断され、出力比較も意味を持ちにくくなります。
以下ではすべて body_width = 0 に設定しています。ここは隠さず明示しておきます。折り返しを有効にしたままだと、この記事の文字数もトークン数も全部変わってしまうからです。自分で変換器をベンチマークするなら、この設定が数値を静かに比較不能にしてしまう、という点に注意してください。
計測方法
html2text は、markitdown の比較でも使った名前付きの 4ページ変換スイートに対して実行しました。あわせて 事前登録済みのプローブ文字列 を使用しています。これは、残っているべき本文文字列と、ページの装飾や周辺要素がちゃんと通ってきたかを示す定型文字列です。4つのファイルは同一、プローブも同一、採点ロジックも4つの変換器で共通です。プローブの生存確認は登録済み文字列の有無を見るだけで、構造の正しさまでは測りません。だからこそ、表とリンクの列は別扱いにしています。
| Converter | Body probes | Output chars | Tokens (o200k) | Markdown table rows | Links |
|---|---|---|---|---|---|
| html2text | 16/16 | 76,452 | 21,176 | 32 | 545 |
| markdownify | 16/16 | 76,868 | 21,062 | 36 | 599 |
| markitdown | 16/16 | 76,995 | 21,336 | 36 | 598 |
| turndown | 16/16 | 95,188 | 26,236 | 0 | 611 |
fourway-scores.json。4つのフィクスチャを対象に、トークンは o200k_base でカウントしています。
4つの中で出力が最小 で、文字数は 76,452。トークン数も markdownify と markitdown とほぼ並び、21,176 に対して 21,062 と 21,336 なので、差は 1.3% に過ぎず、実質的には誤差の範囲と言えます。
4ページのスイートでは、表の行数は 32 行。markdownify と markitdown の 36 行より少なくなりました。4行分の差は、次に触れる外側パイプの有無ではなく、Wikipedia フィクスチャの不規則な表で生じています。
リンク数は 545 で最少。ほかは 598〜611 でした。リンク保持が重要なら、ご自身のページで必ず確認してください。ここでは、html2text がグループ内ではなく、明確に下回っていた唯一の列です。
私の正規表現では見えなかった表

これは、危うく誤った結論のまま公開するところだったので、あえて書いておく価値があります。
最初に作った表行カウンタは、先頭と末尾にパイプがあることを前提にしていました。^\|.*\|$ です。このルールでは、html2text のスコアは 5ファイル中 1 行 でした。4ページの変換スイートに、別途用意した複雑な表の合成フィクスチャを足したにもかかわらずです。しかしこのカウンタは、表ではなく Markdown の1スタイルしか測っていませんでした。

実際には、こう出力されます。
Team Name | Year | Wins | Losses | Win %
---|---|---|---|---
Boston Bruins | 1990 | 44 | 24 | 0.55
外側のパイプはありません。これは一般的な pipe-table 構文ですが、このハーネスでは Markdown レンダラ間での互換性までは検証していません。外側パイプを前提にした正規表現には見えないのです。そこで、パイプを含む複数行と、その中に区切り行がある形を数えるようにカウンタを書き換えたところ、html2text は 4ページスイートで 32 行、5ファイル全体では 91 行 に増えました。
つまり、html2text に表がない、という話ではありません。この4つの変換器のうち2つは外側パイプ付きで出力し、1つはそうしない、という点が重要なのです。Markdown を後段で独自のパターンマッチにかけるなら、ここは実務上かなり大事です。最初の数値だけを信じていたら、この事実は完全に見落としていました。
何を除去し、どこで行を落とすのか
相反する2つの結果があります。
<script> と <style> は除去されます。 これらの要素の中にしか現れないマーカーで数えたところ、html2text の出力には script マーカー 0 個、style マーカー 0 個 でした。turndown では 10 個と 84 個 残っています。Wikipedia フィクスチャでは、MediaWiki のインライン JavaScript 設定と CSS が 8行分、合計 14,644 文字 にもなりました (script-style-stripping.json)。モデル用出力としては、このフィクスチャで観測された不要テキストの最大要因でした。下流コストのモデル化はしていません。
難しいページでは表の行を落とします。 4ページスイートの整合結果は次の通りです。
| Fixture | html2text | markdownify |
|---|---|---|
| Books to Scrape | 0 行 | 0 行 |
| Quotes to Scrape | 0 行 | 0 行 |
| Hockey statistics | 27 行 | 27 行 |
| Wikipedia | 5 行 | 9 行 |
| 4ページ合計 | 32 行 | 36 行 |
別途用意した複雑な表の合成フィクスチャでは、html2text が 59 行、markdownify が 62 行で、5ファイル合計はそれぞれ 91 行と 98 行になります。これは 4ページの主要比較には含めていません。クリーンな hockey の表では両者は一致しましたが、Wikipedia のように入れ子で不規則な表では、html2text は markdownify より少ない 5 行しか出しません。
要するに、単純な表では同じ、ややこしい表では html2text の方が保持率が低い ということです。Wikipedia みたいな表を扱うページなら、採用前に必ず試してください。統計ページのような表なら、この観点では両者はほぼ互換です。
ライセンス
| Library | Licence | Packages | Disk |
|---|---|---|---|
| html2text | GPL-3.0-or-later | 1 | 0.2 MiB |
| markdownify | MIT | 5 | 1.8 MiB |
| turndown | MIT | 3 (npm) | 8.8 MiB |
公式リファレンス: PyPI 上の html2text
確認したのは3か所です。PyPI のメタデータ、GitHub リポジトリ、そしてインストール済みパッケージ内の METADATA ファイルで、そこには License-Expression: GPL-3.0-or-later と記載されています。
この意味は、ソフトウェアがどう統合され、どのように提供され、どう配布されるかによって変わります。社内利用やネットワーク越しの利用は、パッケージを含んだソフトウェアを出荷するケースとは GPL 的に別の論点になりますが、この記事は法的解釈を目的としていません。ソフトウェアを配布するチームは、統合形態と配布形態を前提に弁護士の確認を受けるべきです。
厄介なのは相関です。最も軽量で、配布物を少しでも小さくしたいからこそ選びたくなるライブラリが、実は配布を最も制約しやすいライセンスを持っているのです。代替の2つはどちらも MIT です。
私は弁護士ではなく、法的助言でもありません。ただ、ここは最も重要になりやすいのに、比較表では最も見落とされやすい属性なので、事実として出典つきで示しておきます。
保守状況
最新リリースは 2025.4.15、リポジトリへの最後の push は 2025年10月。テスト時点からおよそ10か月前で、41 リリース分が積み上がっています。requires_python >= 3.9 で、Python 3.14.2 でも問題なくインストール・実行できました。
markdownify(テストの6週間前に最新リリース)や turndown(4か月前)よりは静かですが、何もない状態よりはかなり活発です。HTML をテキストへ変換するという、あまり変化のない問題領域を考えると、10か月の空白は放置というより安定と見るのが自然です。とはいえ、95 件の未解決 issue はやはり要注意で、導入前には自分の用途に近いものをざっと確認しておく価値があります。
メモリ使用量と、壊れた HTML に対する挙動
ここでは、2つの運用上の問いを分けて測っています。
より広いストレステストの文脈は、10ライブラリのメモリ使用量と不正 HTML 比較 にまとめています。
ピーク常駐メモリ は /usr/bin/time -l で測定し、各セルごとに新しいプロセスを起動しました。import floor は、ライブラリを読み込んで待機しているだけで消費するコスト、peak は文書処理も含んだ値です。
| Library | Runtime | Import floor | 226 KB peak | 10 MB peak |
|---|---|---|---|---|
| html2text | python3.14 | 18.7 | 19.9 | 71.2 |
| pyquery | python3.14 | 30.3 | 33.9 | 172.5 |
| resiliparse | python3.14 | 20.5 | 25.1 | 225.1 |
| markdownify | python3.14 | 23.9 | 28.9 | 278.5 |
| goose3 | python3.14 | 44.1 | 52.4 | 398.5 |
| cheerio | node22 | 66.8 | 76.5 | 398.5 |
| justext | python3.14 | 30.3 | 36.6 | 431.2 |
| newspaper4k | python3.14 | 52.6 | 61.8 | 668.5 |
| trafilatura | python3.14 | 52.5 | 64.8 | 927.1 |
| turndown | node22 | 47.8 | 68.4 | 2947.1 |
memory-results.json。Python と Node のベースラインは相互比較できません。両方とも中にインタプリタが含まれているためです。
html2text は、この測定軸の両方で最軽量 でした。import floor は 18.7 MiB、10 MB フィクスチャでのピークは 71.2 MiB で、増分 RSS は 52.5 MiB。式にすると (71.2 - 18.7) / 10 = 5.25×、つまりフィクスチャサイズの 5.25 倍です。表の絶対値と増分の両方を見つつ、先ほどの Python/Node ベースラインの注意点も忘れないでください。
壊れた HTML。14 個の壊れた文書を用意しました。各文書は壊し方が1つだけ違います。閉じていないタグ、入れ子が崩れたインライン要素、スペースを含む未クォート属性、余計な閉じタグ、<html> がまったくない文書、重複属性、タグ途中で切れた文書、壊れた entity、閉じていない <script>、偽の charset 宣言、マークアップを含むコメント、そして 600 階層のネスト。さらに、同じサイズの整形式コントロールを 2つ追加しました。「何も返さなかった」だけでは、同じサイズのきれいな文書でも黙るライブラリなのか分からないからです。
html2text は 14/14 で例外なし、14/14 で空出力なし、壊れたフィクスチャのセントネル 33 個もすべて復元しました (malformed-results.json)。1つのフィクスチャはこの集計から除外しています。HTML5 では、閉じていない <script> の後ろはすべて script コンテンツとして扱われるため、そこを失うのは正しく、復元してしまう方が規格からの逸脱だからです。
長所と短所
良い点。 1パッケージ、0.2 MiB、依存ゼロで、この比較では圧倒的に最小。4つの中で出力も最小で、トークン数も markdownify と markitdown とほぼ同等。認識しやすい pipe-table 構文を出力できる。Python 3.14 で動く。系譜が長く、安定している。
弱い点。 GPL-3.0-or-later であり、代替2つとは異なる。デフォルトの body_width=78 が出力を強制折り返しし、無効化しない限り測定や diff を変えてしまう。保持されたリンク数が最少(545、他は 598〜611)。表は外側パイプなしのスタイルで、単純な後段正規表現を壊しやすい。未解決 issue が 95 件あり、markdownify よりリリース頻度はやや静かです。
どんな人に向き、どんな人には向かないか
html2text を使うべき人 は、依存予算が本当に厳しく、想定する統合・配布形態についてライセンス確認を済ませている人です。依存関係がゼロの 1パッケージというのは、監査・配備の対象が小さいという明確な運用上の利点があります。
最初の1行で body_width = 0 にする のを忘れないでください。折り返し済みのプレーンテキストが特に欲しいのでない限り、必須です。
配布ソフトで copyleft が問題になるなら避けるべき です。markdownify は MIT で、トークン数は同等、表でも markitdown と並び、ディスク使用量は 1.6 MiB 多いだけです。リンク保持を重視する場合も避けてください。ここでは最少でした。そして、後段のツールが表行に外側パイプを前提としているなら、そこでも避けた方がいいです。
マネージド API はどこに入るのか
html2text は、すでに持っている HTML を変換するだけです。ページ取得も JavaScript の実行も、ボット対策層の処理もしません。これは4つの変換器すべてに共通で、実際の多くの対象サイトではむしろこちらが難しい部分です。
5つの変換器で同じフィクスチャを比較した結果は、5-way HTML-to-Markdown 比較 をご覧ください。
取得・描画・抽出まで含むホスト型サービスは、私たちの Thunderbit を含め、まったく別の層で動きます。Thunderbit はここではベンチマークしていません。境界は、手元の HTML を Markdown に変換する話と、URL を取得して処理するサービスの違いです。この文章では、同一指標での品質・遅延・コスト比較は行っていません。
公平に言えば、HTML をすでに持っていて、Markdown が欲しく、配布形態上 GPL が問題にならないなら、html2text は無料で、しかも驚くほど小さいです。ページ取得が必要だったり、文章ではなく行データが欲しかったりするなら、それは別の買い物です。
より広い領域については、web scraping API の総覧 でホスト型の選択肢を、オープンソース scraper のピラー記事 でセルフホスト型を紹介しています。Python で HTML を Markdown に変換する方法 は、実践的な手順ガイドです。
html2text は使うべきか?
フットプリントが重要で、折り返しを意図的に無効化し、配布モデルがライセンス審査を通るなら、有力候補です。
4ページスイートでは、トークン数は markdownify と markitdown に対して 1.3% 以内でした。ただし、だからといって全体品質が同じという意味ではありません。html2text はリンク保持数が少なく、不規則な Wikipedia フィクスチャでの行保持も少なめでした。すぐ効いてくる運用上のポイントは2つです。body_width = 0 と、外側パイプなしの表スタイルです。
GPL の審査で外れるなら、markdownify は MIT で、ここでは出力サイズとトークン数が近く、リンク数も不規則表の行数も多く保持し、この環境ではディスク使用量が 1.6 MiB 多いだけでした。
Web データ抽出に Thunderbit を試す Get Started Free
FAQ
html2text は表を変換できますか?
はい。最初のカウンタでは 5ファイル中 1 行しか出ないことになっていましたが、そのカウンタが間違っていました。先頭と末尾のパイプを必須にしていたのに対し、html2text は Team Name | Year | Wins のようにパイプだけで出力するからです。これは一般的な pipe-table Markdown ですが、このハーネスではレンダラ横断の互換テストはしていません。修正版のカウンタでは、4ページスイートで html2text は 32 行、markdownify は 36 行、複雑な表の追加フィクスチャ込みでは 91 行対 98 行でした。
body_width は何をする設定で、なぜ変更するのですか?
デフォルトで 78 文字ごとに強制折り返しします。ターミナルで読めるプレーンテキストには妥当ですが、それ以外にはあまり向きません。折り返しが入ると文の途中に改行が入ったり、長い URL が分割されたり、トークン化も変わります。このレビューの数値はすべて body_width = 0 で取得したものです。デフォルトのままだと、全部違う数値になっていたはずです。
GPL は本当に制約になりますか?
正確な統合形態と配布形態によります。社内利用やネットワーク利用と、ソフトウェアを配布する場合では見方が異なりますが、この文章では法的な結論は出していません。ソフトウェアを出荷するチームは、GPL-3.0-or-later の条項を弁護士に確認してもらうべきです。markdownify と turndown は MIT です。html2text の license expression は、PyPI メタデータ、GitHub、インストール済みパッケージの METADATA ファイルで確認しました。
2025年4月のリリースは問題ですか? 単体では、おそらく問題ではありません。HTML-to-text 変換は比較的安定した問題領域で、ライブラリは Python 3.14.2 でも正常にインストール・実行でき、41 リリース分の履歴もあります。実際に見るべきなのは 95 件の未解決 issue で、導入前に自分の入力に似たものがないか確認する価値があります。リポジトリが静かということは、最終的に自分が直す側になるかもしれない、ということでもあります。
ここで未テストのものは何ですか?
4つの変換フィクスチャと、別の複雑な表フィクスチャ1つだけでは、まだ小さなスイートです。とはいえ、12 個の synthetic な不正 HTML 文書と 2 つのコントロールは試しました。html2text は 14/14 で例外なし、14/14 で空出力なし、採点対象のセントネル 33 個もすべて復元しました。現実の壊れたページ、より広い不正パターン、入れ子リスト、定義リスト、脚注、数式は未検証です。ignore_links、ignore_images、unicode_snob、single_line_break などの全オプションは、body_width を除いてデフォルトのままです。リンクの差は観測しましたが原因は調べておらず、Markdown の往復変換もテストしていません。


