PythonでPDFからテキストを一括抽出する方法

## はじめに

研究論文やレポートなど、大量のPDFファイルからテキストデータを抽出したいことがありませんか？
手作業で行うと時間がかかりますが、Pythonを使えば簡単に自動化できます。

この記事では、**pdfminer.six**ライブラリを使ってPDFファイルからテキストを一括抽出するプログラムを作成し、その使い方を初心者の方にも伝えられるように解説したいと思います！


## 必要な環境・ライブラリ

### Python環境
- Python 3.6以上

### 必要なライブラリ
```bash
pip install pdfminer.six
```

## コード全体

<details>
<summary>コード全体（クリックして展開）
（是非コピペして使用してみてください。）</summary>

```python
"""
PDFファイルからテキストを一括抽出するスクリプト

機能:
- 指定フォルダ内のPDFファイルを自動検索
- PDFファイルが見つからない場合は自動終了
- 各PDFファイルからテキストを抽出し、同名のTXTファイルに保存
- 処理状況をリアルタイムで表示
- エラーが発生したファイルも継続処理

使い方:
1. PDF用フォルダ（pdf_files）にPDFファイルを配置
2. このスクリプトを実行
3. 出力フォルダ（pdf_extracts）に抽出されたテキストファイルが保存される

ディレクトリ構成:
📁 プロジェクトフォルダ/
├── 🐍 extract_pdf.py (このスクリプト)
├── 📁 pdf_files/ (PDFファイルを配置)
│   ├── 📄 論文1.pdf
│   └── 📄 論文2.pdf
└── 📁 pdf_extracts/ (自動作成・テキストファイルが出力)
    ├── 📄 論文1.txt
    └── 📄 論文2.txt

カスタム設定（main関数内で変更可能）:
- PDF_DIR: PDFファイルがあるディレクトリ（デフォルト: "pdf_files"）
- OUTPUT_DIR: 出力ディレクトリ名（デフォルト: "pdf_extracts"）
"""

from pdfminer.high_level import extract_text
from pathlib import Path


# ディレクトリの設定と開始情報の出力を行う。（相対パスのみ対応）
# Args: pdf_dir:PDFファイルがあるディレクトリ（相対パス）
#       output_dirname:出力ディレクトリ名（相対パス）
# Returns: tuple:(pdf_directory, output_directory)
def setup_directories(pdf_dir, output_dirname):
    print("PDF テキスト抽出スクリプトを開始します...")
    
    base_dir = Path(__file__).parent / pdf_dir
    
    if not base_dir.exists():
        print(f"エラー: ディレクトリが見つかりません: {base_dir}")
        exit(1)
    
    output_dir = Path(__file__).parent / output_dirname
    output_dir.mkdir(exist_ok=True)
    
    print(f"PDFディレクトリ: {base_dir}")
    print(f"出力ディレクトリ: {output_dir}")
    print("-" * 50)
    return base_dir, output_dir


# 指定されたディレクトリからPDFファイルを検索し結果を出力する。
# Args: directory: 検索するディレクトリ
# Returns: list:PDFファイル名のリスト（空の場合は終了）
def find_pdf_files(directory):
    pdf_files = []
    for file in directory.iterdir():
        if file.is_file() and file.suffix.lower() == '.pdf':
            pdf_files.append(file.name)
    
    if not pdf_files:
        print("PDFファイルが見つかりませんでした。")
        exit(1)
    
    print(f"{len(pdf_files)}個のPDFファイルが見つかりました。")
    print("-" * 50)    
    return pdf_files


# PDFファイルを処理する。
# Args: base_dir: PDFファイルがあるディレクトリ 
#       output_dir:出力ディレクトリ, pdf_files:PDFファイルのリスト
def process_pdf_files(base_dir, output_dir, pdf_files):
    for i, pdf_file in enumerate(pdf_files, 1):
        print(f"[{i}/{len(pdf_files)}] 処理中: {pdf_file}")
        
        input_path = base_dir / pdf_file
        output_filename = input_path.stem + '.txt'
        output_path = output_dir / output_filename
        
        try:
            text = extract_text(str(input_path))
            with open(output_path, "w", encoding='utf-8') as f:
                f.write(text)
            print(f"✅️成功")            
        except Exception as e:
            print(f"❌️エラー: {e}")

    print(f"処理完了: {len(pdf_files)}個のPDFファイルを処理しました。")
    print("-" * 50)

# メイン処理
def main():
    PDF_DIR = "pdf_files"
    OUTPUT_DIR = "pdf_extracts"
    base_dir, output_dir = setup_directories(PDF_DIR, OUTPUT_DIR)
    pdf_files = find_pdf_files(base_dir)
    process_pdf_files(base_dir, output_dir, pdf_files)

if __name__ == "__main__":
    main()
```

