第4章 使用Biopython访问PubMed API:Entrez编程接口

这一章我们聊聊怎么用Biopython去调PubMed的API。说白了,就是让你用Python代码直接拿到PubMed里那些文献的元数据。我个人觉得这是整个药物靶点关系抽取管线里最基础也最实用的一环——数据都拿不到,后面分析个啥?

核心目标:掌握Entrez编程接口的调用方式,学会用Biopython获取PubMed文献的标题、作者、摘要、日期、DOI等元数据。

4.1 为什么要用Biopython?

直接调PubMed API行不行?当然行。但手动处理XML解析、重试机制、速率限制这些细节,工作量不小。Biopython的Entrez模块把这些都封装好了。

我记得刚开始做这个项目时,直接裸写requests调API,结果没加速率控制,被PubMed封了IP好几次。后来换成Biopython,一句Entrez.read()就搞定了XML解析,再也不用自己写SAX解析器了。

你想想看,Biopython帮我们做了三件事:

  • 自动处理HTTP连接和SSL
  • 内置XML解析器,把返回数据变成Python字典
  • 遵守NCBI的调用频率限制(默认每秒3次)

4.2 安装与基础配置

安装没什么特别的,一行命令搞定:

pip install biopython

但有个重要的事——你必须设置你的电子邮件地址。这是NCBI的硬性要求,不设置的话会被拒绝访问。我建议你在代码开头就写上:

from Bio import Entrez
Entrez.email = "your_email@example.com"

小提示:不要把email设成空字符串或者假的邮箱地址。NCBI查出来会封IP的,我在项目里见过同事踩过这个坑。

4.3 Entrez编程接口核心函数

Entrez模块提供了几个关键函数,我把它们整理成一个表格,方便你对照着用:

函数 功能 返回类型
Entrez.esearch() 搜索PubMed,返回匹配的文献ID列表 包含IdList的字典
Entrez.efetch() 根据ID获取完整元数据记录 文件句柄
Entrez.esummary() 获取文献摘要信息(比efetch轻量) 文件句柄
Entrez.read() 解析XML响应为Python对象 字典/列表
Entrez.parse() 流式解析大量数据,避免内存溢出 生成器

为什么要区分efetch和esummary?嗯,这里要注意:efetch返回的是完整的XML记录,内容多、速度慢;esummary只返回摘要字段,适合快速获取标题、作者、日期这些基本信息。

4.4 获取文献元数据实战

来,直接上代码。这是我在项目中反复使用的模式:

from Bio import Entrez

# 设置邮箱(必要)
Entrez.email = "your_email@example.com"

# 定义要查询的PubMed ID列表
pmids = ["38915493", "38915492", "38915491"]

# 获取文献元数据
handle = Entrez.efetch(db="pubmed", id=pmids, rettype="xml", retmode="text")
records = Entrez.read(handle)
handle.close()

拿到的records是一个字典,结构比较复杂。我习惯把它打印出来看看结构:

print(records.keys())
# 输出:dict_keys(['PubmedArticle', 'PubmedBookArticle'])

真正的文献数据在PubmedArticle这个列表里。每条记录都是一个Record对象,可以像字典一样访问。提取元数据的关键字段路径如下:

元数据字段 访问路径
标题 record['MedlineCitation']['Article']['ArticleTitle']
摘要 record['MedlineCitation']['Article']['Abstract']['AbstractText']
作者列表 record['MedlineCitation']['Article']['AuthorList']
期刊名称 record['MedlineCitation']['Article']['Journal']['Title']
出版日期 record['MedlineCitation']['Article']['Journal']['JournalIssue']['PubDate']
DOI record['MedlineCitation']['Article']['ELocationID'](需过滤EIdType为doi)
PMID record['MedlineCitation']['PMID']
MESH词条 record['MedlineCitation']['MeshHeadingList']

避坑提醒:有些字段可能不存在!比如没有摘要的文献,Abstract键会缺失。我曾经在跑批量处理时没做校验,程序直接崩了。建议用.get()方法或者try-except包裹。

下面是一个完整的提取函数,我在项目里一直用这个模板:

def extract_article_metadata(record):
    """从单条PubMed记录中提取元数据"""
    citation = record['MedlineCitation']
    article = citation['Article']
    
    metadata = {}
    metadata['pmid'] = str(citation['PMID'])
    metadata['title'] = article.get('ArticleTitle', '')
    
    # 作者处理(可能有多个)
    author_list = article.get('AuthorList', [])
    authors = []
    for author in author_list:
        last_name = author.get('LastName', '')
        fore_name = author.get('ForeName', '')
        authors.append(f"{last_name} {fore_name}")
    metadata['authors'] = ', '.join(authors)
    
    # 期刊信息
    journal = article.get('Journal', {})
    metadata['journal'] = journal.get('Title', '')
    
    # 出版日期
    pub_date = journal.get('JournalIssue', {}).get('PubDate', {})
    year = pub_date.get('Year', '')
    month = pub_date.get('Month', '')
    day = pub_date.get('Day', '')
    metadata['pub_date'] = f"{year}-{month}-{day}"
    
    # 摘要
    abstract = article.get('Abstract', {})
    abstract_text = abstract.get('AbstractText', [])
    metadata['abstract'] = ' '.join([str(text) for text in abstract_text])
    
    # DOI
    elocation_ids = article.get('ELocationID', [])
    for eid in elocation_ids:
        if eid.attributes.get('EIdType') == 'doi':
            metadata['doi'] = str(eid)
            break
    
    return metadata

# 批量处理
records_list = []
for article in records['PubmedArticle']:
    meta = extract_article_metadata(article)
    records_list.append(meta)
    
print(f"共提取 {len(records_list)} 条文献元数据")

我的习惯:拿到元数据后,先保存成CSV或JSON,避免反复调API。PubMed API有速率限制,频繁请求会被限流。我一般是一次性拿几百篇,存本地后慢慢分析。

4.5 常见问题与避坑

操作过程中你可能会遇到几个典型问题,我列出来:

  1. HTTP 400错误:通常是参数写错了,比如db拼写成pubmed(少个d)。检查一下db参数。
  2. XML解析失败:偶尔PubMed返回的XML格式有问题。加个try-except捕获Entrez.Parser.NotXMLError。
  3. 速率限制被ban:不要短时间大量发请求。Biopython默认有间隔,但如果你手动调了Entrez.sleep_between_tries,记得设合理值。
  4. 字段不存在:用.get()而不是直接索引访问。这个前面说过了,但值得再强调一次。

曾经踩过的坑:有次我一次性请求了5000篇文献的efetch,结果超时了。后来改用Entrez.parse()流式处理,分200篇一批次调用,才稳定跑通。建议单次请求不超过200个ID。

我把整个Entrez调用流程画成了一张图,方便你理解数据是怎么流转的:

Entrez编程接口数据流 Python脚本 1. 构建查询参数 (db, id, rettype) 2. 发送HTTP请求到PubMed API 3. 返回XML格式的响应数据 4. 解析XML → 提取元数据 注意事项 • 必须设置email • 单次≤200个ID • 用.get()防缺失

这个流程看起来简单,但每一步都有细节。尤其是第三步的XML解析,数据嵌套很深,不过用Biopython的Entrez.read(),解析工作全交给它了,你只管从解析后的字典里取数据就行。

掌握了这些,你已经可以批量获取PubMed文献的元数据了。下一...