導入文
近年、レポートや学術論文、企業の契約書など膨大なPDFが情報源として利用される場面が増えてきました。
しかし、PDFはレイアウト重視のフォーマットであり、テキストデータをそのまま抜き取ると「横に並んだ文字列」「画像化された文字」「混在する表データ」など、想像以上に扱いづらいものです。
このようなPDFから必要なデータを確実に抽出するには「分解」プロセス(ページ単位、レイヤー単位、要素単位で解析すること)が必須になります。本記事では、初心者でもわかりやすいステップ・バイ・ステップでPDF分解からデータ抽出までを実装例と共に解説します。さらに、ツール選定のポイントやよくあるミス・対処法もまとめますので、実務で活用したい方は必見です。
PDFの構造を知る – 分解に必要な基礎知識
PDFは「ページ」「オブジェクト」「ファイルの内部構造」の3層で構成されます。
| レイヤー | 主な内容 | 抽出で抱えるポイント |
|---|---|---|
| ページレイヤー | 文書全体のページ数、ページサイズ、ページ単位のレイアウト | ページごとに異なるフォントやテキストブロックが混在 |
| オブジェクトレイヤー | テキスト、画像、図形、表、注釈、リンクなど | 画像化された文字はOCRが必要、表はセル境界が不明確 |
| メタデータレイヤー | 著者、作成日、ファイルサイズ、暗号化情報 | 鍵付きPDFはまず脱暗号化処理が必要 |
まずはどのレイヤーで何を取得したいかを明確にすることで、抽出ツールや手順を選定しやすくなります。
1. 推奨ツールとインストール手順
| ツール | 特徴 | コマンド |
|---|---|---|
| PDFMiner‑six | PDF → レイアウト情報保持、Python 3 で動作 | pip install pdfminer.six |
| PyMuPDF (fitz) | 画像・テキスト抽出・分割、PDF の画像化処理が高速 | pip install pymupdf |
| pdfplumber | テーブル抽出が得意、簡易 API | pip install pdfplumber |
| Tesseract OCR | 画像内文字を抽出、マルチ言語対応 | conda install -c conda-forge pytesseract |
| Apache Tika | マルチフォーマット、Java ベース | apt-get install tika |
Tip: まずは pdfplumber で試してみて、抽出できない箇所があれば PyMuPDF で画像として切り出し、 Tesseract で OCR する手順がおすすめです。
2. PDFをページ単位で分割する方法
import os
import fitz # PyMuPDF
pdf_path = "sample.pdf"
output_dir = "pages"
os.makedirs(output_dir, exist_ok=True)
doc = fitz.open(pdf_path)
for i in range(len(doc)):
page = doc.load_page(i)
# 1枚ずつpngとして保存
pix = page.get_pixmap()
pix.save(f"{output_dir}/page_{i+1}.png")
# 画像化したページからテキスト抽出(OCR)
# 以下のコードは Tesseract がインストールされている前提
from PIL import Image
import pytesseract
img = Image.open(f"{output_dir}/page_{i+1}.png")
txt = pytesseract.image_to_string(img, lang="jpn")
with open(f"{output_dir}/page_{i+1}.txt", "w", encoding="utf-8") as f:
f.write(txt)
- ポイント:
fitz.get_pixmap()は PDF を高解像度で画像化。 - 注意: OCR 精度は画像 DPI に大きく依存します。
fitz.get_pixmap(dpi=300)などで解像度を上げると精度向上が期待できます。
3. テキスト抽出+テーブル検出
import pdfplumber
pdf_path = "sample.pdf"
with pdfplumber.open(pdf_path) as pdf:
for page_number, page in enumerate(pdf.pages, 1):
# ページ全体のテキスト
text = page.extract_text()
with open(f"text_page_{page_number}.txt", "w", encoding="utf-8") as f:
f.write(text)
# テーブルの抽出
tables = page.extract_tables()
for t_idx, table in enumerate(tables, 1):
with open(f"table_page_{page_number}_{t_idx}.csv", "w", encoding="utf-8") as f:
for row in table:
f.write(",".join([cell.strip() if cell else "" for cell in row]) + "\n")
extract_text()は PDF 内のテキストレイヤーから文字列を取得。extract_tables()は 行・列の境界を自動推定してCSVで出力。- もしテーブルが正しく検出されない場合は
page.extract_table({ "vertical_strategy": "lines" })などで戦略を変更できます。
4. 画像化された文字列を OCR で取り扱う
PDF には 画像化された文字(スキャン済み文書)が多く存在します。
OCR を併用することで文字列を取得できます。
import pytesseract
from PIL import Image
image_path = "page_5.png"
img = Image.open(image_path)
# 日本語モデルが入っていない場合は事前に tesseract‑language‑data をインストール
text = pytesseract.image_to_string(img, lang="jpn+eng")
print(text)
注意: OCR 結果はテキスト構造が フラット にしか戻りません。
- 行ごとに区切る、列見出しを決めるなど後処理が必要です。
pytesseract.image_to_data()を使えば 座標情報付き で取得でき、表構造を再構築しやすくなります。
5. 変換結果をデータベースにロード
抽出した文字列や CSV を PostgreSQL に保存する例です。
import csv
import psycopg2
conn = psycopg2.connect(dbname="mydb", user="user", password="pass")
cur = conn.cursor()
# テーブル作成(既存なら DROP )
cur.execute("""
CREATE TABLE IF NOT EXISTS extracted (
id SERIAL PRIMARY KEY,
page_num INT,
row_num INT,
column_num INT,
content TEXT
);
""")
conn.commit()
# CSV から挿入
with open("table_page_3_1.csv", "r", encoding="utf-8") as f:
reader = csv.reader(f)
for row_num, row in enumerate(reader, 1):
for col_num, cell in enumerate(row, 1):
cur.execute("""
INSERT INTO extracted (page_num, row_num, column_num, content)
VALUES (%s, %s, %s, %s);
""", (3, row_num, col_num, cell))
conn.commit()
cur.close()
conn.close()
- データクレンジング: 先頭・末尾の空白、改行コード、特殊文字は
strip()で除去。 - 重複除去:
UNIQUE (page_num, row_num, column_num)を追加して重複インサートを防止。
6. よくあるトラブルと対処法
| 事象 | 原因 | 対策 |
|---|---|---|
| OCR が文字を認識しない | 画像がノイズ多い、解像度低い | pytesseract.image_to_string(..., config="-c tessedit_char_whitelist=...") で推定文字セットを限定 |
| テーブルの境界が不正確 | PDF 内の線が薄い、図の一部と重なっている | extract_table のパラメータ vertical_strategy="lines" / "text" を試す |
| PDF が暗号化されていて読み込み失敗 | パスワード保護 | PyPDF2 でパスワード解放、または qpdf で脱暗号化 |
| 大量ファイルを扱ってメモリ不足 | 高解像度 PNG を多数生成 | fitz.open().load_page(i).get_pixmap(dpi=200) で DPI を下げるか、画像は一時ディスクに保存して逐次処理 |
| テキストの重複抽出 | PDF のテキストレイヤーと OCR 結果の両方を連続取得 | page.extract_text() と OCR の結果を結合する前に重複チェックを実装 |
7. 実践に向けたワークフローまとめ
- PDF構造の把握 → どのレイヤーで何が必要か決定。
- ツール選定 →
pdfplumberでテキスト・テーブル、PyMuPDFで画像切り出し、TesseractでOCR。 - 全ページスキャン → 画像化して OCR し、テキストデータを一元保存。
- テーブル抽出 →
pdfplumberで CSV 化、必要に応じてセル位置を調整。 - データクレンジング → 文字列の正規化、重複除去。
- データベース投入 → PostgreSQL / MySQL へテーブル化し、インデックス設定。
- 検証 → いくつかのページを目視で確認し、抽出精度を測定。
- 自動化 → 上記スクリプトをバッチ化、CI/CD で定期実行。
まとめ
PDF 分解は「ページ」「オブジェクト」「メタデータ」という階層を意識して、必要な情報を段階的に取得するアプローチが成功の鍵です。
初心者のポイント
- まずは簡易 API でテキストを取得(
pdfplumber)- 画像が多い場合は PyMuPDF で PNG 化 → Tesseract OCR
- 抽出したデータは CSV で保存し、後からデータベースに容易にロード
実際に数十ページの PDF で試し、抽出精度と処理速度を比較すれば、自分の業務に最適なツールセットが見えてくるはずです。ぜひ本記事を踏まえて、PDF データ抽出のワークフローを構築してみてください。


コメント