mcpField
O
openalex-database

openalex-database

github.com/jaechang-hitsデータ・データベース38479 回インストール1 週間前に更新
リポジトリ

openalex-database について

openalex-databaseは、OpenAlex REST APIで学術記録を検索するスキルです。分野横断の文献検索、引用分析、著者検索、メタデータ補完に利用できます。

主な機能

  1. 01条件指定とページネーションによる論文検索
  2. 02DOIまたはOpenAlex IDでの論文検索
  3. 03著者とORCIDレコードの検索
  4. 04引用ネットワークの取得
  5. 05概念・機関・出版物の分析

活用シーン

  1. 01システマティックレビュー用コーパスの構築
  2. 02引用・共著ネットワークの分析
  3. 03DOIリストへの論文メタデータ追加
  4. 04テーマや国別の研究動向の追跡

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jaechang-hits/sciagent-skills --skill openalex-database

インストール後、Claude Code で/openalex-databaseを入力して読み込まれたか確認します。

詳細

OpenAlex 学術データベース

概要

OpenAlex は、2億5,000万件以上の学術論文、9,000万人以上の著者、11万以上のジャーナル、1万以上の機関を収録した、無料で利用できるオープンアクセスのインデックスです。Microsoft Academic Graph の後継として、抄録、オープンアクセスの URL、被引用数、参考文献、著者を識別する ID(ORCID)、概念タグなど、豊富なメタデータを提供します。REST API は認証なしで1日あたり最大100,000件のリクエストが可能です。メールアドレスを指定するポライトプールを利用すると、優先的に処理されます。

使用する場面

  • 生物医学分野に限らず、あらゆる学術分野を横断して検索し、システマティックレビュー用の文献コーパスを構築する
  • 計量書誌学的分析、共引用クラスタリング、参考文献グラフの探索のために、引用ネットワークを取得する
  • ORCID/OpenAlex の著者 ID を使って、機関をまたいだ著者 ID の曖昧さを解消する
  • 複数の DOI に対応するオープンアクセスの全文 URL を見つけ、ダウンロード可能な論文コーパスを構築する
  • 出版年、機関、国、研究概念ごとの出版傾向を分析する
  • DOI やタイトルから取得したメタデータ(被引用数、抄録、掲載誌)で論文リストを充実させる
  • PubMed に収載された生物医学文献には pubmed-database を、bioRxiv のプレプリントには biorxiv-database を使用する

前提条件

  • Python パッケージ:requests、pandas
  • データ要件:DOI、OpenAlex の Work ID(W…)、著者名、ORCID ID、または検索語
  • 環境:インターネット接続。API キーは不要です
  • レート制限:匿名の場合は10リクエスト/秒です。[email protected] クエリパラメーターを追加すると、ポライトプールに参加できます(優先度が高くなりますが、上限は同じです)
pip install requests pandas

クイックスタート

import requests

BASE = "https://api.openalex.org"

# Search for works on CRISPR
r = requests.get(f"{BASE}/works",
                 params={"search": "CRISPR gene editing",
                         "filter": "publication_year:2023",
                         "per_page": 5,
                         "mailto": "[email protected]"})
r.raise_for_status()
data = r.json()
print(f"Total results: {data['meta']['count']}")
for work in data["results"][:3]:
    print(f"  {work['title'][:80]} ({work['publication_year']}) cites={work['cited_by_count']}")

コア API

クエリ 1:論文の検索

タイトルや抄録のキーワードとフィルターを使って論文を検索します。

import requests, pandas as pd

BASE = "https://api.openalex.org"

def search_works(query, filters=None, per_page=25, mailto="[email protected]"):
    params = {"search": query, "per_page": per_page, "mailto": mailto}
    if filters:
        params["filter"] = ",".join(f"{k}:{v}" for k, v in filters.items())
    r = requests.get(f"{BASE}/works", params=params)
    r.raise_for_status()
    return r.json()

