1
1

arXivとPubMedから最新論文を取ってきてslackに投稿させる方法

Posted at

はじめに

すでにarXivで同じことをしている人はたくさんいるけれども,私の分野だとPubMedも必要だったのでガッチャンコ(死語)した。両方から最新の論文データ(タイトルやアブストラクト)を引っ張ってきて,ChatGPTを使って解説させる。

基本的には色々な情報を繋ぎ合わせただけ。元ネタは下記。

ChatGPT APIの準備

皆大好きOpenAIでAPI利用登録をする。

「新規利用登録で~~ドル分使える!」みたいな記事も多かったけど,私が登録したときにはサイフはすっからかんだった。ChatGPT無料で使いまくってるからね,しょうがないね。

登録後,構築したシステムにいくら掛かるかわからないけど恐る恐る5ドル課金

Slack Appの準備

ネットの記事の通りにアプリを登録する。

ここで実行テストをするんだけど,マルチバイト文字はパーセントエンコーディングをしないと動かなかった。

つまり,投稿したいチャンネルの名前を「#論文」としていたんだけど,以下だと動かない。

$ curl -X POST 'https://slack.com/api/chat.postMessage' \
-d 'token=xoxb-668814143316-2441900189495-E0TZsRkP18KjsKVNByhZwIIG' \
-d 'channel=#論文' \
-d 'text=hinna hinna'

以下だと動く。エンコーディングしてやる必要がある。

$ curl -X POST 'https://slack.com/api/chat.postMessage' \
-d 'token=xoxb-668814143316-2441900189495-E0TZsRkP18KjsKVNByhZwIIG' \
-d 'channel=#%E8%AB%96%E6%96%87' \
-d 'text=hinna hinna'

Pythonスクリプト

パッケージ関係

from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
import arxiv
import openai
import random
import openai
import requests
from lxml import etree
import datetime

arXivからデータ取得

こんな感じ。

  1. タイトル,雑誌,アブストラクト,キーワード,等の全てのフィールドから関心のあるキーワードに合致し,新しいもの(1年以内)を検索して
  2. 新しい順に50件取得して
  3. そのうち2件をランダムに選択する

PubMedとデータの形式を合わせたいので自作の辞書型に必要な情報を格納して返すようにする。

クエリについては公式を参照

AX_QUERY = """
all:("acoustic" AND ("enrironmental noise" OR "road traffic noise" OR "railway noise" OR "aircraft noise" OR "wind turbine noise" OR "community noise" OR "traffic noise"))
&lastUpdatedDate:[{mindate} TO {maxdate}]
""".format(
  mindate = (datetime.datetime.today() + datetime.timedelta(days=-365)).strftime("%Y/%m/%d"),
  maxdate = datetime.datetime.today().strftime("%Y/%m/%d")
).replace('\n', '').replace('"', '%22')


NUM_PAPERS = {
  "arXiv": 2,
  "PubMed": 2
}

def get_arxiv_papers():
  ax_client = arxiv.Client(page_size = 100)
  search = arxiv.Search(
    query=AX_QUERY,  
    max_results=50,  
    sort_by=arxiv.SortCriterion.SubmittedDate, 
    sort_order=arxiv.SortOrder.Descending, 
  )
  
  result_list = []
  for result in ax_client.results(search):
    result_list.append({
      "title": result.title, 
      "abstract": result.summary, 
      "date": result.published.strftime("%Y-%m-%d %H:%M:%S"),
      "url": result.entry_id
    })
      
  results = random.sample(result_list, k=NUM_PAPERS["arXiv"])
  
  return results

PubMedからデータ取得

arXivとやっていることは同じ。

  1. タイトル,雑誌,アブストラクト,キーワード,等の全てのフィールドから関心のあるキーワードに合致し,新しいもの(1年以内)を検索して
  2. 新しい順に50件取得して
  3. そのうち2件をランダムに選択する

もっと簡単な書き方があるんだろうと思うけど,動けばよいので気にしていない。

こちらのクエリも公式を参照。

PM_QUERY = """
db=pubmed
&retmode=json
&retstart=0
&retmax=50
&sort=pub_date
&mindate={mindate}
&maxdate={maxdate}
&term="envrironmental noise" OR "road traffic noise" OR "railway noise" OR "aircraft noise" OR "wind turbine noise" OR "community noise" OR "traffic noise"
""".format(
  mindate = (datetime.datetime.today() + datetime.timedelta(days=-365)).strftime("%Y/%m/%d"),
  maxdate = datetime.datetime.today().strftime("%Y/%m/%d")
).replace('\n', '').replace(" ", "+")


