ChatGPTのAPIを利用してPDFスライドを要約してWordに出力するPythonコード


## 概要

この記事では、PDF形式のスライドを読み込み、OpenAIのGPT-4を使用して各スライドの要約を生成し、さらに各スライドの重要な用語と説明リストを作成、それをWord文書に出力するPythonコードを紹介します。Anaconda環境で必要なライブラリのインストール方法から、`poppler`のインストール方法、コードの詳細な説明までをステップ・バイ・ステップで解説します。文章はGPT4oで作成しています。

## 動作環境

- OS: Windows 10
- Python: 3.8 以上 (Anaconda環境)
- 必要なライブラリ: `pdfplumber`, `openai`, `python-docx`, `pdf2image`, `reportlab`
- 必要なツール: `poppler`（バージョン24.07.0）


## 出力例

引用元：経産省、半導体・デジタル産業戦略
　https://www.meti.go.jp/policy/mono_info_service/joho/conference/semicon_digital/0011/0011-2.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36629/19b304b6-fa67-358a-8473-b60e03fcacfc.png)
アウトプット：各スライドの下部にSummaryが追加される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36629/02aa5b17-1f29-43e7-90cb-177d07a762ab.png)
アウトプット：ページの最終に重要な用語と説明をリスト化

## コード全文

```python
import pdfplumber
import openai
from docx import Document
from docx.shared import Inches
from pdf2image import convert_from_path
import os
import time
import gc

# OpenAI APIキーの設定
openai.api_key = 'ChatGPTのAPI'

# 入力と出力のファイルパスをここで設定
pdf_path = '元のPDFファイルのパス.pdf'
output_word_path = '出力ファイルの名.docx'
temp_image_dir = '一時画像格納パス/temp_images'

# 一時画像保存用のディレクトリを作成
if not os.path.exists(temp_image_dir):
    os.makedirs(temp_image_dir)

def summarize_text(text):
    while True:
        try:
            print("Sending text to OpenAI for summarization...")
            response = openai.ChatCompletion.create(
                model="gpt-4o-mini",
                messages=[
                    {"role": "system", "content": "You are a helpful assistant."},
                    {"role": "user", "content": f"以下のテキストを日本語で箇条書きで要約してください。ただし、文頭に[・-]は不要です。:\n{text}"}
                ],
                max_tokens=500
            )
            summary = response['choices'][0]['message']['content'].strip()
            print("Received summary from OpenAI.")
            return summary
        except openai.error.RateLimitError:
            print("Rate limit exceeded. Waiting for 60 seconds...")
            time.sleep(60)

def extract_keywords_and_definitions(text):
    while True:
        try:
            print("Extracting keywords and definitions...")
            response = openai.ChatCompletion.create(
                model="gpt-4o-mini",
                messages=[
                    {"role": "system", "content": "You are a helpful assistant."},
                    {"role": "user", "content": f"以下のテキストから重要な用語を抽出し、それぞれ「用語(英語での用語の名前)：説明文」で書いてください。フレーズ「以上が、テキストから抽出した重要な用語とその説明です。」および「以下は、テキストから抽出した重要な用語とその説明です。」も含めないでください。リストに番号も不要です。必ずこの形式を守ってください。:\n{text}"}
                ],
                max_tokens=500
            )
            keywords_definitions = response['choices'][0]['message']['content'].strip()
            print("Received keywords and definitions from OpenAI.")
            return keywords_definitions
        except openai.error.RateLimitError:
            print("Rate limit exceeded. Waiting for 60 seconds...")
            time.sleep(60)

def format_keywords_definitions(doc, keywords_definitions):
    lines = keywords_definitions.split("\n")
    seen_terms = set()  # 用語の重複を避けるためのセット
    for line in lines:
        if ":" in line and line.strip():
            term, definition = line.split(":", 1)
            term_name = term.split("(")[0].strip()  # 用語部分のみを抽出
            if term_name not in seen_terms:
                seen_terms.add(term_name)
                # 用語リストを「・用語(英語での名称)：説明文」の形式にする
                p = doc.add_paragraph()
                p.add_run(f"・{term}：{definition.strip()}")
        elif line.strip() and line not in seen_terms:  # 空行をスキップ
            seen_terms.add(line.strip())
            doc.add_paragraph(f"・{line.strip()}")

def main():
    print("Starting PDF processing...")
    
    # PDFを開く
    with pdfplumber.open(pdf_path) as pdf:
        doc = Document()
        total_pages = len(pdf.pages)
        
        # PDFページを低解像度のJPEG画像に変換
        print(f"Converting {total_pages} PDF pages to images...")
        images = convert_from_path(pdf_path, 150, output_folder=temp_image_dir, fmt='jpeg')
        print("Conversion to images complete.")
        
        all_keywords_definitions = ""

        for i, page in enumerate(pdf.pages, start=1):
            print(f"Processing slide {i}/{total_pages}...")
            text = page.extract_text()
            image_path = os.path.join(temp_image_dir, f"slide_{i}.jpg")
            images[i-1].save(image_path, 'JPEG')  # JPEGで画像を保存
            images[i-1].close()  # 画像ファイルを閉じる
            print(f"Slide {i} saved as image.")
            
            if text:
                summary = summarize_text(text)
                keywords_definitions = extract_keywords_and_definitions(text)
                all_keywords_definitions += keywords_definitions + "\n"
                
                # Wordにスライドの画像と要約を追加
                print(f"Adding slide {i} image and summary to Word document...")
                doc.add_heading(f'Slide {i}', level=1)
                doc.add_picture(image_path, width=Inches(6))
                doc.add_heading('Summary', level=2)
                for bullet_point in summary.split("\n"):
                    if bullet_point.strip():
                        doc.add_paragraph(f"{bullet_point.strip()}", style='List Bullet')  # [・-]は削除
                doc.add_page_break()

        # 用語リストをWordファイルに追加
        if all_keywords_definitions:
            print("Adding keywords and definitions to Word document...")
            doc.add_heading('Keywords and Definitions', level=1)
            format_keywords_definitions(doc, all_keywords_definitions)
        
        # Wordファイルとして保存
        print("Saving Word document...")
        doc.save(output_word_path)
        print("Processing complete. Summary saved to Word file.")

        # ガベージコレクションを実行して未解放のリソースを解放
        gc.collect()

        # 一時画像を削除
        print("Cleaning up temporary image files...")
        for image_file in os.listdir(temp_image_dir):
            os.remove(os.path.join(temp_image_dir, image_file))
        print("Cleanup complete.")

# 実行
if __name__ == "__main__":
    main()

```

