mcpField
H
hmdb-database

hmdb-database

github.com/jaechang-hits数据与数据库38468 次安装1 周前更新
代码仓库

关于 hmdb-database

使用 Python 查询本地下载的 Human Metabolome Database XML,面向代谢组学研究者,涵盖代谢物属性、生物学背景、疾病关联、谱图和跨库标识符。

主要功能

  1. 01按 HMDB ID 或名称查找
  2. 02提取化学属性与分类
  3. 03检索通路和生物学位置
  4. 04查询疾病关联及浓度范围
  5. 05读取 MS 和 NMR 峰数据
  6. 06映射多个数据库的标识符

适用场景

  1. 01根据质谱数据筛选代谢物
  2. 02分析代谢物列表的相关通路
  3. 03研究疾病相关生物标志物
  4. 04生成跨数据库 ID 对照表

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jaechang-hits/sciagent-skills --skill hmdb-database

安装后在 Claude Code 中输入/hmdb-database确认已加载。

详细介绍

HMDB 数据库 — 本地 XML 访问

概述

通过使用 Python 的 ElementTree 解析本地下载的 XML,查询人类代谢组数据库(HMDB,包含 220,000 多条代谢物记录)。涵盖代谢物查找、化学性质、生物学背景(通路、酶、生物流体)、疾病/生物标志物关联、用于代谢物鉴定的光谱数据,以及与 KEGG、PubChem、ChEBI 和 DrugBank 的跨数据库 ID 映射。

适用场景

  • 按 HMDB ID 或名称查找代谢物信息(描述、化学类别、细胞位置)
  • 获取用于代谢组学分析的化学性质(分子量、分子式、SMILES、InChI、logP、PSA)
  • 查找一组代谢物的通路关联和酶链接
  • 查找代谢物所在的生物流体/组织(血液、尿液、CSF、唾液)
  • 查询疾病关联和正常/异常浓度范围,用于生物标志物发现
  • 提取 NMR 或 MS 光谱峰列表,用于代谢物鉴定
  • 将 HMDB ID 映射到 KEGG、PubChem、ChEBI、DrugBank 或其他数据库
  • 如需药物专属数据(相互作用、靶点、药理学),请改用 drugbank-database-access
  • 如需无需下载即可实时查询化合物性质,请改用 pubchem-compound-search

前置条件

  • HMDB XML 下载:在 https://hmdb.ca/downloads 注册并下载 hmdb_metabolites.xml.zip(解压后约 6 GB)
  • Python 包:lxml(XPath 速度更快)或标准库 xml.etree.ElementTree、pandas
  • 没有公共 REST API:HMDB 没有可供程序调用的 REST API。所有访问都通过解析本地 XML 或使用网页界面完成
  • R 包(可选):CRAN 上的 hmdbQuery 提供了一些查询封装,但功能有限且已过时
  • 速率限制:本地 XML 解析不适用。网页界面没有公开的速率限制说明,但不适用于抓取
pip install lxml pandas

快速入门

import xml.etree.ElementTree as ET

NS = {'hmdb': 'http://www.hmdb.ca'}

tree = ET.parse('hmdb_metabolites.xml')  # 60-120s for full XML
root = tree.getroot()

# Build lookup index (HMDB ID + lowercase name -> element)
metabolite_index = {}
for met in root.findall('hmdb:metabolite', NS):
    accession = met.find('hmdb:accession', NS)
    name = met.find('hmdb:name', NS)
    if accession is not None and accession.text:
        metabolite_index[accession.text] = met
    if name is not None and name.text:
        metabolite_index[name.text.lower()] = met

def find_metabolite(query):
    """Find metabolite by HMDB ID or name (case-insensitive)."""
    return metabolite_index.get(query) or metabolite_index.get(query.lower())

met = find_metabolite('HMDB0000122')  # Glucose
name = met.find('hmdb:name', NS).text
formula = met.find('hmdb:chemical_formula', NS).text
print(f"{name}: {formula}")
# Glucose: C6H12O6

核心 API

1. XML 设置和代谢物查找
import xml.etree.ElementTree as ET

NS = {'hmdb': 'http://www.hmdb.ca'}
tree = ET.parse('hmdb_metabolites.xml')
root = tree.getroot()
print(f"Total metabolite entries: {len(root.findall('hmdb:metabolite', NS))}")
# Total metabolite entries: ~220000+