# Search with filters
data = search_works("single-cell RNA sequencing",
                    filters={"publication_year": "2020-2024",
                             "open_access.is_oa": "true"},
                    per_page=10)

print(f"Open-access scRNA-seq papers 2020-2024: {data['meta']['count']}")
rows = []
for w in data["results"]:
    rows.append({
        "title": w["title"],
        "year": w["publication_year"],
        "citations": w["cited_by_count"],
        "doi": w.get("doi"),
        "oa_url": w.get("open_access", {}).get("oa_url"),
    })
df = pd.DataFrame(rows)
print(df[["title", "year", "citations"]].head())
# Paginate through all results
def paginate_works(query, filters=None, max_results=200, mailto="[email protected]"):
    """Retrieve up to max_results works, paginating automatically."""
    all_results = []
    cursor = "*"
    while len(all_results) < max_results:
        params = {"search": query, "per_page": 200,
                  "cursor": cursor, "mailto": mailto}
        if filters:
            params["filter"] = ",".join(f"{k}:{v}" for k, v in filters.items())
        r = requests.get(f"{BASE}/works", params=params)
        data = r.json()
        all_results.extend(data["results"])
        cursor = data["meta"].get("next_cursor")
        if not cursor:
            break
    return all_results[:max_results]

papers = paginate_works("transformer protein structure", max_results=100)
print(f"Retrieved {len(papers)} papers")
クエリ 2:DOI または OpenAlex ID で検索

DOI または OpenAlex ID を指定して、単一の論文を取得します。

import requests

BASE = "https://api.openalex.org"

# By DOI
doi = "10.1038/s41592-019-0458-z"  # Scanpy paper
r = requests.get(f"{BASE}/works/https://doi.org/{doi}",
                 params={"mailto": "[email protected]"})
r.raise_for_status()
work = r.json()

print(f"Title   : {work['title']}")
print(f"Year    : {work['publication_year']}")
print(f"Citations: {work['cited_by_count']}")
print(f"Journal : {work.get('primary_location', {}).get('source', {}).get('display_name')}")
abstract = work.get("abstract_inverted_index")
if abstract:
    # Reconstruct abstract from inverted index
    words = {pos: word for word, positions in abstract.items() for pos in positions}
    text = " ".join(words[i] for i in sorted(words))
    print(f"Abstract (first 200): {text[:200]}")
クエリ 3:著者の検索と ORCID の照会

著者レコードを検索し、ORCID 識別子を特定して、出版物のリストを取得します。

import requests, pandas as pd

BASE = "https://api.openalex.org"

# Search for an author
r = requests.get(f"{BASE}/authors",
                 params={"search": "Jennifer Doudna",
                         "per_page": 5,
                         "mailto": "[email protected]"})
authors = r.json()["results"]

for a in authors[:3]:
    print(f"Author: {a['display_name']}")
    print(f"  OpenAlex ID : {a['id']}")
    print(f"  ORCID       : {a.get('orcid', 'n/a')}")
    # 2024+: singular `last_known_institution` was replaced by plural list `last_known_institutions[0]`
    insts = a.get("last_known_institutions") or []
    print(f"  Institution : {insts[0]['display_name'] if insts else 'n/a'}")
    print(f"  Works count : {a['works_count']}")
    print(f"  h-index     : {a['summary_stats'].get('h_index', 'n/a')}")
    print()
# Get all papers by an author (by ORCID)
orcid = "0000-0001-9161-999X"  # Jennifer A. Doudna (correct ORCID; the 8742-3594 variant 404s)
r = requests.get(f"{BASE}/works",
                 params={"filter": f"author.orcid:{orcid}",
                         "sort": "cited_by_count:desc",
                         "per_page": 10,
                         "mailto": "[email protected]"})
papers = r.json()["results"]
for p in papers[:5]:
    print(f"  [{p['publication_year']}] {p['title'][:70]} (cites: {p['cited_by_count']})")
クエリ 4:引用ネットワークの取得

論文が引用する文献と、その論文を引用している文献を取得します。

import requests, pandas as pd

