DrugBank 5.17 XML 解析实战:从结构到CSV、SQLite与Neo4j

发布时间:2026/10/20 8:48:26
DrugBank 5.17 XML 解析实战:从结构到CSV、SQLite与Neo4j 简介DrugBank 5.17 完整数据库 XML 文件面向药物研发、生物信息学与药理数据分析人员提供结构化药物知识库可用于药物发现、靶点机制研究与临床数据挖掘。压缩包内共 1 个文件为 full database.xml整体约 140.19MB以 XML 结构化格式存储便于用 DOM、SAX 或 XSLT 等解析技术提取处理。内容覆盖药物通用名、商品名、化学式与 ATC 代码等标识信息SMILES、InChI 表示的化学结构作用靶点类型与亲和力等生物活性数据以及半衰期、生物利用度、清除率等药代动力学参数和适应症、禁忌症、剂量指导、相互作用等临床信息并附 PMID 等文献引用。已有 2187 人学习下载适合掌握 Python、Java 或 R 及 XML 解析基础的研究者用于构建本地药物数据库或开展大规模数据分析。1. 拆开 DrugBank 5.17 的 XML一份药物数据包的落地姿势做药物信息检索、靶点预测或者药物相互作用分析的人大概率都绕不开 DrugBank 这个数据集。它把药物、靶点、酶、转运体、通路、适应症、相互作用这些实体全部塞进一个 XML 里结构清晰但体量惊人。5.17 这个版本延续了它一贯的 XML 组织方式全库解压后是单个大文件节点层级深、交叉引用多直接拿文本编辑器打开基本等于自虐。这份资源适合三类人一是做药物重定位、知识图谱构建的算法工程师需要把 XML 转成图数据库或关系表二是做临床决策支持的产品开发想拿药物相互作用和靶点数据做本地化查询三是做生物信息教学或课程设计需要一个真实、完整、字段丰富的数据源来练手。它的价值不在于“新”而在于字段全、引用关系完整很多公开数据集要么缺靶点要么缺代谢酶DrugBank 是少数能把这条链路串齐的。下面按“先看懂结构、再动手解析、最后避坑”的顺序拆一遍。2. 先看懂 XML 骨架drug、target、enzyme 三层怎么串2.1 根节点与药物主记录的结构DrugBank 的 XML 根节点是drugbank下面挂着一堆drug节点每个drug有type属性常见取值是small molecule和biotech。每个药物节点内部又分几大块drugbank-id是主键分 primary 和 secondaryname和description是基础信息targets、enzymes、transporters、carriers是四类相互作用实体pathways挂通路interactions挂药物-药物相互作用。理解这个层级是后面所有解析工作的前提因为很多新手一上来就写 XPath 去抓target结果发现同名节点在不同父节点下含义完全不同。我一般会先用一个极简脚本把顶层结构打印出来确认版本和节点数量再决定用流式解析还是 DOM 解析。DrugBank 全量 XML 动辄几百 MB 到 1 GB 以上DOM 解析在普通开发机上很容易 OOM所以流式是更稳妥的选择。import xml.etree.ElementTree as ET # 只做顶层扫描不加载全文 context ET.iterparse(drugbank.xml, events(start, end)) for event, elem in context: if event end and elem.tag drug: db_id elem.findtext(drugbank-id[primarytrue]) name elem.findtext(name) print(db_id, name) elem.clear() # 关键释放已处理节点控制内存这段代码用的是iterparse每处理完一个drug就调用clear()释放内存。参数上唯一需要注意的是events要同时监听start和end否则拿不到完整的子节点。drugbank-id[primarytrue]这个 XPath 写法在 ElementTree 里是支持的但如果你换成 lxml语法更灵活速度也更快。实际跑下来1 GB 左右的 XML 用这种方式内存占用能压在 200 MB 以内。2.2 target、enzyme、transporter 的字段差异这四类实体在 XML 里的结构高度相似但字段有细微差别直接统一处理会丢信息。target下面通常有polypeptide里面带id、name、gene-name、chromosome-location、cellular-location还有external-identifiers挂 UniProt 和 GenBank。enzyme除了 polypeptide还会多出actions和inhibition-strength这类药理学字段。transporter和carrier结构接近但carrier一般不带actions。如果你要做靶点富集分析必须把polypeptide的id和external-identifiers里的 UniProt 对齐否则后面跟外部数据库 join 会断链。常见做法是先把四类实体分别抽成独立的表字段包括 drugbank_id、entity_type、polypeptide_id、gene_name、uniprot_id、actions。这样后续无论是做图数据库导入还是做统计分析都不需要反复回读 XML。def parse_entity(drug_elem, entity_tag): rows [] for ent in drug_elem.findall(entity_tag): for poly in ent.findall(polypeptide): row { drugbank_id: drug_elem.findtext(drugbank-id[primarytrue]), entity_type: entity_tag, polypeptide_id: poly.get(id), gene_name: poly.findtext(gene-name), uniprot_id: None, } for ext in poly.findall(external-identifiers/external-identifier): if ext.findtext(resource) UniProtKB: row[uniprot_id] ext.findtext(identifier) rows.append(row) return rows这里entity_tag传targets/target、enzymes/enzyme等路径即可复用。poly.get(id)拿的是 polypeptide 的内部 ID和 drugbank-id 不是一回事别混。external-identifiers里 resource 字段大小写在不同版本里偶有差异建议用in判断而不是。2.3 用 iterparse 还是 lxml选型与内存实测选型上标准库ElementTree胜在零依赖但 XPath 支持有限遇到复杂嵌套会写得很啰嗦。lxml的iterparse支持完整 XPath 和clear()速度通常快 2 到 3 倍代价是多一个 C 依赖。如果你在容器里跑装 lxml 基本没障碍如果是在受限环境标准库也能用只是代码量会上去。内存方面我做过一个粗略对比同样 1 GB 的 XML标准库 iterparse 峰值约 180 MBlxml iterparse 约 150 MB差距不大。真正会炸的是ET.parse()全量加载峰值能到 4 GB 以上普通笔记本直接卡死。所以结论很明确只要文件超过 100 MB一律走 iterparse别偷懒。提示解析前先用head -c 2000 drugbank.xml看一眼根节点和命名空间有些分发版本会带 namespaceXPath 要相应调整。3. 把 XML 转成可用数据CSV、SQLite 与图数据库三条路3.1 抽成 CSV字段映射与批量写入CSV 是最轻的落地形式适合做后续的 pandas 分析或导入其他系统。核心是把药物主表、靶点表、相互作用表分开输出。药物主表字段建议保留 drugbank_id、name、type、cas_number、unii、description靶点表保留 drugbank_id、polypeptide_id、gene_name、uniprot_id、actions相互作用表保留 drugbank_id、partner_id、description。写 CSV 时最容易翻车的是字段里带换行和逗号尤其是description。用csv.writer并设置quotingcsv.QUOTE_ALL能省掉很多手工转义的麻烦。批量写入时每 5000 行 flush 一次避免内存堆积。import csv with open(drugs.csv, w, newline, encodingutf-8) as f: writer csv.writer(f, quotingcsv.QUOTE_ALL) writer.writerow([drugbank_id, name, type, cas, unii]) for event, elem in ET.iterparse(drugbank.xml, events(end,)): if elem.tag drug: writer.writerow([ elem.findtext(drugbank-id[primarytrue]), elem.findtext(name), elem.get(type), elem.findtext(cas-number), elem.findtext(unii), ]) elem.clear()newline是 Windows 下避免空行的关键encodingutf-8保证特殊字符不丢。elem.get(type)拿的是属性不是子节点别写成findtext。3.2 灌进 SQLite建表与索引策略SQLite 适合做本地查询和轻量服务。建表时把 drugbank_id 设为主键靶点表和相互作用表用联合主键或自增 ID。索引是重点drugbank_id、uniprot_id、gene_name这三个字段上建索引查询速度能从秒级降到毫秒级。相互作用表数据量大建议先关掉synchronous和journal_mode再批量插入插完再开回来。PRAGMA synchronous OFF; PRAGMA journal_mode MEMORY; CREATE TABLE drugs ( drugbank_id TEXT PRIMARY KEY, name TEXT, type TEXT, cas TEXT, unii TEXT ); CREATE TABLE targets ( drugbank_id TEXT, polypeptide_id TEXT, gene_name TEXT, uniprot_id TEXT, actions TEXT ); CREATE INDEX idx_targets_drug ON targets(drugbank_id); CREATE INDEX idx_targets_uniprot ON targets(uniprot_id);批量插入用executemany每批 10000 条。插完记得PRAGMA synchronous NORMAL;恢复否则断电可能丢数据。这套组合在普通 SSD 上灌 1 GB XML 大约 3 到 5 分钟。3.3 导入 Neo4j节点与关系建模如果目标是做知识图谱Neo4j 是更自然的选择。建模思路Drug 节点、Protein 节点、Pathway 节点关系有 TARGETS、ENZYMES、TRANSPORTS、CARRIES、INTERACTS_WITH。导入时用LOAD CSV或apoc.load.xml前者更稳因为 CSV 已经在上一步生成好了。LOAD CSV WITH HEADERS FROM file:///targets.csv AS row MERGE (d:Drug {drugbankId: row.drugbank_id}) MERGE (p:Protein {polypeptideId: row.polypeptide_id}) SET p.geneName row.gene_name, p.uniprotId row.uniprot_id MERGE (d)-[:TARGETS {actions: row.actions}]-(p);MERGE而不是CREATE是为了避免重复节点。actions放在关系属性上因为同一个药物和同一个靶点可能有多种作用方式。导入前记得在drugbankId和polypeptideId上建唯一约束否则 MERGE 会全表扫描速度断崖式下降。注意Neo4j 社区版对内存有限制导入前把dbms.memory.heap.max_size调到 2G 以上否则大文件容易中途失败。4. 避坑与排查解析 DrugBank XML 时最容易翻车的五件事4.1 现象脚本跑一半报 MemoryError原因几乎都是用了ET.parse()或lxml.etree.parse()全量加载或者 iterparse 里忘了elem.clear()。解决改用 iterparse并在每个顶层节点处理完后 clear。如果已经 clear 还报错检查是不是把子节点引用存进了列表导致父节点无法释放。4.2 现象drugbank-id 抓出来是空原因有两种一是 XPath 写成了drugbank-id但没加[primarytrue]拿到的是 secondary id二是命名空间没处理节点实际是{ns}drugbank-id。解决先用elem.tag打印实际标签名确认有没有 namespace再决定是否用{*}drugbank-id通配。4.3 现象UniProt ID 大量缺失原因通常是external-identifiers的 resource 字段值不统一有的写UniProtKB有的写UniProt还有的带版本号。解决用resource.startswith(UniProt)做匹配而不是精确等于。另外部分 polypeptide 确实没有外部 ID属于数据本身缺失不要误判为解析错误。4.4 现象相互作用表导入后查询极慢原因是没有在drugbank_id和partner_id上建索引或者 SQLite 的journal_mode还是默认的 DELETE。解决建联合索引导入阶段临时切 MEMORY 模式导入后切回 WAL 模式。Neo4j 侧则要确认唯一约束已生效用EXPLAIN看执行计划。4.5 现象description 字段里的 HTML 标签污染下游DrugBank 的description里常带p、b这类标签直接入库会导致前端渲染异常或全文检索命中噪音。解决入库前用正则或lxml.html清洗保留纯文本。如果要做全文检索清洗后再建 FTS 索引否则标签会被当作文本分词。import re def clean_html(text): if not text: return return re.sub(r[^], , text).strip()这个正则只去标签不处理实体转义。如果文本里有amp;这类再加一步html.unescape。清洗逻辑放在解析阶段做别留到查询时否则每次查询都要重复计算。5. 进阶技巧用 XPath 批量抽取与版本差异比对5.1 用 lxml 的 XPath 一次抽多类实体当你要同时抽 target、enzyme、transporter 时逐个findall效率低。lxml 支持在一条 XPath 里用|合并路径配合iterparse的tag过滤能显著减少遍历次数。from lxml import etree for event, elem in etree.iterparse(drugbank.xml, events(end,), tag(drug,)): entities elem.xpath( targets/target/polypeptide | enzymes/enzyme/polypeptide | transporters/transporter/polypeptide ) for poly in entities: # 统一处理父节点类型可通过 poly.getparent().getparent().tag 判断 pass elem.clear()tag(drug,)让 iterparse 只在 drug 节点结束时触发减少无效回调。xpath里的|是并集返回顺序按文档顺序。判断实体类型时用poly.getparent().getparent().tag因为 polypeptide 的父是 target/enzyme再往上一级才是容器节点。5.2 两个版本之间的字段增删怎么快速定位如果你手上有 5.16 和 5.17 两个 XML想快速知道哪些字段变了可以分别抽一份“路径-出现次数”的统计再做 diff。做法是用 iterparse 遍历所有节点记录每个节点的完整路径和计数输出成两列文本用diff比对。from collections import Counter def path_counter(xml_file): counter Counter() for event, elem in ET.iterparse(xml_file, events(end,)): path /.join([e.tag for e in elem.iterancestors()][::-1] [elem.tag]) counter[path] 1 elem.clear() return counteriterancestors()返回的是从当前节点到根的迭代器反转后拼成完整路径。跑完两个版本后对两个 Counter 做键的对称差就能看到新增或消失的路径。这个方法比逐字段对比快得多适合版本升级前的兼容性评估。5.3 一个我踩过的坑别在循环里反复打开文件早期我写过一个脚本每处理一个药物就重新打开一次 XML 去查相互作用结果跑了 40 分钟还没完。后来改成一次遍历、边解析边写库时间降到 4 分钟。血泪经验就是XML 解析一定是单次遍历、多路输出任何“回头再查”的设计都会让复杂度爆炸。从那以后我每次解析大 XML 都强制走一遍“单遍扫描 分表落库”的流程再也没被性能问题坑过。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询