对于内存受限的环境,请使用 iterparse:

met_names = {}
for event, elem in ET.iterparse('hmdb_metabolites.xml', events=('end',)):
    if elem.tag == '{http://www.hmdb.ca}metabolite':
        acc = elem.find('{http://www.hmdb.ca}accession')
        name = elem.find('{http://www.hmdb.ca}name')
        if acc is not None and name is not None and acc.text and name.text:
            met_names[acc.text] = name.text
        elem.clear()
print(f"Parsed {len(met_names)} metabolites via iterparse")
2. 化学性质
def get_chemical_properties(met_element):
    """Extract chemical properties from a metabolite entry."""
    def txt(path):
        el = met_element.find(path, NS)
        return el.text if el is not None and el.text else None
    # Fields: accession, name, chemical_formula, average_molecular_weight,
    # monisotopic_molecular_weight, smiles, inchi, inchikey, state, iupac_name
    return {tag: txt(f'hmdb:{tag}') for tag in [
        'accession', 'name', 'chemical_formula', 'average_molecular_weight',
        'monisotopic_molecular_weight', 'smiles', 'inchi', 'inchikey', 'state']}

props = get_chemical_properties(find_metabolite('HMDB0000158'))  # L-Tyrosine
print(f"{props['name']}: MW={props['average_molecular_weight']}, SMILES={props['smiles']}")
# Extract taxonomy / chemical classification (ClassyFire ontology)
def get_classification(met_element):
    tax = met_element.find('hmdb:taxonomy', NS)
    if tax is None:
        return {}
    def txt(tag):
        el = tax.find(f'hmdb:{tag}', NS)
        return el.text if el is not None and el.text else None
    return {'kingdom': txt('kingdom'), 'super_class': txt('super_class'),
            'class': txt('class'), 'sub_class': txt('sub_class'),
            'direct_parent': txt('direct_parent')}

print(get_classification(find_metabolite('HMDB0000122')))
# {'kingdom': 'Organic compounds', 'super_class': 'Organooxygen compounds', ...}
3. 生物学背景
def get_pathways(met_element):
    """Extract metabolic pathway associations."""
    pathways = []
    for pw in met_element.findall('hmdb:pathways/hmdb:pathway', NS):
        name = pw.find('hmdb:name', NS)
        smpdb_id = pw.find('hmdb:smpdb_id', NS)
        kegg_id = pw.find('hmdb:kegg_map_id', NS)
        pathways.append({
            'name': name.text if name is not None else None,
            'smpdb_id': smpdb_id.text if smpdb_id is not None else None,
            'kegg_map_id': kegg_id.text if kegg_id is not None else None,
        })
    return pathways

for pw in get_pathways(find_metabolite('HMDB0000122'))[:5]:
    print(f"  {pw['smpdb_id']}: {pw['name']}")
def get_biolocations(met_element):
    """Extract biofluid, tissue, and cellular locations."""
    bp = 'hmdb:biological_properties/hmdb:'
    def texts(path):
        return [el.text for el in met_element.findall(path, NS) if el.text]
    return {'biofluids': texts(f'{bp}biospecimen_locations/hmdb:biospecimen'),
            'tissues': texts(f'{bp}tissue_locations/hmdb:tissue'),
            'cellular': texts(f'{bp}cellular_locations/hmdb:cellular')}

locs = get_biolocations(find_metabolite('HMDB0000122'))
print(f"Glucose biofluids: {locs['biofluids']}")
def get_enzymes(met_element):
    """Extract associated enzymes/proteins with UniProt IDs."""
    return [{'name': (p.find('hmdb:name', NS).text
                      if p.find('hmdb:name', NS) is not None else None),
             'uniprot_id': (p.find('hmdb:uniprot_id', NS).text
                            if p.find('hmdb:uniprot_id', NS) is not None else None),
             'gene_name': (p.find('hmdb:gene_name', NS).text
                           if p.find('hmdb:gene_name', NS) is not None else None)}
            for p in met_element.findall('hmdb:protein_associations/hmdb:protein', NS)]

enz = get_enzymes(find_metabolite('HMDB0000122'))
print(f"Glucose-associated proteins: {len(enz)}")
for e in enz[:3]:
    print(f"  {e['gene_name']} ({e['uniprot_id']}): {e['name']}")
