PubMedで定期的に検索したい論文情報をCSVデータとして蓄積させていくスクリプトの作成

定期的に新しい論文が無いか調べることあると思います。けど、時間があるときに不定期に調べる程度で、あれ？この論文見たっけかな？なんてこともあるかと思います。そこで定期的に調べる検索ワードで出てくる論文を一覧データとして蓄積させていくスクリプトを書きました。全くの独学でやってるので変な部分もあるかもですが悪しからずです。

#１．使用モジュール一覧。
書いてるけど、結局使ってない…なんてものがもしかしたら混ざっているかもしれません。試行錯誤して作ったのでとりあえず、動けばいいかレベルです。

```python:module.py
from Bio import Entrez
from Bio import Medline
from Bio.Entrez import efetch, read
import pandas as pd
from time import sleep
import openpyxl
from datetime import datetime
from datetime import timedelta
from dateutil.relativedelta import relativedelta
import os
import numpy as np
import sys
```

#２．本日の日付をゲット。
論文情報を蓄積させていくためには、前回調査した日を記録しておく必要があります。ゼロから毎回論文情報をゲットするようなスクリプトはNCBIのサーバに高負担をかけてしまうので要注意。時間をゲットするためにはstrftimeとstrptimeってのがあるようです。時間として数値を扱いたいときはstrptime、文字列として使用したいときはstrftimeなようです（要調査）。

```python:date_get.py
DATE = (datetime.now().strftime("%Y/%m/%d"))
```

#３．メアド記入。
BeautifulsoupではなくNCBIのAPIを使用する場合はメアドを事前に連絡する必要があります。サーバーに負担がかかるようなことをするとメールで連絡がくるそうなのです。けど、このAPI使って多数の論文情報を一括ゲットしようとする場合、そんな負担かけたつもりなくても接続が自動で切断されちまいます…メールは来たことないけど…。

```python:mail.py
Entrez.email =  input("メールアドレスを入力してください : ")
```

#４．エクセルのテンプレ作成 or 読み込み。
新規に検索する場合はテンプレを作成し、すでに継続して調査している案件があるならば検索条件を読み込みます。
このスクリプトを使ってもらう際に「このExcelファイルを検索条件を記載するテンプレとして使ってね！」ってテンプレExcelを別途渡すのも億劫なのでテンプレが同じディレクトリにない場合は自動でテンプレが作成されるようにしています。if文からのsys.exit()って可視性よいですね。
検索タイトルは「iPS細胞関連」と「ES細胞関連」という感じの整理用のもので、検索ワードはそのタイトルで実際に検索として使う「myc」とかの単語を入れるセルです。最終検索日の日付をvalueとすると数値として認識されてしまうみたいなので注意です。データフレームからリストにして、さらに辞書型にするというなんかカッコ悪いことしてるので何とかした方がよいですね。

```python:template.py
###エクセルファイルが無い場合
path = os.path.abspath(".")
files = os.listdir(path)
if not "文献調査.xlsx" in files:
  Header = pd.DataFrame(columns=["検索タイトル", "検索ワード", "最終検索日"])
  with pd.ExcelWriter("文献調査.xlsx") as writer:
    Header.to_excel(writer,sheet_name="検索式",index = False)
  sys.exit()
###########################################################################
###Excelのシート読み込みとセル列をDFで取得、リストに変換
df = pd.read_excel("文献調査.xlsx", sheet_name="検索式")
SRCHTTLs_df = df["検索タイトル"]
SRCHWRDs_df = df["検索ワード"]
SAVEDDATEs_df = df["最終検索日"]
count = SAVEDDATEs_df.count()
SRCHTTLs = SRCHTTLs_df.values.tolist()
SRCHWRDs = SRCHWRDs_df.values.tolist()
###日付はvaluesを付けると日付ではなく値で入ってしまうので注意。
SAVEDDATEs = SAVEDDATEs_df.tolist()
SRCHTTLs_SRCHWRDs = dict(zip(SRCHTTLs, SRCHWRDs))
SRCHWRDs_SAVEDDATEs = dict(zip(SRCHWRDs, SAVEDDATEs))
SRCHTTLs_SAVEDDATEs = dict(zip(SRCHTTLs, SAVEDDATEs))
```
具体的にはこんな感じでエクセルに書いてもらえれば、iPS細胞関連では関連遺伝子4つを検索ワードとして検索がかけられる感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481632/27a221db-b76a-0d92-7426-816b99729d16.png)



#５．既存保存データの読み込み。
同じディレクトリに過去に調査したCSVファイルが既にあるならばリストとして保存、なければCSVファイルを作成します。

