⌨️

PDFからテキスト抽出~AIを使って論文を要約してみる~

に公開

今回は、PDFからテキスト情報を抽出し、内容を要約するPythonスクリプトを作成してみました。
内容としては、pymupdf4llmというライブラリを使用して、論文情報を抽出し、その内容をAIに要約させるというものです。このスクリプトを使用することで、論文をより効率的に読むことができると思っています。
一括で複数の論文を要約できるので、複数の論文を読む際により効果を発揮するかと思います。

使用しているライブラリ

・os, glob, pathlib: ファイルシステム操作のための標準ライブラリ。ファイルパスの処理やフォルダ内のPDFファイル検索に使用。
・time: 日時の処理や処理時間の計測に使用する標準ライブラリ。
・logging: ログ出力を管理するための標準ライブラリ。処理の進捗状況やエラーを記録。
・argparse: コマンドライン引数を解析するための標準ライブラリ。入出力フォルダの指定などに使用。
・dotenv: 環境変数を.envファイルから読み込むためのライブラリ。APIキーなどの秘密情報を管理。
・pymupdf4llm: PDFファイルをマークダウン形式に変換するライブラリ。画像や表も含めてテキスト抽出が可能。
・openai (AzureOpenAI): Azure OpenAIサービスとの通信を行うためのクライアントライブラリ。文章要約の処理に使用。


