以前、MarkItDownでpptx/docx/pdfをMarkdownに変換するWebツールを作った記事を書きました。ドラッグ&ドロップだけで各種ファイルをMarkdown化できて、普段の資料整理やLLMへの入力前処理にとても重宝しています。
MarkItDownで、pptx/docx/pdfを → Markdownに変換するWebツールを作った
ところが、実運用でひとつだけ致命的に苦手なパターンにぶつかりました。
罫線のある2カラム表のPDFです。この記事は、その弱点を pdfplumber で克服した実装の記録です。
問題:2カラム表PDFが「左右交互」に混ざる
きっかけは、クライアントから回ってきた求人票のPDFでした。「ホールスタッフ」「キッチンスタッフ」の2職種を左右に並べた、よくある比較表形式です。
これを MarkItDown に通すと、こうなりました。
募集する人材 ・お客様との会話を楽しめる方を歓迎!
・調理経験のある方を歓迎します。経
・接客が好きで、笑顔で明るく対応で
験が浅くても丁寧に教えます!
きる方、大歓迎です!左カラムの1行 → 右カラムの1行 → 左カラムの2行目 → … と、左右が1行ずつ交互に出てきて、まったく読めません。編集で直すのも一苦労です。
なぜこうなるのか
原因はPDFという形式の根本にあります。PDFは「文字を座標に置いただけ」で、「ここは表のセル」という構造情報を持っていません。だから抽出ツールは、文字を上から下・行単位(Y座標順)に拾っていきます。
2カラムの表では、同じ行に「左セルの1行目」と「右セルの1行目」が並んでいます。すると素直にY座標順で読むと、左右が1行ずつ交互に出てくる——というわけです。MarkItDown(内部はpdfminer)が悪いのではなく、PDFにセル境界の情報が無いのが根本原因です。
解決策:pdfplumberの「罫線ベース抽出」
そこで導入したのが pdfplumber です。
pdfminer.six の上に構築された軽量ライブラリで、罫線(線)を手がかりにセルを認識できます。今回の求人票は罫線のある表だったので、これがハマりました。
import pdfplumber
with pdfplumber.open(path) as pdf:
page = pdf.pages[0]
tables = page.extract_tables({
"vertical_strategy": "lines", # 縦罫線でセルを区切る
"horizontal_strategy": "lines", # 横罫線でセルを区切る
})これだけで、左右のカラムが別々のセルとして正しく分離されました。
| ホールスタッフ | キッチンスタッフ | |
| 職種 | ホール・接客 | 調理・仕込み |
| 給与 | 時給1,100円~ | 時給1,200円~ |
pdfplumber は MLモデルもGPUも不要なので、共用レンタルサーバー(今回はカラフルボックス)でもそのまま動きます。ここが Docling や Marker のようなAIベースの高精度ツールとの大きな違いで、「動かす環境を選ばない」のが個人開発では効きます。
実装:チェックボックスで切り替える
ここで大事な設計判断がありました。pdfplumberの表モードを「常時ON」にしてはいけない、ということです。
理由は、罫線が無い/薄いPDFや、表ではないレイアウトPDFに表モードを適用すると、逆に本文が欠落するからです(枠の中しか拾わないため)。しかも「罫線の有無」だけでは自動判定が誤爆します(枠線=データ表とは限らない)。
そこで、チェックボックスで手動切り替えにしました。
- デフォルトOFF → 従来どおり MarkItDown。どんなPDFでも全文が素直に出る(安全側)
- ON → pdfplumber の表モード(2カラム表向け)
バックエンド(FastAPI)側は、フラグを受け取って分岐するだけです。
@app.post("/convert")
async def convert_file(
file: UploadFile = File(...),
use_pdfplumber: bool = Form(False),
):
...
markdown_content = None
if use_pdfplumber and ext == ".pdf":
tables_md = pdf_tables_to_markdown(temp_file_path)
if tables_md:
markdown_content = tables_md # 表モードで成功
# 表が無ければ / OFF なら MarkItDown にフォールバック
if markdown_content is None:
result = md_converter.convert(temp_file_path)
markdown_content = result.text_content or ""表が1つも検出できなければ MarkItDown にフォールバックするので、ONのまま表の無いPDFを入れても壊れません。この「安全側に倒す」設計が、実運用では効きます。
精度改善:ここからが本番
罫線でセルを分離できても、セルの中身にはまだPDF特有のクセが残ります。ここを詰めるのが、実は一番手間のかかった部分でした。3つの課題を順に潰しました。
課題1:PDFの「折り返し」と「意図した改行」を区別する
PDFのセル内テキストは、幅に応じて途中で機械的に折り返されています。
・調理経験のある方を歓迎します。経
験が浅くても丁寧に教えます!この「経/験」は、単に幅で折り返されただけ。繋げて1行にしたい。一方、箇条書きの ・ や段落の切れ目は、意図した改行なので残したい。
この区別を、4つの判定で行いました。次の条件のどれかに当てはまれば「意図した改行」として残し、それ以外は「折り返し」として詰めて連結します。
- y座標のギャップが通常行間の1.4倍を超える(=段落の区切り)
- 行頭が箇条書き記号(
・※【など) - 前行末が文末記号(
。!?♪) - 前行末がメール/URL/ドメイン(後述)
特にy座標ギャップの判定が効きました。「箇条書きの後に、句点なしで新しい段落が始まる」ような、記号では判別できないケースも、行間の空きを見れば段落の区切りとわかるからです。
課題2:ページをまたぐと表が「分裂」する
PDFの後半でpdfplumberが検出する列数が「3列→2列」に変わり、テーブルが途中で分裂してしまいました。Markdownにすると、本文の途中に区切り線(|---|---|)が紛れ込みます。
対策は、ドキュメント内の全テーブルを1つに統合し、列数を最大値に揃えること。短い行は右側を空セルで埋めます。求人票のような「1枚もの」のPDFなら、これで1つの連続テーブルにまとまります。
課題3:メールアドレス直後がくっつく
recruit@example.com株式会社◯◯人事部…メールアドレスは .jp で終わり文末記号ではないため、次の行と連結されてしまいました。そこで、行末がメール/URL/ドメインなら改行を挟む判定を追加。ただし 実働7.5時間 や PDF,Word,Excel のような紛らわしい文字列を誤爆しないよう、正規表現は慎重に組みました。
結果:そのまま貼れるテーブルに
3つの課題を潰した結果、冒頭の「左右交互で読めない」状態が、そのままMarkdownとして貼れるテーブルになりました。
| 募集する人材 | ・お客様との会話を楽しめる方を歓迎!<br>・接客が好きで、笑顔で明るく対応できる方、大歓迎です!<br>… | ・調理経験のある方を歓迎します。経験が浅くても丁寧に教えます!<br>… |左右のカラムがきちんと分かれ、セル内の箇条書きも保たれ、折り返しは詰められています。
求人票のような帳票PDFなら、実用十分な品質です。
正直に:弱点もある
いいことばかりではないので、限界も書いておきます。
- 罫線のある表が前提。罫線が無い/薄いPDFは崩れます
(その場合はチェックOFFで通常変換が無難) - 1ファイル=1帳票向け。1つのPDFに列数の違う表が複数あると、1テーブルに融合されます
- 社名の直後に本文が続くような、汎用ルールで拾えない連結が稀に残ります(実害は小さい)
- スキャン画像のPDFは非対応(OCRしていないため)
もっと高精度が必要なら Docling や Marker、あるいは Mistral OCR のようなAPIという選択肢もありますが、いずれも「重い」か「外部送信・課金」のトレードオフがあります。共用サーバーで完結し、罫線表なら十分な精度という点で、pdfplumber は個人開発のツールにちょうど良い落としどころでした。
まとめ
- MarkItDown は万能に近いが、罫線のある2カラム表PDFは苦手(左右が交互に混ざる)
- pdfplumberの罫線ベース抽出で列を正しく分離できる。
MLモデル不要で共用サーバーでも動く - 「常時ON」は誤爆するので、チェックボックスで切り替え+フォールバックが安全
- セル内の折り返し/段落/箇条書き/メールを判定して整形すると、実用品質になる
PDFからの表抽出は「完璧」を目指すと沼ですが、用途を絞れば軽量ツールで十分戦えるというのが今回の学びでした。よかったら実際に、罫線のある表PDFで試してみてください。
pptx/docx/pdf→Markdown 変換ツール
関連記事
MarkItDownで、pptx/docx/pdfを → Markdownに変換するWebツールを作った
レンタルサーバー「カラフルボックス」で Python FastAPIをデプロイし、Webアプリを公開する方法

