markitdown と共有している 4 つの HTML テストデータでは、markdownify は当方のカウンタで数えた Markdown のテーブル行数が markitdown と同じになり、36 対 36 でした。さらに、確認対象の 16 個の本文文字列もすべて復元できました。出力トークン数は 21,062 で名目上は最少でしたが、上位 3 つのコンバーター差は 1.3% 以内に収まっています。インストールサイズは 1.8 MiB、ライセンスは MIT、スナップショット時点の GitHub スター数は 2,235 でした。
このカテゴリではあまり語られていないライブラリですが、今回の数値を見る限り、私ならこれを選びます。
markdownify とは
markdownify は、BeautifulSoup ベースの Python 向け HTML→Markdown 変換ライブラリです。構成はかなりシンプルで、BeautifulSoup で解析して、ツリーをたどりながら Markdown を吐き出すだけです。検証バージョンは 1.2.3、MIT ライセンス、2,235 スター、未解決 issue は 42 件、最終リリースは 2026-06-30 で、継続的にメンテされており、リリース数は 44 回です。
公式リファレンス: python-markdownify の公式リポジトリ。

API は 1 つの関数で完結します。
from markdownify import markdownify
md = markdownify(html)
要素ごとの挙動を上書きしたいならクラス版 (MarkdownConverter) も使えますし、見出しスタイル、箇条書きの記号、コード言語の検出、要素の除外など、便利なオプションもそろっています。ただ、ふつうの使い方ならこの 1 行で十分で、実際それだけでちゃんと動きます。
pip install markdownify では 5 パッケージ と 1.8 MiB が入り、コールドインポートは 0.046 秒。今回比べた 3 つの変換器の中では最速でした。依存関係は BeautifulSoup とその周辺が中心なので、すでに Python プロジェクトで使っているケースも多く、その場合の追加コストはほぼゼロに近いことがよくあります。
測定方法
markitdown で使ったのと同じ 4 つの HTML テストデータを、別途このベースラインで登録済みの probe 文字列 とともに実行しました。本文の完全一致を確認するための 16 個の文字列に加え、ページの装飾要素に関するチェックも行っています。なお、5 つ目のテストデータである Nothing but tables は、テーブル中心の別診断であり、以下の 4 件の集計には含めていません。
| 変換器 | 本文 probe | 出力文字数 | トークン数(o200k) | Markdown のテーブル行数 | リンク数 |
|---|---|---|---|---|---|
| markdownify | 16/16 | 76,868 | 21,062 | 36 | 599 |
| html2text | 16/16 | 76,452 | 21,176 | 32 | 545 |
| 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 件すべてで同一ルールを適用しました。markitdown の保存済み出力も再集計しており、公開値と完全一致しています。
注目すべき点は 3 つです。
markitdown とテーブル行数で並ぶこと。 4 つの共通テストデータすべてで 36 行ずつ。しかも同じヒューリスティックで数えています。これでセル単位の完全一致までは証明できませんが、少なくともこのカウンタに対しては、両者とも同じ数の Markdown テーブル行を露出している、ということです。
トークン数が最少であること。 21,062 は、html2text の 21,176 と markitdown の 21,336 をわずかに下回り、turndown の 26,236 より 19.7% 少ない値です。上位 3 つは互いに 1.3% 以内なので、これははっきりした勝ちというより、ほぼ引き分けに近いです。本当に差がつくのは turndown との差です。
すべての本文 probe を保持できたこと。 16/16 でした。他の 3 つも同様で、本文の保持そのものはこのカテゴリの差別化ポイントではありません。
このライブラリがうまく扱うテーブル
コンバーターの差が出るのは、ホッケーの統計テストデータです。markdownify は次のように出力します。
| Team Name | Year | Wins | Losses | OT Losses | Win % | ... |
| --- | --- | --- | --- | --- | --- | --- |
| Boston Bruins | 1990 | 44 | 24 | | 0.55 | ... |
標準的な GFM 形式で、先頭と末尾にパイプがあり、区切り行もあります。しかも、24 と 0.55 の間にある空セルに注目してください。空セルを落とさず、そのまま出力しています。 これは一見地味ですが、実際にはかなり重要です。空セルを消してしまうコンバーターは、その後ろの値をすべて 1 列ずつずらしてしまい、それでも見た目だけはちゃんとしたテーブルに見えてしまいます。

