mcpField
D
drugbank-database-access

drugbank-database-access

github.com/jaechang-hits数据与数据库38467 次安装6 天前更新
代码仓库

关于 drugbank-database-access

drugbank-database-access 是通过 Python 查询本地 DrugBank XML 数据库的工作流,支持药物与相互作用查询、靶点和属性提取,以及跨数据库标识符映射。

主要功能

  1. 01按 ID、名称或 CAS 号搜索药物
  2. 02获取药物相互作用详情
  3. 03提取靶点、酶和通路
  4. 04读取化学属性并计算相似度
  5. 05映射至外部数据库标识符

适用场景

  1. 01筛查用药清单中的相互作用
  2. 02查找关联特定蛋白靶点的药物
  3. 03比较药物属性以开展化学信息学分析
  4. 04将 DrugBank 记录与外部数据库 ID 关联

安装

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

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

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

附带文件references/chemical_analysis.mdreferences/interactions_targets.md

详细介绍

DrugBank 数据库 — 本地 XML 访问

概述

通过 Python 的 ElementTree 解析本地下载的 XML 文件,查询 DrugBank 综合药物数据库(14,000+ 条药物记录、5,000+ 个蛋白质靶点、17,000+ 条药物相互作用)。涵盖药物查询、相互作用检查、靶点/通路提取、化学性质分析和跨数据库标识符映射。

适用场景

  • 按 DrugBank ID、名称或 CAS 号查询药物信息(描述、适应症、作用机制、药理学)
  • 检查药物间相互作用及严重程度分类,以保障多重用药安全
  • 提取药物靶点、酶、转运体和载体及其 UniProt 登录号
  • 获取化学性质(SMILES、InChI、分子量),用于化学信息学分析
  • 将 DrugBank 条目映射到外部数据库(PubChem、ChEMBL、UniProt、KEGG)
  • 根据分子指纹构建药物相似度矩阵
  • 如需实时生物活性数据(IC50、Ki、EC50),请改用 chembl-database-bioactivity
  • 如需查询化合物性质但不想下载数据库,请改用 pubchem-compound-search

先决条件

  • DrugBank 账户:在 https://go.drugbank.com/ 注册(免费学术许可)
  • XML 下载:注册后下载 drugbank_all_full_database.xml.zip(解压后约 1.5 GB)
  • Python 包:lxml、rdkit(相似度)、pandas(表格分析)
  • REST API(可选):开发者层级每月 3,000 次请求;批量处理请使用本地 XML
pip install lxml pandas
pip install rdkit-pypi          # chemical similarity
pip install drugbank-downloader  # programmatic XML download

快速开始

import xml.etree.ElementTree as ET

NS = {'db': 'http://www.drugbank.ca'}  # Required for ALL XPath queries

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

# Build lookup index (DrugBank ID + lowercase name → element)
drug_index = {}
for drug in root.findall('db:drug', NS):
    db_id = drug.find('db:drugbank-id[@primary="true"]', NS)
    name = drug.find('db:name', NS)
    if db_id is not None and name is not None:
        drug_index[db_id.text] = drug
        drug_index[name.text.lower()] = drug

def find_drug(query):
    """Find drug by DrugBank ID, name (case-insensitive), or CAS number."""
    result = drug_index.get(query) or drug_index.get(query.lower())
    if result is not None:
        return result
    for drug in root.findall('db:drug', NS):  # CAS fallback
        cas = drug.find('db:cas-number', NS)
        if cas is not None and cas.text == query:
            return drug
    return None

drug = find_drug('DB00945')  # Aspirin
name = drug.find('db:name', NS).text
print(f"{name}: {drug.find('db:description', NS).text[:100]}...")

核心 API

1. 数据访问与设置
import xml.etree.ElementTree as ET

NS = {'db': 'http://www.drugbank.ca'}
tree = ET.parse('drugbank_all_full_database.xml')
root = tree.getroot()
print(f"Total drug entries: {len(root.findall('db:drug', NS))}")

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

