⚕️

NCBIのAPI経由でPubMedから症例報告を取得する

2023/06/19に公開

Entrez Programming Utilitiesについて。
PubMedのフィルタ条件指定の方法などが公式ドキュメントに書いてなかったので。

https://www.ncbi.nlm.nih.gov/books/NBK25500/

なんらかの病名を指定してcase reportを取得する場面を想定します。

API Keyの取得 (Optional)

アカウント作成後のページ で。
何度も叩かないのであれば無くて良い。

PubMed IDの取得

こんな感じのコードを書いて ESearch API を叩く。自分はPythonにしたけどHTTPがしゃべれてXMLがパースできれば言語は何でも良い。
恐らく病名は MeSH (Medical Subject Headings) で指定した方が漏れなく取得できるが下のコードではサボっている。

import requests
import xml.etree.ElementTree as ET

BASEURL_SEARCH = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi'

API_KEY = "xxxx"


def fetch_pmids(
    disease_name: str,
    min_date_ymd: str = "2000/01/01",
    max_date_ymd: str = "2023/04/01",
    offset: int = 0,
    limit: int = 20,
) -> list[str]:
    ret = _call_search_api(disease_name, min_date_ymd, max_date_ymd, offset, limit)
    return _extract_document_ids_from_api_result(ret)


def _call_search_api(disease_name: str, min_date_ymd: str, max_date_ymd: str, offset: int, limit: int) -> ET.XML | None:
    params = {
        'db': 'pubmed',
        'api_key': API_KEY,
        # 検索条件
        # language: japanese OR english
        # filter: casereports
        # filter: text availability: abstract (free full-textを追加するなら "free full text"[sb] も追加) 
        # filter: humans
        # 疾患名 in title
        'term': f'"{disease_name}"[title] AND hasabstract AND (japanese[lang] OR english[lang]) AND casereports[filter] AND humans[filter]',
        'usehistory': 'y',
        'datetype': 'pdat',
        'mindate': min_date_ymd,
        'maxdate': max_date_ymd,
        'retstart': offset,
        'retmax': limit,
    }
    res = requests.get(BASEURL_SEARCH, params)
    # print(res.text)
    rootXml = ET.fromstring(res.text)
    return rootXml


def _extract_document_ids_from_api_result(rootXml: ET.XML) -> list[str]:
    doc_ids = []
    for elm in rootXml.findall("IdList/Id"):
        doc_ids.append(elm.text)
    return doc_ids

急性心不全の症例報告のPubMed IDを取得してみる。

disease_name = 'acute heart failure'
pubmed_ids = fetch_pmids(disease_name, limit=10)
pprint(pubmed_ids)

出力

['37043755',
 '37012142',
 '37002114',
 '36988947',
 '36981930',
 '36977512',
 '36948447',
 '36940128',
 '36935111',
 '36934324']

1つ目の結果 37043755 は次のcase reportなので正しそう。
https://pubmed.ncbi.nlm.nih.gov/37043755/

PubMed ID指定でAbstractの取得

EFetch APIを使う。

BASEURL_FETCH = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi'

def call_fetch_api(pmids: list[str]) -> ET.XML:
    params = {
        'db': 'pubmed',
        'api_key': API_KEY,
        'id': ",".join(pmids),
        'rettype': 'abstract'
    }
    res = requests.get(BASEURL_FETCH, params)
    rootXml = ET.fromstring(res.text)
    return rootXml

XMLをパースする (非常に面倒くさいしAbstractText要素にHTMLタグが含まれていると素直に取得できない)

xml_result_fetch_api = call_fetch_api(pubmed_ids)

for el in xml_result_fetch_api.findall("PubmedArticle"):
  el_article = el.find('MedlineCitation').find('Article')
  # print(ET.tostring(el_article))
  title = el_article.find("ArticleTitle").text
  el_abst = el.find('MedlineCitation').find('Article').find('Abstract')
  if el_abst:
    print('Title:')
    print(title)
    abstract_text = el_abst.findtext('AbstractText')
    if abstract_text is None or len(abstract_text.strip()) == 0:
        # AbstractText要素にHTMLタグが含まれていると text プロパティで取得できない
        abstract_text = ET.tostring(el_abst.find("AbstractText"), encoding="unicode")[14:-15].strip()
    print('Abstract:')
    print(abstract_text)

EFetch API に QueryKey と WebEnv を渡す方法

Entrez Programming Utilities のAPIサーバーは usehistory=y を指定すると ESearch API の結果をサーバーに保持してくれるので、改めてPubMed IDのリストを渡さなくとも QueryKeyWebEnv を指定すればよい。

QueryKeyWebEnv は ESearch APIの結果に含まれている。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE eSearchResult PUBLIC "-//NLM//DTD esearch 20060628//EN" "https://eutils.ncbi.nlm.nih.gov/eutils/dtd/20060628/esearch.dtd">
<eSearchResult>
  <Count>3638</Count>
  <RetMax>10</RetMax>
  <RetStart>0</RetStart>
  <QueryKey>1</QueryKey>            ← これ
  <WebEnv>MCID_xxxxxxxxxxx</WebEnv> ← これ
  <IdList>
    <Id>37043755</Id>
    <Id>37012142</Id>
    <Id>37002114</Id>
    <Id>36988947</Id>
    <Id>36981930</Id>
    <Id>36977512</Id>
    <Id>36948447</Id>
    <Id>36940128</Id>
    <Id>36935111</Id>
    <Id>36934324</Id>
  </IdList>
  <TranslationSet>
    <Translation>
      <From>acute</From>
      <To>"acute"[All Fields] OR "acutely"[All Fields] OR "acutes"[All Fields]</To>
    </Translation>
    <Translation>
      <From>heart failure</From>
      <To>"heart failure"[MeSH Terms] OR ("heart"[All Fields] AND "failure"[All Fields]) OR "heart failure"[All Fields]</To>
    </Translation>
    <Translation>
      <From>casereports[filter]</From>
      <To>Case Reports[PT]</To>
    </Translation>
    <Translation>
      <From>humans[filter]</From>
      <To>humans[MH]</To>
    </Translation>
  </TranslationSet>
  <QueryTranslation>("acute"[All Fields] OR "acutely"[All Fields] OR "acutes"[All Fields]) AND ("heart failure"[MeSH Terms] OR ("heart"[All Fields] AND "failure"[All Fields]) OR "heart failure"[All Fields]) AND ("japanese"[Language] OR "english"[Language]) AND "case reports"[Publication Type] AND "humans"[MeSH Terms] AND 2000/01/01:2023/04/01[Date - Publication]   
  </QueryTranslation>
</eSearchResult>

まとめ

公式ドキュメントのサンプルコードがPerlで面くらったけどXMLさえなんとかしたら使えます。

Discussion