【すべて自動化】論文の取得,要約,保存 ①コード編

## はじめに
### この記事について
この記事ではPython、ChatGPTのAPI、NotionのAPIを使って論文の取得、要約、保存を自動化する方法について解説します。

**注意点2つ**
*コード編となっており環境構築については触れません。環境構築は「環境構築編」として別記事で紹介します。
*本人がPython初心者ということもありいたらな点が多くあると思いますがご容赦ください。

### 大まかな流れ
①arXivから論文を取得
②ChatGPTのAPIを使って論文を要約
③Notionに保存

### ゴール
本記事では論文の取得、要約、保存の仕組みを理解することができます。

それではやっていきましょう！

## importと初期化
```
import requests
import feedparser
import io
import PyPDF2
import os
import schedule
import time
import logging
from openai import OpenAI
from notion_client import Client
from dotenv import load_dotenv

# 環境変数の読み込み
load_dotenv()

logging.basicConfig(level=logging.INFO)

# APIキーの設定
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_ID = os.getenv("NOTION_DB_ID")

# APIエンドポイントの定義
ARXIV_API_ENDPOINT = "http://export.arxiv.org/api/query?"

# Notionクライアントの初期化
notion_client = Client(auth=NOTION_API_KEY)

# OpenAIクライアントの初期化
openai_client = OpenAI(api_key=OPENAI_API_KEY)
```

## arXivからの論文取得
### Notionに保存されている論文を取得
```
def get_all_paper_titles():
    # Notionのデータベースから全てのエントリを取得する
    response = notion_client.databases.query(NOTION_DB_ID)
    entries = response["results"]

    # 全ての論文のタイトルを取得する
    all_titles = [entry["properties"]["名前"]["title"][0]["text"]["content"] for entry in entries]
    return all_titles
```
#### 部分解説
```
all_titles = 
[entry["properties"]["名前"]["title"][0]["text"]["content"] 
for entry in entries]
```
各エントリの"properties"辞書から"名前"プロパティを取得し、その"名前"プロパティの"title"リストの最初の要素から"text"辞書を取得し、最終的にその"text"辞書の"content"を取得しています。


### arXivから論文の検索と取得
全体像
```
def search_arxiv_papers(query, max_results=5):
    # arXivから論文を検索して結果を返す。
    all_titles = get_all_paper_titles()
    papers = []
    start = 0

    while len(papers) < max_results:
        params = {
            "search_query": query,
            "start": start,
            "max_results": max_results
        }
        response = requests.get(ARXIV_API_ENDPOINT, params=params)
        feed = feedparser.parse(response.content)
        new_papers = [
            {"title": entry.title, "pdf_link": entry.link.replace("abs", "pdf"), "published": entry.published}
            for entry in feed.entries
        ]
        for paper in new_papers:
            if paper["title"] not in all_titles or len(papers) < max_results:
                papers.append(paper)
        start += max_results

    if not papers:
        print("条件に合った論文がありません")
    return papers[:max_results]
```
#### 部分解説
```def search_arxiv_papers(query, max_results=5):
    all_titles = get_all_paper_titles()
    papers = []
    start = 0
```
queryは検索のキーワード
max_results=5は一回の処理で取得する論文の数

```
while len(papers) < max_results:
```
一回の処理で論文取得数になるまで検索、取得を続ける

```
feed = feedparser.parse(response.content)
```
このコードは、feedparserというPythonライブラリを使用して、HTTPレスポンスのコンテンツを解析しています。具体的には、arXiv APIから取得したレスポンス（論文の情報が含まれている）を解析しています。

feedparser.parse(response.content)の部分では、以下の2つの操作が行われています：

1. response.content：これは、HTTPレスポンスの本文（コンテンツ）を取得します。この例では、arXiv APIからのレスポンスの本文を取得しています。この本文は、通常、XMLまたはJSON形式のデータを含んでいます。

2. feedparser.parse()：これは、feedparserライブラリの関数で、XML形式のフィード（この場合はarXiv APIからのレスポンス）を解析します。この関数は、XMLフィードをPythonの辞書とリストの構造に変換します。これにより、フィードの各エントリ（この場合は論文）の情報に簡単にアクセスできます。

したがって、このコードは、arXiv APIから取得した論文の情報を含むXMLフィードを解析し、その情報をPythonのデータ構造に変換しています。これにより、その後のコードで論文の情報にアクセスしやすくなります。



