news 2026/9/24 19:53:14

用DailyMed API构建药物情报检索:SPL解析与说明书结构化实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用DailyMed API构建药物情报检索:SPL解析与说明书结构化实践

做医药数据相关开发这几年,我越来越觉得 DailyMed 是个被低估的宝藏数据源。很多人一上手药物情报抓取,第一反应就是扑向 openFDA,因为它的接口直观,返回的是 JSON,文档也花哨;但真正跑起来做药品说明书结构化、标签对比、药物警戒监控之后,你会慢慢发现 openFDA 的 label 字段是经过二次整理的,有些原始说明书内容被丢弃,更新存在滞后,字段粒度也不够细。后来我把主数据源切到了 DailyMed——它由 NIH 下属的 NLM(美国国家医学图书馆)运营,提供的是官方审核后、可直接下载的 SPL(Structured Product Label)结构化产品标签,稳定、免费、不需要鉴权,而且在药品标签完整性上比 openFDA 细得多。这篇文章就把我做药物情报检索这段时间实践 DailyMed API 的完整过程拆出来,包括我怎么设计查询链路、怎么解析 SPL XML、中途踩过哪些坑,以及怎么把简单的 API 调用升级成一个可持续更新的药物标签知识库。适合正在做药品数据中台、说明书抽取、用药安全监测或者药学知识图谱的开发者参考。

1. 为什么我建议药物情报优先接 DailyMed,而不是只盯着 openFDA

1.1 DailyMed 在 NIH/NLM 药品标签体系中的位置

DailyMed 本质上不是"一个 API",而是 NLM 面向公众提供的美国上市药品标签发布平台。它的数据源头是 FDA 审核通过的 SPL 文件,经过 NLM 的加工、索引和在线发布,最终呈现在你面前。换句话说,DailyMed 是"FDA 审完的说明书"与"可以机器读取的结构化数据"之间的桥梁。

这里有个容易被忽略的点:FDA 是审核方,NLM 是发布方。DailyMed 挂在 NLM 域名下,NLM 又是 NIH 的下属机构,所以你在标题里看到 NIH/NLM 这个组合,指的是数据基础设施的归属,而不是数据来源本身。理解这一点非常关键,因为它决定了这套数据的权威性——你拿到的是美国药政体系里的"官方最终版标签",不是某个爬虫从 PDF 里 OCR 出来的野数据。

DailyMed 的覆盖范围也很值得说:它不仅包含处方药,还囊括非处方药、部分生物制剂、疫苗等,临床相关的说明标签都能在这里找到。每条标签对应唯一的 setid,同一药品的修订历史、包装规格、NDC 编码全部挂在同一个文档体系下,这对做历史版本回溯和包装层级分析来说,是难得的干净数据。

1.2 DailyMed 与 openFDA 的数据差异:一个原始标签,一个二次整理

很多人会问:openFDA 的 drug/label 接口也能拿说明书,为什么还要费劲去解析 SPL XML?我直接用实际体验来对比。

对比维度DailyMedopenFDA
数据性质NLM 发布的原始 SPL 标签全文FDA 整理的 API 化 JSON,字段经过再分组
数据模型HL7 V3 XML,保留说明书原始章节结构和 XHTML 文本扁平 JSON,章节字段有限,部分原始文本被裁剪
标签完整性高,保留完整 section、子 section、表格、格式信息中,部分字段如"患者信息"存在缺失
历史版本较完整,同一 setid 下保留修订轨迹相对有限,不利于版本差异分析
更新时效标签审批后较快同步,越接近数据链源头有一定处理链路,存在延迟场景
鉴权方式无强制鉴权,合理限速即可推荐申请 key,不强制但限流不同
适用场景深度说明书解析、知识图谱、标签对比快速查询、跨库统计、接口联调Demo

最简单粗暴的类比是:openFDA 是"别人嚼过的精加工食品",DailyMed 是"从原产地直接发货的原材料"。如果你只是想知道某个药品的适应症大致写了什么,openFDA 够用;但如果你要做不良反应词条的结构化抽取、比较同一通用名下不同厂家的禁忌症差异、或者监控某款药说明书某一段落的修订历史,DailyMed 的 SPL 全文几乎是唯一能给你完整证据链的地方。

1.3 DailyMed API 的定位:适合谁、解决什么问题

