毎日特定のキーワードでPubMed検索して引っかかった論文の要約をChatGPTでおこないメールで通知する

実現したこと

研究者にとって、最新の医学研究論文をフォローすることは非常に重要ですが、同時に時間がかかる作業でもあります。本記事では、毎朝7時に設定したキーワードに基づいて前日にPubMedに掲載された論文を自動的に収集し、AIを使用して日本語で要約した上でメール通知する仕組みを紹介します。

以前、Microsoft teamsバージョンの記事は書きました↓

Slackバージョンの記事も書きました↓

しかし、TeamsもSlackもラボで使っていないという場合もあると思いますのでメールで通知する仕組みも紹介します。

目的

  1. 研究室のメンバーが最新の研究動向を効率的に把握できるようにする

  2. 興味のある論文を見逃すリスクを減らす

  3. 英語論文の内容を日本語で簡単に理解できるようにする

  4. 学生の研究リテラシーと英語力の向上を支援する

実装の概要

この仕組みは以下の要素で構成されています:

  1. Raspberry Pi(ラズパイ)上の Python 仮想環境

  2. PubMedを使用した論文検索

  3. OpenAI GPTモデルを使用した論文要約

  4. smtplibを利用してメール送信

実装手順

Raspberry pi (ラズパイ)仮想環境

# Pythonのバージョンチェック
python -V

# Venvで仮想環境構築
sudo python3.xx -m venv hoge
source hoge/bin/activate
sudo chown -R fuga:fuga /home/fuga/仮想環境フォルダ名

ライブラリのインストール

pip install openai requests xmltodict python-dotenv

OpanAI APIキー取得

OpenAIのウェブサイトでアカウントを作成し、APIキーを取得します。詳細な手順はこちらの記事を参照してください。

Pythonスクリプト

以下のPythonスクリプトを作成し、script.pyとして保存します。このスクリプトが論文の検索、要約、Slackへの投稿を行います。

cd hoge
sudo nano script.py
from openai import OpenAI
import os
import requests
import xmltodict
from datetime import datetime, timedelta
from dotenv import load_dotenv
import time
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

load_dotenv()

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

SMTP_SERVER = os.getenv("SMTP_SERVER")
SMTP_PORT = os.getenv("SMTP_PORT")
SMTP_USERNAME = os.getenv("SMTP_USERNAME")
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD")
SENDER_EMAIL = os.getenv("SENDER_EMAIL")
RECIPIENT_EMAIL = os.getenv("RECIPIENT_EMAIL")
CC_EMAIL = os.getenv("CC_EMAIL", "")
BCC_EMAIL = os.getenv("BCC_EMAIL", "")

PUBMED_QUERIES = os.getenv("PUBMED_QUERIES").split(',')

PUBMED_PUBTYPES = [
    "Journal Article",
    "Books and Documents",
    "Clinical Trial",
    "Meta-Analysis",
    "Randomized Controlled Trial",
    "Review",
    "Systematic Review",
]
PUBMED_TERM = 1

PROMPT_PREFIX = (
    "あなたは高度に教育と訓練をした研究者です。以下の論文を、タイトルと要約の2点をそれぞれ改行で分けて日本語で説明してください。要点は必ず箇条書き形式で書いてください。"
)

def main():
    client = OpenAI(api_key=OPENAI_API_KEY)
    
    today = datetime.now()
    yesterday = today - timedelta(days=PUBMED_TERM)

    for query in PUBMED_QUERIES:
        while True:
            try:
                ids = get_paper_ids_on(yesterday, query)
                print(f"{query} の論文ID数: {len(ids)}")
                output = ""
                paper_count = 0
                for i, id in enumerate(ids):
                    summary = get_paper_summary_by_id(id)
                    pubtype_check_result = check_pubtype(summary["pubtype"])
                    print(f"ID {id} のpubtype: {summary['pubtype']}, チェック結果: {pubtype_check_result}")
                    if not pubtype_check_result:
                        continue
                    paper_count += 1
                    abstract = get_paper_abstract_by_id(id)
                    print(f"ID {id} のタイトル: {summary['title']}")
                    print(f"ID {id} の要約: {abstract}\n")
                    input_text = f"\ntitle: {summary['title']}\nabstract: {abstract}"

                    response = client.chat.completions.create(
                        messages=[
                            {
                                "role": "user",
                                "content": PROMPT_PREFIX + "\n" + input_text,
                            },
                        ],
                        model="gpt-4o-mini",
                    )
                    
                    content = response.choices[0].message.content.strip()
                    
                    pubmed_url = f"https://pubmed.ncbi.nlm.nih.gov/{id}"
                    output += f"PubMed の新着論文のお知らせ ({query})\n\n{content}\n\n{pubmed_url}\n\n\n"

                if output:
                    send_email(query, output, to_yyyymmdd(yesterday))
                else:
                    print(f"No new papers for query: {query}")

                break
                
            except openai.RateLimitError as e:
                print("Rate limit exceeded. Waiting for 300 seconds before retrying.")
                time.sleep(300)
            except Exception as e:
                print(f"An error occurred: {e}")
                time.sleep(60)