```python:read_make_CSV.py
###既存のCSVを確認、対象URLのリストと比較して新規検索対象があれば新しいシート作成
sheets_pre = [x for x in files if x[-4:] == '.csv']
sheets = []
for sheet_pre in sheets_pre:
  sheet = sheet_pre.replace(".csv", "")
  sheets.append(sheet)
total_sheets = list(sheets + SRCHTTLs)
new_sheets  = list(set(total_sheets) - set(sheets))
###########################################################################
####新規検索対象のCSV作成
for new_sheet in new_sheets:
  df = pd.DataFrame(columns=["PubMedID", "検索日", "タイトル", "著者","文献", "要旨"])
  df.to_csv(new_sheet + ".csv", index = False, encoding="utf-8_sig")
```

#６．ここから長いfor文です。
##６－１．データフレームの作成とNCBIのAPIに渡す条件整理。
もしも初めて実施する論文条件であれば、何年から取得するのかをを聞いてくるようにしました。余談ですがPubMedにある論文データって1950年以前はないみたいです。これやってて初めて知った…。もし過去に調査した履歴があるならばExcelファイルの最終検索日を読み込みます。年月日は文字列(str)で読み込んで、strptimeで年月日として認識させています。
データの取りこぼしが無いように前回調査された”SAVEDDATE”から1日だけ除算”timedelta(dayx = -1)”して、前回調査した一日前”FIRSTDATE”をデータ取得開始日になるようにしました。

```python:make_df.py
###データフレーム保存用
ANAYSIS_DATA = pd.DataFrame(index=[], columns=[])
new_list = {}  
dataframe = {}
###########################################################################
for SRCHTTL in SRCHTTLs: 
  SRCHWORD = SRCHTTLs_SRCHWRDs[SRCHTTL]
  print(SRCHTTL)
  if SRCHTTL in new_sheets:
    while True:
      try:
        FIRSTDATE = input("何年の論文から取得しますか？ e.g. 1950/01/01: ")
        FIRSTDATE = datetime.strptime(FIRSTDATE,"%Y/%m/%d")
        break;
      except:
        print ("日付は「●●●●/●●/●●」で記入してください")
  else:
    SAVEDDATE = SRCHTTLs_SAVEDDATEs[SRCHTTL]
###########################################################################
###日付を微修正。エクセルから読み込むときに日付として認識されていない場合がある。
    SAVEDDATE = str(SAVEDDATE)
    print (SAVEDDATE)
    Y_SAVEDDATE = SAVEDDATE[0:4]
    M_SAVEDDATE = SAVEDDATE[5:7]
    D_SAVEDDATE = SAVEDDATE[8:10]
    #SAVEDDATE = SAVEDDATE[0:3] + SAVEDDATE[5:6] + SAVEDDATE[7:8]
    print (Y_SAVEDDATE)
    print (M_SAVEDDATE)
    print (D_SAVEDDATE)
    SAVEDDATE = Y_SAVEDDATE+ "/" + M_SAVEDDATE+ "/" + D_SAVEDDATE
    print (SAVEDDATE)  
    SAVEDDATE = datetime.strptime(SAVEDDATE,"%Y/%m/%d")
    FIRSTDATE = (SAVEDDATE + timedelta(days = -1)).strptime("%Y/%m/%d")
#mindate 使うときはmaxdateも入れなければmindate作動せず。
#HANDLE = Entrez.esearch(db = "pubmed",mindate = FIRSTDATE, maxdate = DATE, term = SRCHWORD , retmax = Number)
```

##６－２．PubMedの論文IDの取得。
上の方で書きましたが、今回使用しているNCBIのAPIはやたらと接続が切られるので、3か月毎の論文IDを順次取得していくようにしました。このNCBIのAPI、検索日期間のmindateかmaxdateのどちらか一方だけを指定することができないみたい…。片方を指定するならばもう一方を指定しないとエラーが返って来てしまいます。
FIRSTDATEを3か月毎に足していって、LASTDATE（ってか現時点）を超えたらwhile文から抜けるようにしています。

```python:get_id.py
###ここで3か月毎に結果を収集
  results = []
  NEWIDs = []
  LASTDATE = datetime.now()
  print(FIRSTDATE)
  print(LASTDATE)
  while FIRSTDATE <= LASTDATE:
    F1 = datetime.strftime(FIRSTDATE,"%Y/%m/%d")
    F2 = (FIRSTDATE + relativedelta(months = 3) +relativedelta(days = -1)).strftime("%Y/%m/%d")
    print(F2)
    HANDLE = Entrez.esearch(db = "pubmed",mindate = F1, maxdate = F2, term = SRCHWORD)
    RECORDs = Entrez.read(HANDLE)
    IDs = RECORDs["IdList"]
    for ID in IDs:
      print(ID) 
      results.append((ID, DATE))
      NEWIDs.append(ID)     
    FIRSTDATE = FIRSTDATE + relativedelta(months = 3)
###########################################################################
###"results"のリストをデータフレームに変換
  LISTS_pre = pd.DataFrame(results, columns = ["PubMedID", "検索日"])
  LISTS = LISTS_pre.set_index(["PubMedID"])
  new_list = pd.DataFrame(LISTS)
```

