LoginSignup
0
0

論文検索を自動化してメール通知(GAS/Pubmed)

Last updated at Posted at 2024-02-17

背景

新しい論文は日々パブリッシュされますが、毎日検索してチェックするのは面倒です。とはいえ、最新の論文には常にキャッチアップしていたい。ということで、GASを用いて検索を自動化して、新規論文があれば定期的にメールで通知してくれる機能を実装しました。

コード全文はこちら。

ポイント

  • 面倒な論文検索を完全自動化
  • GASのトリガー機能で定期実行できる(例えば毎朝)
  • もちろんPCを起動しなくてOK
  • Abstractの翻訳付き
  • APIキーの取得不要
  • そして環境構築が不要

流れ

GASを用いて以下を定期実行する。

  1. 検索】指定ワードでPubmed検索し、新しい順にPMIDを指定件数だけ取得する(PMIDはPubmed上の識別子)
  2. 評価】取得したPMIDが新規かどうかを評価する(前回実行時のログと比較)
  3. 情報取得】新規だと判断された論文の情報を取得(タイトル、Abstract、ジャーナル名など)
  4. 翻訳】Abstractを日本語に翻訳
  5. メール】論文情報を指定したメールアドレスに送信
  6. ログ】PMIDなどをログとして残す

任意の条件(例えば、毎朝〇時、〇時間に一度など)でトリガーを設定して定期実行する。

実装

この記事では主に、APIを用いてPubmedから情報を取得する方法について、解説を入れながら載せています。
自動化に際して、前回ログの読み込みログの出力も必要不可欠ですが、そこら辺の解説は割愛しています。気になる方はコード全文を参照してください。

目次

  1. Pubmed検索(PMID)
  2. 論文情報の取得
  3. Abstractの取得と翻訳
  4. メールの送信

実装の中でPubmed APIを利用しますが、2024年2月時点の仕様に基づきます。変更された場合はコードの修正が必要になります。ちなみに、APIキーを取得せずに利用できますが、取得すると秒あたりのアクセス上限が増えます(3回/secから10回/sec)。

補足Google Apps Script(GAS)JavaScript(JS)をもとにした言語なので、基本的には同じように記述します。Google検索のときは「~~ javascript」みたいに検索すると、情報がたくさん出てきます。ただし、JSにあるけどGASには無い関数とかもあるので注意してください。

1. Pubmed検索(PMIDの取得)

参考サイト
https://qiita.com/iwashi-kun/items/bd0d772c6db0c0023e30
https://www.y-shinno.com/pubmed-api/

APIにアクセスするためのリンク。
term=:検索ワード
retmax=:取得件数
retmode=:取得形式。今回はjsonを指定。

https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term={検索ワード}&retmax={件数}&retmode=json

//APIキーを取得済の場合`&api_key=~~~`を追加する。
https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=%s&retmax=%s&retmode=json&api_key=****************************

UrlFetchApp.fetch()メソッドでAPIに接続し、JSON形式で取得する。
'esearchresult' の次の階層 'idlist' を指定して、ヒットした論文のPMIDを取得する。

// 指定したword でPubmed検索 
let word = '********'; //検索ワード
let num = 10; //取得するPMIDの件数

let query = Utilities.formatString('https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=%s&retmax=%s&retmode=json',word,num);

//APIに接続、JSON形式で取得、読み取り可能な形式に変換
let response = UrlFetchApp.fetch(query).getContentText();
let response_json=JSON.parse(response);

pmids = response_json['esearchresult']['idlist'].slice();

2. 論文情報の取得

上記で取得したPMIDをもとに論文の情報(JSON形式)を取得します。必要な情報だけパースして取り出し、取り出し用オブジェクトに格納します(なにかと使い勝手が良いのでオブジェクト形式を採用)。ちなみに、for文でループすると遅いのでmap()を使っています。

以下のコードでは次を取得している。

  • パブリッシュ日
  • タイトル
  • ジャーナル名
  • issn

最終的には、各論文の情報が入ったオブジェクトを格納した配列が返される。
中身のイメージはこんな感じ。
[{論文1の情報}, {論文2の情報}, {論文3の情報}, {論文4の情報}, …]