turndown は同じ入力に対して、各値を独立した段落として出してしまい、列構造がまったく残りません。html2text は別スタイルですが、外側のパイプを付けない正しいテーブルを生成します。
Markdown をモデルに渡すなら、パイプと空セルの保持があるからこそ、「Boston が何試合負けたか」を推測ではなく正しく答えられます。
turndown より優れている 2 つの削除ポイント
テーブルの忠実度は見た目でわかる違いですが、こちらはもっと重要なのに、ほとんど語られていません。
markdownify は <script> と <style> の中身を削除します。turndown は削除しません。これらの要素の内部にしか現れないマーカーで数えると、markdownify の出力には script マーカー 0、style マーカー 0、turndown には 10 と 84 が含まれていました。Wikipedia のテストデータでは、これは 59,561 文字と 74,939 文字の差になります。さらに、MediaWiki のインライン JavaScript 設定と CSS が 8 行だけで 14,644 文字、つまり差分の 95% を占めていました。(script-style-stripping.json)
モデルに渡す用途では、これはこの比較の中でいちばんコストの高い差です。JavaScript の設定ブロブはトークンだけ食って、情報価値はほぼありません。markdownify は何も指定しなくてもそれを取り除きます。html2text も同じです。
テストデータごとに見ると、markdownify と html2text は、単純なテーブルでは完全に一致し、複雑になると差が出ます。
| テストデータ | markdownify | html2text |
|---|---|---|
| Hockey statistics | 27 rows | 27 rows |
| Wikipedia | 9 rows | 5 rows |
| Nothing but tables(別診断) | 62 rows | 59 rows |
markdownify は、2 つの診断比較のどちらでも、認識される行数がより多くなっています。特に Wikipedia では、このカウンタ上、html2text の 5 行に対して 9 行を保持しました。これは、選定前に代表的な入れ子テーブルや不規則なテーブルを確認すべきという有益な注意喚起ではありますが、出力された各セルが完全に意味的に正しいことの証明ではありません。
インストールとライセンスを他候補と比べると
| ライブラリ | パッケージ数 | ディスク容量 | コールドインポート | ライセンス | スター数 | 最終リリース |
|---|---|---|---|---|---|---|
| markdownify | 5 | 1.8 MiB | 0.046 s | MIT | 2,235 | 2026-06-30 |
| html2text | 1 | 0.2 MiB | 0.077 s | GPL-3.0-or-later | 2,168 | 2025-04-15 |
| turndown | 3 (npm) | 8.8 MiB | 0.056 s | MIT | 11,386 | 2026-04-03 |
公式リファレンス: PyPI 上の markdownify。
install-and-import.json と metadata-snapshot.json。各ライブラリは、それぞれ空の環境に個別にインストールしています。
html2text はディスク上で 9 分の 1 ほど小さい一方、ライセンスは GPL-3.0-or-later です。配布する製品では、ライセンス責任者がここを確認すべきです。この記事は法的助言ではありません。markdownify は MIT で、一般にはより寛容ですが、それでも通常のコンプライアンス確認には含めるべきです。
1.8 MiB というサイズも、すでに BeautifulSoup を使っているプロジェクトなら、ある意味では名目上の数字です。多くの Python スクレイピング系プロジェクトがこれに当てはまるため、その場合 markdownify の追加コストはかなり小さくなります。
リリース頻度は 3 つの中で markdownify がいちばん健全です。44 回のリリースがあり、検証の 6 週間前にも更新が入っていました。一方、html2text の最終リリースは 2025 年 4 月でした。
Python の 1 行で実際に何が起きるのか
このライブラリは markdownify(html) で完結しますが、この呼び出しが裏で何をしているのかは明確にしておく価値があります。というのも、この比較で効いていた 3 つの判断のうち 3 つが、まさにここにあるからです。
BeautifulSoup で解析し、要求されなくても <script> と <style> を削除し、外側のパイプ付きで空セルを保持した GFM 形式のテーブルを出力します。パーサーの系譜だけで壊れた入力への耐性が保証されるわけではないので、その点は下で別途測定しています。
これらはどれも自分で設定するオプションではありません。デフォルト動作として最初からそうなっています。turndown のデフォルトが JavaScript を残し、html2text のデフォルトが 78 文字でハードラップするこのカテゴリでは、追加調整なしで使えるライブラリにはそれ自体の価値があります。
もちろんオプションもあり、必要なら heading_style、bullets、code_language、strip、convert で要素の許可/除外を細かく制御できますし、MarkdownConverter で要素ごとに挙動を上書きすることもできます。ここでの測定は、すべてデフォルトオプションの通常関数のみを使っています。
メモリと、壊れた HTML がそれに与える影響