drug_names = {}
for event, elem in ET.iterparse('drugbank_all_full_database.xml', events=('end',)):
    if elem.tag == '{http://www.drugbank.ca}drug':
        db_id = elem.find('{http://www.drugbank.ca}drugbank-id[@primary="true"]')
        name = elem.find('{http://www.drugbank.ca}name')
        if db_id is not None and name is not None:
            drug_names[db_id.text] = name.text
        elem.clear()  # Free memory
print(f"Parsed {len(drug_names)} drugs via iterparse")
2. 药物信息查询
def get_drug_info(drug_element):
    """Extract comprehensive drug information."""
    def txt(path):
        el = drug_element.find(path, NS)
        return el.text if el is not None and el.text else None

    return {
        'drugbank_id': txt('db:drugbank-id[@primary="true"]'),
        'name': txt('db:name'),
        'type': drug_element.get('type'),
        'description': txt('db:description'),
        'indication': txt('db:indication'),
        'mechanism_of_action': txt('db:mechanism-of-action'),
        'cas_number': txt('db:cas-number'),
        'groups': [g.text for g in drug_element.findall('db:groups/db:group', NS)],
    }

info = get_drug_info(find_drug('Metformin'))
print(f"{info['name']} ({info['type']}): Groups={info['groups']}")
# Search by name pattern (partial match)
def search_by_name(pattern):
    pattern_lower = pattern.lower()
    return [d for d in root.findall('db:drug', NS)
            if d.find('db:name', NS) is not None
            and pattern_lower in d.find('db:name', NS).text.lower()]

statins = search_by_name('statin')
print(f"Found {len(statins)} drugs matching 'statin'")
3. 药物间相互作用
def get_interactions(drug_element):
    """Extract all drug-drug interactions."""
    return [{
        'drugbank_id': i.find('db:drugbank-id', NS).text,
        'name': i.find('db:name', NS).text,
        'description': i.find('db:description', NS).text,
    } for i in drug_element.findall('db:drug-interactions/db:drug-interaction', NS)]

def classify_severity(description):
    """Classify severity from interaction description text."""
    if not description:
        return 'unknown'
    dl = description.lower()
    if any(w in dl for w in ['contraindicated', 'avoid', 'fatal', 'life-threatening']):
        return 'major'
    if any(w in dl for w in ['increase', 'decrease', 'enhance', 'reduce', 'alter']):
        return 'moderate'
    return 'minor'

interactions = get_interactions(find_drug('Aspirin'))
print(f"Aspirin has {len(interactions)} interactions")
for i in interactions[:3]:
    print(f"  [{classify_severity(i['description'])}] {i['name']}")
# Check pairwise interaction between two drugs
def check_interaction(drug1_elem, drug2_elem):
    id2 = drug2_elem.find('db:drugbank-id[@primary="true"]', NS).text
    for inter in get_interactions(drug1_elem):
        if inter['drugbank_id'] == id2:
            inter['severity'] = classify_severity(inter['description'])
            return inter
    return None

result = check_interaction(find_drug('Warfarin'), find_drug('Aspirin'))
if result:
    print(f"[{result['severity']}] {result['description'][:150]}")
4. 药物靶点与通路
def get_targets(drug_element, target_type='targets'):
    """Extract targets/enzymes/transporters/carriers.
    target_type: 'targets', 'enzymes', 'transporters', or 'carriers'
    """
    results = []
    for target in drug_element.findall(f'db:{target_type}/db:{target_type[:-1]}', NS):
        t = {
            'name': (target.find('db:name', NS).text
                     if target.find('db:name', NS) is not None else None),
            'actions': [a.text for a in target.findall('db:actions/db:action', NS) if a.text],
        }
        poly = target.find('db:polypeptide', NS)
        if poly is not None:
            t['uniprot_id'] = poly.get('id')
            gene = poly.find('db:gene-name', NS)
            t['gene_name'] = gene.text if gene is not None else None
        results.append(t)
    return results