把 DailyMed API 说成一个"查询接口"其实是低估了它。它更准确的角色应该是:一个面向药品标签全文的结构化数据分发服务。它解决的问题不是"给我几个字段",而是"把整个说明书变成可编程、可检索、可对比的结构化文档"。

适合用 DailyMed 的核心场景我列一下:

  • 药品说明书结构化:从 SPL XML 里抽取适应症、禁忌症、不良反应、用法用量等标准 section;
  • 药品情报检索:按通用名、商品名、NDC 反查标签,做竞品分析或管线调研;
  • 药物警戒监控:定期拉取标签更新,比对版本号,发现说明书里警告内容的变化;
  • 药学知识图谱:以 setid 为主键,把药品、成分、剂型、制造商、NDC 关联成网络;
  • 临床决策支持:为院内用药系统提供结构化说明书数据。

我这里先说明一点:DailyMed API 是没有强制 API Key 的,这也是它经常被忽视的原因之一。没有 key 意味着上手成本低,但也意味着你需要自己控制调用频率,别把它当成不受限的公共资源那么薅。

2. 不了解 SPL,就谈不上结构化查询:先读懂药品标签的数据骨架

2.1 SPL 标准本质:HL7 V3 文档,不是普通 PDF

SPL 的全称是 Structured Product Label,它是 HL7 V3 标准下的一种 XML 文档类型。FDA 要求药企以 SPL 格式递交说明书,所以每一份美国上市药品的官方说明书,本质上都是一份结构化的 XML 文件,而 DailyMed 就是把这些文件标准化托管并对外分发。

你要把它和 PDF 说明书区分开。PDF 是给人看的,SPL 是给机器看的。PDF 里一段"禁忌症"可能是一行纯文本,SPL 里它会落在<section>节点的<text>子节点里,并且关联一个标准编码。解析 PDF 你需要 OCR、版面分析、清洗,解析 SPL 你只需要 XPath 和命名空间处理。这也是我坚持用 DailyMed 的原因——把文本抽取正确率从"看运气"提升到"看标准"。

一个典型的 SPL XML 根节点是这样的:

<document xmlns="urn:hl7-org:v3"> <id root="2.16.840.1.113883.4.699" extension="1a2b3c4d-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/> <effectiveTime value="20240115"/> <versionNumber value="12"/> <component> <structuredBody> <!-- 这里的 component 里才是各种 section --> </structuredBody> </component> </document>

看到这个结构你就该明白,用正则去抓说明书是下下策。正确做法是先把命名空间和节点层级弄清楚,再写提取逻辑。

2.2 SPL 的 section 编码体系:如何精准定位说明书章节

SPL 文档结构里,说明书正文由若干<section>组成,每个 section 都带有<title><code>。title 是给人看的章节名,code 是给机器用的标准代码。这套代码体系与 LOINC 有映射关系,在 DailyMed 的 XML 里通常能直接看到。

举个例子,一个 section 节点大致长这样:

<component> <section> <code code="34090-1" codeSystem="2.16.840.1.113883.6.1"/> <title>INDICATIONS AND USAGE</title> <text> <paragraph>Ibuprofen is a nonsteroidal anti-inflammatory drug...</paragraph> </text> </section> </component>

注意这里的<text>节点内部不是纯文本,而是 XHTML 片段,可能有<paragraph><list><table><link>等各种子节点。这意味着你要抽取 section 内容时,不能简单调.text,得把整个 XHTML 子树里的文本全部拼接出来。

解析策略就很清晰了:先定位 section,再按 code 或 title 判断是哪个章节,最后把 text 子树里的文本完整提取。这套逻辑比 openFDA 的"固定字段名"更通用,因为无论说明书怎么改版,section 的标题和编码都是标准化的。

2.3 一条 SPL 标签里的关键节点地图

拿到一份 SPL XML 后,我建议先按下面的节点地图做一次人工扫描,再写代码:

节点路径含义为什么重要
/document/idsetid标签的唯一标识,所有版本共享同一个 setid
/document/effectiveTime生效时间判断标签版本新旧的关键
/document/versionNumber版本号同一 setid 下修订次数的体现
manufacturedProduct生产商/产品信息可提取 NDC、厂家名称、商品名
activeIngredient活性成分提取成分名、剂量、强度
component/section说明书章节适应症、禁忌症、不良反应等核心内容

