PythonでPDFからテキストを抽出する
pypdf・pdfplumber・PyMuPDF の3つで同じ日本語PDFを読み、速度と表・2段組の出方を比べました。抽出できないPDFを見分けるコードと、フォルダ内のPDFを横断検索するコードも全文を載せています。
1. 結論:文字はpypdf、表はpdfplumber、速さはPyMuPDF
今回の検証PDF(日本語・テキスト層あり)は3つとも化けずに読めました。差は表・段組みの出方、速度、ライセンスです。
本文の文字だけなら pypdf。表を行と列のまま取りたい・文字の位置も使いたいなら pdfplumber。大量のPDFを短時間で処理したい・ページの画像化もしたいなら PyMuPDF。PyMuPDF を選ぶ場合は下のライセンス欄も確認してください。
| やりたいこと | 担当 | 理由 |
|---|---|---|
| 本文を文字列にして検索・保存する | pypdf | 純Pythonで pip だけで入る |
| PDFの結合・分割もしたい | pypdf | 書き出しもできる(pdfplumber は抽出・解析専用で書き出し機能がない) |
| 罫線つきの表を行・列のまま取り出す | pdfplumber | extract_tables() が表を崩さず復元した |
| 文字の位置(座標)で範囲を切り出す | pdfplumber | extract_words() が単語ごとに位置を返す |
| 大量のページを短時間で読む | PyMuPDF | 60ページの抽出が46ms(pypdf 177ms・pdfplumber 504ms) |
| ページの画像化・表示もしたい | PyMuPDF | 描画エンジン MuPDF のバインディング |
| パッケージ(2026年9月29日時点) | 最新版 | ライセンス | 対応Python |
|---|---|---|---|
| pypdf | 6.19.0 | BSD-3-Clause | 3.9以上 |
| pdfplumber | 0.11.10 | MIT | 3.10以上(依存の pdfminer.six による) |
| PyMuPDF | 1.28.2 | AGPL-3.0 または Artifex 商用 | 3.10以上 |
2. 3つの違い:同じPDFを抽出して比べた
Windows 11 / Python 3.12.10 / pypdf 6.19.0 / pdfplumber 0.11.10 / PyMuPDF 1.28.2。検証PDFは reportlab でメイリオを埋め込んで作った日本語3ページ(段落・罫線つきの表・2段組)と、それを150dpiの画像にしたスキャン相当のPDFです。自作PDFでの結果なので、Wordや複合機のPDFでは出方が変わることがあります。
pip install pypdf pdfplumber pymupdf
全ページの文字を1つの文字列にする最小コードです。PyMuPDF は import pymupdf と書きます(旧名 fitz は第5章)。
# pypdf
from pypdf import PdfReader
reader = PdfReader("sample_text.pdf")
text = "\n".join(page.extract_text() for page in reader.pages)
# pdfplumber
import pdfplumber
with pdfplumber.open("sample_text.pdf") as pdf:
text = "\n".join((page.extract_text() or "") for page in pdf.pages)
tables = pdf.pages[1].extract_tables()
# PyMuPDF
import pymupdf
with pymupdf.open("sample_text.pdf") as doc:
text = "\n".join(page.get_text() for page in doc)
速度:60ページで46ms〜504ms
3ページ版は10回、60ページ版(同じPDFを20回連結)は開いてから全ページ抽出までを5回測った中央値です。
| ライブラリ | 3ページ | 60ページ |
|---|---|---|
pypdf extract_text() | 9.2ms | 177ms |
| pypdf(layout モード) | 8.6ms | 160ms |
pdfplumber extract_text() | 18.2ms | 504ms |
PyMuPDF get_text() | 2.0ms | 46ms |
この検証PDFでは PyMuPDF が最も短く、最も長い pdfplumber でも60ページで約0.5秒でした。数ファイルなら速度より「出方」で選べば十分です。
出方の違い一覧
| 項目 | pypdf(既定) | pdfplumber | PyMuPDF(既定) |
|---|---|---|---|
| 表 | 1セル1行に分解 | 1行1レコード(半角スペース区切り) | 1セル1行に分解 |
| 2段組 | 左右の行が交互に出る | 同じ高さの左右を1行に連結 | 左右の行が交互に出る |
| 合字(fi・fl) | 合字のまま | fi・fl に分解 | 合字のまま |
| 画像だけのPDF | 3つとも空文字(エラーなし) | ||
pypdf の extraction_mode="layout" は空白で紙面の配置を再現し、文字数は425字から584字に増えました。公式は実験的な機能としています(API の説明)。
表:行と列のまま欲しいなら extract_tables()
2ページ目の表を extract_text() で読むと、pypdf と PyMuPDF は「月/対応件数/平均時間(分)/…」とセルが縦に16行並びました。pdfplumber は 7月 98 14.2 通常 の1行1レコードですが、半角スペース区切りなのでセル内に空白があると列の境目が曖昧になります。表として取るなら pdfplumber の page.extract_tables() で、今回の表は完全に復元されました。
[[['月', '対応件数', '平均時間(分)', '備考'],
['7月', '98', '14.2', '通常'],
['8月', '114', '13.8', '夏季休業あり'],
['9月', '128', '15.1', '月末集中']]]
「表 → 行 → セル」の入れ子リストなので、csv.writer に渡せばCSVで保存できます(数字も文字列で返る点に注意)。PyMuPDF の page.find_tables() でも同じ表を取り出せましたが、実行時に pymupdf_layout パッケージを勧める案内が出ました。
2段組:どれも「読む順」は保証しない
3ページ目の2段組は、pypdf と PyMuPDF の既定では左右の行が1行ずつ交互に出ました。pdfplumber は 左段: 抽出対象のPDFは 右段: 同じ見た目でも と同じ高さの左右を1行につなげます。どちらも「左段を読み終えてから右段」の順にはならず、PyMuPDF の get_text(sort=True) も空白で左右の位置を再現するところまでです。
3. 抽出できないPDFの見分け方とOCRの選択肢
pypdf の公式(Extract Text)のとおり、スキャンしただけのPDFは中身が画像なので文字を抽出できません。厄介なのは、読んでもエラーにならないことです。スキャン相当のPDFは4通りの読み方すべてで3ページとも空文字 '' が返り、例外は出ませんでした。検索に混ざると「該当なし」と区別できないので、先にテキスト層の有無を判定します。
# -*- coding: utf-8 -*-
"""PDFにテキスト層があるかを判定する(記事掲載用コード)"""
import sys
from pathlib import Path
from pypdf import PdfReader
def has_text_layer(path, min_chars=20):
"""先頭3ページのどれかに min_chars 文字以上の文字があればテキスト層ありとみなす"""
reader = PdfReader(path)
if reader.is_encrypted:
return None # パスワードが要る(判定不能)
for page in reader.pages[:3]:
text = (page.extract_text() or "").strip()
if len(text) >= min_chars:
return True
return False
if __name__ == "__main__":
sys.stdout.reconfigure(encoding="utf-8")
folder = Path(sys.argv[1] if len(sys.argv) > 1 else ".")
for pdf in sorted(folder.glob("*.pdf")):
result = has_text_layer(pdf)
label = {True: "テキスト層あり", False: "テキスト層なし(OCRが必要)", None: "暗号化(要パスワード)"}[result]
print(f"{pdf.name}: {label}")
has_text_layer.py として保存し、フォルダを引数に渡します。通常・スキャン相当・暗号化の3ファイルでの出力です。
> python has_text_layer.py docs_dir
locked.pdf: 暗号化(要パスワード)
report_2026_09.pdf: テキスト層あり
scanned_memo.pdf: テキスト層なし(OCRが必要)
「先頭3ページに20文字以上」は筆者の目安です。表紙だけ文字があるPDFも「あり」になるので、全ページ見るなら reader.pages[:3] を reader.pages に変えます。reconfigure の行は、Windows のコンソールで表示できない文字による停止を防ぎます。
テキスト層がなかったときの選択肢
画像の文字を読むにはOCR(光学文字認識)が要ります。代表的な方法は次の3つで、どれも OCR エンジン Tesseract 本体の別途インストールが必要です。当サイトではOCRは未検証で、紹介のみです。
| 方法 | 向いている使い方 |
|---|---|
| pytesseract | ページ画像から文字を読む(日本語は jpn、縦書きは jpn_vert の言語データ) |
| ocrmypdf | スキャンPDFに文字の層を足し、検索できるPDFにするコマンドラインツール |
PyMuPDF の get_textpage_ocr() | いつもの get_text() の流れでOCRする |
PyMuPDF の公式は、OCRは通常の抽出の約1000倍遅いと説明しています(OCR のレシピ)。OCRは読み間違いも起こり得るため、判定で「なし」のファイルだけをOCRに回すと時間と誤読を減らせます。
Tesseract 本体と日本語データのOS別の入れ方は、『退屈なことはPythonにやらせよう 第3版』22章「画像中の文字認識」の書評記事の解説にまとめています。リンク先は当サイトの書評記事で、アフィリエイト広告を含みます。
4. フォルダ内のPDFを横断検索する
サブフォルダも含めて探し、ファイル名・ページ番号・該当行を表示するスクリプトです。
# -*- coding: utf-8 -*-
"""フォルダ内のPDFを横断検索し、ファイル名・ページ・該当行を表示する(記事掲載用コード)"""
import sys
from pathlib import Path
from pypdf import PdfReader
def search_pdfs(folder, keyword):
for pdf in sorted(Path(folder).glob("**/*.pdf")):
try:
reader = PdfReader(pdf)
if reader.is_encrypted:
print(f"[スキップ] {pdf.name}: 暗号化されています")
continue
for page_no, page in enumerate(reader.pages, start=1):
text = page.extract_text() or ""
for line in text.splitlines():
if keyword in line:
print(f"{pdf.name} | {page_no}ページ | {line.strip()}")
except Exception as e:
print(f"[エラー] {pdf.name}: {e}")
if __name__ == "__main__":
sys.stdout.reconfigure(encoding="utf-8")
folder, keyword = sys.argv[1], sys.argv[2]
search_pdfs(folder, keyword)
search_pdfs.py として保存し、フォルダと検索語を渡します。第3章と同じ3ファイルでの結果です。
> python search_pdfs.py docs_dir 月末集中
[スキップ] locked.pdf: 暗号化されています
report_2026_09.pdf | 2ページ | 月末集中
report_2026_09.pdf | 3ページ | 検索キーワード: 「月末集中」 は2ページ目と3ページ目に出ます。
> python search_pdfs.py docs_dir 128
[スキップ] locked.pdf: 暗号化されています
report_2026_09.pdf | 1ページ | 今月の対応件数は 128 件で、前月比 12% 増となりました。
report_2026_09.pdf | 2ページ | 128
2ページ目のヒットが 128 だけなのは、pypdf が表を1セル1行に分解するためです。表の行ごと見たいなら、読み込みを pdfplumber に替えると「9月 128 15.1 月末集中」の形で出ます。壊れたPDFは except でエラーを表示して次へ進みます。
限界は3つです。スキャンPDFは静かに素通りします(scanned_memo.pdf はエラーもヒットもなし)。第3章の判定を先に流せば、探せていないファイルが分かります。1行ずつ照合するので行をまたぐ語は見つからず、PDF側が合字の「efficient」だと「efficient」ではヒットしません(対処は第5章)。
5. つまずきやすい点(暗号化・合字・縦書き・旧名)
(1) パスワード付きのPDF
AES-256で暗号化し、閲覧パスワードを付けたPDFでの結果です。
| ライブラリ | パスワードなしで読む | パスワードの渡し方 |
|---|---|---|
| pypdf | FileNotDecryptedError | reader.decrypt(pw)(誤りなら0、今回の正解では2) |
| pdfplumber | open() で PdfminerException | pdfplumber.open(path, password=pw) |
| PyMuPDF | ValueError: document closed or encrypted | doc.authenticate(pw)(0なら失敗、今回の正解では6) |
pypdf でAES暗号化を解くには cryptography が必要で、単体なら pip install pypdf[crypto] で入ります(公式のインストール手順)。今回は pdfplumber の依存として入っていました。
(2) 合字:「efficient」で検索しても見つからない
「fi」「fl」は、作成ソフトやフォントによっては1文字の合字(fi・fl)で埋め込まれます。検証PDFの「efficient」は、pypdf と PyMuPDF では合字のまま出て "efficient" in text が False でした。pdfplumber は自動で分解します。unicodedata.normalize("NFKC", ...) をかけると、3つとも True になりました。
import unicodedata
text = "英単語の合字 efficient / flow"
print("efficient" in text) # False
print("efficient" in unicodedata.normalize("NFKC", text)) # True
副作用として、NFKC は半角カナを全角に、全角英数字を半角に変えます(カタカナ ABC123 → カタカナ ABC123、UAX #15)。原文は保存し、検索用のコピーと検索語にだけかけるのが無難です。
(3) 縦書きのPDF
縦書きは当サイトで未検証です。公式の仕様で確認できたのは次の3点です。
- pypdf の layout モードは、回転した文字を既定で除外する
- pdfplumber は既定値が横書き前提(
line_dir・char_dir引数で変更可) - PyMuPDF は
get_text("dict")の各行に、縦書きかどうか(wmode)を返す
縦書きが多いなら、自分のPDFで3つを試してください。
(4) 文字化けや (cid:数字) が出る
PDF内の「字形と文字コードの対応表」が不完全だと、どのライブラリでも正しい文字に戻せない場合があります。pdfminer.six(pdfplumber の土台)のFAQは、この場合 (cid:x) の形で出ると説明しています。目安は「PDFビューアーでコピー&貼り付けして読めるか」で、貼っても化けるならOCRが近道です。
(5) PyPDF2 も import fitz も旧名
古い記事でよく見る PyPDF2 は pypdf の旧名です。2022年12月の pypdf 3.1.0 で名前が pypdf に戻り、PyPDF2 は3.0.1で更新が止まりました(3.1.0 のリリースノート)。PyPDF2 も今なお入りますが、新しく書くなら pip install pypdf です。
PyMuPDF も 1.24.3 から import pymupdf が正式な書き方です。1.28.2 で import fitz を使うと、動作はしますが次の警告が出ました。
The `fitz` API is deprecated and will be removed in future. Use `import pymupdf` instead.
6. 画面付きのツールにしたいなら
PDFを扱う中級者向けアプリの記事(完成コードつき)が参考になります。
| 記事 | この記事との対応 |
|---|---|
| PDFビューアー | PyMuPDF でページを画像化して表示。コードは旧名 import fitz なので、1.28.2 では第5章(5)の警告が出る |
| PDFマージツール | pypdf で複数のPDFを1つにまとめる |
| 請求書作成アプリ | PDFを「作る」側。本記事の検証PDFも同じ reportlab で生成 |
ほかの定番ライブラリはPythonライブラリ一覧にあります。
PDFからOCRまで一冊で通すなら、17章「PDFとWord文書」と22章のOCRを含む『退屈なことはPythonにやらせよう 第3版』(2026年3月刊)が候補です。章構成は書評記事、紹介は参考書比較ページにあります。リンク先は当サイトの書評記事・書籍紹介ページで、アフィリエイト広告を含みます。
7. よくある質問(FAQ)
Q. PyPDF2 と pypdf の違いは何ですか
同じプロジェクトの旧名と現在の名前です。2022年12月の pypdf 3.1.0 で改名され、PyPDF2 は3.0.1で更新が止まりました。新しいコードは from pypdf import PdfReader で書きます。
Q. スキャンしたPDFから文字を取り出せますか
pypdf・pdfplumber・PyMuPDF だけでは取り出せません。中身が画像のため、検証ではどれも空文字を返しました。OCR が必要で、本記事では Tesseract を使う方法(pytesseract・ocrmypdf・PyMuPDF の OCR 機能)を紹介しています。
Q. PDFの表をCSVやExcelに変換できますか
罫線のある表なら、pdfplumber の extract_tables() で行と列のリストとして取り出し、csv モジュールや pandas で保存できます。当サイトで検証したのは罫線つきの表だけなので、罫線のない表では結果を目で確認してください。
Q. PyMuPDF は仕事で使っても大丈夫ですか
PyMuPDF は AGPL-3.0 と商用のデュアルライセンスです。ツールの配布やWebサービスでの提供では、AGPL の条件を満たすか商用ライセンスが必要になり得るため、Artifex の公式案内や社内の担当部署で確認してください。合わなければ BSD-3-Clause の pypdf や MIT の pdfplumber が候補です。
本記事は、生成AIを活用して下書きし、運営者が内容を確認・編集したうえで公開しています。AIの利用方針は免責事項をご覧ください。
8. まとめ
- 文字だけなら pypdf、表や位置も要るなら pdfplumber、大量処理なら PyMuPDF(配布前にライセンスを確認)
- 画像だけのPDFはエラーにならず空文字が返る。検索の前にテキスト層を判定し、「なし」だけをOCRに回す
- 合字は NFKC で揃うが、半角・全角の区別も消えるので検索用のコピーにだけかける
- PyPDF2 は旧名。新しく書くなら pypdf、PyMuPDF は
import pymupdf
まずは手元のPDFフォルダで第3章の判定コードを流してください。