关于 drugbank-database-access
drugbank-database-access 是通过 Python 查询本地 DrugBank XML 数据库的工作流,支持药物与相互作用查询、靶点和属性提取,以及跨数据库标识符映射。
主要功能
- 01按 ID、名称或 CAS 号搜索药物
- 02获取药物相互作用详情
- 03提取靶点、酶和通路
- 04读取化学属性并计算相似度
- 05映射至外部数据库标识符
适用场景
- 01筛查用药清单中的相互作用
- 02查找关联特定蛋白靶点的药物
- 03比较药物属性以开展化学信息学分析
- 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:pathway | SMPDB 通路关联 |
| 性质 | db:calculated-properties、db:experimental-properties | SMILES、MW、logP 等 |
| 外部 ID | db:external-identifiers | PubChem、ChEMBL、KEGG、UniProt 交叉引用 |
外部标识符映射
| XML 中的资源名称 | 数据库 | 示例 |
|---|---|---|
PubChem Compound | PubChem CID | 2244 |
ChEMBL | ChEMBL | CHEMBL25 |
KEGG Drug / KEGG Compound | KEGG | D00109 / C01405 |
UniProtKB | UniProt | P23219 |
PharmGKB | PharmGKB | PA452615 |
ChEBI | ChEBI | 15365 |
计算属性类型
常见的 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_type | get_targets() | 'targets' | 可选值:targets、enzymes、transporters、carriers |
radius | Morgan 指纹 | 2 | 指纹半径;2 = ECFP4,3 = ECFP6 |
nbits | Morgan 指纹 | 2048 | 位向量长度;值越大,哈希冲突越少 |
events | ET.iterparse() | — | 解析事件;使用 ('end',) 在结束标签处触发 |
最佳实践
- 启动时构建内存索引:只解析一次(30-60 秒),按 ID 和小写名称构建字典。切勿在循环中重新解析
- 始终传入命名空间字典:每个
find()/findall()都需要NS。遗漏它是结果为空的首要原因 - 内存受限时使用
iterparse:配合elem.clear(),避免加载完整的 1.5 GB 树 - 防范 None:并非所有药物都包含所有字段。在访问
.text前,始终检查el is not None - 优先使用计算属性,而非实验属性:几乎所有药物都有计算属性(SMILES、logP、MW)
- 为多重用药缓存相互作用映射:预先构建一次
{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 Startdrug-queries.md(387)→ Core API 2interactions.md(426)→references/interactions_targets.md+ Core API 3targets-pathways.md(519)→references/interactions_targets.md+ Core API 4chemical-analysis.md(591)→references/chemical_analysis.md+ Core API 5drugbank_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 属性范围的描述符
参考资料
- DrugBank 网站:https://go.drugbank.com/
- DrugBank XML 架构:https://docs.drugbank.com/xml/
- drugbank-downloader:https://pypi.org/project/drugbank-downloader/
- Wishart DS 等(2018)。DrugBank 5.0。Nucleic Acids Res. 46(D1):D1074-D1082。https://doi.org/10.1093/nar/gkx1037