PDB文件结构解析:记录类型与ATOM记录详解
说到PDB文件,很多人第一反应就是“一堆数字和字母”。我第一次看到PDB时,心里直呼“这是什么天书”。后来才明白,这个格式是有章可循的。每行前面四个字母,就是所谓的“记录类型”,相当于文件的骨架。
今天我们就来拆解这个骨架。重点看它的核心——ATOM记录。
认识PDB格式的骨架——记录类型
PDB文件是一行一行的文本。每行以1-6字符的“记录名”开头。整个文件大概有几十种记录类型,但常用的就那么几个。
核心记录类型一览
| 记录类型 | 位置(列) | 作用 | 出现频率 |
|---|---|---|---|
| HEADER | 1-6 | 文件头,描述分子类型、日期 | 1行 |
| TITLE | 1-6 | 实验标题 | 1行 |
| COMPND | 1-6 | 化合物名称 | 1-多行 |
| ATOM | 1-6 | 标准氨基酸/核苷酸原子坐标 | 数百至数万行 |
| HETATM | 1-6 | 非标准残基/配体/溶剂原子 | 不定 |
| TER | 1-6 | 链结束标志 | 每链1行 |
| END | 1-6 | 文件结束 | 1行 |
你可能注意到了,ATOM和HETATM长得几乎一样。区别在哪?说白了,ATOM是蛋白质或核酸自身的原子;HETATM是其他分子,比如水、配体、金属离子。
个人习惯:我每次拿到PDB文件,先看HEADER行了解分辨率,再看COMPND确认分子对象。然后直接跳到ATOM行——那才是我们真正要处理的数据。
解剖ATOM记录——每一列都有故事
ATOM记录是PDB文件的主力。一条标准ATOM行共80列,我把它分成几个“功能块”。
ATOM记录的80列布局
COLUMNS DATA FORMAT EXAMPLE
1 - 6 记录名 A6 "ATOM "
7 - 11 原子序号 I5 " 1 "
13 - 16 原子名称 A4 " N "
17 交替位置指示符 A1 " "
18 - 20 残基名称 A3 "ALA "
22 链标识符 A1 "A"
23 - 26 残基序号 I4 " 1 "
27 插入代码 A1 " "
31 - 38 X坐标 F8.3 " 8.236"
39 - 46 Y坐标 F8.3 " 14.324"
47 - 54 Z坐标 F8.3 " 3.215"
55 - 60 占有率 F6.2 " 1.00"
61 - 66 温度因子 F6.2 " 15.30"
77 - 78 元素符号 A2 " N "
79 - 80 电荷 A2 " "
嗯,这里要注意:原子名称的列位置是13-16,而不是12-15。我见过不少新人搞混这一点,结果解析出的原子名多了一个空格,导致后面对接拓扑出问题。
让我拆开说说几个关键字段。
原子名称(Atom name)
这个字段比较特殊。它右对齐,占4列。为了对齐,通常第一个字符是空格。比如“ CA ”表示Cα原子,“ N ”表示N原子。你的解析器必须处理这种对齐方式。
残基名称(Residue name)
三个字母的氨基酸/核酸代码。比如“ALA”、“LYS”、“DG”等。这是判断残基类型的唯一依据。我建议用标准对照表做映射,不要自己猜。
坐标(X/Y/Z)
三个8.3格式的浮点数,单位是Ångström。注意:并不是所有PDB文件的坐标范围都在合理范围内。我曾经拿到一个文件,坐标范围达到10的6次方,显然是单位错了(应该是pm而非Å)。遇到这种情况,先检查HEADER中的实验条件。
占有率与温度因子
占有率通常为1.00,表示该原子在晶体中完全占据此位置。温度因子(B-factor)表示原子热运动的大小。B值高说明该区域柔性大。
我曾经踩过的坑:有一次做分子对接,我忽略了交替位置指示符(第17列)。结果文件中实际上有两组坐标,我全吃了进去,导致配体位置完全错误。从那以后,我养成了先检查交替位置的惯例。
避坑指南之我手写一个ATOM解析器
虽然现在有很多库帮你解析PDB,但理解底层逻辑至关重要。这是用Python写的一个简单解析函数:
def parse_atom_line(line):
if not line.startswith("ATOM") and not line.startswith("HETATM"):
return None
rec = {}
rec['record_name'] = line[0:6].strip()
rec['serial'] = int(line[6:11].strip())
rec['name'] = line[12:16].strip()
rec['alt_loc'] = line[16:17].strip()
rec['resname'] = line[17:20].strip()
rec['chain'] = line[21:22].strip()
rec['resnum'] = int(line[22:26].strip())
rec['icode'] = line[26:27].strip()
rec['x'] = float(line[30:38].strip())
rec['y'] = float(line[38:46].strip())
rec['z'] = float(line[46:54].strip())
rec['occupancy'] = float(line[54:60].strip())
rec['bfactor'] = float(line[60:66].strip())
rec['element'] = line[76:78].strip()
rec['charge'] = line[78:80].strip()
return rec
注意看,我的strip()用得很多。PDB格式是定宽的,但很多字段开头或结尾有空格。不加strip(),你拿到的原子名可能是" N "而不是"N"。
从上面这段代码,你也可以看到PDB的核心结构:每一行是一个原子,把它转成字典后,就能方便地做后续处理了。
HETATM与ATOM的区别
刚才说过,HETATM和ATOM在列格式上完全一样。但处理时要注意:
- ATOM:属于标准蛋白质/核酸链,用于构建主链/侧链模型
- HETATM:属于外部分子,如配体、水、金属、修饰残基
为什么区分它们?因为做分子力场参数化时,ATOM的残基有现成的力场参数,而HETATM需要手动分配原子类型和电荷。
举个例子,一个包含NAG糖链的PDB文件:
ATOM 1 N ALA A 1 8.236 14.324 3.215 1.00 15.30 N
ATOM 2 CA ALA A 1 9.782 14.255 2.151 1.00 15.30 C
HETATM 1001 C1 NAG A 500 11.450 18.200 6.780 1.00 20.50 C
HETATM 1002 O5 NAG A 500 10.100 18.900 5.800 1.00 20.50 O
前两行是ALA残基的ATOM,后两行是糖链配体的HETATM。如果我不加区分地直接用标准残基的参数,配体那边的处理就会出问题。
工具:用BioPython快速提取关键信息
当然,实际项目中你不会每次都手写解析器。BioPython的Bio.PDB模块封装得很好:
from Bio.PDB import PDBParser
parser = PDBParser(QUIET=True)
structure = parser.get_structure("example", "1ubq.pdb")
for model in structure:
for chain in model:
for residue in chain:
for atom in residue:
print(atom.get_full_id(), atom.coord)
if residue.get_id()[0] != ' ':
# 表示非标准残基(HETATM)
print("Found HETATM residue:", residue.get_resname())
这段代码里,residue.get_id()[0]就是判断残基类型的关键。遍历时区分ATOM和HETATM,可以帮助你决定后续的处理策略。
好了,关于PDB文件结构,尤其是ATOM记录,我们就讲到这里。理解这些基础,后面的特征提取才能顺利展开。