跳转至

生信数据库批量查询

彭文强 | 2026届 | 生信分析工程师求职

1. 一句话说明

生信数据库批量查询就是用编程方式(API/命令行)一次性从 NCBI、UniProt、Ensembl 等公共数据库中获取成百上千条数据,替代手动逐条搜索下载的低效操作。


2. 为什么要学

你目前的情况

你的 T2D 项目中已经用过 prefetch + fasterq-dump 从 NCBI SRA 下载宏基因组测序数据,但那只是"知道下载命令"。系统学习批量查询能让你:

场景 手动查(网页) 批量查(API)
下载 100 个 SRA 样本的元数据 点 100 次,复制粘贴半天 一个脚本 30 秒搞定
查 T2D 相关基因在 KEGG 中的通路 一个个搜,容易漏 批量提交基因列表,全部返回
获取差异菌对应蛋白的功能注释 UniProt 网页一条条查 REST API 一次查完
做文献综述(T2D + gut microbiome) PubMed 手动翻页 Entrez 自动拉取摘要
查宏基因组 MAG 中基因的同源信息 Ensembl 逐个搜 REST API 批量获取

白话总结:手动查数据库像去超市一个个挑菜,API 批量查像直接给超市发采购单,效率差 100 倍。面试中说"我能用 Python 批量查询各大数据库做自动化分析",直接加分。


3. 核心概念白话版

3.1 REST API(表述性状态转移应用编程接口)

白话:API 就是数据库留给程序员的"后门"。
网页版数据库是给人看的(点按钮、翻页面),
API 是给程序用的(发个网址请求,直接返回数据)。

类比:
- 网页查询 = 去银行柜台排队办业务(得一个个等)
- API 查询 = 用手机银行(自动化、批量、快速)