4. 疾病和生物标志物查询
def get_diseases(met_element):
    """Extract disease associations with OMIM IDs and PubMed references."""
    diseases = []
    for d in met_element.findall('hmdb:diseases/hmdb:disease', NS):
        name = d.find('hmdb:name', NS)
        omim = d.find('hmdb:omim_id', NS)
        pmids = [r.find('hmdb:pubmed_id', NS).text
                 for r in d.findall('hmdb:references/hmdb:reference', NS)
                 if r.find('hmdb:pubmed_id', NS) is not None and r.find('hmdb:pubmed_id', NS).text]
        diseases.append({'name': name.text if name is not None else None,
                         'omim_id': omim.text if omim is not None else None,
                         'pubmed_ids': pmids})
    return diseases

diseases = get_diseases(find_metabolite('HMDB0000122'))
print(f"Glucose disease associations: {len(diseases)}")
for d in diseases[:3]:
    print(f"  {d['name']} (OMIM: {d['omim_id']})")
def get_concentrations(met_element, biospecimen='Blood'):
    """Extract normal/abnormal concentration data filtered by biospecimen."""
    result = {'normal': [], 'abnormal': []}
    for ctype, key in [('normal_concentrations', 'normal'),
                       ('abnormal_concentrations', 'abnormal')]:
        for c in met_element.findall(f'hmdb:{ctype}/hmdb:concentration', NS):
            bio = c.find('hmdb:biospecimen', NS)
            if bio is None or bio.text != biospecimen:
                continue
            def txt(tag):
                el = c.find(f'hmdb:{tag}', NS)
                return el.text if el is not None else None
            result[key].append({'value': txt('concentration_value'),
                                'units': txt('concentration_units'),
                                'condition': txt('subject_condition')})
    return result

conc = get_concentrations(find_metabolite('HMDB0000122'), 'Blood')
print(f"Glucose blood: {len(conc['normal'])} normal, {len(conc['abnormal'])} abnormal")
5. 光谱数据
def get_ms_spectra(met_element):
    """Extract MS/MS spectral peak lists (m/z + intensity)."""
    spectra = []
    for spec in met_element.findall('hmdb:spectra/hmdb:spectrum', NS):
        stype = spec.find('hmdb:type', NS)
        if stype is None or 'MS' not in (stype.text or ''):
            continue
        peaks = [{'mz': float(p.find('hmdb:mass_charge', NS).text),
                  'intensity': float(p.find('hmdb:intensity', NS).text or 0)}
                 for p in spec.findall('hmdb:ms_ms_peaks/hmdb:ms_ms_peak', NS)
                 if p.find('hmdb:mass_charge', NS) is not None
                 and p.find('hmdb:mass_charge', NS).text]
        spectra.append({'type': stype.text, 'num_peaks': len(peaks), 'peaks': peaks})
    return spectra

spectra = get_ms_spectra(find_metabolite('HMDB0000122'))
print(f"Glucose MS spectra: {len(spectra)}")
def get_nmr_spectra(met_element):
    """Extract NMR spectral peak lists (1H, 13C). Same pattern as MS above."""
    spectra = []
    for spec in met_element.findall('hmdb:spectra/hmdb:spectrum', NS):
        stype = spec.find('hmdb:type', NS)
        if stype is None or 'NMR' not in (stype.text or ''):
            continue
        nucleus = spec.find('hmdb:nucleus', NS)
        shifts = [float(p.find('hmdb:chemical_shift', NS).text)
                  for p in spec.findall('hmdb:nmr_one_d_peaks/hmdb:nmr_one_d_peak', NS)
                  if p.find('hmdb:chemical_shift', NS) is not None
                  and p.find('hmdb:chemical_shift', NS).text]
        spectra.append({'type': stype.text,
                        'nucleus': nucleus.text if nucleus is not None else None,
                        'num_peaks': len(shifts), 'chemical_shifts': shifts})
    return spectra