实际解析时你会发现,一份 SPL 文件内部嵌套很深,尤其manufacturedProduct经常出现多层嵌套,activeIngredient的结构也可能因药企的书写习惯而异。不要指望一份 XPath 通吃所有标签,正确的姿势是先写一个递归遍历工具,把整个 XML 树的结构 dump 出来,人工确认一遍再固化提取逻辑。

3. DailyMed Services v2 端点实战拆解:从搜索路由到参数规范

3.1 调用方式与"无鉴权"的正确理解

DailyMed 的 REST 服务基础地址是https://dailymed.nlm.nih.gov/dailymed/services/v2。你可以直接在浏览器里打开这个路径,会看到可用端点的文档和示例。它没有强制要求 API Key,但这绝不意味着你可以无限制并发调用。

我的建议是:在请求头里声明一个可识别的 User-Agent,标明你的项目名称和用途,比如:

curl -H "User-Agent: my-drug-monitor/1.0 (contact: dev@example.com)" \ "https://dailymed.nlm.nih.gov/dailymed/services/v2/druglist.json?drug_name=ibuprofen"

这既是工程礼仪,也是减少被限流的有效方式。绝大多数公共数据服务不会明文写"你必须带 User-Agent",但一旦出现反常流量,没有标识的请求最先被限制。

3.2 druglist:按药品名搜索的入口

拿到一个药品名后,第一件事通常是用druglist端点搜索它对应的drug_id。请求长这样:

curl "https://dailymed.nlm.nih.gov/dailymed/services/v2/druglist.json?drug_name=amoxicillin"

返回结果里会包含符合条件的药品列表,核心字段是drug_id,这个 id 是 DailyMed 内部维护主键,后续获取详情都靠它。搜索逻辑对大小写不敏感,支持模糊匹配,但你要注意:drug_name匹配的是商品名和通用名,并不保证只返回一个结果。比如搜索 "ibuprofen",你会同时拿到不同规格、不同厂家的多条记录。

此时不要盲目取第一条,建议把返回结果里的drug_namedosage_formroute打印出来,人工或代码过滤出目标剂型。很多医药数据项目最后跑偏,都是从"没过滤剂型,拿注射液说明书当口服片说明书用"开始的。

3.3 drug 详情与 NDC 反向检索

拿到drug_id后,访问https://dailymed.nlm.nih.gov/dailymed/services/v2/drug/{drug_id}.json可以获取这条药品记录的详情,包括生产商、剂量形式、给药途径、活性成分等等。

但实际工作中,我更多时候是先拿到一个 NDC(National Drug Code,国家药品代码),然后再反查它对应的标签。NDC 是美国药品流通环节里的"身份证号",在业务系统、供应链数据、保险理赔数据里非常常见。要把 NDC 和说明书关联起来,DailyMed 提供了spls端点:

curl "https://dailymed.nlm.nih.gov/dailymed/services/v2/spls.json?ndc=55111-0673-01"

返回结果里最核心的是setid,拿到 setid 就等于拿到了这份标签的身份证,后续下载全文都靠它。这里有个细节:NDC 的格式并不统一,有的带连字符,有的没有,有的 10 位,有的 11 位,查询前最好先做标准化处理,否则很容易查不到。

3.4 spls 端点:用 setid 下载 SPL 全文

setid是 UUID 格式,是 SPL 标签在整个生命周期里不变的标识。拿到 setid 之后,用下面这个端点就能下载完整 XML:

curl "https://dailymed.nlm.nih.gov/dailymed/services/v2/spls/{setid}.xml"

你也可以尝试在同路径下换成.json后缀,DailyMed 部分场景会返回 JSON 格式的标签结构,但我个人强烈建议直接用 XML。原因很简单:JSON 版本是经过映射的,section 树的结构可能被拍平,丢失嵌套,而 XML 是最原汁原味的 SPL 文档,解析逻辑一旦跑通,所有药品说明书都能复用同一套代码。

另外提醒一句,SPL 文件动辄几百 KB,包含大量 XHTML 文本,下载时不要用太短的超时时间,网络波动时会断。

3.5 端点速查表

端点用途关键参数
/druglist.json按药品名搜索药品列表drug_name
/drug/{drug_id}.json获取药品详情路径参数
/drug/{drug_id}/ndc.json获取该药品下 NDC 列表路径参数
/spls.json按 NDC 或 setid 检索 SPLndcsetid
/spls/{setid}.xml下载原始 SPL XML路径参数
/spls/{setid}.json下载 SPL 的 JSON 对照版本路径参数

