mcpField
O
openalex-database

openalex-database

github.com/jaechang-hits数据与数据库38479 次安装1 周前更新
代码仓库

关于 openalex-database

openalex-database 是通过 OpenAlex REST API 查询学术记录的技能,适用于跨学科文献检索、引用分析、作者查询和元数据补充。

主要功能

  1. 01按条件搜索作品并分页
  2. 02通过 DOI 或 OpenAlex ID 查询作品
  3. 03查找作者及 ORCID 记录
  4. 04获取引用网络
  5. 05分析概念、机构和出版物

适用场景

  1. 01构建系统性文献综述语料
  2. 02绘制引用与合著网络
  3. 03补充 DOI 列表的论文元数据
  4. 04按主题或国家追踪研究产出

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/openalex-database确认已加载。

详细介绍

OpenAlex 学术数据库

概述

OpenAlex 是一个免费、开放访问的索引,收录了 2.5 亿多篇学术著作、9,000 多万名作者、11 万多种期刊和 1 万多家机构。它接替了 Microsoft Academic Graph,并提供丰富的元数据:摘要、开放获取 URL、引用次数、参考文献、作者消歧 ID(ORCID)和概念标签。REST API 每天最多可免费使用 100,000 次,无需身份验证;加入礼貌访问池(通过 email 参数)可获得优先处理。

适用场景

  • 搜索所有学术领域(不只是生物医学),构建系统性文献综述语料库
  • 检索引文网络,用于文献计量分析、共被引聚类或参考文献图遍历
  • 使用 ORCID/OpenAlex 作者 ID 跨机构消歧作者身份
  • 为一组 DOI 查找开放获取全文 URL,以构建可下载的论文语料库
  • 按年份、机构、国家或研究概念分析发表趋势
  • 通过 DOI 或标题丰富论文列表的元数据(引用次数、摘要、发表载体)
  • PubMed 收录的生物医学文献请使用 pubmed-database;bioRxiv 预印本请使用 biorxiv-database

前置条件

  • Python 包:requests、pandas
  • 数据要求:DOI、OpenAlex Work ID(W…)、作者姓名、ORCID ID 或搜索词
  • 环境:互联网连接;无需 API 密钥
  • 速率限制:匿名访问为 10 req/s;添加 [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。每页最多返回 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–200每页结果数
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. 从倒排索引重建摘要:并非所有作品都有摘要;重建前请检查 abstract_inverted_index is not None,以避免 KeyError。

  5. 按作品 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 查询返回 404OpenAlex 中未收录该 DOI改用标题搜索;OpenAlex 收录了 250M+ 条记录,但并未涵盖 100% 的文献
筛选器返回 0 条结果字段名称错误或筛选语法有误检查筛选语法:field:value,中间不能有空格;在 API 文档中确认字段名称
cited_by_count 已过时引用次数会定期更新引用次数会定期刷新,但可能延迟数天;适合用于分析趋势,不适合用作精确数据

相关技能

  • pubmed-database — 使用 MeSH 受控词表的生物医学文献;更适用于临床和生命科学领域
  • biorxiv-database — 尚未被 OpenAlex 收录的生物医学预印本
  • scientific-brainstorming — 将文献作为输入的假设生成工作流
  • literature-review — 使用 OpenAlex 设计系统性文献综述的指南

参考资料

评论

支持 Markdown