BLAST结果格式解析(tabular, XML)

拿到BLAST输出结果,第一件事是什么?

我个人习惯先看格式。BLAST默认给人看的格式是带标题、对齐块、打分条的“美观”版面,但机器读起来简直要命。你想想看,几千条比对记录,每一条都带一堆排版符号,写脚本来解析能把人逼疯。

还好NCBI给了我们更聪明的选项:outfmt 6(表格格式)和outfmt 5(XML格式)。这一节我带你把这些格式解剖清楚,顺便讲讲我在实际项目中踩过的坑。

outfmt 6:那个“偷懒”的表格格式

说白了,outfmt 6就是一行一条比对记录,用制表符分隔。格式简单到令人感动。

qseqid sseqid pident length mismatch gapopen qstart qend sstart send evalue bitscore

每条记录包含12个字段,下面是标准定义:

字段名含义类型
qseqid查询序列ID字符串
sseqid目标序列ID字符串
pident序列一致性百分比浮点数
length比对长度整数
mismatch错配数整数
gapopen缺口开放数整数
qstart查询序列起始位置整数
qend查询序列结束位置整数
sstart目标序列起始位置整数
send目标序列结束位置整数
evalue期望值浮点数(科学计数)
bitscore比特分数浮点数

经验提醒:我刚开始做特征工程时,以为evalue越低越好,就直接把它当负向特征用。后来发现有些假阳性的evalue也很低。建议你把bitscore和pident组合起来,比单用evalue靠谱得多。

运行BLAST时加这个参数就能输出表格格式:

blastn -query query.fasta -db nt -outfmt 6 -out result.tab

想要自定义字段顺序?可以用这个:

blastn -query query.fasta -db nt -outfmt "7 qseqid sseqid evalue bitscore" -out result_custom.tab

XML格式:那个“丰满”的备选方案

表格格式虽然简洁,但信息密度上比XML差远了。XML格式把每条比对的详细信息都包进去了——包括比对中间区域的逐位匹配状态、HSP分段、框架信息。

我一般在做深度特征提取时才会用XML,比如要分析gap具体在哪些位点出现、比对区域的碱基匹配度分布之类。

典型XML结构长这样:

<?xml version="1.0"?>
<!DOCTYPE BlastOutput PUBLIC "-//NCBI//NCBI BlastOutput/EN" "http://www.ncbi.nlm.nih.gov/dtd/NCBI_BlastOutput.dtd">
<BlastOutput>
  <BlastOutput_program>blastn</BlastOutput_program>
  <BlastOutput_db>nt</BlastOutput_db>
  <BlastOutput_iterations>
    <Iteration>
      <Iteration_iter-num>1</Iteration_iter-num>
      <Iteration_query-ID>Query_1</Iteration_query-ID>
      <Iteration_query-def>Sample_sequence</Iteration_query-def>
      <Iteration_hits>
        <Hit>
          <Hit_num>1</Hit_num>
          <Hit_id>gi|12345|ref|NM_001234.1|</Hit_id>
          <Hit_def>Species gene mRNA</Hit_def>
          <Hit_len>1850</Hit_len>
          <Hit_hsps>
            <Hsp>
              <Hsp_num>1</Hsp_num>
              <Hsp_bit-score>654.3</Hsp_bit-score>
              <Hsp_score>678</Hsp_score>
              <Hsp_evalue>1.2e-45</Hsp_evalue>
              <Hsp_query-from>1</Hsp_query-from>
              <Hsp_query-to>450</Hsp_query-to>
              <Hsp_hit-from>100</Hsp_hit-from>
              <Hsp_hit-to>549</Hsp_hit-to>
              <Hsp_qseq>ATCGACGTAGCTAGCTAGCTAGC...</Hsp_qseq>
              <Hsp_hseq>ATCGACGTAGCTAGCTAGCTAGC...</Hsp_hseq>
              <Hsp_midline>|||| ||| |||||||||||||||...</Hsp_midline>
            </Hsp>
          </Hit_hsps>
        </Hit>
      </Iteration_hits>
    </Iteration>
  </BlastOutput_iterations>
</BlastOutput>