### PDFをダウンロードしテキストに変換
```
def download_paper_text(url):
    # 指定されたURLからPDFをダウンロードし、テキストに変換する。
    response = requests.get(url)
    with io.BytesIO(response.content) as pdf_file:
        reader = PyPDF2.PdfReader(pdf_file)
        return "\n".join(page.extract_text() for page in reader.pages)
```
#### 部分解説
1. io.BytesIO(response.content)：この部分は、HTTPレスポンスの本文（ここではPDFファイルのバイナリデータ）をバイトストリームとして扱います。バイトストリームは、バイナリデータを読み書きするためのPythonのオブジェクトです。

2. as pdf_file：この部分は、バイトストリームをpdf_fileという名前の変数に割り当てます。この変数を通じて、PDFファイルの内容にアクセスできます。

3. reader = PyPDF2.PdfReader(pdf_file)：この部分は、PyPDF2.PdfReaderを使用して、バイトストリームからPDFファイルを読み込みます。これにより、PDFファイルの各ページにアクセスできます。

4. "\n".join(page.extract_text() for page in reader.pages)：この部分は、PDFファイルの各ページのテキストを抽出し、それらを改行（"\n"）で連結します。page.extract_text()は、ページのテキストを抽出するためのメソッドです。for page in reader.pagesは、PDFファイルの各ページに対してこの操作を行います。

## 論文を要約
```
def generate_summary(system_message, user_message):
    #ChatGPTを使用してテキストの要約を生成する。
    response = openai_client.chat.completions.create(
        model="gpt-4-1106-preview",
        messages=[
            {"role": "system", "content": system_message},
            {"role": "user", "content": user_message},
        ]
    )
    summary = response.choices[0].message.content
    token_usage = ", ".join(map(str, response.usage))
    return f"{summary} Tokens Used: {token_usage}"
```
## データベースに保存
```
def add_to_notion_database(db_id, title, link, summary, published):
    # Notionデータベースに新しいエントリを追加する。
    response = notion_client.pages.create(
        parent={"database_id": db_id},
        properties={
            "名前": {"title": [{"text": {"content": title}}]},
            "URL": {"url": link},
            "公開日": {"date": {"start": published}}
        },
        children=[
            {
                "object": "block",
                "type": "paragraph",
                "paragraph": {
                    "rich_text": [{"type": "text", "text": {"content": summary}}]
                }
            }
        ]
    )
    print("Notion database entry created:", response)
```

## 実行
全体像
```
def main():
    logging.info("Task started")
    query = "artificial intelligence OR deep learning OR quantum mechanics OR generative AI OR generative models OR prompt OR large language models"
    papers = search_arxiv_papers(query)

    for paper in papers:
        print(f"Title: {paper['title']}")
        print(f"PDF Link: {paper['pdf_link']}")
        print(f"Published: {paper['published']}")

        pdf_text = download_paper_text(paper['pdf_link'])

        with open('prompt.txt', 'r', encoding='utf-8') as file:
            system_message = file.read()

        summary = generate_summary(system_message, pdf_text)
        print(f"Summary: {summary}\n")

        add_to_notion_database(NOTION_DB_ID, paper['title'], paper['pdf_link'], summary, paper['published'])
    logging.info("Task finished")

if __name__ == "__main__":
    schedule.every().day.at("06:00").do(main)
    while True:
        schedule.run_pending()
        time.sleep(1)
```
#### 部分解説
```
with open('prompt.txt', 'r', encoding='utf-8') as file:
            system_message = file.read()
```
このコードは、Pythonのopen関数を使用して、'prompt.txt'という名前のテキストファイルを読み込んでいます。このテキストファイルには論文要約のプロンプトが記述されています。

1. open('prompt.txt', 'r', encoding='utf-8')：この部分は、'prompt.txt'という名前のファイルを読み込むために使用されます。'r'は読み込みモードを意味し、'encoding='utf-8''はファイルがUTF-8エンコーディングであることを指定します。

2. as file：この部分は、開いたファイルをfileという名前の変数に割り当てます。この変数を通じて、ファイルの内容にアクセスできます。

3. with：このキーワードは、ファイルを安全に開き、操作が完了したら自動的に閉じるために使用されます。これにより、プログラマーが明示的にファイルを閉じる必要がなくなります。

4. system_message = file.read()：この部分は、readメソッドを使用してファイルの全内容を読み込み、その内容をsystem_messageという名前の変数に割り当てます。

このコード全体は、'prompt.txt'という名前のテキストファイルを開き、その全内容を読み込み、その内容をsystem_messageという変数に保存します。

## 終わりに
記事を読んでいただきありがとうございました。

ほんとはもっと詳細解説を加えて初心者でも理解できるように記事を書こうと思ったのですが、思った以上に大変だったのでまずはこのくらいにしておきます、、、。

これを機にQiitaに記事を投稿していこうと思いますのでよろしくお願いします。