</details>

## 実行例

```
PDF テキスト抽出スクリプトを開始します...
PDFディレクトリ: C:\project\pdf_files
出力ディレクトリ: C:\project\pdf_extracts
--------------------------------------------------
3個のPDFファイルが見つかりました。
--------------------------------------------------
[1/3] 処理中: 論文1.pdf
✅️成功
[2/3] 処理中: 論文2.pdf
✅️成功
[3/3] 処理中: 論文3.pdf
❌️エラー: PDFファイルが破損しています
処理完了: 3個のPDFファイルを処理しました。
--------------------------------------------------
```

## 使い方

### 1. ライブラリのインストール
```bash
pip install pdfminer.six
```

### 2. ディレクトリ構成の準備
プロジェクトフォルダに以下の構成でファイルを配置：

```
📁 プロジェクトフォルダ/
├── 🐍 extract_pdf.py (スクリプトファイル)
└── 📁 pdf_files/ (PDFファイルを配置するフォルダを作成)
    ├── 📄 論文1.pdf
    ├── 📄 論文2.pdf
    └── 📄 論文3.pdf
```

### 3. ディレクトリ名の変更（必要に応じて）
`main()`関数内の設定を変更：

```python
def main():
    PDF_DIR = "pdf_files"        # PDFファイルのフォルダ名
    OUTPUT_DIR = "pdf_extracts"  # 出力フォルダ名
    # ...
```

### 4. 実行
```bash
python extract_pdf.py
```

### 5. 結果確認
`pdf_extracts`フォルダに抽出されたテキストファイルが保存されます。

```
📁 プロジェクトフォルダ/
├── 🐍 extract_pdf.py
├── 📁 pdf_files/
│   ├── 📄 論文1.pdf
│   ├── 📄 論文2.pdf
│   └── 📄 論文3.pdf
└── 📁 pdf_extracts/ (自動作成)
    ├── 📄 論文1.txt
    ├── 📄 論文2.txt
    └── 📄 論文3.txt
```


## コードの詳細解説

### 1. ライブラリのインポート

```python
from pdfminer.high_level import extract_text
from pathlib import Path
```

- `pdfminer.high_level.extract_text`: PDFからテキストを抽出する関数
- `pathlib.Path`: ファイルパスを扱うモダンなライブラリ（Python 3.4以降推奨）

### 2. setup_directories関数 - ディレクトリの設定と確認

```python
def setup_directories(pdf_dir, output_dirname):
    print("PDF テキスト抽出スクリプトを開始します...")
    
    base_dir = Path(__file__).parent / pdf_dir
    
    if not base_dir.exists():
        print(f"エラー: ディレクトリが見つかりません: {base_dir}")
        exit(1)
    
    output_dir = Path(__file__).parent / output_dirname
    output_dir.mkdir(exist_ok=True)
    
    print(f"PDFディレクトリ: {base_dir}")
    print(f"出力ディレクトリ: {output_dir}")
    print("-" * 50)
    return base_dir, output_dir
```

**ポイント**：
- `Path(__file__).parent`: スクリプトファイルと同じディレクトリを基準にする
- `base_dir.exists()`: PDFディレクトリが存在するかチェック
- `output_dir.mkdir(exist_ok=True)`: 出力ディレクトリを自動作成
- 存在しないディレクトリの場合は`exit(1)`でプログラム終了

### 3. find_pdf_files関数 - PDFファイルの検索

```python
def find_pdf_files(directory):
    pdf_files = []
    for file in directory.iterdir():
        if file.is_file() and file.suffix.lower() == '.pdf':
            pdf_files.append(file.name)
    
    if not pdf_files:
        print("PDFファイルが見つかりませんでした。")
        exit(1)
    
    print(f"{len(pdf_files)}個のPDFファイルが見つかりました。")
    print("-" * 50)    
    return pdf_files
```