drug = find_drug('Imatinib')
targets = get_targets(drug, 'targets')
enzymes = get_targets(drug, 'enzymes')
print(f"Imatinib — Targets: {len(targets)}, Enzymes: {len(enzymes)}")
for t in targets[:3]:
    print(f"  {t['name']} (UniProt: {t.get('uniprot_id', 'N/A')}) — {t['actions']}")
def get_pathways(drug_element):
    """Extract SMPDB pathway associations."""
    pathways = []
    for pw in drug_element.findall('db:pathways/db:pathway', NS):
        name = pw.find('db:name', NS)
        smpdb = pw.find('db:smpdb-id', NS)
        pathways.append({
            'smpdb_id': smpdb.text if smpdb is not None else None,
            'name': name.text if name is not None else None,
        })
    return pathways

for pw in get_pathways(find_drug('Metformin')):
    print(f"  {pw['smpdb_id']}: {pw['name']}")
5. 化学性质与相似度
def get_property(drug_element, kind_name, section='calculated'):
    """Get a single property value by kind name."""
    prefix = f'db:{section}-properties/db:property'
    for prop in drug_element.findall(prefix, NS):
        kind = prop.find('db:kind', NS)
        if kind is not None and kind.text == kind_name:
            return prop.find('db:value', NS).text
    return None

def get_all_properties(drug_element):
    """Extract all calculated and experimental properties as a dict."""
    props = {}
    for section in ('calculated', 'experimental'):
        for prop in drug_element.findall(f'db:{section}-properties/db:property', NS):
            kind = prop.find('db:kind', NS)
            value = prop.find('db:value', NS)
            if kind is not None and value is not None:
                key = f'{section}_{kind.text}' if section == 'experimental' else kind.text
                props[key] = value.text
    return props

drug = find_drug('Aspirin')
print(f"SMILES: {get_property(drug, 'SMILES')}")
print(f"MW: {get_property(drug, 'Molecular Weight')}")
print(f"LogP: {get_property(drug, 'logP')}")
# Tanimoto similarity between drugs using RDKit Morgan fingerprints
from rdkit import Chem
from rdkit.Chem import AllChem, DataStructs

def drug_similarity(drug1_elem, drug2_elem, radius=2, nbits=2048):
    smi1, smi2 = get_property(drug1_elem, 'SMILES'), get_property(drug2_elem, 'SMILES')
    if not smi1 or not smi2:
        return None
    mol1, mol2 = Chem.MolFromSmiles(smi1), Chem.MolFromSmiles(smi2)
    if mol1 is None or mol2 is None:
        return None
    fp1 = AllChem.GetMorganFingerprintAsBitVect(mol1, radius, nBits=nbits)
    fp2 = AllChem.GetMorganFingerprintAsBitVect(mol2, radius, nBits=nbits)
    return DataStructs.TanimotoSimilarity(fp1, fp2)

sim = drug_similarity(find_drug('Aspirin'), find_drug('Ibuprofen'))
print(f"Aspirin vs Ibuprofen: {sim:.3f}")
6. 跨数据库集成
def get_external_ids(drug_element):
    """Extract all external database identifiers."""
    ids = {}
    for ident in drug_element.findall('db:external-identifiers/db:external-identifier', NS):
        resource = ident.find('db:resource', NS)
        identifier = ident.find('db:identifier', NS)
        if resource is not None and identifier is not None:
            ids[resource.text] = identifier.text
    return ids

ids = get_external_ids(find_drug('Imatinib'))
print(f"PubChem: {ids.get('PubChem Compound')}, ChEMBL: {ids.get('ChEMBL')}, "
      f"KEGG: {ids.get('KEGG Drug')}, UniProt: {ids.get('UniProtKB')}")
# Build cross-reference table for multiple drugs
import pandas as pd