let data = pmids.map((id, i) => {
  Utilities.sleep(0.35 * 1000)//アクセス制限回避のために一時停止

  //pmidを用いてそれぞれの論文から情報を取得するためのurlを作成
  let url= 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&id='+id;

  json = JSON.parse(UrlFetchApp.fetch(url).getContentText());

  let obj = {
    pmid: id,
    pubdate: json['result'][id]['pubdate'],
    title: json['result'][id]['title'],
    journal_name: json['result'][id]['fulljournalname'],
    issn: json['result'][id]['issn'],
    link: 'https://pubmed.ncbi.nlm.nih.gov/'+id+'/',
}); 

3. Abstractの取得と翻訳

同様にAPIを用いて、Abstractの取得する。

let abst = pmids.map((id, i) => {
  Utilities.sleep(0.35 * 1000)

  let url ='https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=XML&id='+id;
  let response_abst = UrlFetchApp.fetch(url).getContentText();
  let document = XmlService.parse(response_abst);
  let body = XmlService.getPrettyFormat().format(document);
});

文字列の整形と翻訳

上記コードでAbstractは取得できるが、このままだとHTMLタグなど余計な文字列が含まれていて邪魔。なので、以下で定義するprocessText関数によって除去します。

次に、LanguageApp.translate()メソッドで翻訳する。
DeepL、Google翻訳などに比べて精度は高くないだろうけど、使い勝手が良いのでこれを採用。

↓個人的に感じているメリット

  • API不要
  • 課金なし
  • 色々書かず一行の記述で済む
  • なんだかんだGoogleが提供している機能なので性能に問題なさそう

コードは以下の通りです。Abstract原文と日本語訳を格納したオブジェクトを、論文の件数分格納した配列が返されます。
中身のイメージはこんな感じ。
[{論文1 Abstractと日本語訳}, {論文2 Abstractと日本語訳}, {論文3 Abstractと日本語訳}, {論文4 Abstractと日本語訳}, …]

let abst = pmids.map((id, i) => {
  Utilities.sleep(0.35 * 1000)

  let url ='https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=XML&id='+id;
  let response_abst=UrlFetchApp.fetch(url).getContentText();
  let document = XmlService.parse(response_abst);
  let body = XmlService.getPrettyFormat().format(document);

  let processed = processText(body);
  let jpn = LanguageApp.translate(processed, 'en','ja');

  let obj = {
    abst: processed,
    abst_jpn: jpn
});


function processText(body){
    //abstrastの両端の文字位置を取得
    let start =[];
    let end =[];
    start = body.indexOf('<Abstract>')+10 ;
    end= body.indexOf('</Abstract>') ;

    //abstract の切り出し
    let raw_text = body.substring(start,end);
    let processed = raw_text.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,'').replace(/[\r\n]+/g,'');

    while( processed.indexOf("  ") >=0){
      processed = processed.replace("  ","").slice()
    }
  return processed;
}

論文情報が格納されているdataと、Abstractが格納されているabstをくっつける。

data = data.map((each, i) => {
    data[i] = Object.assign(data[i],abst[i]);
    return data[i];
});

ここで得られるdataを、メール送信時に用いる。

4. メールの送信

GmailApp.sendEmail()でメールを送信する。

基本的なパラメータは以下の通り。
GmailApp.sendEmail(recipient, subject, body, options)

名前 説明
recipient String 受信者のアドレス
subject String 件名(最大半角 250 文字(全角 125 文字)
body String メールの本文
options Object ドキュメント参照

以下のコードではoptionsでHTML形式を指定している。

//メールの送信を実行する関数
function sendEmail(data, meta){

  //メールの送信設定
  subject='New paper';

//メール文面の作成
  let body = Utilities.formatString(
    'PMIDS: %s<br>Journal: %s<br>Publish date: %s<br><br>Tile:<br><b>%s</b><br><br>%s<br><br>Abstract:<br>%s<br><br>%s',
    data.pmid,
    data.journal_name,
    data.pubdate,
    data.title,
    data.link,
    data.abst,
    data.abst_jpn
    );

  address = meta.email;
  //Gmailの送信
  GmailApp.sendEmail(address,subject,body,{htmlBody:body});
  console.log(address);
}
送信メール例(画像) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655949/1450e759-4de4-e69b-78b4-1b7853a838c4.png)

補足GmailAppクラスには他にもいろいろな使い方があって便利です。
https://developers.google.com/apps-script/reference/gmail/gmail-app?hl=ja

最後に

Pumed検索とメール送信の実装は上記に書いた通りです。これをもとにして自動化&定期実行するためのコードは、こちらを参照してください。

0
0
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
0
0