論文をChatGPTに要約させて毎朝メールに通知を送ってもらうシステムの構築

**※この記事は以下の記事を参考に「Slackへの投稿」を「メールへの送信」に変更したものです．内容の大部分をリンク先から拝借していることを予め記しておきます．**
[最新の論文をChatGPTで要約して毎朝Slackに共有してくれるbotを作る！](https://zenn.dev/ozushi/articles/ebe3f47bf50a86)

# 目標
![スクリーンショット 2023-04-14 17.13.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3299487/83bbc2b0-5b85-dcf1-6784-3ab69b79e7f1.png)
こんな形で指定した時刻にメールを送ってもらいます．
（左上の目隠しは送信元，右上の目隠しは受信先のメールアドレスです）

# 手順
1. OpenAIのアカウントを作成しOpenAIのAPIキーを取得
2. Gmailからメールを送るためにアプリパスワードを取得
3. Pythonコードを作成
4. Google Cloud Platform（GCP）でコードの実行を自動化

手順の大部分とPythonコードのほぼ全てを元記事（上述）から引用しています．

# 1. OpenAIのAPIキーを取得
以下の記事を参考にOpenAIのAPIキーを取得します．

[[初心者向け]OpenAI API始めよう！](https://dev.classmethod.jp/articles/first_step-openai_api/)

取得したAPIキーはPythonコードの中で必要になります．メモしておいてください．


# 2. Googleアカウントでアプリパスワードを取得
この記事ではGoogleのGmailからメールを送信することを想定します．他のメールを送信元に用いる際は送信元のSMPTサーバーとポート番号の情報が必要になります．

以下の記事などを参考にアプリパスワードと呼ばれるパスワードを取得します．

[【google】アプリでG-Mailなどを使うためのアプリパスワード発行のやりかた♪](http://nyanto.net/2016/01/entry-776.html)

アプリパスワードとはアプリケーションやプログラムからGmailを呼び出す際に必要となる16文字のアルファベット文字列です．アプリパスワードもメモしておいてください．

# 3. Pythonコードを作成
上述の[元記事](https://zenn.dev/ozushi/articles/ebe3f47bf50a86)のコードをほぼコピペしてます．ただし，Slackとの連携部分を削除し，その代わりにメール送信をするためのコードに書き換えています．

* **コメント**
    * arXivでの検索条件は「クエリ」で指定．詳細は[Arxiv API](https://qiita.com/KMD/items/bd59f2db778dd4bf6ed2)こちらを参考に．
    * num_papersを大きくすると失敗することがある．
    * 「ローカルで実行可能なテストコード」は```Python 3.11.2```で動作確認済み．
        * バージョン確認はターミナルで```python3 --version```と打つ．
    * 「ローカルで実行可能なテストコード」の実行手順は[元記事作者さんのGit](https://github.com/zushi0516/arxiv_paper2slack)の記載に従う
        * ```pip install -r requirement.dat```
        * ```python3 local_arxiv_mail.py```
    * GPTに送るプロンプトやメール本文は好みに応じて整形可能．

## 以下のコード実行に必要なPythonパッケージ
```txt:requirement.dat
aiohttp==3.8.4
aiosignal==1.3.1
arxiv==1.4.3
async-timeout==4.0.2
attrs==22.2.0
certifi==2022.12.7
charset-normalizer==3.1.0
feedparser==6.0.10
frozenlist==1.3.3
idna==3.4
multidict==6.0.4
openai==0.27.2
requests==2.28.2
sgmllib3k==1.0.0
slack==0.0.2
slack-sdk==3.20.2
tqdm==4.65.0
urllib3==1.26.15
yarl==1.8.2
```

## 自動実行するコード
```Python:main.py
import os
import arxiv
import openai
import random
import smtplib

#OpenAIのapiキー
openai.api_key = '(メモしたOpenAIのAPIキー)'

def get_summary(result):
    #GPTに送るプロンプトを元記事から少し変更しています．
    system = """与えられた論文の要点を3点のみでまとめ、以下のフォーマットで日本語で出力してください。```
    タイトル（英語）
    ・要点1
    ・要点2
    ・要点3
    ```"""

    text = f"title: {result.title}\nbody: {result.summary}"
    response = openai.ChatCompletion.create(
                model="gpt-3.5-turbo",
                messages=[
                    {'role': 'system', 'content': system},
                    {'role': 'user', 'content': text}
                ],
                temperature=0.25,
            )
    summary = response['choices'][0]['message']['content']
    title_en = result.title
    title, *body = summary.split('\n')
    body = '\n'.join(body)
    date_str = result.published.strftime("%Y-%m-%d %H:%M:%S")
    message = f"発行日: {date_str}\n{result.entry_id}\n{title_en}\n{title}\n{body}\n"
    
    return message

def main(event, context):
    query ='ti:%22 scalar meson %22' #私が使っているクエリです

    # arxiv APIで最新の論文情報を取得する
    search = arxiv.Search(
        query=query,        # 検索クエリ
        max_results=100,    # 取得する論文数
        sort_by=arxiv.SortCriterion.SubmittedDate,  # 論文を投稿された日付でソートする
        sort_order=arxiv.SortOrder.Descending,      # 新しい論文から順に取得する
    )
    #searchの結果をリストに格納
    result_list = []
    for result in search.results():
        result_list.append(result)
    #ランダムにnum_papersの数だけ選ぶ
    num_papers = 3
    results = random.sample(result_list, k=num_papers)
    
    #送信元のメールアドレス
    sender_email = "hogehoge@gmail.com"

    #送信先のメールアドレス
    receiver_email = "hogehogehoge@hogehoge"

    #Gmailアカウントのユーザー名とアプリパスワード
    username = "hogehoge@gmail.com"
    password = "(取得したアプリパスワード)"

    # SMTPサーバーの設定：Gmailは以下の情報で固定です
    smtp_server = "smtp.gmail.com"
    port = 587

    #論文情報をメールに送信する
    for i,result in enumerate(results):
        # メールに送るメッセージを組み立てる
        subject = "今日の論文です! " + str(i+1) + "本目\n"
        body    = get_summary(result)

        # メールを送信する
        with smtplib.SMTP(smtp_server, port) as server:
            server.starttls()
            server.login(username, password)
            message = f"Subject: {subject}\n\n{body}"
            message = message.encode('utf-8')
            server.sendmail(sender_email, receiver_email, message)

        print("メールを送信しました")
```

## ローカルで実行可能なテストコード
```Python:local_arxiv_mail.py
import os
import arxiv
import openai
import random
import smtplib

#OpenAIのapiキー
openai.api_key = '(メモしたOpenAIのAPIキー)'

def get_summary(result):
    system = """与えられた論文の要点を3点のみでまとめ、以下のフォーマットで日本語で出力してください。```
    タイトル（英語）
    ・要点1
    ・要点2
    ・要点3
    ```"""

    text = f"title: {result.title}\nbody: {result.summary}"
    response = openai.ChatCompletion.create(
                model="gpt-3.5-turbo",
                messages=[
                    {'role': 'system', 'content': system},
                    {'role': 'user', 'content': text}
                ],
                temperature=0.25,
            )
    summary = response['choices'][0]['message']['content']
    title_en = result.title
    title, *body = summary.split('\n')
    body = '\n'.join(body)
    date_str = result.published.strftime("%Y-%m-%d %H:%M:%S")
    message = f"発行日: {date_str}\n{result.entry_id}\n{title_en}\n{title}\n{body}\n"
    
    return message

query ='ti:%22 scalar meson %22'

# arxiv APIで最新の論文情報を取得する
search = arxiv.Search(
    query=query,        # 検索クエリ
    max_results=100,    # 取得する論文数
    sort_by=arxiv.SortCriterion.SubmittedDate,  # 論文を投稿された日付でソートする
    sort_order=arxiv.SortOrder.Descending,      # 新しい論文から順に取得する
)
#searchの結果をリストに格納
result_list = []
for result in search.results():
    result_list.append(result)
#ランダムにnum_papersの数だけ選ぶ
num_papers = 3
results = random.sample(result_list, k=num_papers)

#送信元のメールアドレス
sender_email = "hogehoge@gmail.com"

#送信先のメールアドレス
receiver_email = "hogehogehoge@hogehoge"

#Gmailアカウントのユーザー名とアプリパスワード
username = "hogehoge@gmail.com"
password = "(取得したアプリパスワード)"

# SMTPサーバーの設定
smtp_server = "smtp.gmail.com"
port = 587

#論文情報をメールに送信する
for i,result in enumerate(results):
    # メールに送るメッセージを組み立てる
    subject = "今日の論文です! " + str(i+1) + "本目\n"
    body    = get_summary(result)

    # メールを送信する
    with smtplib.SMTP(smtp_server, port) as server:
        server.starttls()
        server.login(username, password)
        message = f"Subject: {subject}\n\n{body}"
        message = message.encode('utf-8')
        server.sendmail(sender_email, receiver_email, message)

    print("メールを送信しました")
```

# 4. Google Cloud Platform（GCP）でコードの実行を自動化

元記事でも引用されている[[サーバーレス ＋ Pythonで定期的にスクレイピングを行う方法](https://gammasoft.jp/blog/schdule-running-python-script-by-serverless/)]この解説を参考にGCPにのせる．このリンク先の記事で完結しているのでおすすめです．

自分は毎朝6時にメールを送ってもらっています．

# 終わりに
Slack投稿をメール送信に書き換える際に，GPTくんの力を借りました．その他にも多くのサイトを参考にさせていただきました．先人の皆様，ありがとうございました．また，[元記事](https://zenn.dev/ozushi/articles/ebe3f47bf50a86)の作者である[ズシ](https://zenn.dev/ozushi)さんにはここで改めて感謝申し上げます．ありがとうございました．