def build_crossref_table(names):
    rows = []
    for name in names:
        d = find_drug(name)
        if d is None: continue
        ids = get_external_ids(d)
        rows.append({'drug': name,
                     'drugbank_id': d.find('db:drugbank-id[@primary="true"]', NS).text,
                     'pubchem': ids.get('PubChem Compound'),
                     'chembl': ids.get('ChEMBL'),
                     'kegg': ids.get('KEGG Drug')})
    return pd.DataFrame(rows)

print(build_crossref_table(['Aspirin', 'Metformin', 'Imatinib', 'Warfarin']).to_string(index=False))

关键概念

XML 命名空间处理

所有 DrugBank XML 查询都需要命名空间前缀。若未提供,XPath 将不会返回任何结果。

NS = {'db': 'http://www.drugbank.ca'}
name = drug.find('db:name', NS).text     # CORRECT
name = drug.find('name')                  # WRONG — returns None!
# For iterparse, use full URI: '{http://www.drugbank.ca}drug'
药物条目结构
部分XPath内容
标识信息db:drugbank-id、db:name、db:cas-number主要标识符
药理学db:description、db:indication、db:mechanism-of-action临床文本、作用机制、PD/PK
相互作用db:drug-interactions/db:drug-interaction含描述的相互作用药物
靶点db:targets/db:target含作用类型的蛋白质靶点
酶/转运体/载体db:enzymes/db:enzyme 等CYP450、P-gp、结合蛋白
通路db:pathways/db:pathwaySMPDB 通路关联
性质db:calculated-properties、db:experimental-propertiesSMILES、MW、logP 等
外部 IDdb:external-identifiersPubChem、ChEMBL、KEGG、UniProt 交叉引用
外部标识符映射
XML 中的资源名称数据库示例
PubChem CompoundPubChem CID2244
ChEMBLChEMBLCHEMBL25
KEGG Drug / KEGG CompoundKEGGD00109 / C01405
UniProtKBUniProtP23219
PharmGKBPharmGKBPA452615
ChEBIChEBI15365
计算属性类型

常见的 kind 值:SMILES、InChI、InChIKey、Molecular Weight、Molecular Formula、logP、logS、Polar Surface Area (PSA)、Rotatable Bond Count、H Bond Acceptor Count、H Bond Donor Count、pKa (strongest acidic)、pKa (strongest basic)、Rule of Five、Bioavailability。

常见工作流

工作流 1:药物发现靶点分析

目标:查找所有靶向特定基因的药物并分析其性质。

import xml.etree.ElementTree as ET
import pandas as pd

NS = {'db': 'http://www.drugbank.ca'}
tree = ET.parse('drugbank_all_full_database.xml')
root = tree.getroot()

target_gene = 'EGFR'
records = []
for drug in root.findall('db:drug', NS):
    for target in drug.findall('db:targets/db:target', NS):
        poly = target.find('db:polypeptide', NS)
        if poly is None:
            continue
        gene = poly.find('db:gene-name', NS)
        if gene is not None and gene.text == target_gene:
            actions = [a.text for a in target.findall('db:actions/db:action', NS) if a.text]
            records.append({
                'drugbank_id': drug.find('db:drugbank-id[@primary="true"]', NS).text,
                'name': drug.find('db:name', NS).text,
                'groups': ', '.join(g.text for g in drug.findall('db:groups/db:group', NS)),
                'actions': ', '.join(actions),
            })

df = pd.DataFrame(records)
print(f"Drugs targeting {target_gene}: {len(df)}")
print(df.to_string(index=False))
工作流 2:多重用药安全筛查

目标:筛查用药清单中的所有成对相互作用,并按严重程度排序。

import xml.etree.ElementTree as ET
import pandas as pd

NS = {'db': 'http://www.drugbank.ca'}
tree = ET.parse('drugbank_all_full_database.xml')
root = tree.getroot()