def get_pubmed_papers():

  # get PMID
  pmid_response = requests.get("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?" + PM_QUERY)
  pmid_response_json = pmid_response.json()
  pmids = pmid_response_json["esearchresult"]["idlist"]
  
  pmids_ext = random.sample(pmids, k = NUM_PAPERS["PubMed"])
  
  # get abstract
  results = []
  for pmid in pmids_ext:
    try:
      response = requests.get(
        "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=xml&id={}".format(pmid)
      )
      root = etree.fromstring(response.content)
      try:
        date_str = root.find('.//DateRevised//Year').text + "-" + root.find('.//DateRevised//Month').text + "-" + root.find('.//DateRevised//Day').text
      except:
        date_str = "Date not found"
        
      try:
        url = "https://doi.org/"+root.find('.//ArticleId[@IdType="doi"]').text
      except:
        url = "URL not found"
        
      results.append({
        "title": root.find('.//ArticleTitle').text, 
        "abstract": ''.join(root.xpath('//Abstract//*/text()')), 
        "date": date_str,
        "url": url
      })
    except:
      continue    
  
  return results

ChatGPTに投げる部分

コスト重視でChatGPT3.5を使用。指示に従わないわからずやだけど,詳しく調べたいときには自分でソースに当たるから問題ない。

プロンプトは,もっと良い書き方があると思うんだよなあ。でもとりあえず動いたので良しとする。

使うときにはAPI keyの指定が必要。ただし,.envファイルにOPENAI_API_KEY=**というかたちで指定しておけばそれでも動く。以下のスクリプトはそうしている。

SYSTEM = """
### 指示 ###
論文の内容を理解した上で,概要,結果,結論,解説を平易な日本語で述べよ。

### 概要の制約 ###
- 論文の背景・目的・手法などについて100文字以内で述べる

### 結果の制約 ###
- 論文で示された最も重要な結果・考察について100文字以内で述べる

### 結論の制約 ###
- 論文より得られる洞察,限界,今後の展望について100文字以内で述べる

### 解説の制約 ###
- 専門用語の使用を避ける
- 300文字以上500文字以内
- 高い視座,広い視野,様々な視点から論文の重要性を述べる

### 対象とする論文の内容 ###
{text}

### 出力形式 ###
タイトル:タイトル(和名)

概要:
概要

結果:
結果

結論:
結論

解説:
解説
"""


def get_summary(result):
  client = openai.OpenAI()
  text = f"title: {result['title']}\nbody: {result['abstract']}"
  response = client.chat.completions.create(
    model="gpt-3.5-turbo-0125",
    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)
  message = f"発行日: {result['date']}\n{result['url']}\n{title_en}\n{title}\n{body}\n"
  
  return message

main部分(slackに投げる部分)

SLACK_API_TOKEN = 'xoxb-hinnahinna'
SLACK_CHANNEL = "#論文"

def main(event, context):
  slk_client = WebClient(token=SLACK_API_TOKEN)

  ax_results = get_arxiv_papers()
  pm_results = get_pubmed_papers()
  all_results = ax_results + pm_results
  
  for i, result in enumerate(all_results):
    try:
      message = "今日の論文です! " + str(i+1) + "本目\n" + get_summary(result)
      response = slk_client.chat_postMessage(
          channel=SLACK_CHANNEL,
          text=message
      )
      print(f"Message posted: {response['ts']}")
    except SlackApiError as e:
      print(f"Error posting message: {e}")


if __name__ == '__main__':
  main(None, None)

その他

上のスクリプトを全部繋げてmain.pyとして,同じフォルダに.envファイルを作ってOPENAI_API_KEY=**というかたちでAPI Keyを入力して,main.pyを実行すれば動く。

スケジューリング

省略。というかまだやっていない。
Googleよりかは自分のところのサーバーで動かしたいな。

使ってみた

こんな感じで投稿される。
もっと検索キーワードは考えた方が良いけど,ま,やりたいことはできた。

image.png

image.png

気になるOpenAI APIの使用料については,1投稿で0.01ドルもいかないくらい。5ドルあればかなり楽しめそうです。

おわりに

とくになし。Enjoy!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1