nmr = get_nmr_spectra(find_metabolite('HMDB0000122'))
print(f"Glucose NMR spectra: {len(nmr)}")
6. 跨数据库映射
def get_external_ids(met_element):
    """Extract cross-database identifiers (KEGG, PubChem, ChEBI, DrugBank, CAS, etc.)."""
    fields = {'kegg_id': 'KEGG', 'pubchem_compound_id': 'PubChem', 'chebi_id': 'ChEBI',
              'drugbank_id': 'DrugBank', 'chemspider_id': 'ChemSpider',
              'cas_registry_number': 'CAS', 'biocyc_id': 'BioCyc', 'pdb_id': 'PDB',
              'foodb_id': 'FooDB', 'metlin_id': 'METLIN'}
    ids = {}
    for tag, label in fields.items():
        el = met_element.find(f'hmdb:{tag}', NS)
        if el is not None and el.text:
            ids[label] = el.text
    return ids

ids = get_external_ids(find_metabolite('HMDB0000122'))
print(f"Glucose cross-refs: {ids}")
# {'KEGG': 'C00031', 'PubChem': '5793', 'ChEBI': '17234', ...}
# Build cross-reference table for a metabolite list
import pandas as pd

queries = ['HMDB0000122', 'HMDB0000158', 'HMDB0000167', 'HMDB0000148']
rows = []
for q in queries:
    m = find_metabolite(q)
    if m is None: continue
    ids = get_external_ids(m)
    rows.append({'name': m.find('hmdb:name', NS).text, 'hmdb_id': q,
                 'kegg': ids.get('KEGG'), 'pubchem': ids.get('PubChem'),
                 'chebi': ids.get('ChEBI')})
print(pd.DataFrame(rows).to_string(index=False))

关键概念

HMDB XML 条目结构
部分XPath内容
标识信息hmdb:accession、hmdb:name、hmdb:iupac_name主要标识符
化学信息hmdb:chemical_formula、hmdb:smiles、hmdb:inchi结构描述符
性质hmdb:average_molecular_weight、hmdb:state物理性质
分类hmdb:taxonomy/hmdb:kingdom、class 等化学分类
生物学信息hmdb:biological_properties生物流体、组织、细胞位置
通路hmdb:pathways/hmdb:pathwaySMPDB + KEGG 通路链接
酶hmdb:protein_associations/hmdb:protein关联蛋白质/酶
疾病hmdb:diseases/hmdb:disease疾病关联 + OMIM ID
浓度hmdb:normal_concentrations、hmdb:abnormal_concentrations生物标志物参考范围
光谱hmdb:spectra/hmdb:spectrumNMR、MS/MS 峰列表
外部 IDhmdb:kegg_id、hmdb:pubchem_compound_id 等跨数据库标识符
本体hmdb:ontology生理/处置/过程角色
数据字段完整性

并非所有条目都包含全部字段。字段覆盖率因代谢物类别而异:

字段类别大致覆盖率说明
登录号、名称、分子式~100%始终存在
SMILES、InChI、MW~90%部分脂质和复杂代谢物缺失
分类学/分类~85%来自 ClassyFire 的化学本体
生物流体分布位置~60%常见人体代谢物的数据最完整
通路~40%经整理的 SMPDB + KEGG 链接
蛋白质关联~35%酶-代谢物关系
疾病关联~25%主要针对生物标志物代谢物
正常浓度~20%临床代谢物的参考范围
MS/MS 谱图~15%实验谱图库
NMR 谱图~10%1H 和 13C 化学位移数据
文件格式比较
格式文件大小最适用场景
完整 XMLhmdb_metabolites.xml~6 GB访问完整数据(所有字段)
SDFstructures.sdf~200 MB化学结构 + 基本属性
CSV各类导出文件~50-500 MB表格数据(属性、浓度)
FASTAhmdb_proteins.fasta~50 MB蛋白质序列查询

常见工作流

工作流 1:通过质谱进行代谢物鉴定

目标:使用分子量,将观测到的 m/z 值与候选代谢物进行匹配。假设已从快速入门中获取 root、NS。

import pandas as pd

observed_mz = 180.063  # [M+H]+ for glucose
adduct_mass = 1.00728  # H+ adduct
target_mw = observed_mz - adduct_mass
tolerance_da = 0.01