关键节点:

  • 顶部信息:程序名、数据库、版本号
  • 迭代循环:每条查询序列是一个Iteration节点
  • Hit节点:每个比中的目标序列
  • Hsp节点:每个比中“片段”(一条序列可以有多个比中区域)
  • Hsp_midline:逐位反映匹配/错配/缺口的状态

两种格式的对比

对比维度outfmt 6(表格)outfmt 5(XML)
文件大小小大(通常3~5倍)
解析难度简单(split了事)中等(需要XML解析器)
信息密度仅摘要字段包含完整比对细节
特征提取支持度基础特征(分数、长度、位置)高级特征(gap分布、HSP分段、序列级相似度)
适用场景快速筛选、初筛模型精细化特征工程

你看这个对比就明白了:如果你的模型只需要evalue、bitscore、identity这些“粗粒度”特征,outfmt 6完全够用。但如果你要分析“比对区域内匹配分布的波动性”这类高级模式,就得用XML。

我曾经在一个抗性基因鉴定的项目里,仅靠outfmt 6的特征死活区分不开两个高度相似的家族。切换到XML之后,我提取了midline连续匹配片段的长度方差——竟然发现这是一个关键区分特征。有时候细节真的藏在全量信息里。

解析代码示例

先看解析outfmt 6有多简单:

import pandas as pd

columns = [
    "qseqid", "sseqid", "pident", "length",
    "mismatch", "gapopen", "qstart", "qend",
    "sstart", "send", "evalue", "bitscore"
]

df = pd.read_csv("result.tab", sep="\t", names=columns, header=None)
print(df.head())

搞定。一行不出错。

XML解析稍微复杂一点,我用ElementTree来实现:

import xml.etree.ElementTree as ET

tree = ET.parse("result.xml")
root = tree.getroot()

hits = []
for iteration in root.iter("Iteration"):
    query_id = iteration.find("Iteration_query-ID").text
    for hit in iteration.iter("Hit"):
        hit_id = hit.find("Hit_id").text
        for hsp in hit.iter("Hsp"):
            evalue = hsp.find("Hsp_evalue").text
            bitscore = hsp.find("Hsp_bit-score").text
            hits.append({
                "query_id": query_id,
                "hit_id": hit_id,
                "evalue": float(evalue),
                "bitscore": float(bitscore)
            })

print(f"共解析出 {len(hits)} 条HSP记录")

注意:XML文件如果特别大(比如上G级别的比对结果),直接全量解析到内存可能会导致OOM。我个人建议用xml.etree.ElementTree.iterparse做流式解析,逐条释放节点。我在处理全基因组比对时就遇到过这个问题,内存爆了两次才学乖。

context = ET.iterparse("large_result.xml", events=("end",))
for event, elem in context:
    if elem.tag == "Hsp":
        bitscore = elem.find("Hsp_bit-score").text
        evalue = elem.find("Hsp_evalue").text
        # 处理该HSP
        elem.clear()  # 释放内存,重要!

解析流程知识图谱

把上面讲的内容理一理,核心逻辑其实就是一条流水线:从原始输出到特征能用,中间需要过滤、解析、精炼三步。

BLAST结果格式解析流程 BLAST原始输出 outfmt 6 表格格式 12个字段 / 制表符分隔 outfmt 5 XML格式 层级结构 / 包含全量比对细节 pandas.read_csv / split 简单、快速、内存友好 iterparse / ElementTree 适合大文件流式解析 特征提取 / 模型输入

这个图的核心信息:

  • BLAST输出可以有两个流向:表格和XML
  • 表格格式解析快,适合大规模初筛
  • XML格式信息多,适合精细化特征工程
  • 最终都汇入特征提取阶段,但提取的“原料”丰富度不同

决定之前,想清楚一个问题

到底选outfmt 6还是outfmt 5?我个人判断标准很简单:

  • 选outfmt 6:数据量超过10万条比对、特征维度在20维以内、需要快速出基线模型
  • 选outfmt 5:模型精度卡在瓶颈、需要从比对细节里挖掘新特征、数据量在万级以下(或有大内存服务器)

好的数据集决定模型上限,特征工程只是逼近这个上限。解析格式这一步,其实就是决定你“能看到多少信息”的起点。