##６－３．既存データとのマージ。
重複する論文IDが無いように既存データと今回得られたデータでマージをかけます。meargeとconcatのどちらでマージするのがいいんでしょうか…。この時点で論文IDはすべて出そろってCSVとして保存され、Excelフォーマットの検索日も更新されているはずです。

```python:get_id.py
  exist_list = pd.read_csv(SRCHTTL + ".csv", encoding="utf_8_sig")
  EXISTIDs_df = exist_list["PubMedID"]
  EXISTIDs = EXISTIDs_df.values.tolist()
  EXISTIDs = [str(n) for n in EXISTIDs]
  exist_list = exist_list.set_index(["PubMedID"])
###########################################################################
####既存IDと新規検索IDの比較。新規IDに含まれていたら既存リストから削除（-1日処理で洩れなくするため必要）
  for EXISTID in EXISTIDs:
    if (EXISTID in NEWIDs):
      exist_list.drop(int(EXISTID),inplace=True)
    else:pass
  exist_list = exist_list.reset_index()
  if len(exist_list["検索日"]) == 0:pass
  else:
    exist_list["検索日"] = exist_list["検索日"].dt.date
  new_list = new_list.reset_index()
###既存リストと新規リストの結合。mergeかconcatかappendか悩むところ。
  #dataframe[SRCHTTL] = pd.merge(exist_list, new_list, 
  #on = ["PubMedID", "タイトル", "著者", "文献", "要旨","検索日"], how = "outer")
  dataframe[SRCHTTL] = pd.concat([exist_list, new_list], axis = 0, sort=False)
###########################################################################
  EXL = pd.ExcelWriter("文献調査.xlsx", engine='xlsxwriter')
  dataframeB = pd.read_excel("文献調査.xlsx", sheet_name="検索式")
  dataframeB["最終検索日"] = DATE
  dataframeB.to_excel(EXL,sheet_name = "検索式",index=False )
  EXL.save()
  dataframe[SRCHTTL].to_csv(SRCHTTL + ".csv", index = False, encoding="utf-8_sig")
###ここでいったんセーブidだけ出そろうはず。
```

##６－４．タイトル、著者、序文等の取得。
今回使用したNCBIのAPIにはesearchってのとefetchてのがあり、このefetchってのがやたらと向こうのサーバに負担をかけるみたい。必要としない情報も一括でゲットしちゃっている模様。正直beautifulsoupで取った方が負担が少ない気もするが、NCBIのおっしゃる通りにefetchで取得します。
論文データが膨大すぎてExcelでは手が負えなくなったら嫌だな…と思って結果はCSVで出力されるようにしたけど、csvだとタイトルや序文中のカンマでいちいち区切ってしまったりするので”'”で囲う必要が出てしまいました。
tsvにしようとしたら何故だがうまくいかなかったです。ここも要検討ですね。
なお、このスクリプトでは単独のIDをアプライしているのに辞書形式かなんかで返ってきちゃってるみたいです。
ここら辺は一度に複数の論文IDをアプライするとか、まだ検討の余地ありです。

```python:efetch.py
  for i in range(len(dataframe[SRCHTTL])):
    if dataframe[SRCHTTL].iat[i,1] is DATE:
      handle = Entrez.efetch(db = "pubmed", id = dataframe[SRCHTTL].iat[i,0], rettype = "medline", retmode = "text")
      records = Medline.parse(handle)
      for record in records:
        ID = "'" + record["PMID"] + "'"
        TI = "'" + record["TI"] + "'"
        AU = "'" + ', '.join(record["AU"])+ "'"
        SO = "'" + record.get("SO")+ "'"
        AB = "'" + record.get("AB")+ "'"
        dataframe[SRCHTTL].iat[i,2] = TI
        dataframe[SRCHTTL].iat[i,3] = AU
        dataframe[SRCHTTL].iat[i,4] = SO
        dataframe[SRCHTTL].iat[i,5] = AB
        dataframe[SRCHTTL].to_csv(SRCHTTL + ".csv", index = False, encoding="utf-8_sig")
      sleep(1)
    else:pass
```

以上、初めてのQiita書き込みでした。実際に使ってみて気づいた点があれば更新していきます。