这几个端点加起来,已经能覆盖从"药品名"到"说明书全文"的完整检索链路。当然,DailyMed 服务端偶尔会调整参数和返回字段,用之前建议先打开基础地址看一眼官方示例,避免字段名发生偏移。

4. 一个完整的药物情报检索链路:从药品名到标签全文的 Python 实现

4.1 场景设定:帮我找布洛芬口服片的所有说明书章节

下面我用一个具体场景把整条链路串起来:我想找所有含布洛芬(ibuprofen)的口服片剂说明书,并抽取其中"适应症""禁忌症""不良反应"三段正文。

这个场景在药物情报检索里非常典型——输入一个通用名,输出一堆结构化标签章节。它既涉及检索,又涉及文本抽取,做完之后基本就能迁移到其他药品上。

4.2 第一步:按药品名搜索并过滤剂型

import requests import time BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" HEADERS = { "User-Agent": "my-drug-monitor/1.0 (contact: dev@example.com)" } def search_drug_by_name(drug_name: str) -> list: resp = requests.get( f"{BASE}/druglist.json", params={"drug_name": drug_name}, headers=HEADERS, timeout=30, ) resp.raise_for_status() data = resp.json() # 兼容不同返回结构:可能是列表,也可能是带 drugList 字段的对象 if isinstance(data, list): return data return data.get("drugList") or data.get("results") or [] def filter_by_dosage_form(drugs: list, keywords=("tablet", "oral")) -> list: result = [] for d in drugs: name = (d.get("drug_name") or "").lower() form = (d.get("dosage_form") or "").lower() if "ibuprofen" in name and any(k in form for k in keywords): result.append(d) return result

这一步的要点是先搜宽、再过滤。不要指望drug_name=ibuprofen只返回目标结果,因为 DailyMed 的搜索是按包含关系匹配的。拿到列表后,用dosage_form字段过滤掉注射液、胶囊、混悬液,只保留片剂和口服相关剂型。

4.3 第二步:取 drug_id 后按 NDC 或 setid 跳转 SPL

过滤后的记录里会带drug_id,但我们需要的是说明书全文,所以下一步是拿到这条记录的 NDC 列表,再用 NDC 去反查 setid:

def get_ndc_list(drug_id: str) -> list: resp = requests.get(f"{BASE}/drug/{drug_id}/ndc.json", headers=HEADERS, timeout=30) resp.raise_for_status() data = resp.json() if isinstance(data, list): return [item.get("ndc") or item.get("ndc11") for item in data if item.get("ndc")] return data.get("ndcList") or [] def get_spl_by_ndc(ndc: str) -> dict: resp = requests.get( f"{BASE}/spls.json", params={"ndc": ndc}, headers=HEADERS, timeout=30, ) resp.raise_for_status() data = resp.json() if isinstance(data, list): return data[0] if data else {} return data.get("splList") or {}

这里有一个非常重要的工程判断:为什么不直接用drug_id拿一个 setid,而是要多绕一道 NDC?因为一个drug_id下可能对应多个包装规格、多个 NDC,每个 NDC 对应的 SPL 版本可能不同。如果你只取第一个,很可能拿到的是一个特定包装的说明书,而不是该药品的完整标签信息。用 NDC 反查能让你的数据更贴近业务侧的真实使用场景。

4.4 第三步:解析 SPL XML,按 section 抽取正文

拿到 setid 后,下载 SPL XML:

def fetch_spl_xml(setid: str) -> str: resp = requests.get(f"{BASE}/spls/{setid}.xml", headers=HEADERS, timeout=60) resp.raise_for_status() return resp.text

接下来是最核心的解析部分。需要注意两点:第一,必须带 HL7 命名空间;第二,section 的text节点是 XHTML 树,不能直接.text取值。

