3. PDB 文件解析入门:用 Biopython 把结构拆明白
PDB 文件,说白了就是一长串坐标和原子名字。但咱们做数据处理,不能只看文本,得把它变成 Python 里的对象才行。这一章我就带你用 Biopython 把 PDB 文件拆开,拿到最基础的三个层级——原子、残基、链。
我个人习惯,拿到一个新结构文件,第一件事就是确认这几个核心对象能不能正常读取。之前在项目里遇到过文件本身没问题,但 PDB 格式里某些残基编号不标准,导致链对象直接为空——那排查起来真是头疼。所以,把底层对象理清楚,后面所有流程才不会翻车。
3.1 环境准备与命令行热身
Biopython 是必备的。如果你还没装,先跑一下。我建议用 conda 装,依赖处理起来省心很多。
conda install -c conda-forge biopython
# 或者用 pip
pip install biopython
装好之后,咱们用 4HHB(血红蛋白)来示范。你可以在 rcsb.org 下载它的 PDB 文件,或者直接用下面代码从本地读。我自己写脚本时习惯先调用 Bio.PDB.PDBParser,把文件路径传进去就好。
3.2 核心对象——结构(Structure)
在 Biopython 里,整个 PDB 文件被包装成一个 Structure 对象。它是顶级容器,里面可以有多个 Model(X 射线解析的多模型结构很常见)。
你想想看,一个 PDB 文件如果包含 NMR 模型,Model 就可能有好几十个。平时我们做晶体结构分析,一般只用到第一个 Model。
from Bio.PDB import PDBParser
parser = PDBParser(QUIET=True) # QUIET=True 可以屏蔽一些不太重要的警告
structure = parser.get_structure("4HHB", "4hhb.pdb")
# 拿到第一个 model
model = structure[0]
print(f"Model ID: {model.id}")
print(f"Number of chains: {len(model)}")
代码跑完之后,你就能看到 Model ID 通常是 0,然后链数量就是 4HHB 的四个链(α1, β1, α2, β2)。
3.3 层层深入:从链(Chain)到残基(Residue)
拿到 Model 之后,接下来就是 Chain。每个链对应 PDB 文件里一条多肽链。我个人习惯拿链时直接按 ID 索引:
# 遍历所有链
for chain in model:
chain_id = chain.id
print(f"Chain ID: {chain_id}")
# 每个链里的残基
for residue in chain:
res_name = residue.get_resname() # 比如 "HIS", "ALA"
res_id = residue.get_id() # 比如 (' ', 42, ' '),注意是元组
print(f" Residue: {res_name} {res_id}")
注意!residue.get_id() 返回的是一个三元组:(hetero_flag, sequence_number, insertion_code)。
hetero_flag 是 ' '(标准残基)、'W'(水分子)或 'H_'(配体)。
我在初学时就踩过这个坑:hetero_flag 为 'W' 的水分子,用索引取残基时一定要过滤掉,不然遍历到水分子它还带着它自己的坐标,干扰主链分析。
if residue.get_id()[0] == ' ': # 只处理标准残基
下面这个表格能帮你快速区分不同残基类型:
| 类型 | hetero_flag | 说明 |
|---|---|---|
| 标准残基 | ' '(空格) | 20 种标准氨基酸 |
| 水分子 | 'W' | 晶体结构中的水 |
| 配体/修饰 | 'H_' 等 | 如 HEM(血红素) |
3.4 原子(Atom)——最底层的数据单元
原子对象负责存坐标、温度因子(B-factor)、元素类型等。对于每个残基,它通常包含主链原子(N, CA, C, O)和侧链原子。
for atom in residue:
atom_name = atom.get_name() # 比如 "CA", "CB", "N"
coord = atom.get_coord() # numpy 数组 [x, y, z]
bfactor = atom.get_bfactor() # 温度因子
element = atom.element # 元素符号,比如 "C", "N", "O"
print(f" Atom: {atom_name} at {coord} B={bfactor:.2f}")
这里有个小技巧:atom.element 在 PDB 中没有直接写元素时可能会是空字符串,我推荐用 atom.get_full_id() 来判断归属。
原子层级的属性我整理了一个清单,方便你对照:
- name:原子名(字符串),如 "CA"、"CB"
- coord:三维坐标(numpy 数组)
- bfactor:温度因子(可拿来评估柔性)
- occupancy:占据率(部分结构无序时 < 1.0)
- element:元素
3.5 完整结构遍历脚本
下面是我常用来做结构审查的脚本:
from Bio.PDB import PDBParser
parser = PDBParser(QUIET=True)
structure = parser.get_structure("4HHB", "4hhb.pdb")
model = structure[0]
for chain in model:
print(f"\n--- Chain {chain.id} ---")
for residue in chain:
# 只取标准残基
if residue.get_id()[0] != ' ':
continue
res_name = residue.get_resname()
res_seq = residue.get_id()[1]
# 提取 Ca 坐标
if "CA" in residue:
ca = residue["CA"]
coord = ca.get_coord()
print(f"{res_name} {res_seq} CA: {coord}")
else:
print(f"{res_name} {res_seq} (no CA atom)")
你把这个脚本跑一遍,就能逐残基地看到 Ca 原子的空间位置。这对后续做结构比对、距离计算都很有用。
3.6 知识结构一览
为了帮你建立整体框架,我用一张图把 PDB 解析流程画出来。这张图展示的是从 PDB 文件到最细粒度坐标的拆解路径。
model["A"],想取某个残基直接 chain[(' ', 100, ' ')]。记住这点,写代码效率能翻倍。
3.7 常见问题与避坑
- 残基编号不连续? 晶体结构里常有缺口,不一定是完整序列。用
residue.get_id()检查插入码也能发现。 - 同分异构体(Alternate Conformation)? PDB 中一个原子可能对应多个位置。Biopython 的 Atom 对象会标出占位符,你可以通过
atom.get_altloc()检测。 - 水分子会不会影响链长度? 别忘了 hetero_flag 是 'W',我写脚本时必加过滤:
if residue.get_id()[0] != ' ': continue - 链的 ID 是大写字母? 大多数 PDB 文件链 ID 是 "A", "B", 但有些用数字。用
model.child_list可列出所有链 ID。
嗯,说到这你应该对 Biopython 如何解析 PDB 文件有个底了。后面几章我们要在上面的对象上做各种计算——距离、角度、配体交互,这些全都建立在今天讲的基础之上。你先把脚本跑一遍,看看输出的原子坐标,感受一下从文件到对象的映射过程。