## ライブラリとツールのインストール

### 1. `poppler` のインストール

`pdf2image`ライブラリは、PDFを画像に変換するために`poppler`というツールを使用します。`poppler`は、PDFファイルの処理を行うためのオープンソースライブラリです。

#### 手順

1. **`poppler`のダウンロード**:
   - `poppler`は、[poppler for Windows](http://blog.alivate.com.au/poppler-windows/)からダウンロードできます。
   - `poppler-24.07.0` のバイナリをダウンロードします。

2. **`poppler`のインストール**:
   - ダウンロードしたZIPファイルを任意の場所に解凍します。例えば、`C:\poppler-24.07.0` に解凍します。

3. **環境変数の設定**:
   - Windowsの「環境変数」に`poppler`のパスを追加します。
   - 「システム環境変数の編集」から「環境変数」を開き、「システム環境変数」の「Path」に`C:\poppler-24.07.0\bin`を追加します。

### 2. Anaconda環境でのライブラリのインストール

次に、Anaconda環境に必要なPythonライブラリをインストールします。

```bash
# 必要なライブラリをインストール
conda install -c conda-forge pdfplumber
conda install -c conda-forge python-docx
conda install -c conda-forge pdf2image
pip install openai
```

`openai` は `pip` でインストールする必要があるので注意してください。

## コードの概要

このコードは、以下のステップで構成されています。

1. **PDFの読み込み**: `pdfplumber` を使用してPDFファイルを読み込みます。
2. **画像変換**: `pdf2image` を使用して各スライドをJPEG画像に変換します。
3. **要約生成**: OpenAIのGPT-4を使用して、各スライドのテキストを要約します。
4. **Word出力**: `python-docx` を使用して、画像と要約をWord文書に挿入します。
5. **一時ファイルのクリーンアップ**: 処理が終わった後、作成された一時画像ファイルを削除します。

## ステップ・バイ・ステップの説明

### 1. ライブラリのインポート
このスクリプトでは、PDFからのテキスト抽出、画像変換、Wordファイルの生成に必要なライブラリをインポートします。

### 2. OpenAI APIキーとファイルパスの設定
ユーザーがOpenAI APIキーとPDFファイルのパス、Wordファイルの出力パスを入力するように促します。

### 3. 一時画像保存用ディレクトリの作成
PDFページを画像として保存するためのディレクトリを作成します。

### 4. PDFテキストの要約
OpenAIのAPIを使用して、PDFから抽出したテキストを要約します。

### 5. 重要用語の抽出
PDFテキストから重要な用語とその説明を抽出します。

### 6. 用語リストのフォーマット
重複を避けながら、抽出した用語リストをWordファイルに追加します。

### 7. メイン処理
PDFの各ページを処理し、要約と用語リストをWordファイルに保存します。

### 8. スクリプトの実行
最後に、スクリプトを実行して、Wordファイルを生成します。