candidates = []
for met in root.findall('hmdb:metabolite', NS):
    mw_el = met.find('hmdb:monisotopic_molecular_weight', NS)
    if mw_el is None or not mw_el.text:
        continue
    mw = float(mw_el.text)
    if abs(mw - target_mw) <= tolerance_da:
        candidates.append({
            'hmdb_id': met.find('hmdb:accession', NS).text,
            'name': met.find('hmdb:name', NS).text,
            'mw': mw, 'delta_da': abs(mw - target_mw),
            'formula': (met.find('hmdb:chemical_formula', NS).text
                        if met.find('hmdb:chemical_formula', NS) is not None else None),
        })

df = pd.DataFrame(candidates).sort_values('delta_da')
print(f"Candidates within {tolerance_da} Da of {target_mw:.3f}: {len(df)}")
print(df.head(10).to_string(index=False))
工作流 2:发现疾病生物标志物

目标:查找与某种疾病相关的所有代谢物及其浓度变化。假设已从快速入门中获取 root、NS。

import pandas as pd

disease_query = 'diabetes'
biomarkers = []
for met in root.findall('hmdb:metabolite', NS):
    for d in met.findall('hmdb:diseases/hmdb:disease', NS):
        dname = d.find('hmdb:name', NS)
        if dname is None or not dname.text:
            continue
        if disease_query.lower() not in dname.text.lower():
            continue
        abnormal = [c for c in met.findall(
            'hmdb:abnormal_concentrations/hmdb:concentration', NS)
            if c.find('hmdb:subject_condition', NS) is not None
            and disease_query.lower() in
            (c.find('hmdb:subject_condition', NS).text or '').lower()]
        biomarkers.append({
            'hmdb_id': met.find('hmdb:accession', NS).text,
            'metabolite': met.find('hmdb:name', NS).text,
            'disease': dname.text,
            'abnormal_measurements': len(abnormal),
        })

df = pd.DataFrame(biomarkers).drop_duplicates(['hmdb_id', 'disease'])
print(f"Metabolites linked to '{disease_query}': {len(df)}")
print(df.sort_values('abnormal_measurements', ascending=False).head(15).to_string(index=False))
工作流 3:根据代谢物列表进行通路富集分析

目标:给定已鉴定代谢物列表,查找过度代表的通路。假设已从快速入门中获取 metabolite_index。

from collections import Counter
import pandas as pd

hit_ids = ['HMDB0000122', 'HMDB0000158', 'HMDB0000167',
           'HMDB0000148', 'HMDB0000064', 'HMDB0000161']

hit_pathways = Counter()
for hid in hit_ids:
    met = metabolite_index.get(hid)
    if met is None:
        continue
    for pw in met.findall('hmdb:pathways/hmdb:pathway', NS):
        pw_name = pw.find('hmdb:name', NS)
        if pw_name is not None and pw_name.text:
            hit_pathways[pw_name.text] += 1

enriched = [(pw, count) for pw, count in hit_pathways.most_common() if count >= 2]
df = pd.DataFrame(enriched, columns=['Pathway', 'Hit_Count'])
print(f"Pathways with 2+ hits from {len(hit_ids)} metabolites:")
print(df.to_string(index=False))

关键参数

参数函数/端点默认值说明
NS(命名空间字典)所有 XPath 查询{'hmdb': 'http://www.hmdb.ca'}所有 find/findall 调用都必须提供
tolerance_daMW 匹配0.01代谢物 ID 匹配的质量容差,单位为道尔顿
biospecimenget_concentrations()'Blood'筛选条件:Blood、Urine、Cerebrospinal Fluid (CSF)、Saliva 等
eventsET.iterparse()('end',)解析事件;使用 ('end',) 可在遇到结束标签时触发
加合物质量MS 鉴定不定1.00728 [M+H]+、22.9892 [M+Na]+、-1.00728 [M-H]-
target_type谱图查询'MS' 或 'NMR'按类型字符串筛选谱图

最佳实践

  1. 启动时构建内存索引:只解析一次(60-120s),按 HMDB ID 和小写名称构建字典。不要在循环中重新解析
  2. 始终传入命名空间字典:每个 find()/findall() 都需要 NS = {'hmdb': 'http://www.hmdb.ca'}。省略该参数会返回空结果
  3. 内存受限时使用 iterparse:完整 XML 需要约 8-12 GB RAM。使用 iterparse 中的 elem.clear() 增量处理
  4. 处理 None 和空文本:许多可选字段可能不存在。访问前始终检查 el is not None and el.text
  5. MS 匹配使用单同位素质量:monisotopic_molecular_weight 适用于质谱;其他计算使用 average_molecular_weight
  6. 大型搜索前按化学类别预筛选:使用分类学/分类,在遍历全部 220K+ 条目之前缩小搜索范围