import xml.etree.ElementTree as ET HL7_NS = "{urn:hl7-org:v3}" def extract_section_text_by_keywords(root, keywords): """ 遍历所有 section,按 title 或 code 匹配关键词, 返回 {title: text} 的映射。 """ results = {} for section in root.iter(f"{HL7_NS}section"): title_el = section.find(f"{HL7_NS}title") title = title_el.text.strip() if title_el is not None and title_el.text else "" if not any(kw.lower() in title.lower() for kw in keywords): continue text_el = section.find(f"{HL7_NS}text") if text_el is None: continue # itertext() 会把整个 XHTML 子树里的所有文本拼接起来 content = "".join(text_el.itertext()).strip() if content: results[title] = content return results def parse_spl_label(xml_text: str): root = ET.fromstring(xml_text) setid_el = root.find(f"{HL7_NS}id") version_el = root.find(f"{HL7_NS}versionNumber") # 提取活性成分,注意层级用递归查,避免不同药企结构差异 ingredients = [] for ing in root.iter(f"{HL7_NS}activeIngredient"): names = [n.text for n in ing.iter(f"{HL7_NS}name") if n.text] if names and names[0].strip(): ingredients.append(names[0].strip()) sections = extract_section_text_by_keywords( root, ["INDICATIONS AND USAGE", "CONTRAINDICATIONS", "ADVERSE REACTIONS"], ) return { "setid": setid_el.get("extension") if setid_el is not None else None, "version": version_el.get("value") if version_el is not None else None, "ingredients": list(set(ingredients)), "sections": sections, }

这段代码我实际跑过很多次,最坑的地方就是activeIngredient的层级。有的标签是activeIngredient -> ingredient -> name,有的中间还夹了别的节点,直接用固定路径很容易漏。用iter递归找所有name节点是相对稳妥的降级方案,虽然偶尔会多抓一两个非成分名,但至少不会返回空。

4.5 组装结果:把抽取数据落成 JSON

最后把上述环节串起来:

def build_label_monitor(ingredient: str, dosage_keywords=("tablet", "oral")): drugs = search_drug_by_name(ingredient) filtered = filter_by_dosage_form(drugs, dosage_keywords) for drug in filtered[:5]: # 示例限制 5 条 drug_id = drug.get("drug_id") if not drug_id: continue ndc_list = get_ndc_list(drug_id) for ndc in ndc_list: spl_info = get_spl_by_ndc(ndc) setid = spl_info.get("setid") if not setid: continue xml_text = fetch_spl_xml(setid) parsed = parse_spl_label(xml_text) parsed["ndc"] = ndc parsed["drug_name"] = drug.get("drug_name") parsed["dosage_form"] = drug.get("dosage_form") parsed["fetched_at"] = time.strftime("%Y-%m-%d %H:%M:%S") yield parsed time.sleep(1) # 限速,别太猛

跑一遍这个生成器,你就得到了布洛芬口服片剂的说明书结构化结果。每一段都被切分到对应的 section 标题下,后面做全文检索、知识图谱、标签对比,全是水到渠成的事。

5. 实测踩坑记录:NDC 格式、空字段、429 限流与更新延迟

5.1 NDC 格式的连环坑:10 位、11 位、连字符

NDC 是我在 DailyMed 实战里遇到的第一个大坑。标准 NDC 是 10 位数字,分成三段,比如55111-0673-01;但很多系统里存的是 11 位数字,比如55111067301,开头补了一个 0。DailyMed 的spls.json?ndc=参数对格式很敏感,你传 10 位和传 11 位可能查出来的结果不同。

我的处理办法是统一做规范化:

def normalize_ndc(ndc: str) -> str: digits = "".join(ch for ch in ndc if ch.isdigit()) # 在 DailyMed 检索时优先用带连字符的 10 位形式 if len(digits) == 11: digits = digits[1:] # 去掉前导补零 return f"{digits[:5]}-{digits[5:9]}-{digits[9:]}"

当然,不同业务系统对 NDC 的规范不一样,这个函数只能作为参考。关键是你要意识到:同一个药品在不同系统里的 NDC 表现形态可能不同,查询前必须统一格式,否则你会花大量时间查"查不到"的问题。

5.2 SPL 的 text 是 XHTML,直接.text会丢一半内容

我在第一次解析 SPL 的时候犯过一个经典错误:用section.find('text').text去取正文,结果发现好多段落是空的,只有第一行文字。后来才意识到,text节点底下还有<paragraph><list><table>这些子元素,.text只会拿到直接子节点的文本,不会递归拼接。

正确做法是我上一节代码里写的"".join(text_el.itertext())。这行代码会把 XHTML 子树里所有文本节点拼成一句完整的话,包括表格里的数字、列表里的条目。代价是丢掉了段落和表格的结构信息,但绝大多数药物情报检索场景里,正文内容比排版结构重要得多。

