19【Pythonクローラー開発】APIを用いたNCBIデータ取得で直面した課題とその解決方法
1. はじめに
こんにちは!TechCommitメンバーの友季子です!
API(Application Programming Interface)を使ってNCBI(National Center for Biotechnology Information)のデータを取得するのは、非常に便利ですが、時にはエラーが発生することもありますよね。私も開発の過程でいくつかのトラブルに遭遇しましたが、その原因と対処方法をシェアしたいと思います。
今回は、私が実際に経験したエラーの原因と、それに対して行った対処法をまとめました。もし、NCBIのAPIを使っていて困っていることがあれば、この情報が少しでも役立てば嬉しいです!
▼NCBIのWebサイト※アメリカ国立生物工学情報センター
それでは、いくつかのエラーとその解決方法についてお話ししますね。
2. 全体の完成コード(サンプル)とAPI周りのコード解説
まずは、全体のコードをご覧ください。以下のコードは、NCBIからデータを取得し、取得結果をExcelファイルとして保存するものです。
私はざっくりお伝えすると、PubMed APIを使用して、指定されたジャーナル、発行日、ボリューム、号数、および著者に基づいて論文データを取得し、そのデータをExcelファイルに保存します。また、追加のデータを取得するためにCitmatch API等も利用しています。
①全体のコード
import requests
import os
import pandas as pd
from datetime import datetime
from xml.etree import ElementTree as ET
def format_date(date_str):
for fmt in ("%Y/%m/%d", "%Y年%m月%d日", "%Y年%m月", "%Y年"):
try:
return datetime.strptime(date_str, fmt).strftime("%Y/%m/%d")
except ValueError:
continue
return date_str
def fetch_pubmed_data(journal=None, pdat=None, volume=None, issue=None, authors=None):
base_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi"
db = "pubmed"
retmode = "json"
params = {
"db": db,
"retmode": retmode,
"term": f"{journal}[journal]" if journal else None,
"mindate": pdat,
"maxdate": pdat,
"volume": volume,
"issue": issue,
"author": authors
}
filtered_params = {key: value for key, value in params.items() if value}
response = requests.get(base_url, params=filtered_params)
if response.status_code == 200:
return response.json()
else:
print(f"Error: {response.status_code}")
print(response.text)
return {"error": f"Failed to fetch data, status code: {response.status_code}", "details": response.text}
def fetch_article_details(uid):
base_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi"
db = "pubmed"
retmode = "xml"
params = {
"db": db,
"retmode": retmode,
"id": uid
}
response = requests.get(base_url, params=params)
if response.status_code == 200:
return response.text
else:
print(f"Error fetching article {uid}: {response.status_code}")
return None
def parse_article_details(xml_data):
root = ET.fromstring(xml_data)
title = root.findtext(".//ArticleTitle")
term = title.replace(" ", "%20") # スペースを%20に置き換え
link = f"https://pubmed.ncbi.nlm.nih.gov/?term={term}"
return title, link
def fetch_citmatch_data(journal, volume, page):
url = f"https://pubmed.ncbi.nlm.nih.gov/api/citmatch/?method=field&journal={journal}&volume={volume}&page={page}"
response = requests.get(url)
if response.status_code == 200:
return response.json()
else:
print(f"Error: {response.status_code}")
print(response.text)
return {"error": f"Failed to fetch data, status code: {response.status_code}", "details": response.text}
def main():
print("PubMed Search")
journal = input("Enter journal name (or leave blank): ")
pdat = input("Enter publication date (YYYY/MM/DD, YYYY年MM月DD日, etc., or leave blank): ")
volume = input("Enter volume (or leave blank): ")
issue = input("Enter issue (or leave blank): ")
authors = input("Enter authors (format: Surname Initial, or leave blank): ")
if not (journal or pdat or volume or issue or authors):
print("Error: At least one search parameter must be provided.")
return
result = fetch_pubmed_data(journal, pdat, volume, issue, authors)
if "error" in result:
print(result)
return
titles = []
links = []
if "esearchresult" in result and "idlist" in result["esearchresult"]:
for uid in result["esearchresult"]["idlist"]:
details = fetch_article_details(uid)
if details:
title, link = parse_article_details(details)
titles.append(title)
links.append(link)
# Fetch additional data using citmatch API
citmatch_result = fetch_citmatch_data("Front Immunol", "13", "826091")
if "result" in citmatch_result and "uids" in citmatch_result["result"]:
for item in citmatch_result["result"]["uids"]:
uid = item["pubmed"]
details = fetch_article_details(uid)
if details:
title, link = parse_article_details(details)
titles.append(title)
links.append(link)
data = {
'Title': titles,
'Link': links
}
df = pd.DataFrame(data)
output_directory = os.path.join(os.path.expanduser('~'), 'Documents')
output_file_path = os.path.join(output_directory, 'ncbi_result_info_api.xlsx')
df.to_excel(output_file_path, index=False, engine='openpyxl')
print(f"Excelファイル '{output_ile_path}' に保存されたよ!")
if __name__ == "__main__":
main()
#2024_08:09_ncbiのデータ取得でAPI版成功_LINKの文字列を作成してデータとれる_テスト進めてPROに出す
②APIまわりの解説をコメントで綴りました。
import requests
import os
import pandas as pd
from datetime import datetime
from xml.etree import ElementTree as ET
def format_date(date_str):
for fmt in ("%Y/%m/%d", "%Y年%m月%d日", "%Y年%m月", "%Y年"):
try:
return datetime.strptime(date_str, fmt).strftime("%Y/%m/%d")
except ValueError:
continue
return date_str
def fetch_pubmed_data(journal=None, pdat=None, volume=None, issue=None, authors=None):
base_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi" # PubMed Search APIのエンドポイントURL
db = "pubmed" # データベースとしてPubMedを指定
retmode = "json" # レスポンス形式をJSONに指定
params = {
"db": db, # 使用するデータベース
"retmode": retmode, # レスポンス形式
"term": f"{journal}[journal]" if journal else None, # ジャーナル名で検索する場合のクエリ
"mindate": pdat, # 発行日(最小日付)
"maxdate": pdat, # 発行日(最大日付)
"volume": volume, # ボリューム番号
"issue": issue, # イシュー番号
"author": authors # 著者名
}
filtered_params = {key: value for key, value in params.items() if value}
response = requests.get(base_url, params=filtered_params)
if response.status_code == 200:
return response.json() # 成功時はJSONデータを返す
else:
print(f"Error: {response.status_code}")
print(response.text)
return {"error": f"Failed to fetch data, status code: {response.status_code}", "details": response.text}
def fetch_article_details(uid):
base_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi" # PubMed Fetch APIのエンドポイントURL
db = "pubmed" # データベースとしてPubMedを指定
retmode = "xml" # レスポンス形式をXMLに指定
params = {
"db": db, # 使用するデータベース
"retmode": retmode, # レスポンス形式
"id": uid # 取得する記事のID
}
response = requests.get(base_url, params=params)
if response.status_code == 200:
return response.text # 成功時はXMLデータを返す
else:
print(f"Error fetching article {uid}: {response.status_code}")
return None
def parse_article_details(xml_data):
root = ET.fromstring(xml_data)
title = root.findtext(".//ArticleTitle") # XMLから記事タイトルを抽出
term = title.replace(" ", "%20") # スペースを%20に置き換え、URLエンコード用
link = f"https://pubmed.ncbi.nlm.nih.gov/?term={term}" # PubMedのリンクを生成
return title, link
def fetch_citmatch_data(journal, volume, page):
url = f"https://pubmed.ncbi.nlm.nih.gov/api/citmatch/?method=field&journal={journal}&volume={volume}&page={page}" # Citmatch APIのエンドポイントURL
response = requests.get(url) # Citmatch APIへのリクエストを送信
if response.status_code == 200:
return response.json() # 成功時はJSONデータを返す
else:
print(f"Error: {response.status_code}")
print(response.text)
return {"error": f"Failed to fetch data, status code: {response.status_code}", "details": response.text}
def main():
print("PubMed Search")
journal = input("Enter journal name (or leave blank): ")
pdat = input("Enter publication date (YYYY/MM/DD, YYYY年MM月DD日, etc., or leave blank): ")
volume = input("Enter volume (or leave blank): ")
issue = input("Enter issue (or leave blank): ")
authors = input("Enter authors (format: Surname Initial, or leave blank): ")
if not (journal or pdat or volume or issue or authors):
print("Error: At least one search parameter must be provided.")
return
result = fetch_pubmed_data(journal, pdat, volume, issue, authors) # PubMed Search APIを使用して検索
if "error" in result:
print(result)
return
titles = []
links = []
if "esearchresult" in result and "idlist" in result["esearchresult"]:
for uid in result["esearchresult"]["idlist"]:
details = fetch_article_details(uid) # PubMed Fetch APIを使用して文献の詳細を取得
if details:
title, link = parse_article_details(details)
titles.append(title)
links.append(link)
# Fetch additional data using citmatch API
citmatch_result = fetch_citmatch_data("Front Immunol", "13", "826091") # Citmatch APIを使用して追加データを取得
if "result" in citmatch_result and "uids" in citmatch_result["result"]:
for item in citmatch_result["result"]["uids"]:
uid = item["pubmed"]
details = fetch_article_details(uid) # PubMed Fetch APIを使用して文献の詳細を取得
if details:
title, link = parse_article_details(details)
titles.append(title)
links.append(link)
data = {
'Title': titles,
'Link': links
}
df = pd.DataFrame(data)
output_directory = os.path.join(os.path.expanduser('~'), 'Documents')
output_file_path = os.path.join(output_directory, 'ncbi_result_info_api.xlsx')
df.to_excel(output_file_path, index=False, engine='openpyxl')
print(f"Excelファイル '{output_file_path}' に保存されたよ!")
if __name__ == "__main__":
main()
3.API周りの仕様
①PubMed E-Utilities API (esearch.fcgi)
目的: PubMedデータベースで文献を検索し、文献IDを取得します。
解説:
base_url: esearch.fcgiはPubMedで文献を検索するエンドポイントです。
params: リクエストパラメータにはデータベース (db)、返却形式 (retmode)、検索クエリ (term)、日付範囲 (mindate, maxdate)、巻号 (volume)、号 (issue)、著者 (author) があります。
requests.get(): APIリクエストを送信し、レスポンスをJSON形式で受け取ります。
②PubMed E-Utilities API (efetch.fcgi)
目的: 文献IDを基に文献の詳細情報を取得します。
解説
base_url: efetch.fcgiは文献の詳細情報を取得するエンドポイントです。
params: リクエストパラメータにはデータベース (db)、返却形式 (retmode)、文献ID (id) があります。
requests.get(): APIリクエストを送信し、レスポンスをXML形式で受け取ります。
③PubMed Citmatch API
目的: 特定のジャーナル、巻号、ページに基づいて文献を検索します。
解説:
url: citmatchエンドポイントを使って、指定されたジャーナル、巻号、ページに基づく文献を検索します。
requests.get(): APIリクエストを送信し、レスポンスをJSON形式で受け取ります。
これらのAPIを組み合わせて、PubMedから文献の検索と詳細取得を行い、結果をExcelファイルに保存しています。
4.NCBI APIのエラーとトラブルシューティング
APIの設定で悩んだこと共有
APIの設定が上手くいかない問題があるとき、下記の対応をしました。
①Excelに出力されたLINK文字列を開いてみると開けない・・・
【対処方法】URLが以下のサンプルリクエストのようになっているか確認する。
#URLが以下のサンプルリクエストのようになっているか確認する。
https://pubmed.ncbi.nlm.nih.gov/api/citmatch/?method=field&journal=Front%20Immunol&volume=13&page=826091
②クリックが上手くいかないときや、スクレイピングがうまくできない・・・
【対処方法】取得したい値がどこまで正しく取れているのかprint文を用いてデバッグする。
③Excelで出力できない問題のコード※実装の修正
【対処方法】65行目あたりを以下に修正したら、Excel出力できました。
link = f"https://pubmed.ncbi.nlm.nih.gov/{title}/"
④ターミナルにジャーナル名を一部だけいれてデータ取得が出来るようにもしたいが、うまくいかない
(※最終的にExcel出力したLink列をブラウザに入力して、データ取得をしたいが、開発の段階ではターミナルに出力してデータ取得を確認していた状況です。)
【対処方法】APIの仕様書通り、Linkの文字列を生成してあげるようにする。
サンプルでは以下の通りとなっていました。
リクエスト
https://pubmed.ncbi.nlm.nih.gov/api/citmatch/?method=field&journal=Front%20Immunol&volume=13&page=826091
レスポンス
{"version":"1.0","operation":"citmatch","success":true,"result":{"count":1,"type":"uids","uids":[{"pubmed":"35251006"}]}}
※リクエストをブラウザで入力すると、上記レスポンスが返ってくることも確認できました。
5.スクレイピングのトラブルシューティング
エラーメッセージの確認
エラーメッセージは問題の手がかりになります。例えば、認証エラー、リクエストフォーマットの誤り、アクセス制限などの情報が含まれることがあります。
URLとパラメータの確認
URLが正しいか、必要なパラメータがすべて含まれているか確認します。URLエンコードが必要な場合もあります。
HTTPリクエストの確認
リクエストメソッド(GETやPOSTなど)が正しいか、リクエストヘッダーやクエリパラメータが適切に設定されているか確認します。
デバッグ
スクリプトにprint文やログを追加し、実際に送信しているリクエストや受け取ったレスポンスを確認します。Jupyter Notebookを使用している場合は、セルごとに実行してデバッグするのが便利です。
APIドキュメントの確認
APIの公式ドキュメントを確認し、仕様が変更されていないか、使用方法が正しいか確認します。
サンプルコードのテスト
APIのサンプルコードや公式のテストコードを試して、基本的な動作を確認します。
6.公式サイト
以上です。
あなたのお役に立てば嬉しいです!