- Python
Python pypdfでPDF操作|結合・分割・テキスト抽出のコード集
pypdfとは、PDFの結合・分割・回転・テキスト抽出・暗号化などをPythonだけで行えるライブラリです。 外部ソフトに頼らずpipで入り、Windows・macOS・Linuxのどれでも同じコードで動きます。以前のPyPDF2はpypdfに統合されており、今から書くならpypdfを使います。
この記事では、2026年10月時点の最新版pypdf 6.19.0で、PDF操作12種類をコピペできるコードと実行結果つきでまとめました。テスト用PDFも日本語フォント(Noto Sans CJK)を埋め込んで自分で作るので、手元で同じ結果を再現できます。さらに、PyPDF2からの移行対応表、同じPDFでpypdf・pdfplumber・PyMuPDFの日本語抽出結果を並べた比較、暗号化PDF・壊れたPDF・画像だけのPDFで出る実際のエラー文と対処も載せています。
検証環境:Python 3.14.7 / pypdf 6.19.0(PyPI最新、2026-09-16公開、Python 3.9以上)/ pdfplumber 0.11.10 / PyMuPDF 1.28.2 / fpdf2 2.8.9。
pypdfでできるPDF操作の早見表
| やりたいこと | コード(from pypdf import PdfReader, PdfWriter) |
補足 |
|---|---|---|
| インストール | pip install pypdf |
AES暗号化を使うなら pip install "pypdf[crypto]" |
| 読み込み・ページ数 | r = PdfReader("a.pdf"); len(r.pages) |
r.pages[0] が1ページ目 |
| 結合 | w = PdfWriter(); w.append("a.pdf"); w.append("b.pdf"); w.write("out.pdf") |
PdfMerger は不要 |
| 1ページずつ分割 | w = PdfWriter(); w.add_page(page); w.write(...) をページ数だけ |
ファイル名に連番 |
| ページ範囲の抽出 | w.append("a.pdf", pages=(1, 4)) |
0始まり・終わりは含まない(2〜4ページ目) |
| 回転 | page.rotate(90) |
90の倍数。時計回り |
| ページ削除 | w = PdfWriter(clone_from="a.pdf"); w.remove_page(2) |
0始まり |
| 並べ替え | for i in [4, 3, 0]: w.add_page(r.pages[i]) |
好きな順に追加 |
| テキスト抽出 | page.extract_text() |
見た目に近い配置は extraction_mode="layout" |
| メタデータを読む | r.metadata.title / .author |
無ければ None |
| メタデータを書く | w.add_metadata({"/Title": "..."}) |
キーは / 始まり |
| 暗号化 | w.encrypt(user_password="...", owner_password="...", algorithm="AES-256") |
pypdf[crypto] が必要 |
| 復号 | r.decrypt("パスワード") |
戻り値0は失敗 |
| 透かし・スタンプ | page.merge_page(stamp_page, over=True) |
over=False で背面に |
| しおり | w.add_outline_item("見出し", ページ番号, parent=...) |
入れ子にできる |
| 圧縮 | page.compress_content_streams() / w.compress_identical_objects(...) |
重複オブジェクトを削除 |
| 既存PDFをまるごと編集用に開く | PdfWriter(clone_from="a.pdf") |
しおり等も引き継ぐ |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python pypdfでPDF操作|結合・分割・テキスト抽出のコード集」
インストールとテスト用PDFの準備
まず、操作の対象になるテスト用PDFを自分で作ります。 ここではfpdf2(PDFを生成するライブラリ)で、日本語フォントNoto Sans CJK JPを埋め込んだ「資料A(3ページ)」「資料B(2ページ)」を作ります。手元のPDFで試す場合は、この手順は飛ばしてかまいません。
python -m venv .venv && . .venv/bin/activate
pip install "pypdf[crypto]" pdfplumber pymupdf fpdf2
python -c "import pypdf; print(pypdf.__version__)"
6.19.0
# make_samples.py fpdf2 2.8.9 でテスト用PDFを作る(日本語フォントを埋め込み)
from fpdf import FPDF
FONT = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc" # 環境に合わせて変更
def make(path, title, pages):
pdf = FPDF(format="A4")
pdf.add_font("NotoJP", fname=FONT, collection_font_number=0) # ttc の0番目(JP)
pdf.set_font("NotoJP", size=14)
for i in range(1, pages + 1):
pdf.add_page()
pdf.cell(text=f"{title} - {i}ページ目", new_x="LMARGIN", new_y="NEXT")
pdf.set_font_size(11)
pdf.multi_cell(0, 8, text="備品の貸出記録:ノートパソコン 3台、プロジェクター 1台。\n"
"返却期限は2026年10月15日(木)です。半角ABC123と全角ABC123。")
pdf.set_font_size(14)
pdf.set_title(title)
pdf.set_author("総務チーム")
pdf.output(path)
make("a.pdf", "資料A", 3)
make("b.pdf", "資料B", 2)
print("created a.pdf(3p) b.pdf(2p)")
created a.pdf(3p) b.pdf(2p)
Windowsなら C:/Windows/Fonts/meiryo.ttc などに置き換えてください。フォントを埋め込まないと、日本語は表示も抽出もできません。
ページ操作:結合・分割・範囲抽出・回転・削除・並べ替え
ページ操作は「PdfReader で読み、PdfWriter に必要なページを入れて書き出す」が基本形です。 6種類をひとつのスクリプトにまとめました。結果を確かめるため、各ページ1行目のテキストを表示する小さな関数 pages_of を使っています(replace("\x00", "") の理由は「テキスト抽出の比較」で説明します)。
# ops.py Python 3.14 / pypdf 6.19.0
from pathlib import Path
from pypdf import PdfReader, PdfWriter
def pages_of(path):
r = PdfReader(path)
return [p.extract_text().replace("\x00", "").splitlines()[0] for p in r.pages]
# 1. 結合
w = PdfWriter()
w.append("a.pdf")
w.append("b.pdf")
w.write("merged.pdf")
print("1 結合:", len(PdfReader("merged.pdf").pages), "ページ")
# 2. 1ページずつに分割
Path("split").mkdir(exist_ok=True)
for i, page in enumerate(PdfReader("merged.pdf").pages, start=1):
w = PdfWriter(); w.add_page(page); w.write(f"split/page_{i:03}.pdf")
print("2 分割:", sorted(p.name for p in Path("split").glob("*.pdf")))
# 3. ページ範囲を抜き出す(2〜4ページ目 = インデックス1〜3)
w = PdfWriter(); w.append("merged.pdf", pages=(1, 4)); w.write("range.pdf")
print("3 範囲:", pages_of("range.pdf"))
# 4. 回転
w = PdfWriter(clone_from="a.pdf")
w.pages[0].rotate(90)
w.write("rotated.pdf")
print("4 回転:", [p.rotation for p in PdfReader("rotated.pdf").pages])
# 5. 削除(3ページ目を削除)
w = PdfWriter(clone_from="merged.pdf")
w.remove_page(2)
w.write("deleted.pdf")
print("5 削除:", pages_of("deleted.pdf"))
# 6. 並べ替え
r = PdfReader("merged.pdf")
w = PdfWriter()
for idx in [4, 3, 0, 1, 2]:
w.add_page(r.pages[idx])
w.write("reordered.pdf")
print("6 並べ替え:", pages_of("reordered.pdf"))
1 結合: 5 ページ
2 分割: ['page_001.pdf', 'page_002.pdf', 'page_003.pdf', 'page_004.pdf', 'page_005.pdf']
3 範囲: ['資料A - 2ページ目', '資料A - 3ページ目', '資料B - 1ページ目']
4 回転: [90, 0, 0]
5 削除: ['資料A - 1ページ目', '資料A - 2ページ目', '資料B - 1ページ目', '資料B - 2ページ目']
6 並べ替え: ['資料B - 2ページ目', '資料B - 1ページ目', '資料A - 1ページ目', '資料A - 2ページ目', '資料A - 3ページ目']
ページ番号は0始まり、範囲 (1, 4) は終わりを含まない(Pythonのスライスと同じ)点に注意すると、範囲指定で迷いません。
| 書き方 | 意味 | 人が数えるページ |
|---|---|---|
pages=(0, 3) |
インデックス0〜2 | 1〜3ページ目 |
pages=(1, 4) |
インデックス1〜3 | 2〜4ページ目 |
pages=(0, 10, 2) |
0,2,4,6,8 | 奇数ページ |
r.pages[-1] |
最後のページ | 最終ページ |
フォルダ内のPDFを名前順にまとめて結合する
from pathlib import Path
from pypdf import PdfWriter
w = PdfWriter()
for pdf in sorted(Path("input").glob("*.pdf")):
w.append(str(pdf), outline_item=pdf.stem) # ファイル名をしおりにする
w.write("all.pdf")
w.close()
outline_item を渡すと、結合した各ファイルの先頭にしおりが付き、ビューアーで目次のように移動できます。
メタデータ・暗号化・透かし・しおり・圧縮
# ops2.py Python 3.14 / pypdf 6.19.0 / fpdf2 2.8.9
from pathlib import Path
from fpdf import FPDF
from pypdf import PdfReader, PdfWriter
# 7. メタデータの読み書き
r = PdfReader("merged.pdf")
print("7 読む:", r.metadata.title, "/", r.metadata.author, "/", r.metadata.producer)
w = PdfWriter(clone_from="merged.pdf")
w.add_metadata({"/Title": "備品資料(結合版)", "/Author": "総務チーム", "/Subject": "2026年10月 棚卸し"})
w.write("meta.pdf")
m = PdfReader("meta.pdf").metadata
print("7 書いた:", m.title, "/", m.subject)
# 8. 暗号化と復号
w = PdfWriter(clone_from="merged.pdf")
w.encrypt(user_password="open-1234", owner_password="owner-5678", algorithm="AES-256")
w.write("locked.pdf")
r = PdfReader("locked.pdf")
print("8 暗号化:", r.is_encrypted)
print("8 間違い:", r.decrypt("wrong"))
print("8 正しい:", r.decrypt("open-1234"), len(r.pages), "ページ")
w = PdfWriter(clone_from=r) # 復号済みの reader から暗号化なしで保存
w.write("unlocked.pdf")
print("8 解除後:", PdfReader("unlocked.pdf").is_encrypted)
# 9. 透かし(スタンプ用PDFを作って全ページに重ねる)
FONT = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"
st = FPDF(format="A4"); st.add_font("NotoJP", fname=FONT, collection_font_number=0)
st.add_page(); st.set_font("NotoJP", size=60); st.set_text_color(200, 200, 200)
with st.rotation(45, x=105, y=148):
st.text(x=45, y=160, text="社外秘")
st.output("stamp.pdf")
stamp = PdfReader("stamp.pdf").pages[0]
w = PdfWriter(clone_from="merged.pdf")
for page in w.pages:
page.merge_page(stamp, over=True) # over=False で文字の下に敷く
w.write("watermarked.pdf")
print("9 透かし:", len(PdfReader("watermarked.pdf").pages), "ページに追加")
# 10. しおり(アウトライン)
w = PdfWriter(clone_from="merged.pdf")
parent_a = w.add_outline_item("資料A", 0)
w.add_outline_item("A-3ページ目", 2, parent=parent_a)
w.add_outline_item("資料B", 3)
w.write("outlined.pdf")
r = PdfReader("outlined.pdf")
def walk(items, depth=0):
for it in items:
if isinstance(it, list): walk(it, depth + 1)
else: print("10 しおり:", " " * depth + it.title, "→", r.get_destination_page_number(it) + 1, "ページ")
walk(r.outline)
# 11. 圧縮
big = PdfWriter()
for _ in range(10):
big.append("merged.pdf")
big.write("big.pdf")
w = PdfWriter(clone_from="big.pdf")
for page in w.pages:
page.compress_content_streams()
w.compress_identical_objects(remove_duplicates=True, remove_unreferenced=True)
w.write("compressed.pdf")
for f in ["big.pdf", "compressed.pdf"]:
print("11 圧縮:", f, f"{Path(f).stat().st_size:,} bytes")
7 読む: None / None / pypdf
7 書いた: 備品資料(結合版) / 2026年10月 棚卸し
8 暗号化: True
8 間違い: 0
8 正しい: 1 5 ページ
8 解除後: False
9 透かし: 5 ページに追加
10 しおり: 資料A → 1 ページ
10 しおり: A-3ページ目 → 3 ページ
10 しおり: 資料B → 4 ページ
11 圧縮: big.pdf 276,473 bytes
11 圧縮: compressed.pdf 57,548 bytes
結果から読み取れるポイントをまとめます。
| 番号 | わかったこと | 使い方のコツ |
|---|---|---|
| 7 | append で結合したPDFには、元のタイトル・作成者は引き継がれない(a.pdf単体では 資料A / 総務チーム が読めた) |
結合後に add_metadata で付け直す |
| 8 | decrypt の戻り値は 0=失敗、1=ユーザーパスワードで成功、2=オーナーパスワードで成功 |
if r.decrypt(pw) == 0: で判定 |
| 9 | スタンプのページを全ページに重ねられる | 透かし用PDFはWordやfpdf2で1枚作っておく |
| 10 | しおりは親子の入れ子にできる | 読むときは r.outline を再帰でたどる |
| 11 | 同じ内容を10回結合した276KBのPDFが、重複オブジェクトの削除で57KBに | フォントや画像の重複が多いPDFほど効く |
ユーザーパスワードは「開くためのパスワード」、オーナーパスワードは「権限(印刷・編集など)を変えるためのパスワード」です。 権限を絞りたいときは encrypt(..., permissions_flag=...) で指定します。なお、6.19.0では compress_identical_objects の引数名 remove_identicals / remove_orphans を使うと DeprecationWarning(7.0.0で削除予定。remove_duplicates / remove_unreferenced を使うよう案内)が出ました。以前の版向けに書かれたコードを使うときは引数名を確認してください。
画像が多いPDFをさらに小さくしたい場合は、ページ内の画像を for img in page.images: img.replace(img.image, quality=60) のように再圧縮する方法もあります(この記事のテストPDFには画像がないため未実行です)。
テキスト抽出:pypdf・pdfplumber・PyMuPDFの日本語比較
テキスト抽出の精度は、ライブラリだけでなく「そのPDFがどう作られたか」で大きく変わります。 そこで3種類のPDFを用意し、同じコードで3つのライブラリの出力を並べました。
| テストPDF | 作り方 | 埋め込みフォント(pdffontsで確認) |
|---|---|---|
| memo.pdf | テキストファイルをLibreOfficeでPDF化 | NotoSansMonoCJKsc(Type 1) |
| a.pdf | fpdf2 2.8.9でNoto Sans CJK(.ttc)を埋め込み | NotoSansCJKJP(CID Type 0C) |
| ipag.pdf | fpdf2 2.8.9でIPAゴシック(.ttf)を埋め込み | IPAゴシック(TrueType) |
# compare.py 同じPDFを3つのライブラリで抽出
import sys
from pypdf import PdfReader
import pdfplumber, pymupdf
for path in sys.argv[1:]:
print(f"===== {path} =====")
print("[pypdf]"); print(repr(PdfReader(path).pages[0].extract_text()[:60]))
with pdfplumber.open(path) as p:
print("[pdfplumber]"); print(repr(p.pages[0].extract_text()[:60]))
with pymupdf.open(path) as d:
print("[PyMuPDF]"); print(repr(d[0].get_text()[:60]))
$ python compare.py memo.pdf a.pdf ipag.pdf
===== memo.pdf =====
[pypdf]
'備品管理のお知らせ\n2026 年10 月の棚卸しを行います。対象はノートパソコン、モニター、プロジェクターです。\n在庫数'
[pdfplumber]
'備品管理のお知らせ\n2026年10月の棚卸しを行います。対象はノートパソコン、モニター、プロジェクターです。\n在庫数は表'
[PyMuPDF]
'備品管理のお知らせ\n2026 年10 月の棚卸しを行います。対象はノートパソコン、モニター、プロジェクターです。\n在庫数'
===== a.pdf =====
[pypdf]
'\x00資\x00料\x00A\x00 \x00-\x00 \x001\x00ペ\x00ー\x00ジ\x00目\n\x00備\x00品\x00の\x00貸\x00出\x00記\x00録\x00:\x00ノ\x00ー\x00ト\x00パ\x00ソ\x00コ\x00ン\x00 \x003\x00台\x00'
[pdfplumber]
''
[PyMuPDF]
'資料A - 1ページ目\n備品の貸出記録:ノートパソコン 3台、プロジェクター 1台。\n返却期限は2026年10月15日('
===== ipag.pdf =====
[pypdf]
'備品の貸出記録:ノートパソコン 3台。2026年10月15日'
[pdfplumber]
'備品の貸出記録:ノートパソコン 3台。2026年10月15日'
[PyMuPDF]
'備品の貸出記録:ノートパソコン 3台。2026年10月15日\n'
(表示の都合で [:60] で切り、3ファイル分の出力を1つにまとめています。)
わかったことは3つです。
- 一般的なPDF(memo.pdf・ipag.pdf)では、3つとも日本語を正しく抽出できました。 違いは細部で、pypdfとPyMuPDFは「2026 年」のように数字と漢字の境目に空白が入り、pdfplumberは入りませんでした。
- フォントの埋め込み方によっては結果が分かれます。 a.pdf(fpdf2でCFF形式の.ttcを埋め込んだもの)では、pypdfは1文字ごとにNUL文字(
\x00)が混ざり、pdfplumberは空文字、PyMuPDFは正しく抽出できました。pypdfの結果はtext.replace("\x00", "")で読める文字列に戻せます(ページ操作の節のpages_ofはこのためです)。 - レイアウトを保った抽出は、pypdfの
extract_text(extraction_mode="layout")で行えます。memo.pdfでは、空行も含めて元の文書に近い形で出力されました。
備品管理のお知らせ
2026年10月の棚卸しを行います。対象はノートパソコン、モニター、プロジェクターです。
在庫数は表計算ソフトで管理し、差異があれば総務チームへ連絡してください。
English text: Inventory check starts on October 5.
どのライブラリを選ぶか
| ライブラリ(2026年10月の最新版) | 得意なこと | ライセンス | 向いている場面 |
|---|---|---|---|
| pypdf 6.19.0 | 結合・分割・回転・暗号化・しおりなどページ単位の編集。純Pythonで依存が少ない | BSD | PDF編集の自動化全般、配布しやすいツール |
| pdfplumber 0.11.10 | 文字の座標・表の抽出(extract_tables) |
MIT | 帳票から表を取り出す |
| PyMuPDF 1.28.2 | 抽出の速さと対応範囲の広さ、画像化、壊れたPDFの修復 | AGPL(商用ライセンスあり) | 大量PDFの全文検索、抽出結果が不安定なPDF |
編集はpypdf、表の抽出はpdfplumber、抽出がうまくいかないPDFや大量処理はPyMuPDF、と使い分けると無理がありません。PyMuPDFはAGPLライセンスのため、配布するアプリに組み込む場合はライセンス条件を確認してください。
PyPDF2からpypdfへの移行対応表
PyPDF2の開発はpypdfに引き継がれており、pypdf 6.19.0では旧名のクラスやメソッドは使えません。 実際に旧名を使うと次のエラーになりました。
import PdfFileReader -> ImportError
import PdfFileWriter -> ImportError
import PdfFileMerger -> ImportError
import PdfMerger -> ImportError
AttributeError : 'PdfReader' object has no attribute 'getPage'
AttributeError : 'PdfReader' object has no attribute 'numPages'
| PyPDF2(旧) | pypdf(現在) |
|---|---|
from PyPDF2 import ... |
from pypdf import ... |
PdfFileReader |
PdfReader |
PdfFileWriter |
PdfWriter |
PdfFileMerger / PdfMerger |
PdfWriter の append() / merge() |
reader.getPage(n) |
reader.pages[n] |
reader.getNumPages() / reader.numPages |
len(reader.pages) |
page.extractText() |
page.extract_text() |
reader.getDocumentInfo() / documentInfo |
reader.metadata |
reader.isEncrypted |
reader.is_encrypted |
reader.getOutlines() / outlines |
reader.outline |
reader.getFields() |
reader.get_fields() |
writer.addPage(page) |
writer.add_page(page) |
writer.addBookmark(...) |
writer.add_outline_item(...) |
writer.addMetadata(...) |
writer.add_metadata(...) |
writer.encrypt(user_pwd, owner_pwd) |
writer.encrypt(user_password=..., owner_password=...) |
page.rotateClockwise(90) |
page.rotate(90) |
page.mergePage(other) |
page.merge_page(other) |
page.mediaBox |
page.mediabox |
page.compressContentStreams() |
page.compress_content_streams() |
移行は「import先を pypdf に変える → キャメルケースをスネークケースに直す → PdfMerger を PdfWriter に置き換える」の3ステップで進めると、ほとんどのコードが動きます。
よくあるエラーと対処
テスト用PDFをわざと壊したり暗号化したりして、出るエラーを確かめました。
from pypdf import PdfReader
from pypdf.errors import PdfReadError, FileNotDecryptedError, EmptyFileError
import pymupdf
# 暗号化PDFをパスワードなしで読む
try:
PdfReader("locked.pdf").pages[0].extract_text()
except FileNotDecryptedError as e:
print("E1", type(e).__name__, ":", e)
# 壊れたPDF(ファイルの後半が欠けている)
data = open("merged.pdf", "rb").read()
open("broken.pdf", "wb").write(data[: len(data) // 2])
try:
PdfReader("broken.pdf", strict=True)
except PdfReadError as e:
print("E2 strict=True", type(e).__name__, ":", e)
try:
PdfReader("broken.pdf") # strict=False(既定)
except PdfReadError as e:
print("E2 strict=False", type(e).__name__, ":", e)
with pymupdf.open("broken.pdf") as d:
print("E2 PyMuPDFで修復して開く:", d.page_count, "ページ", "修復:", d.is_repaired)
d.save("repaired.pdf")
print("E2 修復後をpypdfで:", len(PdfReader("repaired.pdf").pages), "ページ")
# 空のファイル
open("empty.pdf", "w").close()
try:
PdfReader("empty.pdf")
except EmptyFileError as e:
print("E4", type(e).__name__, ":", e)
# 画像だけのPDF(スキャンしたPDFを想定)
print("E5 画像PDF:", repr(PdfReader("scan.pdf").pages[0].extract_text()))
E1 FileNotDecryptedError : File has not been decrypted
E2 strict=True PdfReadError : EOF marker not found
E2 strict=False PdfStreamError : Stream has ended unexpectedly
E2 PyMuPDFで修復して開く: 5 ページ 修復: True
E2 修復後をpypdfで: 3 ページ
E4 EmptyFileError : Cannot read an empty file
E5 画像PDF: ''
(実際の出力には、このほか EOF marker not found や Object 21 0 not defined. といったログの警告行も表示されます。)
| エラー・症状 | 原因 | 対処 |
|---|---|---|
FileNotDecryptedError: File has not been decrypted |
暗号化PDFを復号せずに読んだ | if r.is_encrypted: r.decrypt(pw) を先に実行 |
DependencyError(AES関連) |
暗号ライブラリが入っていない | pip install "pypdf[crypto]" |
PdfReadError: EOF marker not found |
ファイルの末尾が欠けている(ダウンロード途中など) | 取得し直す。strict=False で読める場合もある |
PdfStreamError: Stream has ended unexpectedly |
ファイルが途中で切れている | PyMuPDFで開いて保存し直すと構造を修復できることがある |
EmptyFileError: Cannot read an empty file |
0バイトのファイル | 読む前に Path(p).stat().st_size を確認 |
extract_text() が空文字 |
画像だけのPDF(スキャン) | OCRが必要(下記) |
| 文字の間にNULや空白が入る | フォントの埋め込み方式による | replace("\x00", "")、またはPyMuPDFで抽出 |
壊れたPDFの修復は、欠けた部分の内容までは戻りません。 今回の例では、PyMuPDFは5ページとして開きましたが、保存したファイルをpypdfで読むと3ページでした。後半が失われたファイルは、元データを取得し直すのが確実です。
また、PdfStreamError は PdfReadError の子クラスなので、except PdfReadError: で両方まとめて受けられます。
画像だけのPDFはOCRが必要
スキャンしたPDFは、ページ全体が1枚の画像なので文字情報を持っていません。上の結果のとおり extract_text() は空文字 になります。文字を取り出すには、OCR(画像から文字を読み取る処理)が必要です。
| 方法 | 内容 |
|---|---|
| OCRmyPDF | スキャンPDFに透明な文字レイヤーを付ける。付けたあとはpypdfで抽出できる |
| Tesseract + pytesseract | ページを画像化して文字認識。日本語は jpn の学習データを使う |
| クラウドのOCRサービス | 手書きや複雑なレイアウトに強い |
いずれもこの記事の環境では未実行のため、導入時は各公式ドキュメントの手順を確認してください。
よくある質問
Q. PyPDF2とpypdfはどちらを使えばよいですか? A. pypdfを使います。PyPDF2の開発はpypdfに引き継がれており、最新の機能や修正はpypdfに入ります。旧コードは本文の移行対応表のとおり書き換えれば動きます。
Q. PdfMergerが見つからないと言われます。
A. pypdf 6.19.0では PdfMerger はimportできません(実行して ImportError を確認)。PdfWriter() を作って append() で順に追加すれば、同じ結合ができます。
Q. 日本語のテキスト抽出で文字の間に空白や記号が入ります。
A. PDFの作られ方によって起きます。NUL文字なら replace("\x00", "") で取り除けます。改善しない場合はPyMuPDFやpdfplumberでも試し、最も読みやすい結果のライブラリを選ぶのが近道です。
Q. パスワード付きPDFのパスワードを外すには?
A. 正しいパスワードで decrypt() したあと、PdfWriter(clone_from=reader) で暗号化せずに保存します。本文の手順8で、保存後に is_encrypted が False になることを確認しています。
まとめ
- pypdfはPDFの結合・分割・回転・暗号化などをPythonだけで行えるライブラリで、2026年10月時点の最新版は6.19.0
- 基本は
PdfReaderで読み、PdfWriterに入れてwrite。既存PDFの編集はPdfWriter(clone_from=...) - ページ番号は0始まり、範囲指定は終わりを含まない
- 結合後はメタデータを付け直す。重複オブジェクトの削除で276KBが57KBに
- 日本語抽出は一般的なPDFなら3ライブラリとも良好。結果が分かれるPDFではPyMuPDFが安定していた
- PyPDF2の旧名はimportできないので、移行対応表で書き換える
- 画像だけのPDFは
extract_text()が空文字。OCRで文字レイヤーを付けてから扱う
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python pypdfでPDF操作|結合・分割・テキスト抽出のコード集」
株式会社RJCでは、PythonによるPDF処理の自動化を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。