第2章 分子文件格式与读取:PDB、mmCIF、SDF

做结构比对,第一步就是把手头的分子文件读进内存。这步看似简单,但坑特别多。我在项目中遇到过,不同格式的数据混在一起,读取逻辑不统一,最后比对结果全乱套。

本章我们重点啃三个主流格式:PDB、mmCIF 和 SDF。我们还要用 Python 的 Bio.PDB 库,把原子坐标和链信息老老实实抽出来。

2.1 三大格式的来龙去脉

先搞清楚它们是什么,适合干什么。

格式全称典型用途特点
PDBProtein Data Bank蛋白质/核酸 X-ray 结构固定列宽、行号区分数据
mmCIFMacromolecular Crystallographic Information FilePDB 官方新标准键-值结构、支持大结构
SDFStructure Data File小分子/配体/化合物库多记录、包含属性字段

我入行时读的第一批数据就是 PDB。这东西历史悠久,但格式确实有点“古板”。每一行都按列对齐,从 1 数到 80,少一个字符都不行。后来 PDB 官方自己都推不动了,mmCIF 就接班了。

核心结论:蛋白质大分子优先用 mmCIF,小分子配体用 SDF,老数据兼容用 PDB。

2.2 PDB 格式——老兵不死

PDB 文件是纯文本,每行开头有个记录名,比如 ATOM、HETATM、TER、END。最重要的就是 ATOM 记录,里面藏着每个原子的三维坐标。

我们来看一条典型的 ATOM 行:

ATOM      1  N   ALA A   1       7.354   0.509  18.683  1.00  0.00           N

这里面每列代表什么?

  • 第 1-6 列:记录名(ATOM)
  • 第 7-11 列:原子序号(1)
  • 第 13-16 列:原子名(N)
  • 第 18-20 列:氨基酸名(ALA)
  • 第 22 列:链标识(A)
  • 第 23-26 列:残基序号(1)
  • 第 31-38 列:X 坐标(7.354)
  • 第 39-46 列:Y 坐标(0.509)
  • 第 47-54 列:Z 坐标(18.683)
小技巧:PDB 的坐标单位是埃(Å)。1 Å = 10⁻¹⁰ m,这个单位在分子对接和构象比对里最常见。

2.3 mmCIF 格式——新王登基

mmCIF 的读法和 PDB 完全不同。它是键-值对结构,很像一个带层级关系的配置文件。

loop_
_atom_site.group_PDB
_atom_site.id
_atom_site.type_symbol
_atom_site.Cartn_x
_atom_site.Cartn_y
_atom_site.Cartn_z
ATOM 1 N 23.456 45.678 12.345
ATOM 2 CA 24.567 46.789 13.456

它用 loop_ 开头定义表格,每个字段名都带 _atom_site. 前缀。说到底就是更规范了,也塞得下更大的蛋白结构。

注意:mmCIF 文件可能非常大(上 GB),不能一次性读到内存里。我建议用流式解析,或者只保留需要的链和残基。

2.4 SDF 格式——小分子专属

SDF 是 MDL 公司定的格式,一个文件可以装多个分子,每个分子之间用 $$$$ 分隔。

一个记录的例子:

LIGAND_NAME
  SDfile
 18 17  0  0  0  0  0  0  0  0999 V2000
    1.2345    2.3456    3.4567 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.3456    3.4567    4.5678 N   0  0  0  0  0  0  0  0  0  0  0  0
...
M  END
> <ID>
LIG-001

> <SMILES>
CC(=O)OC1=CC=CC=C1C(=O)O

$$$$

第 4 行是原子计数和坐标块;M END 表示坐标结束;后面 > 开头的部分是属性字段,可以放 ID、SMILES、活性值等。

我在做虚拟筛选项目时,从 PubChem 下了几百万个 SDF,就是靠属性字段过滤活性的。这招非常实用。

2.5 用 Python 读取 PDB 文件

