UniProt蛋白资源数据库实战:模块、检索语法与REST API
做蛋白相关分析的人,几乎绕不开一个名字:UniProt。不管你是在做质谱鉴定后的蛋白注释、给结构预测模型准备训练集,还是在查一个突变位点到底落在哪个功能域上,最后十有八九会落到这个全球蛋白资源数据库里。但很多人对它的认知停留在“一个能搜蛋白序列的网站”,真正用起来才发现,检索字段、注释可信度、ID 映射、版本管理这些东西,比想象中要绕。我这些年带过几个做蛋白组和分子进化的项目,中间因为 UniProt 的用法不对踩过的坑不算少——比如把 TrEMBL 的自动注释当权威结论写进报告,比如跨库映射时批量丢失条目却浑然不觉。
这篇东西想解决的问题很具体:把 UniProt 这个蛋白资源数据库拆开给你看,哪些模块该用、检索语法怎么写、REST 接口怎么接进流程、注释字段哪些能信哪些要溯源、版本和 ID 失效怎么处理。适合刚进实验室要处理蛋白数据的新手,也适合已经天天用它但想把手上的流程做扎实一些的老手。下面全部是我自己实操下来的路径和判断,不是官网文档的翻译。
1. 先把 UniProt 的家底摸清:四个模块各管什么
很多人第一次打开 UniProt 首页,看到搜索框就输入基因名,然后在一堆结果里挑第一个点进去。这个用法在查单个蛋白时够用,但只要稍微上点规模,就会出问题——因为你不知道自己点进去的那条记录,到底是人工审校过的,还是自动预测生成的。这个区别在写文章、做临床解读、做下游建模时,权重完全不一样。
1.1 Swiss-Prot 和 TrEMBL 不是“好数据”和“坏数据”的二分
UniProtKB 是核心,它被切成两半:**Swiss-Prot(人工审校)**和TrEMBL(自动注释)。前者是领域专家逐条读过文献、核对过实验证据后写的注释,条目量在几十万级;后者是基因组测序产出后由算法自动生成的翻译产物和功能推断,条目量在两亿多级别。数量级差了三四个数量级,这个比例本身就说明了问题。
但我不建议你把 TrEMBL 直接理解成“不可用”。正确的理解是:Swiss-Prot 的注释是可引用的,TrEMBL 的注释是待验证的线索。举个具体的例子,我给一个非模式物种的转录组做蛋白功能描述,绝大多数序列只能对上 TrEMBL 条目。这时候我会把 TrEMBL 的蛋白名、功能描述、GO 映射全部当作“候选标签”来用,用于聚类和富集分析的初筛完全没问题;但如果要在文章里写“该蛋白具有 XX 酶活性”,就必须回到 Swiss-Prot 找同源条目,或者找到原始文献证据。
实操上有个很省事的分流办法:检索结果页左侧会有一个 Reviewed / Unreviewed 的筛选。在写脚本批量拉取时,对应的是查询语句里加reviewed:true。我个人的习惯是,任何进入“结论层”的分析都只用 reviewed,任何进入“探索层”的分析两边的结果都留着,但结果表里必须带上一个来源标记列。
还有一个容易被忽略的点:Swiss-Prot 条目的annotation_score字段。这个分数是 1 到 5,反映的是注释的完整程度,不是正确性。分数 5 意味着这条记录的功能、催化活性、亚细胞定位、结构域、相互作用这些字段基本都填全了;分数 1 可能就只有一条序列和一个名字。我在筛选建库用的参考蛋白集时,经常用annotation_score >= 4加reviewed:true做双重过滤,把噪声压下去。实测下来,这个组合比单纯用 reviewed 过滤,在后续功能富集里假阳性会少一些。
1.2 UniParc 和 UniRef:两个被严重低估的入口
大部分人只用 UniProtKB,但另外两个模块在某些场景下才是正解。
UniParc是“原始序列的归档库”,它做的是去重归档——同一条序列不管从哪个来源进来、被谁注释成什么样,在 UniParc 里只有一个带UPI前缀的稳定标识。这个东西的价值在于跨版本稳定性。你要追踪一条序列在十年间的记录变化,或者你的数据来源混杂了不同数据库的不同版本,UniParc 的 UPI 是唯一一个不会因为注释更新而消失的锚点。我在做一个跨年份的病原蛋白比较时,就吃过没提前用 UPI 锚定的亏,早期版本的 accession 有一批后来被合并或删除,靠名字去回溯非常痛苦。
UniRef是聚类库,分 UniRef100、UniRef90、UniRef50 三档,数字代表序列相似度阈值。它的作用是压缩冗余。当你需要构建一个系统发育分析的参考集,或者给注释流程准备一个不那么臃肿的搜索库时,用 UniRef50 或 UniRef90 比直接用整个 UniProtKB 快一个数量级,而且结果更有代表性。这里的选择逻辑很简单:要最大灵敏度就用 UniRef100(几乎等于全库),要控制计算量就退到 UniRef90,做远缘同源分析时 UniRef50 的代表序列反而更好用。
Proteomes模块则是按物种组织的。做单一物种分析时,直接从 Proteomes 下载该物种的完整蛋白集,比在 UniProtKB 里用 taxonomy 条件去筛要干净得多——因为 Proteomes 里的条目是经过该物种基因组注释项目确认的,不会混进一堆同属其他物种的条目。
1.3 注释字段的层级:哪些能直接引用,哪些必须溯源
这是我认为最需要建立直觉的一块。UniProt 的注释不是一个整体,它是分层拼起来的,可信度差别很大。
| 注释类型 | 典型字段 | 来源性质 | 我的使用策略 |
|---|---|---|---|
| 序列与基础标识 | Sequence、Accession、Gene names | 直接测定或提交 | 可直接用 |
| 功能描述 | Function、Catalytic activity | 人工审校条目多为文献证据 | reviewed 可直接引用 |
| 结构域与家族 | Domain、Family、Similarity | 基于比对与结构预测 | 作为定位参考,不当结论 |
| 翻译后修饰 | PTM、Modified residue | 实验鉴定或预测混合 | 必须看 evidence 标签 |
| 变异 | Natural variant、Mutagenesis | 文献或数据库汇入 | 临床解读必须核对原文 |
| 交叉引用 | Cross-references(PDB、GO、KEGG 等) | 外部库同步 | 注意同步时间差 |
关键在evidence这个标签。Swiss-Prot 条目里几乎每一条注释后面都挂着 ECO 编号的证据码,能区分是实验证据(如 ECO:0000269)、推断证据(如 ECO:0000255)、还是自动生成。你在网页上点击注释右侧的小图标就能展开。写代码拉数据时,如果用的是txt或tsv格式,很多证据码是不会带出来的,需要换成xml或json格式才有完整的证据结构。这个细节我建议你提前想清楚,因为一旦你的流程只拉 tsv,后续想补证据就得重新跑一遍全量。
2. 检索语法与批量下载:从手点变成可复现
单个蛋白查询没什么好讲的,真正的时间成本在批量任务上。这块我把几个高频路径整理成可以直接抄的写法。
2.1 高级检索的字段语法,比关键词好用太多
UniProt 的搜索框支持 Lucene 风格的字段查询,这套语法的好处是结果可复现——你可以把整个查询串写进脚本文档或者方法学部分,别人能一模一样地跑出来。下面这些是我用得最频繁的组合:
# 人源、已审校、注释完整度较高 organism_id:9606 AND reviewed:true AND annotation_score:5 # 指定家族 + 长度区间 + 必须有结构 family:"protein kinase superfamily" AND length:[300 TO 600] AND database:pdb # 按 GO 术语反查蛋白 go:0004672 AND reviewed:true # 按亚细胞定位筛膜蛋白 cc_scl_term:SL-0039 AND reviewed:true # 排除某类注释,比如排除掉仅有预测证据的 reviewed:true NOT evidence:ECO_0000255几个踩过坑的地方值得说明。第一,organism_id用的是 NCBI Taxonomy 的数字 ID,不是物种拉丁名,人源是 9606,小鼠是 10090,这个必须查准,写错了物种结果会整体偏移。第二,长度区间的写法必须是[a TO b],方括号和 TO 都要大写,写成a-b是无效的,而且不会报错,只会默默返回空结果——这个坑我见过不止一个人栽。第三,database:pdb这类交叉引用筛选,本质上筛的是“有结构记录的条目”,不等于结构质量高,PDB 里的低分辨率结构也算。
提示:网页端跑完一次查询之后,URL 里会带上完整的查询串。我习惯直接把 URL 存下来当作分析记录的一部分,比事后重写一遍查询条件靠谱得多。
另外,结果页面右上角有个 “Columns” 按钮,可以自定义显示的列。默认只显示 accession、名称、基因、物种、长度,但你可以把序列、GO 术语、亚细胞定位、结构域这些统统加进去。我把常用的列配置保存成模板之后,同样的查询每次导出的表结构是一致的,省了很多整理时间。
2.2 批量下载的三条路,各有适用边界
要把数据落到本地,路径有三条,我按使用频率排一下。
第一条,网页端批量导出的 TSV/Excel。适合几百到几千条的量级。缺点是列数受限于你能选的字段,而且证据码、交叉引用这种嵌套结构导不出来。
第二条,FTP 全量下载。适合需要建本地库、做离线比对、或者要跑大规模扫描的场景。核心目录结构大概是这样:
# Swiss-Prot 全量(只含人工审校条目,体积小,日常够用) https://ftp.uniprot.org/pub/databases/uniprot/current_release/knowledgebase/complete/uniprot_sprot.fasta.gz https://ftp.uniprot.org/pub/databases/uniprot/current_release/knowledgebase/complete/uniprot_sprot.dat.gz # 完整 UniProtKB(含 TrEMBL,体积大,注意磁盘) https://ftp.uniprot.org/pub/databases/uniprot/current_release/knowledgebase/complete/uniprot_trembl.fasta.gz # 按物种取的参考蛋白集 https://ftp.uniprot.org/pub/databases/uniprot/current_release/knowledgebase/reference_proteomes/这里的经验是:别一次性把全量 TrEMBL 拉到本地,解压后几百 GB 的体量,除非你有专门的计算节点。绝大多数项目用 Swiss-Prot 全量加目标物种的 TrEMBL 子集就足够了。我在一个项目里图省事直接拉了全量,结果磁盘告警,清理花的时间比分析本身还长。
第三条,REST API 按需拉取。适合流程化、需要精确控制字段和格式的场景,下一节展开。
2.3 ID Mapping 是最大的隐形坑区
跨库转换这件事,看起来只是点几下,实际上是整个流程里最容易出静默错误的地方。比如你手上有两万个 Ensembl 基因 ID,想映射成 UniProt accession,映射完成之后你得到了 18600 条结果,剩下 1400 条去哪了?如果不去核对,这 7% 的数据就无声无息地消失了,而且不影响流程跑通,只在最终结论里埋一个偏差。
常见的数据丢失原因有几类。一是一对多:一个基因对应多个蛋白异构体,映射工具默认可能只保留主异构体,或者干脆全部展开导致行数膨胀。二是版本不匹配:源库和目标库的版本不同步,某些新提交的条目在旧版 UniProt 里还查不到。三是ID 类型判断错:Ensembl 的基因 ID、转录本 ID、蛋白 ID 是三种不同的东西,前缀和格式很像,映射时选错类型会大批量失败。
我的处理办法是每次都把“输入数、成功数、失败清单”三个数记下来,失败清单单独存一份,事后人工抽查一部分。如果失败率超过 5%,我会先停下来检查 ID 类型和版本,而不是直接接受结果。这个习惯帮我拦下过好几次问题,其中一次是上游给的 ID 里混进了一批已经退役的旧标识。
3. 用 REST API 把 UniProt 接进自动化流程
只要你的分析需要重复跑,就值得把接口用起来。UniProt 的 REST 服务设计得比较规整,几个端点覆盖了绝大多数需求。
3.1 三个核心端点与参数组织方式
最基础的是搜索端点,/uniprotkb/search,支持分页、字段选择和多种输出格式。关键参数有三个:query是检索条件,fields指定返回哪些列,format决定格式(tsv、json、fasta、xml等)。写的时候注意fields用的是字段的内部名称而不是显示名称,比如蛋白名是protein_name,物种是organism_name,序列是sequence。
# 拉取一批人源激酶的 accession、名称、长度、序列 curl -G "https://rest.uniprot.org/uniprotkb/search" \ --data-urlencode 'query=organism_id:9606 AND reviewed:true AND family:"protein kinase superfamily"' \ --data-urlencode 'fields=accession,protein_name,length,sequence' \ --data-urlencode 'format=tsv' \ --data-urlencode 'size=500' \ -o kinases.tsv第二个是单条记录端点,/uniprotkb/{accession}.{format},适合精确取一条完整记录。我一般用.json拿结构化的全字段,用.fasta拿序列。
第三个是 ID 映射端点,/idmapping,它是异步的,分三步走:先提交任务拿 jobId,再轮询状态,最后取结果。
# 第一步:提交映射任务 curl --data-urlencode 'from=Ensembl' \ --data-urlencode 'to=UniProtKB' \ --data-urlencode 'ids=P12345,Q8N158,P00533' \ https://rest.uniprot.org/idmapping/run # 第二步:用返回的 jobId 查状态 curl https://rest.uniprot.org/idmapping/status/{jobId} # 第三步:状态为 FINISHED 后取结果 curl https://rest.uniprot.org/idmapping/results/{jobId}这条链路的几个实操要点:任务结果保留时间有限,跑完尽快取走落盘,别指望过几天再来拿;提交的 ID 数量有上限,超了要分批;轮询别写死循环高频请求,加个几秒的间隔,既礼貌也不会被限流。
3.2 用 Python 包一层:重试、限流、断点
直接裸调 curl 在调试阶段没问题,进了生产流程就得加保护。我一般的写法是封装一个带重试和限流的函数,把“网络抖动”和“服务端 5xx”这两类问题挡在外面。
import time, requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def make_session(): s = requests.Session() retry = Retry( total=4, backoff_factor=1.2, # 重试间隔指数增长,避免密集重试 status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["GET", "POST"], ) s.mount("https://", HTTPAdapter(max_retries=retry)) return s def fetch_page(session, query, fields, size=500, cursor=None): params = {"query": query, "fields": fields, "format": "tsv", "size": size} if cursor: params["cursor"] = cursor r = session.get("https://rest.uniprot.org/uniprotkb/search", params=params, timeout=60) r.raise_for_status() next_cursor = None link = r.headers.get("Link", "") if 'rel="next"' in link: next_cursor = link.split("cursor=")[1].split(">")[0] return r.text, next_cursor这里有两个细节值得展开。一是分页用 cursor 而不是 page 参数,cursor 方式在结果集动态变化时更稳定,不会出现翻页时漏条或重复。二是backoff_factor别设太小,1 秒以下的重试在服务端压力大时等于没退避。我最早设 0.3,遇到过一次连续 503,几秒钟内重试耗尽直接失败。
还有一个我吃过亏的点:拉下来的 tsv 一定要检查行数。接口正常返回 HTTP 200 不等于数据完整,分页中途断掉的情况是存在的。我的习惯是流程结束时打印“预期条数 vs 实得条数”,前者从响应头里的x-total-results取,两者不一致就告警。
3.3 要不要建本地库:一个判断标准
很多人问要不要把 UniProt 整个镜像到本地。我的判断标准是看你的查询频次和查询模式。如果你每天要跑几十上百次按序列相似性的搜索,那本地建库是值得的,用 DIAMOND 或者 MMseqs2 建索引,比对速度比在线接口快得多,也不受网络影响。但如果你的需求主要是按 accession 取记录、按条件筛条目,那在线接口完全够用,本地化反而是负担——光是版本更新这一项就够烦的。
真要建本地库的话,几个建议:只建 Swiss-Prot 加目标物种的 TrEMBL 子集;建库时把 accession、序列、长度、物种这几个关键字段单独导出一份轻量索引表,方便快速定位;记录下建库时用的是哪一版(release 号),因为后续所有分析结果都要标注版本,否则半年后你没法复现。
4. 注释怎么读:从序列到功能之间那段距离
拿到一条 UniProt 记录,真正的阅读门槛不在序列,而在注释部分怎么解读。这块我按几类最常见的分析场景来说。
4.1 功能、结构域与修饰:三个必须区分的信息层
一条记录里的 Function 段落讲的是这个蛋白干什么,Domain 段落讲的是它有哪些结构模块,PTM 段落讲的是它被怎么修饰过。这三层的信息密度和可信度是递减的。
Function 段落在 Swiss-Prot 里通常写得比较克制,只写有实验支持的结论,而且很多句子后面挂着证据码。我读这段的时候会特别留意两类表述:带 “By similarity” 的是从同源蛋白推的,不是本蛋白实测;带 “Probable” 或 “Potential” 的是低置信度推断。这两类在写结论时必须降级表述。
Domain 段落的价值主要在于定位。做突变分析时,知道某个位点落在哪个结构域里,比知道这个蛋白整体功能要实用得多。但要注意,结构域边界是基于比对和预测给出的,不同来源的边界可能差几十个氨基酸。如果你的分析对这个边界敏感(比如要截取结构域做表达),最好去交叉引用里的 InterPro 或 Pfam 条目二次确认。
PTM 段落是最需要谨慎的。磷酸化位点、泛素化位点这类信息,实验验证的和预测的在记录里混在一起,只看位置不看证据码很容易把预测位点当实测位点用。我的做法是:任何进入定量分析的修饰位点,必须带实验证据码,否则只作为候选。这个原则在一次磷酸化富集分析里救过我——有一批位点看起来证据充分,展开一看全是预测,如果按实测位点写进结论就是硬伤。
4.2 变异注释与临床解读的边界
Natural variant字段记录了文献报道的序列变异,包括疾病相关变异、多态性位点等等。这个字段在临床遗传分析里用得很频繁,但有几个限制必须心里有数。
第一,UniProt 收录的变异不是经过临床系统评级的那套体系。它记录的是“有文献报道过这个变异”,至于致病性等级、人群频率、共分离证据,这些要去专门的地方看。把它当变异数据库用会出问题。
第二,同一个变异在不同蛋白异构体上的编号可能不同。UniProt 的默认编号基于 canonical isoform,如果你手上的序列是另一个异构体,位点会整体错位。这个错误非常隐蔽,因为在错位之后,落点依然可能是个看起来合理的位置。我的处理办法是每次做位点映射前,先把序列和 canonical 序列做一次比对,确认编号体系的对应关系,而不是直接按长度相减。
第三,Mutagenesis字段记录的是实验构建的突变,和自然变异是两码事,不要混。前者是研究手段,后者是观察结果。
4.3 几个典型下游场景里的用法差异
质谱蛋白鉴定。流程一般是搜库得到蛋白 ID,然后回 UniProt 注释。这里的要点是搜库用的参考库必须标明来源和版本,用 Swiss-Prot 搜和用含 TrEMBL 的库搜,结果差异很大——前者假阳性低但漏检多,后者覆盖广但会引入大量未审校蛋白。做定量比较时,两组的搜库库必须完全一致,否则差异里混进了数据库偏差。
结构预测的训练与验证集构建。关键是避免数据泄漏。如果你用 UniProt 里“有 PDB 交叉引用”的条目做测试集,而训练集里包含了这些蛋白的同源序列,指标会虚高。我的做法是按序列相似度聚类后划分数据集,并且在记录里保存每个条目的 UniRef50 聚类 ID,划分时以聚类为单位而不是以条目为单位。另外要注意 PDB 覆盖的蛋白在 UniProt 里是有偏的,偏向于容易结晶的、研究热门的,这个偏倚会传导到模型性能上。
分子进化分析。重点在序列集的质量控制。直接从 UniProt 拉一个家族的成员,很可能混进大量片段序列、异常长度、以及注释为“Fragment”的条目。这些必须在建树前剔掉,否则比对质量会很差。我一般会加几个硬过滤:长度落在中位数 0.5 到 1.5 倍之间、不含连续 X、reviewed:true优先。另外,同一物种内的高度相似序列要控制比例,不然一棵树会被某个物种的扩增子撑歪。
5. 常见问题与排查实录
这一节都是实际撞过的,按发生频率排。
5.1 ID 失效与版本漂移:最该提前设计的一环
UniProt 的 accession 大部分是稳定的,但也有合并、拆分、删除的情况。旧 accession 在检索时一般还能跳转到新条目,但你的本地文件里存的是旧 ID,下游一对就断了。这个问题在跨年度的长期项目里几乎必然出现。
我的处理策略有三层。第一层,任何结果表里同时保留 accession 和 entry name,前者相对稳定,后者是随注释更新会变的,两个一起存便于交叉核对。第二层,在项目开始时记录 release 版本号,所有分析结果的元数据里带上这个号,之后复现时直接用对应版本的 FTP 归档,而不是用当前版本。第三层,对长期维护的数据集做定期体检:拿本地存的 accession 批量查一次,把返回为空或指向新 ID 的记录挑出来,更新映射表。这个体检脚本我一般半年跑一次,成本很低。
注意:UniProt 保留历史版本的归档目录,做复现分析时一定用归档版而不是 current release。current 会滚动更新,你上个月跑出来的结果这个月就复现不了。
5.2 问题速查表
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| 查询返回 0 条,但确信有数据 | 字段名写错、区间语法错、物种 ID 错 | 先用宽条件查一次,再逐步加条件定位 |
| 批量映射后条数明显变少 | 一对多被压缩、ID 类型选错、版本不同步 | 统计输入/输出数,抽查失败清单 |
| 拉下来的表缺列 | fields 参数用了显示名称而非内部名 | 用/configure/uniprotkb/result-fields查字段名 |
| 序列与预期长度差很多 | 拿到的是异构体或片段条目 | 检查Sequence段的isoform与Fragment标记 |
| 变异位点对不上 | 编号基于不同异构体 | 与 canonical 序列比对确认编号体系 |
| 注释写得很详细但不可引 | 条目实际来自 TrEMBL | 检查reviewed标记与证据码 |
| 接口偶发 503 | 请求过于密集 | 加退避重试,降低并发 |
| 结果无法复现 | 未记录 release 版本 | 补记录版本号,改用归档数据 |
5.3 几条不会写在文档里的经验
别迷信“注释完整”等于“注释正确”。annotation_score 高的条目通常是研究热门的蛋白,热门蛋白的注释更新频繁,历史上也存在过被修订的情况。真正稳妥的做法是看证据码,而不是看分数。
异构体问题是隐性错误的第一来源。我统计过自己早期出过的分析错误,跟异构体编号相关的占了相当比例。现在的习惯是,任何涉及位点、长度、结构域边界的分析,第一步先确认用的是哪条序列,把它和 canonical 序列的关系搞清楚。
下载的数据一定要做完整性校验。FTP 上的大文件下载中断是常事,尤其是网络不稳的时候。gz 文件可以用gzip -t快速验一遍,FASTA 可以数一下以>开头的行数是不是和预期量级相符。这个动作只花几十秒,但能挡住后面几小时的无效计算。
接口返回的 TSV 里,空值不等于空。有些字段没填,导出后是空字符串,和你真正想要的“无”是同一表现。做统计的时候如果不区分,会把“未注释”和“注释为无”混在一起。我一般在读表时显式把空串转成 NA,再单独统计缺失比例,缺失率高的字段直接不进分析。
给团队定一个字段命名规范。这件事听起来很琐碎,但多个人协作时,同一个字段有人叫uniprot_id,有人叫acc,有人叫protein_accession,合并表的时候就是灾难。我现在的做法是项目启动时先定一份字段对照表,所有脚本按同一套名字输出,成本很低,收益很大。
最后分享一个我一直在用的检查动作:任何用 UniProt 数据得出的结论,在写进报告之前,我会随机抽 5 条记录,回到网页端把它们的关键注释、证据码、交叉引用原样看一遍。这个动作大概十分钟,但它拦下过至少三次“批量处理时字段错位”的问题。自动化流程跑得越顺,越需要这种手动的抽样核对——因为批量处理不会报错,它只会安静地给你一个看起来合理的错误答案。