より広い負荷テストの文脈は、10 ライブラリのメモリと壊れた HTML 比較 にあります。
この比較のバッチで未検証とされていた 2 つの項目も、今回は計測しました。
ピーク常駐メモリ は /usr/bin/time -l で測定し、セルごとに新規プロセスを起動しています。インポート時の最小値はライブラリが読み込まれて待機しているだけのコスト、ピーク値には文書本体の処理が含まれます。
| ライブラリ | 実行環境 | インポート時最小値 | 226 KB 時のピーク | 10 MB 時のピーク |
|---|---|---|---|---|
| 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 のベースライン同士は直接比較できません。どちらにもインタプリタのコストが含まれているためです。
markdownify は中間の位置です。インポート時最小値は 23.9 MiB、10 MB 文書でのピークは 278.5 MiB でした。これは html2text のピークの 3.9 倍で、turndown の約 10 分の 1 です。BeautifulSoup を下層に抱えている以上、そこがトレードオフです。
壊れた HTML については、12 個の文書でそれぞれ 1 つだけ壊れた箇所を作っています。具体的には、閉じ忘れタグ、入れ子の崩れたインライン要素、スペースを含む未クォート属性、余計な閉じタグ、<html> がまったくない文書、重複属性、タグ途中で切れた文書、壊れたエンティティ、閉じ忘れた <script>、偽の charset 宣言、マークアップを含むコメント、600 階層のネストです。加えて、サイズをそろえた 正常系コントロール 2 件 もあります。というのも、「何も返さなかった」という結果は、同サイズの正常文書にも無言なライブラリに対しては、壊れ具合を示す証拠にならないからです。
markdownify は 14 件中 1 件 で例外を投げ、0 件 で空出力となり、壊れたテストデータ全体で 33 個中 30 個 の sentinel を復元しました。(malformed-results.json) この集計からは 1 つ除外したテストデータがあります。HTML5 の仕様上、閉じ忘れた <script> の後ろはすべて script コンテンツなので、そこで失われるのは正しく、逆に復元されるほうが逸脱だからです。
長所と短所
良い点。 同じルールで数えたときのテーブル忠実度は markitdown と同等。4 つの中でトークン数が最少。MIT。1.8 MiB で、すでに BeautifulSoup があるなら追加コストはかなり小さい。コールドインポートは 0.046 秒で最速。継続的にリリースされている。空のテーブルセルを保持する。MarkdownConverter で要素ごとに変換を上書きできる。素直に 1 行で呼ぶのが正解。
気になる点。 BeautifulSoup に依存するため、すでに入っていなければ html2text よりディスクを 9 倍使います。2,235 スターは turndown よりコミュニティが小さいことを意味し、変なケースに当たったときの実例も少なめです。Python 専用なので Node スタックでは使えません。さらに未解決 issue が 42 件あります。
誰に向いていて、誰には向かないか
この 4 つのテストデータに近い Python ワークロードなら、まず markdownify を試すべきです。 このカウンタでは markitdown とテーブル行数が同じで、トークン最少グループに入り、今回の実行ではインポートも最速でした。MIT ライセンスでもあります。すでに BeautifulSoup に依存しているなら、追加のインストール負荷はかなり小さい可能性があります。実環境で実際の差分を確認してください。
依存コストを数百 KB 単位で抑えたいなら html2text も検討候補です。 ただし、出力形状が自分のページに合う場合に限ります。配布前には、ライセンス責任者と GPL-3.0-or-later を確認してください。
すでに PDF や Office 文書を変換しているなら markitdown も候補です。 ここでは HTML 出力の行数は同じでしたが、より広い忠実度まで同等だとは示していません。
Node では見送ってください。 その場合の自然な答えは turndown で、しかも turndown-plugin-gfm を併用する前提です。turndown のコアはテーブルを一切出力しないからです。
マネージド API を使う場面
markdownify は、すでに手元にある HTML を Markdown に変換します。取得もしませんし、JavaScript のレンダリングもしませんし、ボット対策層の処理もしません。今回の 4 つのコンバーターはいずれも同様で、多くの実サイトでは、実はその前半のほうが難所です。
同じ 5 つのコンバーターでの比較は、5-way HTML→Markdown 比較 をご覧ください。
Thunderbit の開発スタックは、その上流側の処理を担当します。POST /distill は URL を取得して Markdown を返し、POST /extract はスキーマ形の JSON を返します。どちらも MCP サーバーと CLI から利用可能で、料金は Thunderbit の料金ページ にあります。これらのホスト型エンドポイントは今回のローカル変換器とはベンチマーク比較していないため、ここは性能比較ではなくカテゴリの違いです。
率直に言えば、HTML を持っていて Markdown が欲しいだけなら、markdownify は無料で、ここにある中でも十分に役立ちます。ページを取りに行きたい、あるいは文章ではなく行として扱いたいなら、それは別の製品を選ぶ話です。
より広い選択肢としては、web scraping API のまとめ でホスト型の選択肢を、open-source scraper の総覧 でセルフホスト型を紹介しています。実務寄りの解説は Python で HTML を Markdown に変換する方法 が参考になります。
markdownify を使うべきか?
Python なら、入力がこのテストデータに近い場合はかなり有力です。まずは自分のテーブルや壊れたページで試してから、デフォルト採用を決めるのがいいでしょう。
テーブル行数は markitdown と同等、トークン数は最少クラス、今回の実行ではインポート最速、そして MIT ライセンスです。一方で、メモリ使用量は html2text より大きく、Python 専用で、壊れた入力テストの 1 件では例外を出しました。ディスク容量やライセンスも、もちろん選定要素の一部です。
私が気になるのはスター数です。turndown は注目度が 5 倍ありますが、デフォルト設定では markdownify が正しく処理したテーブルを 1 つも扱えません。このカテゴリでは、人気と実測挙動が一致していないのです。だからこそ、この比較をやる意味がありました。
Thunderbit で Web データ抽出を試す Get Started Free
FAQ
markdownify は HTML 変換で markitdown と同等と言えますか? 今回の 4 つのテストデータでは、認識された Markdown テーブル行数はどちらも 36 行、確認した 16 文字列はすべて復元でき、文字数ベースでも 0.2% 以内でした。ただし、セル単位の完全一致やレンダリング一致を検証したわけではありません。さらに markitdown は PDF と Office 形式にも対応しているため、この比較はあくまで HTML 出力の実測に関するものです。
BeautifulSoup は必要ですか? はい。これがパーサーであり、1.8 MiB の大半でもあります。すでに BeautifulSoup を使っているなら、markdownify の追加コストは小さいです。もし使っておらず、ディスク容量を本気で気にするなら、0.2 MiB・1 パッケージの html2text もありますが、ライセンスは GPL-3.0-or-later です。
空のテーブルセルはどう扱いますか? 保持します。データに欠けがあるテストデータでは、出力でも空セルがその位置のまま残るため、その後ろの値も正しい列に収まります。空セルを飛ばすコンバーターは、一見すると正しい表に見えても、すべての値が 1 列左にずれた状態になり、しかも見た目では分からないので、むしろ厄介です。
関数とクラス、どちらを使うべきですか?
一般的には markdownify() 関数です。特定要素の変換を上書きしたいときだけ MarkdownConverter を使ってください。ここでの測定はすべて、デフォルト設定の通常関数で行っています。
ここで未検証だったものは何ですか? 4 つの共通テストデータと 1 つのテーブル専用診断だけでは、代表コーパスとは言えません。別の壊れた HTML セットでは 12 種類の破損と 2 つのコントロールを扱いましたが、壊れた HTML のすべての形を網羅したわけではありません。ネストしたリスト、定義リスト、脚注、数式、Markdown→HTML の往復、セル単位のテーブル一致、設定バリエーションは比較していません。変換速度も比較対象外です。