# ライブラリのインストール
pip install python-dotenv  # .env ファイル読み込み用
pip install pymupdf4llm    # PDF処理用
pip install openai         # OpenAI/Azure OpenAI API用
実際のコード
main.py
import os
import time
import glob
import logging
import argparse
from pathlib import Path
from dotenv import load_dotenv
import pymupdf4llm
from openai import AzureOpenAI

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class PDFSummarizer:
    def __init__(self, input_folder, output_folder):
       
        self.input_folder = Path(input_folder)
        self.output_folder = Path(output_folder)
        
        # 環境変数の読み込み
        load_dotenv()
        
        # Azure OpenAI設定
        self.azure_endpoint = os.getenv("BASE")
        self.api_key = os.getenv("API_KEY")
        self.api_version = os.getenv("API_VERSION", "2024-08-01-preview")
        self.deployment_name = os.getenv("DEPLOYMENT_NAME", "gpt-4o")
        
        # Azure OpenAIクライアントの初期化
        self.client = AzureOpenAI(
            azure_endpoint=self.azure_endpoint,
            api_key=self.api_key,
            api_version=self.api_version
        )
        
        if not self.output_folder.exists():
            self.output_folder.mkdir(parents=True)
            logger.info(f"出力フォルダを作成しました: {self.output_folder}")
    
    def extract_text_from_pdf(self, pdf_path):
        
        try:
            # pymupdf4llmを使ってPDFをマークダウンに変換
            markdown_text = pymupdf4llm.to_markdown(str(pdf_path))
            return markdown_text
        
        except Exception as e:
            logger.error(f"テキスト抽出中にエラーが発生しました: {e}")
            return ""
    
    def summarize_with_ai(self, text, max_tokens=1000):
        
        try:
            # テキストが長すぎる場合は切り詰める(APIのトークン制限のため)
            if len(text) > 100000:  # 適切なサイズに調整
                logger.warning(f"テキストが長すぎるため、最初の100,000文字に切り詰めます。")
                text = text[:100000]
            
            # Azure OpenAI APIを使用して要約
            response = self.client.chat.completions.create(
                model=self.deployment_name,
                messages=[
                        {"role": "system", "content": "あなたは学術論文を分析・要約する専門AIです。論文の構造を理解し、重要な情報を正確に抽出して論理的にまとめる能力に優れています。特に数値データ、実験結果、統計的有意性などの定量的情報を正確に捉えることができます。"},
                        {"role": "user", "content": f"以下の学術論文を構造化して要約してください。論文:{text}要約には以下の項目を含めてください:\n"
                         "1. 【研究概要】:この研究の目的と背景を2-3文で簡潔に説明\n"
                         "2. 【手法】:使用された研究方法、実験設計、分析手法を箇条書きで簡潔に説明\n"
                         "3. 【主要な数値結果】:重要な実験結果や統計データを数値とともに列挙(p値、効果量、相関係数、パーセンテージなど)\n"
                         "4. 【主な発見と結論】:研究から得られた主要な発見と著者の結論を3-5点で説明\n"
                         "5. 【研究の限界と今後の展望】:著者が言及している研究の限界点と将来の研究方向性\n"
                         "専門用語は適切に説明し、複雑な概念はより平易な言葉で言い換えてください。また、元の論文で強調されている重要なポイントや革新的な発見は特に詳しく説明してください。"}
                ],
                max_tokens=max_tokens,
                temperature=0.3  # 低い温度でより焦点を絞った要約
            )
            
            summary = response.choices[0].message.content.strip()
            return summary
        
        except Exception as e:
            logger.error(f"要約中にエラーが発生しました: {e}")
            return "要約の生成に失敗しました。" + str(e)
    
    def create_markdown_output(self, summary, pdf_path):
        # 現在の日付を取得
        current_date = time.strftime("%Y-%m-%d")
        
        title = pdf_path.stem.replace('_', ' ').replace('-', ' ')
        
        # マークダウンコンテンツの作成
        md_content = f"""# {title} 要約

## メタデータ

- **ファイル**: {pdf_path.name}
- **要約日**: {current_date}

## 要約
{summary}
"""
        return md_content
    
    def process_pdf(self, pdf_path):
        
        try:
            # PDFからテキストを抽出
            text = self.extract_text_from_pdf(pdf_path)
            if not text:
                logger.error("テキストを抽出できませんでした。")
                return False
            
            # AIによる要約
            summary = self.summarize_with_ai(text)
            
            # マークダウン出力の作成
            md_content = self.create_markdown_output(summary, pdf_path)
            
            # 出力ファイルのパス
            output_file = self.output_folder / f"{pdf_path.stem}.md"
            
            # ファイルに書き込み
            with open(output_file, 'w', encoding='utf-8') as f:
                f.write(md_content)
            
            logger.info(f"要約が完了しました: {output_file}")
            return True
        
        except Exception as e:
            logger.error(f"PDF処理中にエラーが発生しました: {e}")
            return False
    
    def process_all_pdfs(self):
        # PDFファイルのリストを取得
        pdf_files = list(self.input_folder.glob("*.pdf"))
        total_files = len(pdf_files)
        
        if total_files == 0:
            logger.warning("PDFファイルが見つかりませんでした。")
            return 0, 0
        
        logger.info(f"{total_files}個のPDFファイルが見つかりました。")
        
        success_count = 0
        failure_count = 0
        
        # 各PDFファイルを処理
        for pdf_path in pdf_files:
            success = self.process_pdf(pdf_path)
            if success:
                success_count += 1
            else:
                failure_count += 1
        
        logger.info(f"処理完了: 成功 {success_count}, 失敗 {failure_count}")
        return success_count, failure_count


def main():
    parser = argparse.ArgumentParser(description='PDFファイルからテキストを抽出し、AIを使用して要約する')
    parser.add_argument('--input', type=str, default='input', help='入力PDFファイルが格納されているフォルダ')
    parser.add_argument('--output', type=str, default='output', help='出力MDファイルを保存するフォルダ')
    parser.add_argument('--file', type=str, help='特定のPDFファイルのみを処理する場合のファイルパス')
    args = parser.parse_args()
    
    summarizer = PDFSummarizer(args.input, args.output)
    
    if args.file:
        # 特定のファイルのみを処理
        pdf_path = Path(args.file)
        if pdf_path.exists() and pdf_path.suffix.lower() == '.pdf':
            summarizer.process_pdf(pdf_path)
        else:
            logger.error(f"指定されたファイルが見つからないか、PDFファイルではありません: {args.file}")
    else:
        # フォルダ内のすべてのPDFを処理
        summarizer.process_all_pdfs()