我推荐用 Bio.PDB 库。它由生信大神 Thomas Hamelryck 开发,能优雅地处理 PDB、mmCIF 和 mmTF 格式。

安装 Bio.PDB

pip install biopython

读取一个 PDB 文件

from Bio.PDB import PDBParser

# 创建解析器
parser = PDBParser(QUIET=True)  # QUIET=True 屏蔽警告

# 解析文件,返回 Structure 对象
structure = parser.get_structure("1ubq", "1ubq.pdb")

QUIET=True 这个参数很关键。一开始我没加,结果跑脚本时满屏的格式警告信息,看得头皮发麻。

遍历模型、链、残基、原子

Bio.PDB 的对象层级是:Structure → Model → Chain → Residue → Atom

for model in structure:
    for chain in model:
        print(f"Chain ID: {chain.get_id()}")
        for residue in chain:
            # 跳过水分子
            if residue.get_id()[0] == " ":
                for atom in residue:
                    print(f"  Atom: {atom.get_name()} | Coord: {atom.get_coord()}")

这样就能拿到每个原子的名字和三维坐标,后面做构象比对就直接用这些数据。

提取链级别的序列和结构

for model in structure:
    for chain in model:
        chain_id = chain.get_id()
        chain_seq = ""
        chain_coords = []
        for residue in chain:
            if residue.get_id()[0] == " ":
                chain_seq += residue.get_resname()
                for atom in residue:
                    if atom.get_name() == "CA":  # 只取 alpha 碳
                        chain_coords.append(atom.get_coord())
        print(f"Chain {chain_id}: {len(chain_coords)} CA atoms")

这里只取了 CA 原子,做构象比对时,CA 是最常用的主干原子。

省流版:直接对整条链比对时,只取 CA 坐标就够。全原子比对计算量太大,精度提升有限。

2.6 读取 mmCIF 和 SDF

mmCIF 读取

from Bio.PDB.MMCIFParser import MMCIFParser

parser = MMCIFParser(QUIET=True)
structure = parser.get_structure("1ubq", "1ubq.cif")

后面的遍历逻辑和 PDB 完全一样,因为 MMCIFParser 也会返回 Structure 对象。这对我们用户来说最友好,接口统一。

SDF 读取(用 RDKit)

from rdkit import Chem

supplier = Chem.SDMolSupplier("ligands.sdf")
for mol in supplier:
    if mol is None:
        continue
    conf = mol.GetConformer()
    for i in range(mol.GetNumAtoms()):
        pos = conf.GetAtomPosition(i)
        print(f"Atom {i}: {pos.x} {pos.y} {pos.z}")

RDKit 是化学信息学的瑞士军刀。不过要注意,SDF 中多记录之间用 $$$$ 分隔,Chem.SDMolSupplier 已经帮我们处理好了。

性能警告:一个 SDF 可能包含几百万个分子。不能用 list() 直接全部加载,内存会爆。我习惯用生成器逐条处理。

2.7 本章知识框架

下面这张图是整个流程的简化版本,帮你梳理 PDB→Bio.PDB→原子坐标的路径:

分子文件格式与读取流程 PDB 文件 mmCIF 文件 SDF 文件 Bio.PDB / RDKit 解析 原子坐标 + 链 / 残基信息 坐标单位:埃(Å) 链标识:A/B/C

流程很清楚:三种格式的文件经过各自的解析器,最终统一输出为原子坐标和链信息。这个结构在后面做构象比对时,会反复用到。

2.8 避坑指南

我曾经吃过这个亏:有一批 X-ray 晶体数据,mmCIF 文件里明明有 4 条链,但用 PDBParser 读出来只有 2 条。后来发现是文件里某些链的残基不连续,Bio.PDB 把它们跳过了。遇到这种情况,检查日志中是否出现了 broken chain 警告,或者改用 MMCIFParser 读。
实用建议:做构象比对之前,先跑一遍完整性检查。确认每条链的 CA 原子数量一致,不然比对时残基对不上,结果全是错的。