常用方法

方法:将所有代谢物属性导出为 CSV

适用场景:创建包含所有代谢物关键属性的扁平表格。

import pandas as pd
records = []
for met in root.findall('hmdb:metabolite', NS):
    def txt(p):
        el = met.find(p, NS)
        return el.text if el is not None and el.text else None
    records.append({'hmdb_id': txt('hmdb:accession'), 'name': txt('hmdb:name'),
                    'formula': txt('hmdb:chemical_formula'),
                    'avg_mw': txt('hmdb:average_molecular_weight'),
                    'smiles': txt('hmdb:smiles')})
pd.DataFrame(records).to_csv('hmdb_properties.csv', index=False)
print(f"Exported {len(records)} metabolites")
方法:按生物流体查找代谢物

适用场景:获取在特定生物流体中检测到的所有代谢物(例如,临床筛查中的尿液)。

biofluid = 'Urine'
urine_mets = []
for met in root.findall('hmdb:metabolite', NS):
    for bf in met.findall(
            'hmdb:biological_properties/hmdb:biospecimen_locations/hmdb:biospecimen', NS):
        if bf.text and bf.text == biofluid:
            urine_mets.append({
                'hmdb_id': met.find('hmdb:accession', NS).text,
                'name': met.find('hmdb:name', NS).text,
            })
            break
print(f"Metabolites in {biofluid}: {len(urine_mets)}")
方法:化学类别分布

适用场景:汇总命中列表或整个数据库中的代谢物化学类别。

from collections import Counter
class_counts = Counter()
for met in root.findall('hmdb:metabolite', NS):
    tax = met.find('hmdb:taxonomy', NS)
    if tax is not None:
        sc = tax.find('hmdb:super_class', NS)
        if sc is not None and sc.text:
            class_counts[sc.text] += 1
print(dict(class_counts.most_common(10)))

故障排查

问题原因解决方案
对已知元素调用 find() 返回 None缺少 XML 命名空间始终传入 NS = {'hmdb': 'http://www.hmdb.ca'}
解析完整 XML 时出现 MemoryError需要约 8-12 GB 内存使用 ET.iterparse() 和 elem.clear()
启动缓慢(>120s)解析 6 GB XML只解析一次并构建索引字典;避免重复解析
按名称未找到代谢物区分大小写或存在同义词统一转换为小写;直接尝试 HMDB ID
某代谢物的谱图为空并非所有条目都有谱图(覆盖率约为 10-15%)查看覆盖率表;使用 METLIN 或 MassBank 获取更多谱图
缺少浓度数据仅涵盖研究充分的临床代谢物(约 20%)与 MetaboAnalyst 或文献交叉核对
同一化合物存在重复条目次级登录号(HMDB00XXXXX 与 HMDB0000XXXX)使用 accession(主登录号),而不是 secondary_accessions
ET.iterparse 缺少数据过早调用 elem.clear()仅在从元素中提取完所有所需字段后再清除

随附资源

独立条目。原始参考文件 hmdb_data_fields.md(268 行,包含字段目录及 XML 元素名称和说明)已整合至上文“核心概念”中的“HMDB XML 条目结构”表和“数据字段完整性”表。字段目录中各元素的 XML 标签名称已在核心 API 代码块中展示。原文中未包含的内容:Web 界面说明(非程序化内容)。

相关技能

  • drugbank-database-access -- 药物专属数据(相互作用、靶点、药理学),采用类似的本地 XML 解析模式
  • pubchem-compound-search -- 无需下载即可查询实时化合物属性;PubChemPy REST API
  • kegg-database -- 通路和化合物数据库,提供 REST API,可交叉查询 HMDB 通路匹配结果
  • matchms-spectral-matching -- 与参考库进行谱图相似性匹配;可补充 HMDB 谱图数据
  • pyopenms-mass-spectrometry -- 完整的 LC-MS/MS 处理流程;使用 HMDB 执行代谢物鉴定步骤

参考资料

评论

支持 Markdown