如果你连表格结构也要保留,那就要再写一个 XHTML 遍历器,把<table>转成二维数组。这个复杂度会明显上升,建议按需引入。

5.3 空字段与缺失 section 的防御

不是每份说明书都包含所有 section。OTC 药品和处方药的结构差异很大,有些非处方药的说明书里根本没有"非临床毒理学",甚至"不良反应"都写得很简略。

解析代码里必须对 section 缺失做防御性处理,否则你会得到大量None。我的做法是在extract_section_text_by_keywords里直接跳过没有 text 的 section,保留一个空字符串作为占位。这样后面的下游分析只需要判断if sections.get("ADVERSE REACTIONS")即可,不会因为字段缺失而崩溃。

5.4 标签更新延迟与版本号判断

DailyMed 的数据虽然有较高的更新频率,但它毕竟不是实时的。你拉回来的标签可能存在两种情况:一是某药品的说明书刚刚修订,DailyMed 还没同步;二是你已经拉到了新版本,但下游业务系统还在用旧版本。

解决思路是在每次抓取时记录两个关键字段:effectiveTimeversionNumbersetid不变,但每次修订都会让versionNumber增加。我通常会在数据库里建一个唯一约束(setid, versionNumber),用INSERT ... ON CONFLICT DO NOTHING做幂等写入,这样重复拉取不会产生脏数据。

5.5 429 限流与重试策略

DailyMed 没有强制鉴权,但内部当然有速率限制。我曾经写过一个很激进的多线程抓取脚本,一口气跑了 200 个 NDC,结果被连续返回 429 状态码。当时的教训是:不要用无脑多线程去打公共数据服务

后来的稳定方案是单线程 + 固定延时 + 指数退避重试:

import time def request_with_retry(url, params=None, max_retry=5): for attempt in range(max_retry): resp = requests.get(url, params=params, headers=HEADERS, timeout=60) if resp.status_code == 200: return resp if resp.status_code == 429: wait = 2 ** attempt print(f"429, wait {wait}s") time.sleep(wait) continue resp.raise_for_status() raise RuntimeError("max retry exceeded")

这个重试逻辑虽然简单,但在实际抓取中非常管用。官方没有给一个明确的 QPS 上限,我自己的经验是控制在每秒 1 次左右,基本不会触发限流。如果你确实需要批量拉全量数据,DailyMed 还提供 FTP 方式的 SPL 批量文件下载,那才是为大数据量设计的通道,API 更适合做增量查询和在线检索。

6. 从查询升级到情报:把 SPL 变成可复用的本地知识库

6.1 数据表设计:以 setid 为主键,以版本号为修订刻度

把 DailyMed API 跑通只是第一步,真正的价值在于你能否把药品标签沉淀成自己的数据资产。我最终落地的方案是在 PostgreSQL 里建了两张核心表。

第一张表存药品主档:

CREATE TABLE drug_label_main ( setid UUID PRIMARY KEY, ndc VARCHAR(20) NOT NULL, drug_name TEXT NOT NULL, dosage_form TEXT, active_ingredients JSONB DEFAULT '[]', manufacturer TEXT, effective_time DATE, version_number INT, raw_xml_path TEXT, last_fetched_at TIMESTAMP DEFAULT now() );

第二张表存按 section 切分的文本:

CREATE TABLE label_section ( id BIGSERIAL PRIMARY KEY, setid UUID NOT NULL, ndc VARCHAR(20) NOT NULL, version_number INT NOT NULL, section_code VARCHAR(20), section_title TEXT, content_text TEXT, created_at TIMESTAMP DEFAULT now(), UNIQUE (setid, ndc, version_number, section_title) );

这样设计的好处是:主档保持每一条标签的最新状态,label_section则记录每次修订下每个章节的内容快照。后续你要做说明书差异对比,只需要按(setid, version_number)分组,比较content_text是否变化。

6.2 结合 RxNorm/RxClass 做药品名标准化

DailyMed 里的药品名是厂商递交的原生名称,同一通用名可能拼写不同,商品名更是五花八门。如果你要做跨库关联,强烈建议引入 NLM 的另一套服务 RxNav,把药品名映射到 RXCUI(RxNorm 概念唯一标识)。