**ポイント**：
- `directory.iterdir()`: ディレクトリ内のファイルを取得
- `file.suffix.lower() == '.pdf'`: 拡張子が.pdfのファイルのみを対象
- PDFファイルが見つからない場合は自動終了
- 見つかったファイル数を表示

### 4. process_pdf_files関数 - PDFファイルの処理

```python
def process_pdf_files(base_dir, output_dir, pdf_files):
    for i, pdf_file in enumerate(pdf_files, 1):
        print(f"[{i}/{len(pdf_files)}] 処理中: {pdf_file}")
        
        input_path = base_dir / pdf_file
        output_filename = input_path.stem + '.txt'
        output_path = output_dir / output_filename
        
        try:
            text = extract_text(str(input_path))
            with open(output_path, "w", encoding='utf-8') as f:
                f.write(text)
            print(f"✅️成功")            
        except Exception as e:
            print(f"❌️エラー: {e}")

    print(f"処理完了: {len(pdf_files)}個のPDFファイルを処理しました。")
    print("-" * 50)
```

**ポイント**：
- `enumerate(pdf_files, 1)`: インデックス付きでループ（1から開始）
- `input_path.stem`: ファイル名から拡張子を除去
- `try-except`: エラーが発生してもプログラムを継続
- `encoding='utf-8'`: 日本語の文字化けを防ぐ
- 処理状況をリアルタイムで表示

### 5. main関数 - メイン処理

```python
def main():
    PDF_DIR = "pdf_files"
    OUTPUT_DIR = "pdf_extracts"
    base_dir, output_dir = setup_directories(PDF_DIR, OUTPUT_DIR)
    pdf_files = find_pdf_files(base_dir)
    process_pdf_files(base_dir, output_dir, pdf_files)
```

**ポイント**：
- 処理の流れが一直線で分かりやすい。
- 設定事項がmain関数のディレクトリ名のみで少ない。
## トラブルシューティング

### よくあるエラーと対処法

1. **ModuleNotFoundError: No module named 'pdfminer'**
   ```bash
   pip install pdfminer.six
   ```

2. **エラー: ディレクトリが見つかりません**
   - `pdf_files`フォルダが存在するか確認
   - スクリプトファイルと同じ階層に`pdf_files`フォルダを作成

3. **PDFファイルが見つかりませんでした。**
   - `pdf_files`フォルダにPDFファイルが配置されているか確認
   - ファイル名の拡張子が`.pdf`になっているか確認

4. **文字化けが発生する**
   - `encoding='utf-8'`が指定されているか確認
   - 一部のPDFは文字化けする場合があります（画像化されたPDFなど）

5. **メモリエラー**
   - 非常に大きなPDFファイルの場合に発生
   - ファイルサイズを確認し、必要に応じて分割処理を検討

6. **アクセス権限エラー**
   - PDFファイルが開いている場合は閉じる
   - ファイルの読み取り権限を確認

### デバッグのコツ

1. **段階的な実行**
   ```python
   # 1つずつ関数を呼び出してテスト
   base_dir, output_dir = setup_directories("pdf_files", "pdf_extracts")
   print(f"ディレクトリ設定完了: {base_dir}")
   
   pdf_files = find_pdf_files(base_dir)
   print(f"見つかったファイル: {pdf_files}")
   ```

2. **詳細なエラー情報の表示**
   ```python
   import traceback
   
   try:
       text = extract_text(str(input_path))
   except Exception as e:
       print(f"詳細エラー: {traceback.format_exc()}")
   ```

## まとめ

この記事では、Pythonを使ってPDFファイルからテキストを一括抽出する方法を紹介しました。

**技術的なポイント**：
- `pdfminer.six`ライブラリを使用
- `pathlib`でファイルパス操作
- 関数分割による責務の明確化

**活用場面**：
- 研究論文の一括テキスト化
- 文書のデジタル化作業

この関数化されたコードは処理が一直線で、実用的になっています。
ぜひ自分の環境に合わせてカスタマイズして使ってみてください！

## 参考リンク

- [pdfminer.six 公式ドキュメント](https://pdfminersix.readthedocs.io/)
- [pathlib 公式ドキュメント](https://docs.python.org/3/library/pathlib.html)

---

最後まで読んでいただきありがとうございました！
誤りやフィードバックのコメントをいただけますと幸いです 🙏