# Build index and interaction maps
idx = {}
inter_map = {}  # drugbank_id → {interacting_id: description}
for drug in root.findall('db:drug', NS):
    name = drug.find('db:name', NS)
    db_id = drug.find('db:drugbank-id[@primary="true"]', NS)
    if name is None or db_id is None:
        continue
    idx[name.text.lower()] = db_id.text
    imap = {}
    for i in drug.findall('db:drug-interactions/db:drug-interaction', NS):
        imap[i.find('db:drugbank-id', NS).text] = i.find('db:description', NS).text
    inter_map[db_id.text] = imap

medications = ['Warfarin', 'Aspirin', 'Omeprazole', 'Atorvastatin', 'Metformin']
report = []
med_ids = [(m, idx.get(m.lower())) for m in medications]
for i, (n1, id1) in enumerate(med_ids):
    if not id1: continue
    for n2, id2 in med_ids[i+1:]:
        if not id2: continue
        desc = inter_map.get(id1, {}).get(id2)
        if desc:
            dl = desc.lower()
            sev = ('MAJOR' if any(w in dl for w in ['contraindicated','avoid','fatal'])
                   else 'MODERATE' if any(w in dl for w in ['increase','decrease','enhance','reduce'])
                   else 'MINOR')
            report.append({'Drug 1': n1, 'Drug 2': n2, 'Severity': sev,
                           'Description': desc[:120]})

df = pd.DataFrame(report)
print(f"=== Polypharmacy Report: {len(medications)} medications, {len(df)} interactions ===")
if not df.empty:
    print(df.sort_values('Severity').to_string(index=False))

关键参数

参数函数/端点默认值描述
NS(命名空间字典)所有 XPath 查询{'db': 'http://www.drugbank.ca'}所有 find/findall 调用都必需
@primary="true"db:drugbank-id—选择主要 DrugBank ID(DB00XXX),而非次要 ID
target_typeget_targets()'targets'可选值:targets、enzymes、transporters、carriers
radiusMorgan 指纹2指纹半径;2 = ECFP4,3 = ECFP6
nbitsMorgan 指纹2048位向量长度;值越大,哈希冲突越少
eventsET.iterparse()—解析事件;使用 ('end',) 在结束标签处触发

最佳实践

  1. 启动时构建内存索引:只解析一次(30-60 秒),按 ID 和小写名称构建字典。切勿在循环中重新解析
  2. 始终传入命名空间字典:每个 find()/findall() 都需要 NS。遗漏它是结果为空的首要原因
  3. 内存受限时使用 iterparse:配合 elem.clear(),避免加载完整的 1.5 GB 树
  4. 防范 None:并非所有药物都包含所有字段。在访问 .text 前,始终检查 el is not None
  5. 优先使用计算属性,而非实验属性:几乎所有药物都有计算属性(SMILES、logP、MW)
  6. 为多重用药缓存相互作用映射:预先构建一次 {drug_id: {interacting_id: desc}}

常用示例

示例:将所有药物属性导出为 CSV
import pandas as pd
records = []
for drug in root.findall('db:drug', NS):
    row = {'drugbank_id': drug.find('db:drugbank-id[@primary="true"]', NS).text,
           'name': drug.find('db:name', NS).text, 'type': drug.get('type')}
    for prop in drug.findall('db:calculated-properties/db:property', NS):
        row[prop.find('db:kind', NS).text] = prop.find('db:value', NS).text
    records.append(row)
pd.DataFrame(records).to_csv('drugbank_properties.csv', index=False)
print(f"Exported {len(records)} drugs")
示例:Lipinski 五法则筛选
def check_lipinski(drug_element):
    props = get_all_properties(drug_element)
    try:
        mw = float(props.get('Molecular Weight', 9999))
        logp = float(props.get('logP', 99))
        hba = int(props.get('H Bond Acceptor Count', 99))
        hbd = int(props.get('H Bond Donor Count', 99))
    except (ValueError, TypeError):
        return None
    violations = sum([mw > 500, logp > 5, hba > 10, hbd > 5])
    return {'MW': mw, 'logP': logp, 'HBA': hba, 'HBD': hbd,
            'violations': violations, 'passes': violations <= 1}