def to_yyyymmdd(date):
    return date.strftime("%Y/%m/%d")

def get_paper_ids_on(date, query):
    url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&sort=pub_date&term={query}&mindate={to_yyyymmdd(date)}&maxdate={to_yyyymmdd(date)}&retmax=1000&retstart=0"
    res = requests.get(url).json()
    return res["esearchresult"]["idlist"]

def get_paper_summary_by_id(id):
    url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&id={id}"
    res = requests.get(url).json()
    return res["result"][id]

def get_paper_abstract_by_id(id):
    url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=xml&id={id}"
    res = requests.get(url).text
    xml_dict = xmltodict.parse(res)
    abstract = xml_dict["PubmedArticleSet"]["PubmedArticle"]["MedlineCitation"]["Article"].get("Abstract", {}).get("AbstractText", "")
    return abstract if abstract else ""

def check_pubtype(pubtypes):
    return any(pubtype in PUBMED_PUBTYPES for pubtype in pubtypes)

def send_email(query, content, search_date):
    msg = MIMEMultipart('alternative')
    msg['Subject'] = f'新着論文のお知らせ ({query}) - 検索対象日: {search_date}'
    msg['From'] = SENDER_EMAIL
    msg['To'] = RECIPIENT_EMAIL
    if CC_EMAIL:
        msg['Cc'] = CC_EMAIL
    
    text = content

    html = content.replace('\n', '<br>')
    html = f'<html><body>{html}</body></html>'

    part1 = MIMEText(text, 'plain')
    part2 = MIMEText(html, 'html')
    msg.attach(part1)
    msg.attach(part2)

    try:
        with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
            server.starttls()
            server.login(SMTP_USERNAME, SMTP_PASSWORD)
            recipients = [RECIPIENT_EMAIL]
            if CC_EMAIL:
                recipients.extend(CC_EMAIL.split(','))
            if BCC_EMAIL:
                recipients.extend(BCC_EMAIL.split(','))
            server.sendmail(SENDER_EMAIL, recipients, msg.as_string())
        print(f"Email sent for query: {query}")
    except Exception as e:
        print(f"Failed to send email for query {query}. Error: {e}")

if __name__ == "__main__":
    main()

環境変数の設定

.envファイルを作成し、以下の内容を記入します:

cd hoge
sudo nano .env
# OpenAI API Key
OPENAI_API_KEY=ここにopenAIのAPIキー

# E-mail setting
SMTP_SERVER=ここにSMTPサーバー記述
SMTP_PORT=587 #メールシステムに依存
SMTP_USERNAME=メールID
SMTP_PASSWORD=ここにパスワード
SENDER_EMAIL=ここに送信メールアドレス
RECIPIENT_EMAIL=ここに宛先メールアドレス #複数の時はxxx@xxx.xx, yyy@yyy.yy
CC_EMAIL=ここにCCメールアドレス #空欄でもOK。複数の時はxxx@xxx.xx, yyy@yyy.yy
BCC_EMAIL=ここにBCCメールアドレス #空欄でもOK。複数の時はxxx@xxx.xx, yyy@yyy.yy

# PubMed search queries, separated by commas
PUBMED_QUERIES=hoge fuga, fuga, hoge, hhh xxx yyy

実行スクリプトの作成

script.shというシェルスクリプトを作成し、以下の内容を記入します:

# シェルスクリプト作成
cd hoge
sudo nano script.sh
#!/bin/sh
PROG_DIR=/home/fuge/hoge
source hoge/bin/activate
python3 $PROG_DIR/script.py
deactivate

Crontabの設定

crontab -e
0 7 * * * cd /home/fuga/huge; sudo bash script.sh

もしくは

0 7 * * * /home/user/hoge/venv/bin/python /home/user/fuga/huge/script.py

これにより、毎朝7時にスクリプトが実行されます。

まとめ

この仕組みを使うことで、以下のメリットが得られます:

  • 毎日自動的に最新の研究論文を収集し、日本語で要約された内容を簡単に確認できる

  • 複数のキーワードを設定することで、幅広い研究分野をカバーできる

  • メールを利用することで、チーム全体で情報を共有しやすい

  • 生成AIによる要約により、素早く論文の概要を把握できる

  • 興味のある論文はPubMedリンクから詳細を確認できる

この仕組みは、研究室の生産性向上に大きく貢献し、学生の研究スキル向上にも役立つでしょう。定期的にキーワードを見直すことで、常に最新かつ関連性の高い情報を得ることができます。

この記事が気に入ったらサポートをしてみませんか?