REST API 的核心:
- 用 URL 表示你要查什么(https://rest.uniprot.org/uniprotkb/P12345)
- 用 HTTP 方法表示你要做什么(GET=查询,POST=提交)
- 返回格式通常是 JSON 或 XML(结构化数据,Python 能直接解析)

3.2 Entrez(NCBI 统一检索系统)

白话:NCBI 旗下有几十个数据库(PubMed、SRA、GEO、GenBank...),
Entrez 是它们的"总管家",让你用同一套接口查所有库。

类比:Entrez 就像购物 App 的"搜索框",
虽然背后有服装、食品、电子等多个店铺(数据库),
但你只需要在一个搜索框里打字就能跨店搜索。

关键限制:
- 每秒最多 3 次请求(没有 API Key 时)
- 注册 API Key 后可以每秒 10 次
- 必须设置 email(NCBI 要求,不设会被封 IP)

3.3 批量下载 vs 批量查询

批量查询:问数据库"给我这些东西的信息"(获取元数据/注释)
批量下载:把实际的大文件(FASTQ、基因组序列)拉到本地

举例:
- 批量查询:查 100 个 SRA 样本的物种、测序平台、文库类型
- 批量下载:把这 100 个样本的 FASTQ 文件全下载下来

工具对应:
- 批量查询 → Python + requests/Biopython
- 批量下载 → prefetch + fasterq-dump(SRA)、wget/curl(通用)

3.4 元数据(Metadata)

白话:元数据就是"关于数据的数据",即描述信息。

举例(SRA 样本的元数据):
- 样本来源:人肠道
- 疾病状态:T2D 患者 / 健康对照
- 测序平台:Illumina NovaSeq 6000
- 文库类型:WGS(全基因组鸟枪法)
- Read 长度:150bp paired-end
- 数据量:5 Gbp

为什么重要:
你做宏基因组分析,第一步永远是筛选合适的样本。
批量查元数据就是帮你快速"选品"——哪些样本符合你的分析条件。

4. 主要数据库详解

4.1 NCBI SRA(Sequence Read Archive)

是什么:全球最大的高通量测序原始数据仓库,存储了几千万个样本的测序文件。

能查什么: - 测序原始数据(FASTQ) - 样本元数据(物种、疾病、测序平台等) - 项目信息(BioProject)、样本信息(BioSample)

API 用法:通过 Entrez 的 esearch + efetch 查询

# ============================================
# SRA 批量查询:查找 T2D 相关宏基因组数据
# ============================================
from Bio import Entrez  # Biopython 的 Entrez 模块
import time  # 用于控制请求频率

# 必须设置邮箱,NCBI 要求(不设会被封 IP)
Entrez.email = "pengwenqiang@example.com"
# 可选:注册 API Key 后每秒可发 10 次请求(默认只有 3 次)
# Entrez.api_key = "你的API_KEY"

# 第一步:搜索(esearch)—— 找到符合条件的记录 ID
handle = Entrez.esearch(
    db="sra",  # 指定搜索 SRA 数据库
    term="type 2 diabetes AND metagenome AND gut",  # 搜索词
    retmax=50,  # 最多返回 50 条结果
    usehistory="y"  # 使用历史服务器(处理大量结果时必须开)
)
results = Entrez.read(handle)  # 解析返回的 XML
handle.close()

print(f"找到 {results['Count']} 条相关记录")  # 打印总数
id_list = results['IdList']  # 获取记录 ID 列表

# 第二步:获取详情(efetch)—— 根据 ID 拿到具体信息
for sra_id in id_list[:5]:  # 先拿前 5 条看看
    handle = Entrez.efetch(
        db="sra",  # 从 SRA 库获取
        id=sra_id,  # 指定记录 ID
        rettype="full",  # 返回完整信息
        retmode="xml"  # XML 格式
    )
    record = handle.read()  # 读取原始 XML 文本
    handle.close()
    print(record[:500])  # 打印前 500 字符看看结构
    time.sleep(0.4)  # 每次请求间隔 0.4 秒,避免被封

4.2 NCBI GEO(Gene Expression Omnibus)

是什么:基因表达数据仓库,存储芯片和 RNA-seq 的表达矩阵和实验设计。

能查什么: - 基因表达矩阵(做差异分析用的) - 实验设计(哪些是实验组、对照组) - 数据系列(GSE)、样本(GSM)、平台(GPL)

API 用法

# ============================================
# GEO 批量查询:查找 T2D 相关转录组数据集
# ============================================
from Bio import Entrez  # Entrez 统一检索
import time

Entrez.email = "pengwenqiang@example.com"

# 搜索 GEO DataSets 中与 T2D 相关的表达谱
handle = Entrez.esearch(
    db="gds",  # GEO DataSets 数据库
    term="type 2 diabetes[Title] AND Homo sapiens[Organism]",  # 标题含 T2D + 人类
    retmax=20  # 返回 20 条
)
results = Entrez.read(handle)
handle.close()

print(f"找到 {results['Count']} 个相关数据集")

# 获取数据集详细信息
for gds_id in results['IdList'][:3]:  # 取前 3 条
    handle = Entrez.esummary(
        db="gds",  # 从 GDS 获取摘要
        id=gds_id  # 指定 ID
    )
    summary = Entrez.read(handle)
    handle.close()
    # 打印数据集标题和描述
    for item in summary:
        print(f"标题: {item.get('title', 'N/A')}")
        print(f"摘要: {item.get('summary', 'N/A')[:200]}")
        print("---")
    time.sleep(0.4)  # 限速

4.3 NCBI PubMed

是什么:生物医学文献数据库,收录超 3600 万篇论文的摘要和引用信息。

能查什么: - 论文标题、摘要、作者、期刊 - 关键词、MeSH 术语 - 引用关系

API 用法

# ============================================
# PubMed 批量查询:获取 T2D 肠道菌群最新文献
# ============================================
from Bio import Entrez
import time

Entrez.email = "pengwenqiang@example.com"

# 搜索 PubMed
handle = Entrez.esearch(
    db="pubmed",  # PubMed 数据库
    term="type 2 diabetes gut microbiome metagenomics",  # 搜索词
    retmax=10,  # 返回 10 条
    sort="relevance"  # 按相关度排序(也可以用 "pub_date" 按时间)
)
results = Entrez.read(handle)
handle.close()

print(f"找到 {results['Count']} 篇相关文献")

# 批量获取文献摘要
ids = ",".join(results['IdList'])  # 把 ID 列表拼成逗号分隔的字符串
handle = Entrez.efetch(
    db="pubmed",
    id=ids,  # 一次传入多个 ID,批量获取
    rettype="abstract",  # 获取摘要
    retmode="xml"  # XML 格式(方便解析)
)
records = Entrez.read(handle)
handle.close()

# 解析并打印每篇文献
for article in records['PubmedArticle']:
    # 层层解析 XML 结构拿到标题和摘要
    medline = article['MedlineCitation']
    title = medline['Article']['ArticleTitle']
    # 摘要可能不存在(综述类文章)
    abstract_list = medline['Article'].get('Abstract', {}).get('AbstractText', ['无摘要'])
    abstract = " ".join(str(a) for a in abstract_list)
    pmid = str(medline['PMID'])
    print(f"[PMID:{pmid}] {title}")
    print(f"  摘要前100字: {abstract[:100]}...")
    print()

4.4 Ensembl

是什么:欧洲生物信息学研究所(EBI)维护的基因组注释数据库,提供基因结构、变异、比较基因组等信息。

能查什么: - 基因坐标、转录本、外显子结构 - 基因功能注释(GO terms) - 物种间同源基因 - 遗传变异(SNP)

API 用法(REST API,不需要额外安装包):

# ============================================
# Ensembl REST API:批量获取基因信息
# ============================================
import requests  # HTTP 请求库
import time

# Ensembl REST API 基础地址
BASE_URL = "https://rest.ensembl.org"

def get_gene_info(gene_symbol, species="homo_sapiens"):
    """根据基因名获取 Ensembl 基因信息"""
    # lookup/symbol 端点:用基因符号查找
    url = f"{BASE_URL}/lookup/symbol/{species}/{gene_symbol}"
    headers = {"Content-Type": "application/json"}  # 要求返回 JSON
    response = requests.get(url, headers=headers)

    if response.status_code == 200:  # 200 表示成功
        return response.json()  # 解析 JSON 返回字典
    else:
        print(f"查询 {gene_symbol} 失败,状态码: {response.status_code}")
        return None

# T2D 相关基因列表(从你的差异分析结果中来)
t2d_genes = ["TCF7L2", "PPARG", "KCNJ11", "SLC30A8", "INS", "IRS1"]

# 批量查询
results = []
for gene in t2d_genes:
    info = get_gene_info(gene)
    if info:
        results.append({
            "symbol": gene,
            "ensembl_id": info.get("id"),  # Ensembl 基因 ID
            "chromosome": info.get("seq_region_name"),  # 染色体
            "start": info.get("start"),  # 起始位置
            "end": info.get("end"),  # 终止位置
            "biotype": info.get("biotype"),  # 生物类型(protein_coding 等)
            "description": info.get("description")  # 基因描述
        })
        print(f"{gene}: {info.get('id')} | Chr{info.get('seq_region_name')}:{info.get('start')}-{info.get('end')}")
    time.sleep(0.2)  # Ensembl 限制每秒 15 次请求

# 批量查询(POST 方式,更高效)
def batch_lookup_genes(gene_ids):
    """用 POST 方式批量查询多个 Ensembl ID"""
    url = f"{BASE_URL}/lookup/id"
    headers = {"Content-Type": "application/json", "Accept": "application/json"}
    # POST 请求体:传入 ID 列表
    data = {"ids": gene_ids}
    response = requests.post(url, headers=headers, json=data)

    if response.status_code == 200:
        return response.json()
    else:
        print(f"批量查询失败: {response.status_code}")
        return None

# 用上一步拿到的 Ensembl ID 做批量查询
ensembl_ids = [r["ensembl_id"] for r in results if r.get("ensembl_id")]
if ensembl_ids:
    batch_results = batch_lookup_genes(ensembl_ids)
    if batch_results:
        for eid, info in batch_results.items():
            print(f"{eid}: {info.get('display_name')} - {info.get('description', '')[:60]}")

4.5 UniProt

是什么:全球最权威的蛋白质序列和功能注释数据库,分为 Swiss-Prot(人工审核)和 TrEMBL(自动注释)。

能查什么: - 蛋白质序列、结构域、修饰位点 - 功能注释(GO terms、EC number) - 蛋白互作、亚细胞定位 - 疾病关联

API 用法(2022 年后新 REST API):

# ============================================
# UniProt REST API:批量查询蛋白质信息
# ============================================
import requests
import time

# UniProt 新版 REST API 基础地址(2022年改版后)
BASE_URL = "https://rest.uniprot.org"

def search_uniprot(query, format="json", size=25):
    """搜索 UniProt 数据库"""
    url = f"{BASE_URL}/uniprotkb/search"
    params = {
        "query": query,  # 搜索语句
        "format": format,  # 返回格式:json/tsv/fasta
        "size": size,  # 每页结果数
        "fields": "accession,id,protein_name,gene_names,organism_name,length,go_f"
        # fields 指定返回哪些字段(减少数据量)
    }
    response = requests.get(url, params=params)

    if response.status_code == 200:
        return response.json()
    else:
        print(f"搜索失败: {response.status_code} - {response.text[:200]}")
        return None

# 搜索 T2D 相关的人类蛋白质
result = search_uniprot(
    query="type 2 diabetes AND organism_id:9606 AND reviewed:true",
    # organism_id:9606 是人类
    # reviewed:true 只搜 Swiss-Prot(人工审核的,质量高)
    size=10
)

if result and "results" in result:
    for entry in result["results"]:
        acc = entry.get("primaryAccession", "N/A")  # UniProt 登录号
        name = entry.get("proteinDescription", {}).get("recommendedName", {}).get("fullName", {}).get("value", "N/A")
        genes = entry.get("genes", [{}])
        gene_name = genes[0].get("geneName", {}).get("value", "N/A") if genes else "N/A"
        organism = entry.get("organism", {}).get("scientificName", "N/A")
        print(f"{acc} | {gene_name} | {name} | {organism}")

# 按 accession 批量获取(直接用 accession 列表)
def get_uniprot_entry(accession):
    """获取单条 UniProt 记录的完整信息"""
    url = f"{BASE_URL}/uniprotkb/{accession}.json"
    response = requests.get(url)
    if response.status_code == 200:
        return response.json()
    return None

# 示例:获取胰岛素受体的详细信息
insulin_receptor = get_uniprot_entry("P06213")
if insulin_receptor:
    print(f"蛋白名: {insulin_receptor['proteinDescription']['recommendedName']['fullName']['value']}")
    print(f"序列长度: {insulin_receptor['sequence']['length']} aa")
    # 打印 GO 功能注释
    for ref in insulin_receptor.get("uniProtKBCrossReferences", []):
        if ref.get("database") == "GO":
            props = {p["key"]: p["value"] for p in ref.get("properties", [])}
            print(f"  GO: {ref['id']} - {props.get('GoTerm', '')}")

4.6 KEGG(Kyoto Encyclopedia of Genes and Genomes)

是什么:京都基因与基因组百科全书,以代谢通路图著称,把基因、酶、代谢物、疾病串联起来。

能查什么: - 代谢通路(Pathway) - 基因 → 酶 → 反应 → 通路的映射关系 - 疾病相关通路 - 微生物功能模块

API 用法(KEGG REST API,免费但有限制):

# ============================================
# KEGG REST API:查询代谢通路
# ============================================
import requests
import time

# KEGG API 基础地址(http 和 https 均可用,官方文档常写 http)
BASE_URL = "https://rest.kegg.jp"

def kegg_find(database, query):
    """在 KEGG 指定数据库中搜索"""
    url = f"{BASE_URL}/find/{database}/{query}"
    response = requests.get(url)
    if response.status_code == 200:
        return response.text  # KEGG 返回的是纯文本,不是 JSON
    return None

def kegg_get(entry_id):
    """获取 KEGG 条目的详细信息"""
    url = f"{BASE_URL}/get/{entry_id}"
    response = requests.get(url)
    if response.status_code == 200:
        return response.text
    return None

def kegg_link(target_db, source_id):
    """查询两个数据库之间的映射关系"""
    url = f"{BASE_URL}/link/{target_db}/{source_id}"
    response = requests.get(url)
    if response.status_code == 200:
        return response.text
    return None

# 示例 1:搜索 T2D 相关通路
print("=== 搜索 T2D 相关通路 ===")
result = kegg_find("pathway", "type II diabetes")
if result:
    for line in result.strip().split("\n"):
        print(line)
        # 输出类似: path:hsa04930  Type II diabetes mellitus - Homo sapiens

# 示例 2:获取 T2D 通路的详细基因列表
print("\n=== T2D 通路包含的基因 ===")
genes_in_pathway = kegg_link("genes", "hsa04930")  # hsa04930 是人类 T2D 通路
if genes_in_pathway:
    gene_list = []
    for line in genes_in_pathway.strip().split("\n")[:10]:  # 只打印前 10 个
        parts = line.split("\t")
        if len(parts) == 2:
            gene_list.append(parts[1])
            print(parts[1])  # 输出类似: hsa:3630(INS 基因)

# 示例 3:查基因对应的所有通路
print("\n=== INS 基因参与的通路 ===")
ins_pathways = kegg_link("pathway", "hsa:3630")  # hsa:3630 是 INS(胰岛素)
if ins_pathways:
    for line in ins_pathways.strip().split("\n"):
        parts = line.split("\t")
        if len(parts) == 2:
            pathway_id = parts[1]
            # 获取通路名称
            info = kegg_find("pathway", pathway_id.replace("path:", ""))
            if info:
                print(f"  {info.strip().split(chr(10))[0]}")
            time.sleep(0.3)  # KEGG 限速

4.7 STRING(蛋白互作网络)

是什么:已知和预测的蛋白-蛋白相互作用网络数据库,整合了实验证据、文本挖掘、共表达等多种证据。

能查什么: - 蛋白互作伙伴 - 互作置信度评分 - 功能富集分析 - 互作网络图

API 用法

# ============================================
# STRING API:查询蛋白互作网络
# ============================================
import requests
import time

# STRING API 基础地址
BASE_URL = "https://string-db.org/api"
# STRING 使用 NCBI taxonomy ID 标识物种
SPECIES_TAXID = 9606  # 人类

def get_string_interactions(protein, required_score=400):
    """获取蛋白质的互作伙伴"""
    url = f"{BASE_URL}/json/network"
    params = {
        "identifiers": protein,  # 蛋白名(基因名也行)
        "species": SPECIES_TAXID,  # 物种
        "required_score": required_score,  # 最低置信度(0-1000)
        "limit": 20  # 最多返回 20 个互作
    }
    response = requests.get(url, params=params)
    if response.status_code == 200:
        return response.json()
    return None

def get_enrichment(proteins):
    """对蛋白列表做功能富集分析"""
    url = f"{BASE_URL}/json/enrichment"
    params = {
        "identifiers": "\r".join(proteins),  # 多个蛋白用回车分隔
        "species": SPECIES_TAXID
    }
    response = requests.get(url, params=params)
    if response.status_code == 200:
        return response.json()
    return None

# 查询 INS(胰岛素)的互作伙伴
print("=== INS 的蛋白互作伙伴 ===")
interactions = get_string_interactions("INS")
if interactions:
    for item in interactions[:10]:
        print(f"  {item['preferredName_A']} <-> {item['preferredName_B']} "
              f"(score: {item['score']:.3f})")

# 对 T2D 相关蛋白做富集分析
print("\n=== T2D 相关蛋白的功能富集 ===")
t2d_proteins = ["INS", "INSR", "IRS1", "IRS2", "PIK3CA", "AKT1", "SLC2A4"]
enrichment = get_enrichment(t2d_proteins)
if enrichment:
    # 只打印 KEGG 通路的富集结果
    kegg_terms = [e for e in enrichment if e.get("category") == "KEGG"]
    for term in kegg_terms[:5]:
        print(f"  {term['term']} | {term['description']} | p={term['p_value']:.2e}")

5. Entrez 编程接口(Biopython)完整教程

5.1 安装和配置

# ============================================
# Biopython Entrez 安装与基础配置
# ============================================
# 安装:conda install -c conda-forge biopython
# 或者:pip install biopython

from Bio import Entrez

# 必须设置!不设置 NCBI 会拒绝请求
Entrez.email = "pengwenqiang@example.com"

# 强烈建议注册 API Key(免费):
# 去 https://www.ncbi.nlm.nih.gov/account/settings/ 注册
# 有 Key:每秒 10 次请求;没有:每秒 3 次
# Entrez.api_key = "你的key"

5.2 Entrez 八大函数速查

函数 作用 白话解释
esearch 搜索,返回 ID 列表 "有哪些符合条件的?"
efetch 根据 ID 获取完整记录 "把这几条的详细信息给我"
esummary 根据 ID 获取摘要 "简单说说这几条是什么"
einfo 查数据库信息 "这个库有哪些字段?"
elink 查记录间关联 "这篇文章引用了哪些?"
epost 上传 ID 列表到服务器 "先存着,等会儿我要批量查"
egquery 跨库搜索计数 "这个词在各个库里各有多少条?"
espell 拼写检查 "我是不是拼错了?"

5.3 完整工作流:搜索 → 获取 → 解析

# ============================================
# 完整 Entrez 工作流:批量获取 T2D 宏基因组的 BioProject 信息
# ============================================
from Bio import Entrez
import time
import json

Entrez.email = "pengwenqiang@example.com"

# === 第一步:搜索 BioProject ===
print("Step 1: 搜索 T2D 相关的宏基因组项目...")
handle = Entrez.esearch(
    db="bioproject",  # BioProject 数据库
    term='("type 2 diabetes" OR "T2D") AND metagenome AND gut',
    retmax=100,  # 最多 100 条
    usehistory="y"  # 开启历史服务器(大结果集必开)
)
search_results = Entrez.read(handle)
handle.close()

count = int(search_results["Count"])
webenv = search_results["WebEnv"]  # 服务器端会话 ID
query_key = search_results["QueryKey"]  # 查询标识
print(f"  找到 {count} 个项目")

# === 第二步:分批获取(避免一次请求太多被封)===
print("\nStep 2: 分批获取项目详情...")
batch_size = 20  # 每批 20 条
all_projects = []

for start in range(0, min(count, 60), batch_size):  # 最多取 60 条
    print(f"  获取第 {start+1}-{start+batch_size} 条...")
    handle = Entrez.esummary(
        db="bioproject",
        query_key=query_key,  # 用之前搜索的 query_key
        WebEnv=webenv,  # 用之前的 WebEnv(避免重复搜索)
        retstart=start,  # 从第几条开始
        retmax=batch_size  # 每次取几条
    )
    summaries = Entrez.read(handle)
    handle.close()

    # 解析每条记录
    for doc in summaries.get("DocumentSummarySet", {}).get("DocumentSummary", []):
        project_info = {
            "uid": doc.attributes.get("uid", ""),
            "name": doc.get("Project_Name", ""),
            "title": doc.get("Project_Title", ""),
            "description": doc.get("Project_Description", "")[:200],
            "organism": doc.get("Organism_Name", ""),
            "registration_date": doc.get("Registration_Date", "")
        }
        all_projects.append(project_info)

    time.sleep(0.5)  # 批次间等待

# === 第三步:输出结果 ===
print(f"\nStep 3: 获取完成,共 {len(all_projects)} 个项目")
for i, proj in enumerate(all_projects[:5], 1):
    print(f"\n[{i}] {proj['title'][:80]}")
    print(f"    物种: {proj['organism']}")
    print(f"    日期: {proj['registration_date']}")

# 可以保存为 JSON 供后续分析
with open("t2d_bioprojects.json", "w", encoding="utf-8") as f:
    json.dump(all_projects, f, ensure_ascii=False, indent=2)
print("\n结果已保存到 t2d_bioprojects.json")

5.4 使用 History Server 处理海量结果

# ============================================
# 处理超大结果集(上万条记录)的正确姿势
# ============================================
from Bio import Entrez
import time

Entrez.email = "pengwenqiang@example.com"

# 搜索所有人类肠道宏基因组数据(可能有几万条)
handle = Entrez.esearch(
    db="sra",
    term="human gut metagenome[Organism] AND WGS[Strategy]",
    retmax=0,  # 先不返回 ID,只看总数
    usehistory="y"  # 关键!把结果存在 NCBI 服务器上
)
results = Entrez.read(handle)
handle.close()

total = int(results["Count"])
webenv = results["WebEnv"]
query_key = results["QueryKey"]
print(f"总共有 {total} 条记录,用 History Server 分批获取")

# 分批获取(每次 500 条,NCBI 建议不超过 500)
batch_size = 500
for start in range(0, min(total, 2000), batch_size):  # 示例只取前 2000 条
    handle = Entrez.efetch(
        db="sra",
        rettype="runinfo",  # SRA 特有:返回 CSV 格式的运行信息
        retmode="text",
        retstart=start,
        retmax=batch_size,
        webenv=webenv,
        query_key=query_key
    )
    data = handle.read()
    handle.close()

    # 保存每批数据(追加模式)
    mode = "w" if start == 0 else "a"
    with open("sra_runinfo.csv", mode) as f:
        if start == 0:
            f.write(data)  # 第一批包含表头
        else:
            # 跳过表头行(后续批次不需要重复写表头)
            lines = data.split("\n")
            f.write("\n".join(lines[1:]))

    print(f"  已获取 {start + batch_size}/{total}")
    time.sleep(1)  # 大量请求时间隔要更长

print("全部保存到 sra_runinfo.csv")

6. 批量下载 SRA 数据(prefetch + fasterq-dump)

6.1 安装 SRA Toolkit

# conda 安装(推荐,最简单)
conda install -c bioconda sra-tools

# 验证安装
prefetch --version
fasterq-dump --version

# 配置缓存路径(避免下载到默认路径撑爆硬盘)
vdb-config --set /repository/user/main/public/root=/home/pweaz/sra_cache

6.2 批量下载流程

# ============================================
# 批量下载 SRA 数据:从 accession list 到 FASTQ
# ============================================

# 第一步:准备 accession 列表文件
# 文件内容每行一个 SRA 编号,例如:
# SRR12345678
# SRR12345679
# SRR12345680

# 第二步:批量预下载(prefetch)—— 先下载压缩的 .sra 文件
# -O 指定输出目录
# --max-size 50G 允许单个文件最大 50GB(默认 20GB 会跳过大文件)
prefetch --option-file sra_accession_list.txt \
         -O ./sra_downloads \
         --max-size 50G \
         --progress

# 第三步:批量转换为 FASTQ(fasterq-dump)
# 比旧版 fastq-dump 快 5-10 倍!
# --split-3:双端数据自动分成 _1.fastq 和 _2.fastq
# --threads 8:用 8 个线程加速
# --temp /tmp/sra_tmp:指定临时目录(需要大空间!约为输出的 2 倍)
for sra_file in ./sra_downloads/*/*.sra; do
    fasterq-dump "$sra_file" \
        --outdir ./fastq_output \
        --split-3 \
        --threads 8 \
        --temp /tmp/sra_tmp \
        --progress
done

# 第四步:压缩 FASTQ(节省 70% 空间)
pigz -p 8 ./fastq_output/*.fastq
# pigz 是多线程版 gzip,-p 8 用 8 线程

6.3 Python 自动化完整脚本

# ============================================
# Python 自动化 SRA 批量下载
# ============================================
import subprocess  # 调用系统命令
import os
from pathlib import Path  # 路径处理

# 配置
ACCESSION_FILE = "sra_accession_list.txt"  # accession 列表文件
OUTPUT_DIR = Path("./fastq_output")  # FASTQ 输出目录
SRA_CACHE = Path("./sra_cache")  # SRA 缓存目录
THREADS = 8  # 线程数

# 创建目录
OUTPUT_DIR.mkdir(exist_ok=True)
SRA_CACHE.mkdir(exist_ok=True)

# 读取 accession 列表
with open(ACCESSION_FILE) as f:
    accessions = [line.strip() for line in f if line.strip()]

print(f"共 {len(accessions)} 个样本待下载")

# 逐个下载并转换
for i, acc in enumerate(accessions, 1):
    print(f"\n[{i}/{len(accessions)}] 处理 {acc}...")

    # Step 1: prefetch 下载
    cmd_prefetch = [
        "prefetch", acc,
        "-O", str(SRA_CACHE),
        "--max-size", "50G"
    ]
    result = subprocess.run(cmd_prefetch, capture_output=True, text=True)
    if result.returncode != 0:
        print(f"  prefetch 失败: {result.stderr[:200]}")
        continue  # 跳过失败的,继续下一个

    # Step 2: fasterq-dump 转换
    sra_path = SRA_CACHE / acc / f"{acc}.sra"
    cmd_fasterq = [
        "fasterq-dump", str(sra_path),
        "--outdir", str(OUTPUT_DIR),
        "--split-3",
        "--threads", str(THREADS)
    ]
    result = subprocess.run(cmd_fasterq, capture_output=True, text=True)
    if result.returncode != 0:
        print(f"  fasterq-dump 失败: {result.stderr[:200]}")
        continue

    # Step 3: 压缩 FASTQ
    for fq in OUTPUT_DIR.glob(f"{acc}*.fastq"):
        subprocess.run(["gzip", str(fq)])

    print(f"  完成! FASTQ 已保存并压缩")

    # 清理缓存(释放空间)
    sra_file = SRA_CACHE / acc / f"{acc}.sra"
    if sra_file.exists():
        sra_file.unlink()

print(f"\n全部完成! FASTQ 文件在: {OUTPUT_DIR}")

7. 用 Python 批量查询 UniProt REST API

# ============================================
# UniProt 批量查询完整示例
# 场景:从宏基因组功能注释结果中,批量查询蛋白功能
# ============================================
import requests
import time
import csv
from io import StringIO

BASE_URL = "https://rest.uniprot.org"

# === 方法 1:ID Mapping(最常用)===
# 场景:你有一批 UniProt ID,想批量获取注释信息
def uniprot_id_mapping(from_db, to_db, ids):
    """
    UniProt ID 映射服务
    from_db: 来源数据库(如 "UniProtKB_AC-ID")
    to_db: 目标数据库(如 "UniProtKB")
    ids: ID 列表
    """
    # 第一步:提交映射任务
    url = f"{BASE_URL}/idmapping/run"
    data = {
        "from": from_db,
        "to": to_db,
        "ids": ",".join(ids)  # 逗号分隔
    }
    response = requests.post(url, data=data)
    job_id = response.json()["jobId"]
    print(f"  任务已提交,Job ID: {job_id}")

    # 第二步:轮询等待结果
    status_url = f"{BASE_URL}/idmapping/status/{job_id}"
    while True:
        status = requests.get(status_url).json()
        if "results" in status or "jobStatus" not in status:
            break
        if status["jobStatus"] == "RUNNING":
            time.sleep(2)  # 每 2 秒检查一次
            print("  等待中...")
        else:
            print(f"  任务状态: {status['jobStatus']}")
            break

    # 第三步:获取结果
    result_url = f"{BASE_URL}/idmapping/results/{job_id}"
    response = requests.get(result_url)
    return response.json()

# 示例:批量查询蛋白
protein_ids = ["P06213", "P01308", "P35568", "Q14524"]  # INSR, INS, IRS1, SCN5A
print("=== ID Mapping 批量查询 ===")
results = uniprot_id_mapping("UniProtKB_AC-ID", "UniProtKB", protein_ids)

if "results" in results:
    for item in results["results"]:
        entry = item.get("to", {})
        acc = entry.get("primaryAccession", "N/A")
        name = entry.get("proteinDescription", {}).get("recommendedName", {}).get("fullName", {}).get("value", "N/A")
        print(f"  {acc}: {name}")

# === 方法 2:流式下载大量结果 ===
def stream_uniprot_search(query, fields, format="tsv"):
    """
    流式搜索,适合下载大量结果
    返回 TSV 格式,可以直接导入 pandas
    """
    url = f"{BASE_URL}/uniprotkb/stream"
    params = {
        "query": query,
        "fields": fields,  # 逗号分隔的字段名
        "format": format
    }
    response = requests.get(url, params=params, stream=True)

    if response.status_code == 200:
        return response.text
    else:
        print(f"查询失败: {response.status_code}")
        return None

# 下载所有与短链脂肪酸(SCFA)代谢相关的细菌蛋白
# (SCFA 在 T2D 肠道菌群研究中非常重要)
print("\n=== 流式下载 SCFA 相关细菌蛋白 ===")
tsv_data = stream_uniprot_search(
    query="short-chain fatty acid AND taxonomy_id:2 AND reviewed:true",
    # taxonomy_id:2 是细菌域
    fields="accession,id,protein_name,gene_names,organism_name,ec,go_f",
    format="tsv"
)

if tsv_data:
    # 解析 TSV
    reader = csv.DictReader(StringIO(tsv_data), delimiter="\t")
    rows = list(reader)
    print(f"  共找到 {len(rows)} 条结果")
    for row in rows[:5]:
        print(f"  {row.get('Entry', '')} | {row.get('Gene Names', '')} | "
              f"{row.get('Protein names', '')[:50]}")

8. 用 Ensembl REST API 获取基因信息

# ============================================
# Ensembl REST API 进阶用法
# ============================================
import requests
import time
import json

BASE_URL = "https://rest.ensembl.org"
HEADERS = {"Content-Type": "application/json"}

# === 获取基因的所有转录本 ===
def get_transcripts(gene_id):
    """获取一个基因的所有转录本信息"""
    url = f"{BASE_URL}/lookup/id/{gene_id}?expand=1"
    # expand=1 会展开子对象(包括转录本列表)
    response = requests.get(url, headers=HEADERS)
    if response.status_code == 200:
        data = response.json()
        transcripts = data.get("Transcript", [])
        return transcripts
    return []

# 查询 TCF7L2(T2D 最强关联基因)的转录本
print("=== TCF7L2 的转录本 ===")
# 先拿到 Ensembl ID
lookup = requests.get(
    f"{BASE_URL}/lookup/symbol/homo_sapiens/TCF7L2",
    headers=HEADERS
).json()
tcf7l2_id = lookup["id"]
print(f"TCF7L2 Ensembl ID: {tcf7l2_id}")

transcripts = get_transcripts(tcf7l2_id)
for t in transcripts[:5]:
    print(f"  {t['id']} | {t['biotype']} | 长度: {t['length']} bp")

# === 获取序列 ===
def get_sequence(seq_id, seq_type="genomic"):
    """
    获取序列
    seq_type: genomic/cds/cdna/protein
    """
    url = f"{BASE_URL}/sequence/id/{seq_id}?type={seq_type}"
    headers = {"Content-Type": "text/plain"}  # 纯文本返回序列
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        return response.text
    return None

# 获取 TCF7L2 蛋白序列
print(f"\n=== TCF7L2 蛋白序列(前 100 aa)===")
protein_seq = get_sequence(tcf7l2_id, "protein")
if protein_seq:
    print(protein_seq[:100] + "...")

# === 变异信息查询 ===
def get_variants(gene_symbol, species="human"):
    """获取基因区域的已知变异"""
    # 先获取基因坐标
    lookup_url = f"{BASE_URL}/lookup/symbol/{species}/{gene_symbol}"
    gene_info = requests.get(lookup_url, headers=HEADERS).json()

    chrom = gene_info["seq_region_name"]
    start = gene_info["start"]
    end = gene_info["end"]

    # 用 overlap 端点获取变异
    # 注意:区域太大会超时,限制在基因范围内
    region = f"{chrom}:{start}-{min(end, start + 50000)}"  # 最多查 50kb
    url = f"{BASE_URL}/overlap/region/human/{region}?feature=variation"
    response = requests.get(url, headers=HEADERS)

    if response.status_code == 200:
        return response.json()
    return []

print(f"\n=== KCNJ11 基因区域的变异(前 5 个)===")
variants = get_variants("KCNJ11")
if variants:
    print(f"  共找到 {len(variants)} 个变异")
    for v in variants[:5]:
        print(f"  {v.get('id', 'N/A')} | {v.get('consequence_type', 'N/A')} | "
              f"位置: {v.get('start', '')}")
    time.sleep(0.2)

# === 比较基因组学:查同源基因 ===
def get_homologs(gene_id, target_species=None):
    """获取同源基因"""
    url = f"{BASE_URL}/homology/id/{gene_id}?type=orthologues"
    if target_species:
        url += f"&target_species={target_species}"
    response = requests.get(url, headers=HEADERS)
    if response.status_code == 200:
        return response.json()
    return None

print(f"\n=== TCF7L2 在小鼠中的同源基因 ===")
homologs = get_homologs(tcf7l2_id, "mus_musculus")
if homologs:
    for h in homologs.get("data", [{}])[0].get("homologies", [])[:3]:
        target = h.get("target", {})
        print(f"  {target.get('id')} | {target.get('species')} | "
              f"相似度: {h.get('dn_ds', 'N/A')}")

9. 和你 T2D 项目的关联

9.1 你的项目能直接用到的查询场景

你项目中的步骤 需要批量查询的内容 用哪个库/API
Kraken2 物种注释后 查各菌种的代谢功能注释 KEGG + UniProt
差异丰度分析后 查差异菌的已知 T2D 关联 PubMed Entrez
功能注释(HUMAnN3)后 查 KO/EC 号对应的通路 KEGG REST API
MAGs 基因预测后 批量获取蛋白功能注释 UniProt ID Mapping
写论文讨论部分 查 T2D 菌群相关最新文献 PubMed + Entrez
想扩大样本量 搜索更多 T2D 宏基因组 SRA Entrez esearch
分析菌群代谢互作 查蛋白互作网络 STRING API

9.2 实战示例:从你的 Kraken2 结果出发

# ============================================
# 实战:把 Kraken2 差异菌的代谢功能查出来
# ============================================
import requests
import time

# 假设你差异分析发现这些菌在 T2D 患者中显著变化
differential_bacteria = [
    "Faecalibacterium prausnitzii",  # 产丁酸菌,T2D 中减少
    "Akkermansia muciniphila",  # 粘液降解菌,T2D 中减少
    "Roseburia intestinalis",  # 产丁酸菌
    "Bacteroides vulgatus",  # 拟杆菌
]

# 在 KEGG 中查这些菌的代谢模块
KEGG_BASE = "https://rest.kegg.jp"

for bacterium in differential_bacteria:
    print(f"\n{'='*50}")
    print(f"查询: {bacterium}")

    # 在 KEGG 的 organism 库中搜索
    result = requests.get(f"{KEGG_BASE}/find/genome/{bacterium}").text

    if result.strip():
        # 解析第一个结果获取 organism code
        first_line = result.strip().split("\n")[0]
        org_code = first_line.split("\t")[0].replace("gn:", "")
        print(f"  KEGG organism: {org_code}")

        # 查该菌的代谢通路
        pathways = requests.get(f"{KEGG_BASE}/list/pathway/{org_code}").text
        pathway_lines = pathways.strip().split("\n")
        print(f"  共有 {len(pathway_lines)} 条代谢通路")

        # 过滤出与碳水化合物/脂肪酸代谢相关的通路(与 T2D 相关)
        keywords = ["carbohydrate", "fatty acid", "butyrate", "propanoate", "insulin"]
        relevant = [l for l in pathway_lines 
                    if any(kw in l.lower() for kw in keywords)]
        if relevant:
            print(f"  T2D 相关通路:")
            for p in relevant[:3]:
                print(f"    {p}")
    else:
        print(f"  KEGG 中未找到该菌")

    time.sleep(0.5)  # 限速

10. 常见报错与解决

报错 1:HTTP 429 Too Many Requests

错误信息:requests.exceptions.HTTPError: 429 Client Error: Too Many Requests
原因:请求太频繁,超过 API 的速率限制
解决:
  1. 每次请求之间加 time.sleep(0.5)
  2. NCBI:注册 API Key(https://www.ncbi.nlm.nih.gov/account/settings/)
  3. Ensembl:每秒不超过 15 次,批量用 POST 方式
  4. UniProt:使用 stream 端点替代分页查询

报错 2:NCBI Entrez 返回空结果

现象:Entrez.read() 返回的 IdList 为空,但网页搜索有结果
原因:
  1. 搜索词语法错误(布尔运算符必须大写:AND/OR/NOT)
  2. 字段限定符拼写错误(如 [Titl] 写成了 [Title] 的缩写形式)
  3. 没设置 Entrez.email(被静默拒绝)
解决:
  - 先在 NCBI 网页上测试搜索词确认有结果
  - 检查布尔运算符大小写:term="T2D[Title] AND metagenome[All Fields]"
  - 确保设置了 Entrez.email

报错 3:UniProt 400 Bad Request

错误信息:400 Client Error: Bad Request
原因:
  1. 新版 API(2022 年后)URL 格式变了
  2. fields 参数中的字段名拼错了
  3. query 语法错误
解决:
  - 旧版 URL(已弃用):https://www.uniprot.org/uniprot/
  - 新版 URL(用这个):https://rest.uniprot.org/uniprotkb/
  - 查 UniProt 官方字段名文档:https://www.uniprot.org/help/return_fields
  - 用 Postman 或 curl 先测试单条请求

报错 4:Ensembl 请求超时

错误信息:requests.exceptions.ReadTimeout 或 504 Gateway Timeout
原因:请求的数据范围太大(如查一整条染色体的变异)
解决:
  - 缩小查询范围(区域不超过 5Mb)
  - 加 timeout 参数:requests.get(url, timeout=30)
  - 用 POST /lookup/id 批量查询替代多次 GET
  - 对于大规模数据,考虑下载 Ensembl 的 FTP 文件

报错 5:fasterq-dump 磁盘空间不足

错误信息:disk is full / no space left on device
原因:fasterq-dump 需要临时空间,约为输出文件的 2-3 倍
解决:
  1. 指定临时目录到大硬盘:fasterq-dump --temp /大硬盘/tmp
  2. 逐个处理,处理完一个压缩一个再下一个
  3. 提前算空间:1个 10GB 的 SRA 约产生 30GB FASTQ + 30GB 临时空间
  4. 下载后立即 pigz 压缩(节省 70%)

报错 6:KEGG API 返回 403 Forbidden

错误信息:403 Forbidden
原因:
  1. KEGG 限制商业用途(学术免费,商业需要授权)
  2. 请求频率过高(虽然没有明确文档,一般每秒 3-5 次安全)
  3. 某些端点确实需要付费(如 KEGG MEDICUS)
解决:
  - 确保用于学术研究
  - 加大请求间隔:time.sleep(0.5)
  - 用 https://rest.kegg.jp(http 和 https 均可)
  - 大规模需求考虑下载 KEGG FTP 数据(需授权)

11. 速查表:各数据库 API 端点

数据库 基础 URL 认证方式 限速 返回格式
NCBI Entrez https://eutils.ncbi.nlm.nih.gov/entrez/eutils/ Email + API Key(可选) 3次/秒(无Key)、10次/秒(有Key) XML/JSON
Ensembl REST https://rest.ensembl.org 无需认证 15次/秒 JSON/XML/FASTA
UniProt REST https://rest.uniprot.org 无需认证 无明确限制,建议<20次/秒 JSON/TSV/FASTA/XML
KEGG REST https://rest.kegg.jp 无需认证 建议<5次/秒 纯文本/图片
STRING https://string-db.org/api 无需认证 建议<1次/秒 JSON/TSV/图片
InterPro https://www.ebi.ac.uk/interpro/api 无需认证 建议<10次/秒 JSON

常用端点速查

=== NCBI Entrez(通过 Biopython 调用)===
Entrez.esearch(db, term)           # 搜索
Entrez.efetch(db, id)              # 获取完整记录
Entrez.esummary(db, id)            # 获取摘要
Entrez.elink(dbfrom, db, id)       # 跨库链接
支持的 db:pubmed, sra, gds, bioproject, biosample, gene, protein, nucleotide...

=== Ensembl REST ===
GET /lookup/symbol/{species}/{symbol}     # 按基因名查
GET /lookup/id/{id}                        # 按 Ensembl ID 查
POST /lookup/id   (body: {"ids":[...]})   # 批量查
GET /sequence/id/{id}?type=protein        # 获取序列
GET /overlap/region/{species}/{region}    # 区域内的注释/变异
GET /homology/id/{id}                     # 同源基因
GET /xrefs/id/{id}                        # 交叉引用(链接到其他数据库)

=== UniProt REST ===
GET  /uniprotkb/search?query=...&fields=...&format=json    # 搜索
GET  /uniprotkb/{accession}.json                           # 单条获取
GET  /uniprotkb/stream?query=...&format=tsv                # 流式下载
POST /idmapping/run                                         # ID 映射(提交)
GET  /idmapping/status/{jobId}                             # 映射状态
GET  /idmapping/results/{jobId}                            # 映射结果

=== KEGG REST ===
GET /find/{database}/{query}       # 搜索
GET /get/{entry_id}                # 获取条目详情
GET /link/{target_db}/{source_id}  # 数据库间映射
GET /list/{database}               # 列出所有条目
GET /get/{pathway_id}/image        # 获取通路图(PNG)

=== STRING ===
GET /api/json/network?identifiers=...&species=...        # 获取互作网络
GET /api/json/enrichment?identifiers=...&species=...     # 功能富集
GET /api/image/network?identifiers=...&species=...       # 获取网络图(PNG)
GET /api/json/interaction_partners?identifiers=...       # 互作伙伴列表

12. 延伸资源

官方文档(遇到问题先查这些)

  • NCBI Entrez 编程文档:https://www.ncbi.nlm.nih.gov/books/NBK25501/
  • Biopython Entrez 教程:https://biopython.org/wiki/Entrez
  • Ensembl REST API 文档:https://rest.ensembl.org/documentation
  • UniProt REST API 文档:https://www.uniprot.org/help/api
  • KEGG REST API 说明:https://www.kegg.jp/kegg/rest/keggapi.html
  • STRING API 文档:https://string-db.org/cgi/help?subpage=api

推荐 Python 包

包名 用途 安装
biopython NCBI Entrez + 序列处理 conda install biopython
requests 通用 HTTP 请求 conda install requests
bioservices 集成多个生物数据库 API pip install bioservices
mygene 批量基因注释查询 pip install mygene
pyensembl Ensembl 本地离线查询 pip install pyensembl

面试高频问题

  1. "你怎么从 SRA 批量下载数据?" → prefetch 下载 .sra,fasterq-dump 转 FASTQ,用 accession list 批量处理

  2. "你会用 API 查询生物数据库吗?" → 用 Biopython Entrez 查 NCBI 全家桶,requests + REST API 查 UniProt/Ensembl/KEGG

  3. "处理大规模数据时怎么避免被封 IP?" → 设 email、注册 API Key、time.sleep 限速、用 History Server 分批获取

  4. "你的 T2D 项目用到了哪些数据库?" → SRA(下载原始数据)、KEGG(通路注释)、UniProt(蛋白功能)、PubMed(文献调研)


最后更新:2026-05-03 | 配合 T2D 宏基因组项目使用