if __name__ == "__main__":
    main()

今回使用した論文
https://www.jstage.jst.go.jp/article/soshikikagaku/41/4/41_20220810-24/_article/-char/ja/

出力結果

内容

41 20220810 24 要約

メタデータ

  • ファイル: 41_20220810-24.pdf
  • 要約日: 2025-04-27

要約

要約: 組織科学 Vol. 41 No. 4 :16-26(2008)


1. 【研究概要】

この論文では、意思決定における感情と理性の関係性を探り、感情が意思決定に与える影響を多角的に検討しています。従来の経営学が理性的な意思決定を重視してきた背景に対し、近年の研究成果を基に、感情が合理性を持つ場合や非合理性を示す場合について議論を展開しています。


2. 【手法】

  • 文献レビュー: 感情と意思決定に関する過去の研究成果を包括的に分析。
  • 実験データの引用: 吊り橋実験(Dutton & Aron, 1974)、フサオマキザルの公平性実験(Brosnan & de Waal, 2003)、選択肢の影響に関する実験(Iyengar & Lepper, 2000)などを引用。
  • 理論モデルの検討: 規範的意思決定モデル(主観的期待効用最大化理論)と限定合理性モデルを比較。
  • 進化心理学の視点: 感情の進化的背景を考察。

3. 【主要な数値結果】

  • 吊り橋実験: 高所での情動喚起が女性への好意に誤帰属されることを確認。
  • フサオマキザルの公平性実験: 不公平な報酬に対してサルが報酬拒否や抗議行動を示した(Brosnan & de Waal, 2003)。
  • 選択肢の影響実験(Iyengar & Lepper, 2000):
    • ジャム販売実験: 24種類のジャムを提示した場合、購買率は3%。6種類の場合は30%。
    • チョコレート試食実験: 選択肢が少ない場合の満足度が高かった。
  • 時間選好実験: 現在の利益を重視する傾向が確認され、1週間後の報酬よりも即時の報酬を選ぶ傾向が強い。

4. 【主な発見と結論】

  1. 感情の合理性:
    • 感情は無意識からのシグナルであり、複雑な問題では理性よりも優れた判断をする場合がある(例: 「腑に落ちない」感覚や帰納的推論)。
    • 無意識は膨大な情報処理を行い、感情を通じて意思決定を促す。
  2. 感情の非合理性:
    • 感情は短期的な利益を追求しやすく、長期的な利益を犠牲にすることがある(例: ダイエットの失敗や赤字事業の撤退回避)。
    • 選択肢が多すぎると意思決定を回避する傾向がある(例: ジャム販売実験)。
  3. 感情と理性の対立:
    • 意識的な理性は感情を非合理的とみなすが、実際には感情が意思決定の主役である。
    • 理性が感情を抑制することも可能だが、感情を完全に排除することはできない。
  4. 進化的視点:
    • 感情は進化の過程で獲得されたものであり、社会秩序の維持や集団生活において重要な役割を果たしている(例: 公正感情や裏切り者検知モジュール)。

5. 【研究の限界と今後の展望】

  • 研究の限界:
    • 感情と理性の相互作用の具体的なメカニズムはまだ解明されていない。
    • 実験データの解釈には異論があり、手続き上の批判も存在する(例

気づき

今回は、業務でpymupdf4llmを使用した経験をもとに、個人的な作業にどう活かせるかを考えてPythonスクリプトを作成してみました。
このスクリプトを使うことで、論文を読む際の心理的ハードルが下がったと感じています。AIによる要約結果には多少の不正確さもありますが、論文の全体像を把握するため第一歩としては十分機能すると思いました。まずは論文全体をざっくり把握し、その後、興味のある部分をだけを深堀りすることで、より効率的な学習スタイルを実現できると感じました。
今後も作業効率を高める情報や、業務で使用した技術についてのアウトプットの場として記事を作成していきます。最後まで読んでいただきありがとうございました。

ヘッドウォータース

Discussion