1. PDB 文件格式基础:搞懂分子结构的“通用语言”

做分子模拟或者结构生物学相关的开发,PDB 文件是绕不开的老朋友。说白了,它就是分子结构的“身份证”。你拿到一个 .pdb 文件,里面记录的就是一个个原子的坐标、属于哪个残基、在蛋白质链的什么位置等等。

我个人习惯把 PDB 文件看作一本“电话簿”。每条记录就是一行,告诉你谁在哪、干什么。不过这本电话簿有自己的规矩,了解清楚了,后面的批量验证才不会出错。

为什么会这样?因为 PDB 格式有严格的行记录类型。每行开头几个字母(比如 ATOM、HETATM、TER)就说明了这行数据的身份。

1.1 核心字段:看懂每一行在说什么

我挑几个最常见的记录类型讲。写代码处理PDB时,靠的就是这些标志性开头。

记录类型 首列标识 含义 备注
ATOM ATOM 标准氨基酸或核酸的原子 最常见,构成蛋白质主链和侧链
HETATM HETATM 非标准原子(配体、水、金属离子等) 处理配体时这是主角
TER TER 链的终止标记 告诉我一条链在这里结尾
END END 文件结束 读到这行就收工
MODEL MODEL NMR 多模型结构的开始 常见于 NMR 解析的结构

我遇到过很多新手,上来就按行号硬解析。我的建议是——永远用前6个字符做类型判断。为什么?因为 PDB 文件里可能有注释行(REMARK)、连键信息(CONECT)等,直接按顺序读会跑偏。

核心原则:用记录类型首列做分支,不要依赖行号。

1.2 原子坐标与链的标识

ATOM 行里记录了最关键的信息。我直接给一个典型的示例:

ATOM      1  N   ALA A   1      -0.525   1.362   0.000  1.00  0.00           N

这行里藏着什么?我们拆开看:

  • ATOM:记录类型
  • 1:原子序号,在文件内唯一
  • N:原子名称(这里是主链氮)
  • ALA:残基名称(丙氨酸)
  • A:链标识(Chain ID),这里链叫“A”
  • 1:残基序号,在链内递增
  • -0.525 1.362 0.000:X、Y、Z 坐标(单位埃)
  • 1.00 0.00:占据率和温度因子(B-factor)
  • N:元素符号

嗯,这里要注意——链标识区分大小写。我遇到过不少新手栽在这里。某次合作,对方直接把 chain B 写成了 b,结果对接程序怎么都跑不通。你想想看,大写 A 和小写 a 在 PDB 规范中是两个不同的链。

1.3 残基与 Hetatm:别把水分子当成氨基酸

ATOM 行记录的是标准氨基酸或核酸的原子。而 HETATM 行则记录非标准的东西。这些包括:水分子、配体、金属离子、修饰过的氨基酸等等。

举个例子,水分子通常长这样:

HETATM 1234  O   HOH A 201       2.345   1.234   5.678  1.00 20.00           O

残基名 HOH 就是水。处理的时候你要分清:

  • 筛选配体时,只取 HETATM 但排除水(排除 HOH)
  • 分析晶体结构时,水的分布有时也是判断数据质量的一个指标

我的习惯:写代码分析配体时,先筛选 HETATM,再用残基名排除水、离子(如 NA、CL),剩下的基本就是配体小分子。

1.4 PDB 整体的结构层次

一个 PDB 文件可以很大,但它有固定的“从大到小”的包含关系。我把这张图画出来,你感受一下:

PDB 文件结构层次 PDB 文件 (.pdb) MODEL(模型;NMR多构象) CHAIN(链;如 A、B、C) RESIDUE(残基;如 ALA、HOH) ATOM / HETATM(原子) 层级包含关系

注意这个结构——文件包含模型,模型包含链,链包含残基,残基包含原子。在处理多链复合物时,要按这种层次组织数据。我曾经处理过一个抗体-抗原复合物,链 A 是抗体轻链,链 B 是抗体重链,链 C 是抗原。如果用链标识做分组,后面的相似性验证就好做多了。

1.5 命令示例:用 Python 快速解析 PDB 头信息

写代码时怎么判断一个 PDB 文件的结构?我习惯先扫描一遍,提取出链列表和每条链的残基范围。下面是一个简单示例:

def scan_pdb_header(pdb_path):
    chains = {}
    with open(pdb_path, 'r') as f:
        for line in f:
            if line.startswith(('ATOM  ', 'HETATM')):
                chain_id = line[21].strip()
                res_name = line[17:20].strip()
                res_num = int(line[22:26].strip())
                if chain_id not in chains:
                    chains[chain_id] = {'residues': set(), 'res_range': [9999, -1]}
                chains[chain_id]['residues'].add(res_name)
                if res_num < chains[chain_id]['res_range'][0]:
                    chains[chain_id]['res_range'][0] = res_num
                if res_num > chains[chain_id]['res_range'][1]:
                    chains[chain_id]['res_range'][1] = res_num
    for ch, info in chains.items():
        print(f"Chain {ch}: {info['res_range'][0]}-{info['res_range'][1]}, "
              f"残基数: {len(info['residues'])}")
    return chains

这个函数专门用于快速预览 PDB 里有哪些链、每条链的残基序号范围。我通常在跑批量验证前先跑一遍这个,确认数据没少链、残基没断层。

注意:PDB 格式里,残基序号在列 23-26(4 字符),链标识在列 22(1 字符)。列计数从 1 开始,这是老 Fortran 格式的遗留。写解析时务必注意起始列!

1.6 避坑指南:PDB 解析中常见的三个坑

  1. 行宽不固定?不可能。PDB 格式理论上每行固定 80 字符,但有些工具导出的文件可能多出空格或注释。别依赖、别偷懒——按列位置解析,不要按空格分割字符串。我当年用 line.split() 接过一个文件,结果配体名里有空格,直接崩了。
  2. 残基序号可能重复。同一文件中,不同链的残基序号可能是一样的(比如链 A 的 1 号和链 B 的 1 号不是同一个)。按 链标识 + 残基序号 做唯一键是稳妥做法。
  3. 水分子会干扰判断。统计残基种类时,记得把 HOH 单独处理或过滤掉。不然配体分析时会混进几百个水分子。

1.7 小结

这一章我们搞清楚了 PDB 文件的核心骨架:ATOM 是主体,HETATM 是补充,链、残基、原子是逐层嵌套的关系。掌握了这些基础字段,就等于拿到了打开分子结构的钥匙。后面做批量相似性验证时,不管是比对原子坐标,还是对比残基序列一致性,都离不开这一章的内容。