BASE = "https://api.openalex.org"

work_id = "W2018426904"  # CRISPR paper

# Get what this paper references
r = requests.get(f"{BASE}/works/{work_id}",
                 params={"select": "referenced_works,cited_by_count,title",
                         "mailto": "[email protected]"})
work = r.json()
ref_ids = work.get("referenced_works", [])
print(f"'{work['title']}' cites {len(ref_ids)} papers")
print(f"Total citations: {work['cited_by_count']}")

# Fetch metadata for references (batch)
if ref_ids:
    ids_str = "|".join(id.split("/")[-1] for id in ref_ids[:10])
    r2 = requests.get(f"{BASE}/works",
                      params={"filter": f"openalex_id:{ids_str}",
                              "per_page": 10,
                              "mailto": "[email protected]"})
    refs = r2.json()["results"]
    for ref in refs[:5]:
        print(f"  [{ref['publication_year']}] {ref['title'][:70]}")
クエリ 5:概念・トピックによるフィルタリングと傾向分析

研究概念で絞り込み、出版傾向を分析します。

import requests, pandas as pd

BASE = "https://api.openalex.org"

# Get concept ID for "Machine Learning". OpenAlex concept search is brittle for
# multi-word phrases ("machine learning biology" returns 0); use the single core term.
r = requests.get(f"{BASE}/concepts",
                 params={"search": "machine learning",
                         "per_page": 3,
                         "mailto": "[email protected]"})
concepts = r.json()["results"]
for c in concepts[:3]:
    print(f"Concept: {c['display_name']} (ID: {c['id']}, level: {c['level']})")

# Count papers per year for a concept
concept_id = "C154945302"  # Machine learning (OpenAlex ID)
r2 = requests.get(f"{BASE}/works",
                  params={"filter": f"concepts.id:{concept_id},publication_year:2015-2024",
                          "group_by": "publication_year",
                          "per_page": 200,
                          "mailto": "[email protected]"})
groups = r2.json()["group_by"]
df = pd.DataFrame(groups).rename(columns={"key": "year", "count": "papers"})
df = df.sort_values("year")
print(df.tail(5).to_string(index=False))
クエリ 6:機関と掲載媒体の検索

特定の機関、ジャーナル、または学会の論文を取得します。

import requests, pandas as pd

BASE = "https://api.openalex.org"

# Papers from a specific journal in the last year
r = requests.get(f"{BASE}/works",
                 params={
                     "filter": "primary_location.source.issn:0028-0836,publication_year:2023",
                     "per_page": 10,
                     "sort": "cited_by_count:desc",
                     "mailto": "[email protected]"
                 })
data = r.json()
print(f"Nature papers 2023: {data['meta']['count']}")
for w in data["results"][:5]:
    print(f"  [{w['cited_by_count']} cites] {w['title'][:70]}")

主要な概念

反転インデックス形式の抄録

OpenAlex は、著作権上の制約により、抄録をプレーンテキストではなく反転インデックス(単語 → 位置のリスト)として保存します。次のコードで復元できます:" ".join(words[i] for i in sorted({pos: w for w, ps in inv.items() for pos in ps}))。

カーソルベースのページネーション

OpenAlex はオフセットではなく、カーソルベースのページネーション(cursor パラメーター)を使用します。cursor="*" から開始し、各レスポンスの next_cursor を使用します。1ページあたりの最大結果数は200件です。カーソルによるページネーションでは最大10,000件を取得できます。

よくあるワークフロー

ワークフロー 1:システマティックな文献検索

目的:システマティックレビュー用に、トピック検索に一致するすべての論文をメタデータとともにダウンロードします。

import requests, time, pandas as pd

BASE = "https://api.openalex.org"
MAILTO = "[email protected]"