调用方式非常简单:

curl "https://rxnav.nlm.nih.gov/REST/rxcui.json?name=ibuprofen"

拿到 RXCUI 之后,再配合 DailyMed 的 NDC 反查,就能实现"业务系统里的药品名 -> RXCUI -> NDC -> DailyMed setid -> SPL 全文"的无缝链路。这一步做完,你的数据就不只是"说明书文本库",而是真正的药物情报网络。

6.3 用标签 section 做说明书差异对比

有了结构化的label_section表,你能做很多有价值的事情。举一个我实际做过的例子:比较同一通用名(比如布洛芬)下不同厂家说明书的"禁忌症"差异。

SQL 写出来很直白:

SELECT drug_name, setid, content_text FROM label_section WHERE section_title ILIKE '%contraindications%' AND setid IN ( SELECT setid FROM drug_label_main WHERE drug_name ILIKE '%ibuprofen%' );

拿到结果后,你可以用文本相似度算法或直接 diff,找出"这个厂家写了、那个厂家没写"的禁忌条目。这类信息在药品采购决策、说明书合规审查、竞品安全情报里都很有价值。

6.4 持续更新的增量方案

DailyMed 是持续更新的,标签会随着 FDA 审批节奏不断修订。我在生产环境用的是一个定时任务,每天跑一次增量更新:先通过 DailyMed 的更新列表或直接按 NDC 列表逐个重查,重点是拿spls.json返回里的published_dateversionNumber做比较,只有版本号变化才重新下载 XML。

这里有一个需要注意的取舍:全量重查最省事,但会消耗大量请求配额;只查增量最经济,但需要你维护一份历史版本号和日期的表。我最终选择的是折中方案——每周做一次全量巡检,每天只对高关注的药品做增量探测。

把流程稳定跑起来之后,DailyMed 就不再是"偶尔查一下的接口",而是一个可以持续产出药物情报的数据基座。我个人在实际操作中最深的体会是:DailyMed 的价值不是某一个端点,而是它把药品说明书从"PDF 文档"变成了"可以程序化操作的数据结构"。最后再分享一个小技巧:每次抓下来的 SPL XML 记得存一份原始文件,命名用setid_version.xml,别只存解析后的 JSON。很多历史问题排查到最后,都是靠原始 XML 定位的——数据可以加工,但原始凭证不能丢。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 19:52:04

量化回测框架选型指南:Backtrader、VectorBT与FinRL的深度对比

1. 从“跑通第一个策略”说起&#xff1a;为什么回测框架的选择比策略本身更致命很多人做量化的第一步&#xff0c;是兴冲冲地打开某个教程&#xff0c;抄一段双均线策略代码&#xff0c;然后跑出一张漂亮的资金曲线&#xff0c;觉得自己找到了圣杯。但真正做过一段时间的人都知…

作者头像 李华
网站建设 2026/9/24 19:51:48

MySQL递归CTE实战:层级表上级路径查询与优化

1. 你大概率也遇到过&#xff1a;层级表查“上级路径”到底难在哪先交代一下背景。做组织架构、商品分类、权限菜单、评论回复链这类业务时&#xff0c;数据表十有八九是“邻接表”设计&#xff1a;每一行只保存一个parent_id&#xff0c;指向父节点。这种结构特别符合人的直觉…

作者头像 李华
网站建设 2026/9/24 19:51:13

现场安全检查流程图PPT制作:目视化设计与闭环管理全拆解

前阵子帮一家制造企业的朋友做现场安全检查的流程图PPT&#xff0c;做到一半我发现&#xff0c;这活儿的难点根本不在PPT操作&#xff0c;而在于怎么把“现场安全检查”这件事想清楚、讲明白。很多企业手里有检查制度、有整改台账&#xff0c;但你要他把整个检查流程画成一张图…

作者头像 李华
网站建设 2026/9/24 19:51:08

SQL+AI双驱动:从建表语句到ER图的高效生成实战

课设和毕设做到数据库设计这一环&#xff0c;很多人的感受是一样的&#xff1a;需求分析勉强能写&#xff0c;ER图却画得头疼。手绘吧&#xff0c;关系一多就乱&#xff1b;用建模工具吧&#xff0c;安装配置比画图还费劲&#xff1b;好不容易画完&#xff0c;老师又说“ER图里…

作者头像 李华