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。

关键点: Structure → Model(s) → Chain(s) → Residue(s) → Atom(s)。这个就是层次结构,记住它,后面就顺了。
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' 的水分子,用索引取残基时一定要过滤掉,不然遍历到水分子它还带着它自己的坐标,干扰主链分析。

避坑指南: 我曾经在计算残基间距离时,直接循环 chain 里所有 residue,结果引入了水分子,距离全乱套。后来加了判断:
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 文件到最细粒度坐标的拆解路径。

PDB 文件解析层次结构 PDB 文件 PDBParser Structure (顶级容器) Model (通常 1 个) Chain (链路经) 聚合关系:Chain → Residue Residue Atom + 坐标 解析器 容器 数据单元 虚线示意:部分遍历路径
我的经验: 初学的时候,你可能会觉得 Structure → Model → Chain → Residue → Atom 这个嵌套很啰嗦。但实话说,这种分层让后续操作非常灵活——想取某条链直接 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 文件有个底了。后面几章我们要在上面的对象上做各种计算——距离、角度、配体交互,这些全都建立在今天讲的基础之上。你先把脚本跑一遍,看看输出的原子坐标,感受一下从文件到对象的映射过程。