def systematic_search(query, year_from, year_to, max_results=500):
    """Paginate through results and return a DataFrame."""
    all_results = []
    cursor = "*"
    filters = f"publication_year:{year_from}-{year_to}"

    while len(all_results) < max_results:
        r = requests.get(f"{BASE}/works",
                         params={"search": query, "filter": filters,
                                 "per_page": 200, "cursor": cursor,
                                 "mailto": MAILTO,
                                 "select": "id,doi,title,publication_year,cited_by_count,open_access"})
        r.raise_for_status()
        data = r.json()
        all_results.extend(data["results"])
        cursor = data["meta"].get("next_cursor")
        if not cursor:
            break
        time.sleep(0.1)

    rows = []
    for w in all_results[:max_results]:
        rows.append({
            "openalex_id": w["id"],
            "doi": w.get("doi"),
            "title": w.get("title"),
            "year": w.get("publication_year"),
            "citations": w.get("cited_by_count"),
            "is_oa": w.get("open_access", {}).get("is_oa"),
            "oa_url": w.get("open_access", {}).get("oa_url"),
        })
    return pd.DataFrame(rows)

# Example: papers on drug repurposing 2019-2024
df = systematic_search("drug repurposing machine learning", 2019, 2024, max_results=200)
df.to_csv("drug_repurposing_literature.csv", index=False)
print(f"Retrieved {len(df)} papers")
print(df[["title", "year", "citations", "is_oa"]].head(5).to_string(index=False))
ワークフロー 2:著者の共同研究ネットワーク

目的:研究者の共著者を特定し、共同研究ネットワークを分析します。

import requests, time, pandas as pd
from collections import defaultdict

BASE = "https://api.openalex.org"
MAILTO = "[email protected]"

def get_author_works(orcid, max_papers=50):
    r = requests.get(f"{BASE}/works",
                     params={"filter": f"author.orcid:{orcid}",
                             "sort": "cited_by_count:desc",
                             "per_page": min(max_papers, 200),
                             "mailto": MAILTO})
    r.raise_for_status()
    return r.json()["results"]

def extract_collaborators(works):
    collab_count = defaultdict(int)
    for work in works:
        for authorship in work.get("authorships", []):
            author = authorship.get("author", {})
            name = author.get("display_name")
            if name:
                collab_count[name] += 1
    return collab_count

# Map collaborators for a researcher
orcid = "0000-0001-9161-999X"   # Jennifer A. Doudna
works = get_author_works(orcid, max_papers=50)
collabs = extract_collaborators(works)

top_collabs = sorted(collabs.items(), key=lambda x: -x[1])
df = pd.DataFrame(top_collabs, columns=["collaborator", "papers_together"])
df = df[df["collaborator"] != "Jennifer A. Doudna"]  # exclude self
print("Top collaborators:")
print(df.head(10).to_string(index=False))
df.to_csv("collaboration_network.csv", index=False)

主要なパラメーター

パラメーターモジュールデフォルト範囲 / オプション効果
searchすべて—テキスト文字列タイトルと抄録を対象とした全文検索
filterすべて—field:value,field:value構造化フィルター(AND 条件)
per_pageすべて251–2001ページあたりの結果数
cursorページネーション"*"カーソル文字列ページネーション用カーソル
sort論文relevancecited_by_count:desc、publication_year:desc結果の並べ替え
selectすべてすべてのフィールドカンマ区切りのフィールド名レスポンスのフィールドを制限(高速化)
group_by論文—フィールド名フィールドごとに件数を集計
mailtoすべて—メールアドレスポライトプールへのアクセス(優先処理)

ベストプラクティス

  1. 必ず mailto を含めます:すべてのリクエストに [email protected] を追加すると、丁寧な利用者向けプールに登録され、レート制限を受けずに優先処理されます。

  2. 大量のページネーションには select を使います:何千件もの結果をページネーションで取得する場合は、必要なフィールドだけ(select=id,doi,title,cited_by_count)を指定して、レスポンスサイズを減らし、解析を高速化します。

  3. オフセットではなくカーソルページネーションを使います:OpenAlex は 10,000 件を超えるオフセットページネーションをサポートしていません。深い階層までたどる場合は、カーソルベースのページネーション(cursor パラメーター)を使います。

  4. 逆引きインデックスからアブストラクトを再構築します:アブストラクトのない論文もあります。再構築時の KeyError を避けるため、abstract_inverted_index is not None を確認してください。

  5. Work ID をキーにキャッシュします:OpenAlex Work ID(W…)は安定した識別子です。プロジェクト内で同じ論文のメタデータを再取得しないよう、取得したメタデータをキャッシュします。