print(check_lipinski(find_drug('Imatinib')))
示例:查找 CYP450 底物
cyp = 'CYP3A4'
substrates = []
for drug in root.findall('db:drug', NS):
    for enz in drug.findall('db:enzymes/db:enzyme', NS):
        n = enz.find('db:name', NS)
        if n is not None and n.text and cyp.lower() in n.text.lower():
            actions = [a.text.lower() for a in enz.findall('db:actions/db:action', NS) if a.text]
            if 'substrate' in actions:
                substrates.append(drug.find('db:name', NS).text)
print(f"{cyp} substrates: {len(substrates)}")

故障排除

问题原因解决方案
对已知元素调用 find() 返回 None缺少 XML 命名空间始终将 NS = {'db': 'http://www.drugbank.ca'} 传入 find()/findall()
解析完整 XML 时出现 MemoryError内存占用约为 2-3 GB使用 ET.iterparse() 并配合 elem.clear()
启动缓慢(>60 秒)解析 1.5 GB XML只解析一次并构建索引字典;避免重新解析
按名称找不到药物大小写敏感或名称不同转换为小写;尝试 CAS 或 DrugBank ID
calculated-properties 为空生物技术/蛋白质药物缺少 SMILES检查 drug.get('type') — 生物技术药物没有小分子属性
AttributeError: 'NoneType'可选 XML 元素不存在在访问 .text 前,使用 el is not None 进行检查
相互作用计数不对称XML 中的相互作用并非对称检查两个方向,或构建对称索引
drugbank-downloader 身份验证失败凭据无效在 https://go.drugbank.com/ 验证账户
REST API 429超出速率限制批量查询时改用本地 XML

捆绑资源

references/interactions_targets.md — 整合相互作用(严重程度判断、批量筛查、描述解析)和靶点/通路(多肽详情、作用目录、酶/转运蛋白覆盖范围、通路富集)。已内联迁移:基础提取(Core API 3-4)。省略:按字段进行冗长的解析,因其与 Core API 重复。

references/chemical_analysis.md — 属性提取、描述符计算、指纹相似度、药物相似性筛选。涵盖:完整属性目录、相似度矩阵、子结构搜索。已内联迁移:SMILES/InChI 提取 + Tanimoto(Core API 5)、Lipinski(Recipe)。省略:3D 构象体(请使用 rdkit-cheminformatics)。

原始内容处理情况(2,717 行:SKILL.md 190 + 5 个参考文件 2,166 + 脚本 351):

  • SKILL.md(190)—— 将原始存根改写为包含 6 个 Core API 模块的文件
  • data-access.md(243)→ Core API 1 + Quick Start
  • drug-queries.md(387)→ Core API 2
  • interactions.md(426)→ references/interactions_targets.md + Core API 3
  • targets-pathways.md(519)→ references/interactions_targets.md + Core API 4
  • chemical-analysis.md(591)→ references/chemical_analysis.md + Core API 5
  • drugbank_helper.py(351)—— 轻量封装:find_drug → Quick Start;get_drug_info/search_by_name → Core API 2;get_interactions/check_interaction/check_polypharmacy → Core API 3;get_targets → Core API 4;get_properties/get_smiles/get_inchi → Core API 5

保留情况:约 550 行 SKILL.md。加上参考文件(约 600 行),总计约 1,150 / 2,717 = 约 42%。原文件为存根;5 个参考文件 → 2 个,按 ceil(5/3)=2 计算。

相关技能

  • chembl-database-bioactivity — 实时生物活性数据库(IC50、Ki、EC50);可补充 DrugBank 的静态药物目录
  • pubchem-compound-search — 无需下载数据库即可查询公开化合物属性
  • rdkit-cheminformatics — 完整的化学信息学工具包,可用于生成 3D 构象体、使用高级指纹,以及计算超出 DrugBank 属性范围的描述符

参考资料

评论

支持 Markdown