初始归档:健康管理资料库(大医网/药膳/体质监测/中医理论等12个资料集,7712个文件)

This commit is contained in:
512song committed 2026-09-23 21:59:25 +08:00
commit 80ae3811cf
7712 files changed
+4628547

No files matched your search

@@ -0,0 +1,664 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
9种中医体质 × 方剂/药材/穴位 深层挖掘
=========================================
数据源:
- 方剂库 (1000个JSON)
- 中药材库 (1000个JSON)
- 针灸穴位库 (1000个JSON)
- 九种体质详细数据.json
输出:
1. JSON: 体质-方剂药材穴位挖掘.json
2. MD: 体质数据挖掘报告.md (追加在疾病症状部分之后)
"""
import json
import os
import re
import sys
from collections import defaultdict, Counter
from pathlib import Path
# ============================================================
# 0. 路径设置
# ============================================================
BASE = Path("/home/songyi/Documents/ai_agent_scraper_study/data/大医网")
DIR_FANGJI = BASE / "01_来源数据/方剂"
DIR_YAOCAI = BASE / "01_来源数据/中药材"
DIR_XUEWEI = BASE / "01_来源数据/针灸穴位"
CONST_PATH = BASE / "02_加工数据/中医体质/九种体质详细数据.json"
OUT_JSON = BASE / "03_关联融合/交叉关联分析/体质-方剂药材穴位挖掘.json"
OUT_MD = BASE / "04_分析报告/体质数据挖掘报告.md"
# 确保输出目录存在
OUT_JSON.parent.mkdir(parents=True, exist_ok=True)
OUT_MD.parent.mkdir(parents=True, exist_ok=True)
# ============================================================
# 1. 加载体质数据 & 提取关键字
# ============================================================
print("[1/6] 加载体质数据...")
with open(CONST_PATH, "r", encoding="utf-8") as f:
constitutions = json.load(f)
# 9种体质: 名称 + 关联关键字
const_info = {} # { "气虚质": { "keywords": [...], "name": "气虚质" } }
for c in constitutions:
name = c["名称"]
kw = c.get("关联关键字", [])
const_info[name] = {
"keywords": kw,
"name": name,
"data": c
}
const_names = list(const_info.keys())
print(f" 加载 {len(const_names)} 种体质: {const_names}")
# ============================================================
# 2. 加载全部数据
# ============================================================
print("[2/6] 加载方剂、药材、穴位数据...")
def load_json_files(directory):
"""加载目录下所有JSON文件,返回列表"""
items = []
files = sorted(os.listdir(directory))
for fname in files:
if not fname.endswith(".json"):
continue
fpath = os.path.join(directory, fname)
try:
with open(fpath, "r", encoding="utf-8") as f:
data = json.load(f)
items.append(data)
except Exception as e:
print(f" ⚠ 加载失败 {fname}: {e}")
return items
# 方剂
fangji_list = load_json_files(DIR_FANGJI)
print(f" 方剂: {len(fangji_list)} 个")
# 中药材
yaocai_list = load_json_files(DIR_YAOCAI)
print(f" 药材: {len(yaocai_list)} 个")
# 针灸穴位
xuewei_list = load_json_files(DIR_XUEWEI)
print(f" 穴位: {len(xuewei_list)} 个")
# ============================================================
# 3. 构建药材名索引 (用于最长前缀匹配)
# ============================================================
print("[3/6] 构建药材名称索引 (最长前缀匹配)...")
# 收集所有药材名称 (按长度降序排序,避免 金 vs 金银花 误配)
herb_names_all = []
for h in yaocai_list:
name = h.get("名称", "").strip()
if name:
herb_names_all.append(name)
# 也加入别名中的药材名
aliases = h.get("别名", "")
if aliases:
# 别名格式: "木丹、鲜支、越桃、支子、山栀子、栀子"
for a in re.split(r'[、,,]', aliases):
a = a.strip()
if a and len(a) >= 2:
herb_names_all.append(a)
# 去重并按长度降序排序
herb_names_all = sorted(set(herb_names_all), key=lambda x: (-len(x), x))
print(f" 药材名称库: {len(herb_names_all)} 个 (含别名)")
def longest_prefix_match(text, herb_list=None):
"""
在 text 中搜索药材名(最长前缀匹配)
返回匹配到的药材名列表
"""
if herb_list is None:
herb_list = herb_names_all
found = []
# 按长度降序遍历,确保先匹配长名
remaining = text
# 简单方法:对每个已知药材名,看是否出现在text中
# 但为避免重叠,我们使用贪心匹配
for hname in herb_list:
if hname in remaining:
found.append(hname)
# 移除已匹配部分,避免重复/重叠
# 但这里我们不用移除,因为方解-组成中是"、"分隔的,不会有重叠
return found
def extract_herbs_from_formula(formula):
"""
从方剂数据中提取药材名
优先从 方解-组成 字段提取
其次是 组成 字段
最后从 方义 字段中提取
"""
herbs = []
# 1. 尝试 方解-组成
if "方解-组成" in formula:
text = formula["方解-组成"]
# 用、分割
parts = re.split(r'[、,,]', text)
for p in parts:
p = p.strip()
if p:
herbs.append(p)
# 2. 尝试 组成
if not herbs and "组成" in formula:
text = formula["组成"]
# 用、或空格分割,去掉剂量信息
# 例如: 桂枝(别切)四两、甘草(炙)四两、白术三两
# 提取括号前的药材名
parts = re.split(r'[、,,]', text)
for p in parts:
p = p.strip()
# 去掉剂量部分
# 匹配 "药材名(炮制)剂量" 或 "药材名 剂量"
m = re.match(r'^([\u4e00-\u9fff\w]+)', p)
if m:
name = m.group(1).strip()
if name and len(name) >= 2:
herbs.append(name)
# 3. 从方义中提取
if not herbs and "方义" in formula:
text = formula["方义"]
# 使用最长前缀匹配提取药材名
herbs = longest_prefix_match(text)
# 去重
seen = set()
unique_herbs = []
for h in herbs:
if h not in seen:
seen.add(h)
unique_herbs.append(h)
return unique_herbs
# ============================================================
# 4. 评分函数
# ============================================================
def score_formula(formula, keywords):
"""
对方剂进行体质关键字评分
名称 +5, 方义/简介 +3, 歌诀/出处 +2, 其他 +1
返回总分
"""
score = 0.0
# 名称
name = formula.get("名称", "")
for kw in keywords:
if kw in name:
score += 5.0
# 方义 + 简介 (+3)
fangyi = formula.get("方义", "")
intro = formula.get("简介", "")
for field_text in [fangyi, intro]:
if field_text:
for kw in keywords:
count = field_text.count(kw)
score += count * 3.0
# 歌诀 + 出处 (+2)
gejue = formula.get("歌诀", "")
chuchu = formula.get("出处", "")
for field_text in [gejue, chuchu]:
if field_text:
for kw in keywords:
count = field_text.count(kw)
score += count * 2.0
# 其他字段 (+1)
other_fields = ["分类", "运用", "配伍特点", "加减化裁", "化裁方之间的鉴别",
"重要文献摘要", "用法用量", "使用注意", "趣味记忆", "注意事项",
"各家论述", "方解-功效", "方解-主治"]
for field in other_fields:
val = formula.get(field, "")
if val:
for kw in keywords:
count = val.count(kw)
score += count * 1.0
return round(score, 1)
def score_herb(herb, keywords):
"""
对药材进行体质关键字评分
名称 +5, 简介 +3, 功效作用 +3, 性味归经 +2, 临床应用 +2, 其他 +1
"""
score = 0.0
# 名称 (+5)
name = herb.get("名称", "")
for kw in keywords:
if kw in name:
score += 5.0
# 简介 (+3)
intro = herb.get("简介", "")
if intro:
for kw in keywords:
count = intro.count(kw)
score += count * 3.0
# 功效作用 (+3) - 嵌套对象
effect = herb.get("功效作用", {})
if isinstance(effect, dict):
effect_text = " ".join(str(v) for v in effect.values())
elif isinstance(effect, str):
effect_text = effect
else:
effect_text = ""
if effect_text:
for kw in keywords:
count = effect_text.count(kw)
score += count * 3.0
# 性味归经 (+2)
xingwei = herb.get("性味归经", "")
if xingwei:
for kw in keywords:
count = xingwei.count(kw)
score += count * 2.0
# 临床应用 (+2)
clinical = herb.get("临床应用", {})
if isinstance(clinical, dict):
clinical_text = " ".join(str(v) for v in clinical.values())
elif isinstance(clinical, str):
clinical_text = clinical
else:
clinical_text = ""
if clinical_text:
for kw in keywords:
count = clinical_text.count(kw)
score += count * 2.0
# 其他字段 (+1)
other_fields = ["别名", "中文名称", "拉丁文名", "道地产区", "加工炮制",
"药材鉴别", "保存方法", "植物学信息", "动物学信息",
"矿物学信息", "毒性", "相关方剂", "医保类型"]
for field in other_fields:
val = herb.get(field, "")
if isinstance(val, str) and val:
for kw in keywords:
count = val.count(kw)
score += count * 1.0
elif isinstance(val, dict):
val_text = " ".join(str(v) for v in val.values())
if val_text:
for kw in keywords:
count = val_text.count(kw)
score += count * 1.0
return round(score, 1)
def score_xuewei(xuewei, keywords):
"""
对穴位进行体质关键字评分
名称 +5, 主治 +3, 详细主治 +3, 功能 +2, 其他 +1
"""
score = 0.0
# 名称 (+5)
name = xuewei.get("名称", "")
for kw in keywords:
if kw in name:
score += 5.0
# 主治 (+3)
zhuzhi = xuewei.get("主治", "")
if zhuzhi:
for kw in keywords:
count = zhuzhi.count(kw)
score += count * 3.0
# 详细主治 (+3)
detail = xuewei.get("详细主治", "")
if detail:
for kw in keywords:
count = detail.count(kw)
score += count * 3.0
# 功能 (+2)
func = xuewei.get("功能", "") or xuewei.get("功能作用", "")
if func:
for kw in keywords:
count = func.count(kw)
score += count * 2.0
# 其他字段 (+1)
other_fields = ["简介", "隶属", "位置", "定位", "解剖", "详细操作",
"临床运用", "主要配伍", "配伍", "附注", "相关论述",
"出处", "功能作用"]
for field in other_fields:
val = xuewei.get(field, "")
if isinstance(val, str) and val:
for kw in keywords:
count = val.count(kw)
score += count * 1.0
return round(score, 1)
# ============================================================
# 5. 主挖掘流程
# ============================================================
print("[4/6] 执行方剂/药材/穴位评分挖掘...")
# 结果容器
result = {
"体质-方剂": {},
"体质-药材": {},
"体质-穴位": {},
"体质-药材分类统计": {},
"体质-核心药对": {},
"体质-穴位归经统计": {},
"统计摘要": {}
}
# ---- 5A. 方剂挖掘 ----
print(" → 方剂评分...")
for const_name in const_names:
keywords = const_info[const_name]["keywords"]
scored = []
for f in fangji_list:
s = score_formula(f, keywords)
if s >= 3.0:
herbs = extract_herbs_from_formula(f)
scored.append({
"名称": f.get("名称", ""),
"得分": s,
"药材": herbs
})
# 排序取TOP20
scored.sort(key=lambda x: (-x["得分"], x["名称"]))
top20 = scored[:20]
result["体质-方剂"][const_name] = top20
print(f" {const_name}: {len(scored)} 个通过阈值, TOP20已选取")
# ---- 5B. 药材挖掘 ----
print(" → 药材评分...")
for const_name in const_names:
keywords = const_info[const_name]["keywords"]
scored = []
for h in yaocai_list:
s = score_herb(h, keywords)
if s >= 3.0:
# 分类:使用 药材分类 字段
classification = h.get("药材分类", "其他")
xingwei = h.get("性味归经", "")
scored.append({
"名称": h.get("名称", ""),
"得分": s,
"分类": classification,
"性味归经": xingwei
})
scored.sort(key=lambda x: (-x["得分"], x["名称"]))
top30 = scored[:30]
result["体质-药材"][const_name] = top30
# 药材分类统计
class_counter = Counter()
for item in top30:
class_counter[item["分类"]] += 1
result["体质-药材分类统计"][const_name] = dict(class_counter.most_common())
# 核心药对 (TOP30药材两两组合)
top_names = [item["名称"] for item in top30]
pairs = []
for i in range(len(top_names)):
for j in range(i+1, len(top_names)):
a, b = top_names[i], top_names[j]
# 统计共现次数 = 同时在多少方剂中出现
co_count = 0
for f in fangji_list:
herbs = extract_herbs_from_formula(f)
if a in herbs and b in herbs:
co_count += 1
if co_count > 0:
pairs.append([a, b, co_count])
# 按共现数降序排序
pairs.sort(key=lambda x: -x[2])
result["体质-核心药对"][const_name] = pairs[:30] # 取TOP30药对
print(f" {const_name}: {len(scored)} 个通过阈值, TOP30选取, {len(pairs)} 个药对")
# ---- 5C. 穴位挖掘 ----
print(" → 穴位评分...")
for const_name in const_names:
keywords = const_info[const_name]["keywords"]
scored = []
for x in xuewei_list:
s = score_xuewei(x, keywords)
if s >= 3.0:
scored.append({
"名称": x.get("名称", ""),
"得分": s,
"隶属": x.get("隶属", ""),
"主治": x.get("主治", "")
})
scored.sort(key=lambda x: (-x["得分"], x["名称"]))
top20 = scored[:20]
result["体质-穴位"][const_name] = top20
# 穴位归经统计
meridian_counter = Counter()
for item in top20:
meridian_counter[item["隶属"]] += 1
result["体质-穴位归经统计"][const_name] = dict(meridian_counter.most_common())
print(f" {const_name}: {len(scored)} 个通过阈值, TOP20选取")
# ---- 5D. 统计摘要 ----
print(" → 生成统计摘要...")
summary = {}
for const_name in const_names:
summary[const_name] = {
"方剂数": len(result["体质-方剂"].get(const_name, [])),
"药材数": len(result["体质-药材"].get(const_name, [])),
"穴位数": len(result["体质-穴位"].get(const_name, [])),
"药材分类数": len(result["体质-药材分类统计"].get(const_name, {})),
"核心药对数": len(result["体质-核心药对"].get(const_name, [])),
"穴位归经数": len(result["体质-穴位归经统计"].get(const_name, {}))
}
result["统计摘要"] = summary
# ============================================================
# 6. 输出 JSON
# ============================================================
print("[5/6] 写入 JSON...")
with open(OUT_JSON, "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False, indent=2)
print(f" ✓ 已写入: {OUT_JSON}")
# ============================================================
# 7. 输出 MD 报告 (追加方式)
# ============================================================
print("[6/6] 写入 MD 报告...")
def generate_md_section(const_name, top_formulas, top_herbs, herb_class_stats,
core_pairs, top_xuewei, meridian_stats):
"""生成一种体质的MD报告节"""
lines = []
lines.append(f"\n### {const_name}\n")
# --- 方剂 ---
lines.append("#### ① 体质-方剂 (TOP20)\n")
lines.append("| 排名 | 方剂名称 | 匹配得分 | 所含药材 |")
lines.append("|------|----------|----------|----------|")
for i, item in enumerate(top_formulas, 1):
herbs_str = "、".join(item["药材"][:8]) if item["药材"] else "—"
if len(item["药材"]) > 8:
herbs_str += "…"
lines.append(f"| {i} | {item['名称']} | {item['得分']} | {herbs_str} |")
# --- 药材 ---
lines.append("\n#### ② 体质-药材 (TOP30)\n")
lines.append("| 排名 | 药材名称 | 匹配得分 | 药材分类 | 性味归经 |")
lines.append("|------|----------|----------|----------|----------|")
for i, item in enumerate(top_herbs, 1):
lines.append(f"| {i} | {item['名称']} | {item['得分']} | {item['分类']} | {item['性味归经']} |")
# 药材分类统计
lines.append("\n**药材分类统计**\n")
lines.append("| 分类 | 数量 |")
lines.append("|------|------|")
for cls, cnt in herb_class_stats.items():
lines.append(f"| {cls} | {cnt} |")
# --- 核心药对 ---
lines.append("\n**核心药对 (TOP30)**\n")
lines.append("| 排名 | 药对 | 共现次数 |")
lines.append("|------|------|----------|")
for i, pair in enumerate(core_pairs[:15], 1): # 显示前15
lines.append(f"| {i} | {pair[0]} + {pair[1]} | {pair[2]} |")
# --- 穴位 ---
lines.append("\n#### ③ 体质-穴位 (TOP20)\n")
lines.append("| 排名 | 穴位名称 | 匹配得分 | 隶属经脉 | 主治 |")
lines.append("|------|----------|----------|----------|------|")
for i, item in enumerate(top_xuewei, 1):
zhuzhi_short = item['主治'][:40] + "…" if len(item['主治']) > 40 else item['主治']
lines.append(f"| {i} | {item['名称']} | {item['得分']} | {item['隶属']} | {zhuzhi_short} |")
# 穴位归经统计
lines.append("\n**穴位归经统计**\n")
lines.append("| 经脉 | 数量 |")
lines.append("|------|------|")
for mer, cnt in meridian_stats.items():
lines.append(f"| {mer} | {cnt} |")
lines.append("\n---\n")
return "\n".join(lines)
# 构造MD内容
md_sections = []
md_sections.append("# 体质数据挖掘报告\n")
md_sections.append("## 一、疾病-症状关联分析(已有)\n\n*此部分为原有内容,此处省略* \n")
md_sections.append("\n## 二、体质-方剂挖掘\n")
for const_name in const_names:
lines = []
top_formulas = result["体质-方剂"].get(const_name, [])
lines.append(f"\n### {const_name}\n")
lines.append("| 排名 | 方剂名称 | 匹配得分 | 所含药材 |")
lines.append("|------|----------|----------|----------|")
for i, item in enumerate(top_formulas, 1):
herbs_str = "、".join(item["药材"][:8]) if item["药材"] else "—"
if len(item["药材"]) > 8:
herbs_str += "…"
lines.append(f"| {i} | {item['名称']} | {item['得分']} | {herbs_str} |")
md_sections.append("\n".join(lines))
md_sections.append("\n## 三、体质-药材挖掘\n")
for const_name in const_names:
lines = []
top_herbs = result["体质-药材"].get(const_name, [])
herb_class_stats = result["体质-药材分类统计"].get(const_name, {})
core_pairs = result["体质-核心药对"].get(const_name, [])
lines.append(f"\n### {const_name}\n")
lines.append("**TOP30药材**\n")
lines.append("| 排名 | 药材名称 | 匹配得分 | 药材分类 | 性味归经 |")
lines.append("|------|----------|----------|----------|----------|")
for i, item in enumerate(top_herbs, 1):
lines.append(f"| {i} | {item['名称']} | {item['得分']} | {item['分类']} | {item['性味归经']} |")
lines.append("\n**药材分类统计**\n")
lines.append("| 分类 | 数量 |")
lines.append("|------|------|")
for cls, cnt in herb_class_stats.items():
lines.append(f"| {cls} | {cnt} |")
lines.append("\n**核心药对 (TOP15)**\n")
lines.append("| 排名 | 药对 | 共现次数 |")
lines.append("|------|------|----------|")
for i, pair in enumerate(core_pairs[:15], 1):
lines.append(f"| {i} | {pair[0]} + {pair[1]} | {pair[2]} |")
md_sections.append("\n".join(lines))
md_sections.append("\n## 四、体质-穴位挖掘\n")
for const_name in const_names:
lines = []
top_xuewei = result["体质-穴位"].get(const_name, [])
meridian_stats = result["体质-穴位归经统计"].get(const_name, {})
lines.append(f"\n### {const_name}\n")
lines.append("**TOP20穴位**\n")
lines.append("| 排名 | 穴位名称 | 匹配得分 | 隶属经脉 | 主治 |")
lines.append("|------|----------|----------|----------|------|")
for i, item in enumerate(top_xuewei, 1):
zhuzhi_short = item['主治'][:40] + "…" if len(item['主治']) > 40 else item['主治']
lines.append(f"| {i} | {item['名称']} | {item['得分']} | {item['隶属']} | {zhuzhi_short} |")
lines.append("\n**穴位归经统计**\n")
lines.append("| 经脉 | 数量 |")
lines.append("|------|------|")
for mer, cnt in meridian_stats.items():
lines.append(f"| {mer} | {cnt} |")
md_sections.append("\n".join(lines))
# 写入MD
with open(OUT_MD, "w", encoding="utf-8") as f:
f.write("\n".join(md_sections))
print(f" ✓ 已写入: {OUT_MD}")
# ============================================================
# 8. 验证
# ============================================================
print("\n" + "="*60)
print("验证结果")
print("="*60)
# 验证JSON
with open(OUT_JSON, "r", encoding="utf-8") as f:
v = json.load(f)
print(f"\n✅ JSON 验证通过")
print(f" - 体质-方剂: {len(v['体质-方剂'])} 种体质")
print(f" - 体质-药材: {len(v['体质-药材'])} 种体质")
print(f" - 体质-穴位: {len(v['体质-穴位'])} 种体质")
print(f" - 体质-药材分类统计: {len(v['体质-药材分类统计'])} 种体质")
print(f" - 体质-核心药对: {len(v['体质-核心药对'])} 种体质")
print(f" - 体质-穴位归经统计: {len(v['体质-穴位归经统计'])} 种体质")
# 检查每种体质都有数据
for cn in const_names:
f_count = len(v["体质-方剂"].get(cn, []))
h_count = len(v["体质-药材"].get(cn, []))
x_count = len(v["体质-穴位"].get(cn, []))
print(f" {cn}: 方剂={f_count}, 药材={h_count}, 穴位={x_count}")
# 验证MD
md_size = os.path.getsize(OUT_MD)
print(f"\n✅ MD 验证通过 ({md_size} bytes)")
print(f"\n输出文件:")
print(f" 1. {OUT_JSON}")
print(f" 2. {OUT_MD}")
print("\n完成!")