よく使うレシピ

レシピ:DOI からメタデータを一括検索

使用する場面:DOI のリストに、被引用数、オープンアクセス URL、アブストラクトを追加します。

import requests, pandas as pd, time

BASE = "https://api.openalex.org"

dois = [
    "10.1038/s41592-019-0458-z",
    "10.1186/s13059-021-02519-4",
    "10.1038/s41587-019-0071-9",
]

rows = []
for doi in dois:
    r = requests.get(f"{BASE}/works/https://doi.org/{doi}",
                     params={"select": "title,publication_year,cited_by_count,open_access",
                             "mailto": "[email protected]"})
    if r.ok:
        w = r.json()
        rows.append({
            "doi": doi, "title": w.get("title"),
            "year": w.get("publication_year"),
            "citations": w.get("cited_by_count"),
            "is_oa": w.get("open_access", {}).get("is_oa"),
        })
    time.sleep(0.1)

df = pd.DataFrame(rows)
print(df.to_string(index=False))
レシピ:国別の論文数を集計

使用する場面:特定のトピックに関する研究成果を地理的に分析します。

import requests, pandas as pd

r = requests.get(
    "https://api.openalex.org/works",
    params={"search": "CRISPR therapeutics",
            "filter": "publication_year:2023",
            "group_by": "authorships.institutions.country_code",
            "per_page": 200,
            "mailto": "[email protected]"}
)
df = pd.DataFrame(r.json()["group_by"]).rename(columns={"key": "country", "count": "papers"})
print(df.sort_values("papers", ascending=False).head(10).to_string(index=False))
レシピ:分野で最も引用されている論文を検索

使用する場面:背景知識を得るために、トピックに関する重要な論文を特定します。

import requests, pandas as pd

r = requests.get(
    "https://api.openalex.org/works",
    params={"search": "protein language model",
            "sort": "cited_by_count:desc",
            "per_page": 10,
            "mailto": "[email protected]"}
)
for w in r.json()["results"]:
    print(f"[{w['cited_by_count']:5d} cites] ({w['publication_year']}) {w['title'][:70]}")

トラブルシューティング

問題原因解決策
HTTP 429 Too Many Requestsレート制限を超過していますリクエストの間に time.sleep(0.15) を追加します。丁寧な利用者向けプール(mailto)を使います
空の abstract_inverted_indexアブストラクトがありません再構築する前に None かどうかを確認します。アブストラクトのない論文もあります
カーソルページネーションで重複が返されるカーソルの有効期限が切れていますcursor="*" を指定してページネーションを再開します
DOI 検索で 404 が返されるDOI が OpenAlex に登録されていません代わりにタイトルで検索します。OpenAlex は 2 億 5,000 万件以上をインデックス化していますが、文献をすべて網羅しているわけではありません
フィルターで結果が 0 件になるフィールド名が間違っているか、フィルター構文に誤りがありますフィルター構文が field:value(スペースなし)であることを確認し、API ドキュメントでフィールド名を確認します
cited_by_count が最新ではない被引用数は定期的に更新されます被引用数は定期的に更新されますが、数日遅れる場合があります。正確な数値ではなく、傾向の把握に使ってください

関連スキル

  • pubmed-database — MeSH 統制語彙を使った生物医学文献。臨床医学やライフサイエンスの分野に適しています
  • biorxiv-database — OpenAlex にまだインデックス化されていない、生物医学分野のプレプリント
  • scientific-brainstorming — 文献を入力として仮説を生成するワークフロー
  • literature-review — OpenAlex を使ったシステマティック・レビューの設計ガイド

参考資料

コメント