第3章:蛋白质结构数据获取——从PDB数据库下载与解析

做蛋白配体预测,第一步就是搞到高质量的蛋白结构数据。说白了,没结构算个啥?

我当年第一次做对接时,满网找结构文件,结果下载回来的全是残缺的……后来才明白,PDB 数据库虽然开放,但数据质量参差不齐,下载与解析的细节直接决定后续管线的成败。这一章咱们就聊聊怎么科学地从 PDB 数据库往下扒数据,以及怎么把它变成程序能直接吃的东西。

3.1 PDB 数据库是什么?

PDB(Protein Data Bank)是目前全球最大的蛋白质结构数据库。你可以把它理解成结构生物学的「豆瓣」,只不过这里存的是原子坐标,而不是影评。

  • 主要收录 X 射线晶体衍射、核磁共振(NMR)、冷冻电镜(Cryo-EM)解析的蛋白结构
  • 每个结构有一个独一无二的 4 位 PDB ID(例如 1hiv、4pe8)
  • 提供两种主流格式:传统 PDB 格式(.pdb)和新版 mmCIF 格式(.cif)——我个人建议尽量用 mmCIF,因为它能处理更大更完整的结构,不截断

你想想看,每天全球有上千个新结构上传,咱们的管线必须能自动识别并下载最新的数据。所以第一步就是学会用程序跟 PDB 服务器打招呼。

避坑指南:PDB ID 老版本是四位大写字母+数字组合(如 4hhb),但更新后有些 ID 包含小写字母或数字。我曾经因为硬编码成大写,导致批量下载时漏掉了好几个结构,排查了一下午……

3.2 下载 PDB 文件:三种主流姿势

先上一张流程图,让你对整个数据获取流程有个整体印象:

蛋白结构数据获取流程 PDB ID 输入 查询 RCSB / PDBe 数据库 选择格式:PDB / mmCIF 下载 .pdb 文件 下载 .cif 文件 解析 → 提取坐标 / 链信息

图上这个流程,说白了就是三步走:有ID → 下文件 → 解析。但实际动手时,这里面全是细节。

3.2.1 方法一:直接 HTTP GET(适合小批量)

RCSB PDB 提供了一个非常直接的 RESTful 下载接口:

https://files.rcsb.org/download/{pdb_id}.pdb
https://files.rcsb.org/download/{pdb_id}.cif

用 Python 的 requests 库就能搞定:

import requests

def download_pdb(pdb_id, format='pdb'):
    base_url = 'https://files.rcsb.org/download/'
    url = f'{base_url}{pdb_id}.{format}'
    resp = requests.get(url, timeout=30)
    resp.raise_for_status()
    with open(f'{pdb_id}.{format}', 'w') as f:
        f.write(resp.text)
    print(f'{pdb_id} 下载完成')
小技巧:查看返回的状态码。如果返回 404,说明这个 ID 可能已经失效或被废弃。我一般会在代码里加一个 retry 逻辑,偶尔网络波动会导致下载中断。

3.2.2 方法二:BioPython 的 PDBList(推荐批量下载)

BioPython 封装了一个超级好用的 PDBList 类,支持断点续传、自动重试:

from Bio.PDB.PDBList import PDBList

pdb_list = PDBList()
pdb_list.retrieve_pdb_file('4pe8', pdir='./structures', file_format='pdb')
# 或者用 mmCIF
pdb_list.retrieve_pdb_file('4pe8', pdir='./structures', file_format='mmCIF')

它内部会先访问 PDB 的 FTP 服务器,如果失败会回退到 HTTP。你想想看,BioPython 连这种边界情况都帮你想好了,比我们自己写一堆 try-except 省事多了。

注意:BioPython 默认下载目录是当前目录,建议永远指定 pdir。我踩过坑:没指定目录,结果 100 个结构文件全部散落在当前文件夹里,后面整理起来想死的心都有。

3.2.3 方法三:PDBe 的 GraphQL API(面向现代应用)

如果你需要更细粒度的元数据(如分辨率、R-factor、配体列表),直接解析文件太慢。PDBe 提供了 GraphQL 接口:

POST https://www.ebi.ac.uk/pdbe/graphql/
Content-Type: application/json

{
  "query": "{
    entry(entry_id: \"4pe8\") {
      resolution
      r_free
      ligand_ids
      structure_determination_method
    }
  }"
}

返回 JSON,非常规整。适合在管线初期筛选结构质量。

3.3 解析 PDB 文件:从文本到数据结构

文件下载到手了,怎么把那些行列坐标提取成能算的矩阵?

3.3.1 手动解析 vs BioPython 解析

你要是新手,别自己写正则去抠 ATOM 行。我曾经犯过这个傻,写了两百行代码,结果遇到非标准格式的 PDB 文件就崩。老老实实用 BioPython 的 PDBParser:

from Bio.PDB.PDBParser import PDBParser

parser = PDBParser(QUIET=True)  # QUIET 抑制警告
structure = parser.get_structure('4pe8', '4pe8.pdb')
# structure[0] 是第一个模型(model),大多数 X-ray 结构只有一个 model
model = structure[0]
# model['A'] 是链 A
chain = model['A']
# 遍历残基
for residue in chain:
    res_id = residue.get_id()
    res_name = residue.get_resname()
    # 遍历原子
    for atom in residue:
        coord = atom.get_vector()
        # coord 是 NumPy 风格的 Vector 对象,可直接转数组

mmCIF 格式解析更简单:

from Bio.PDB.MMCIFParser import MMCIFParser

parser = MMCIFParser(QUIET=True)
structure = parser.get_structure('4pe8', '4pe8.cif')

注意:mmCIF 解析出来的结构对象和 PDB 解析出来的一模一样,上层代码完全不用改——BioPython 做了统一抽象。

3.4 提取关键信息:残基与配体坐标

做蛋白配体预测,最关心的是配体结合位点的残基。我一般会这么处理:

  • 先区分蛋白质残基和非蛋白质分子(水、离子、配体)
  • 保留配体周围 8~10 Å 内的残基作为结合口袋
  • 完整保存整个蛋白的坐标,用于后续对接或打分

代码示例——提取所有配体并计算其原子中心:

from Bio.PDB import Vector

def get_ligands(structure):
    ligands = []
    for model in structure:
        for chain in model:
            for residue in chain:
                # 异质性残基(HETATM)通常是配体
                if residue.get_id()[0] != ' ':
                    atoms = [atom.get_vector() for atom in residue]
                    center = sum(atoms, Vector(0,0,0)) / len(atoms)
                    ligands.append((residue.get_resname(), center))
    return ligands
避坑经验:我遇到过配体名称"UNL"(unknown ligand)的情况,这种结构多半是解析不完全,建议直接跳过。还有,mmCIF 格式下配体的链标识可能为 "." 或 "?",要在代码里兼容处理。

3.5 数据完整性与校验

结构下载下来,别急着往下传。至少检查这几样:

检查项 为什么要查 常用手段
分辨率 分辨率 > 3.5 Å 的结构通常位置不准 从 PDB 文件 REMARK 2 或 header 中解析
是否完整(无缺失残基) 晶体结构常有 loop 缺失 比较序列长度与实测 CA 原子数
水分子是否过多 太多水会影响对接采样 计算水分子与总原子数比率
配体是否与论文一致 有的 PDB 配体是 buffer 分子 预先维护一个已知配体列表

举例,用 BioPython 读取分辨率:

structure = parser.get_structure('id', 'file.pdb')
resolution = structure.header['resolution']
# 或者从 mmCIF 的 _refine.ls_d_res_high 字段获取

3.6 批量下载与并发处理

管线上线后,每天要处理几百上千个结构,单线程太慢了。我习惯用 multiprocessing + PDBList:

from multiprocessing import Pool
from Bio.PDB.PDBList import PDBList

def download_one(pdb_id):
    pl = PDBList()
    pl.retrieve_pdb_file(pdb_id, pdir='./structures', file_format='mmCIF')
    return pdb_id

with Pool(processes=8) as pool:
    results = pool.map(download_one, pdb_id_list)

注意:PDBList 内部有连接池限制,用太多进程反而会触发限流。我试过 32 进程直接被 RCSB 封 IP 了……建议 4~8 个就够了。

伦理与合规提醒:大规模下载前请先看 PDB 的使用条款(通常可以接受学术使用),并且加一个 User-Agent 标识你的项目。不要用默认的 Python-requests,否则容易被屏蔽。

3.7 小结:一条实用的下载解析管线

把这个片段整合一下,就是咱们管线里最常见的一段:

# 1. 输入 PDB ID 列表
pdb_ids = ['4pe8', '1hiv', '3cyx']

# 2. 下载 mmCIF 文件
pdb_list = PDBList()
for pid in pdb_ids:
    pdb_list.retrieve_pdb_file(pid, pdir='./cif', file_format='mmCIF')

# 3. 解析并提取信息
parser = MMCIFParser(QUIET=True)
for pid in pdb_ids:
    struct = parser.get_structure(pid, f'./cif/{pid}.cif')
    # 打印基本信息
    print(f'{pid}: {len(struct[0])} chains, resolution {struct.header["resolution"]}')
    # 继续做口袋提取或其他处理

嗯,到这里,蛋白质结构数据的获取与解析就讲完了。实际操作中你会发现不同来源的文件格式差异很大(有些来自 PDB-REDO 的修正版,有些来自 AlphaFold 的预测结构),解析逻辑还要更灵活一些。但掌握了 PDB 数据库的下载规律和 BioPython 的核心用法,剩下的就是见招拆招了。