312 lines
12 KiB
Python
312 lines
12 KiB
Python
#!/usr/bin/env python3
|
||
"""药膳食疗 × 中药材 深度关联分析"""
|
||
import json, os, glob, re
|
||
from collections import Counter, defaultdict
|
||
|
||
TOP_DIR = "/home/songyi/Documents/ai_agent_scraper_study/data/大医网/"
|
||
DY_DIR = os.path.join(TOP_DIR, "药膳食疗/")
|
||
ZY_DIR = os.path.join(TOP_DIR, "中药材/")
|
||
|
||
# ===================== 加载数据 =====================
|
||
print("加载药膳食疗库...")
|
||
all_diets = []
|
||
for f in glob.glob(os.path.join(DY_DIR, "*.json")):
|
||
with open(f, encoding='utf-8') as fh:
|
||
all_diets.append(json.load(fh))
|
||
|
||
print("加载中药材库...")
|
||
all_herbs = {}
|
||
herb_aliases = defaultdict(set) # 别名→标准名
|
||
for f in glob.glob(os.path.join(ZY_DIR, "*.json")):
|
||
with open(f, encoding='utf-8') as fh:
|
||
h = json.load(fh)
|
||
name = h.get('名称', '')
|
||
all_herbs[name] = h
|
||
for alias in str(h.get('别名', '')).split('、'):
|
||
a = alias.strip()
|
||
if a:
|
||
herb_aliases[a].add(name)
|
||
|
||
print(f"药膳: {len(all_diets)} 条 | 药材: {len(all_herbs)} 条 | 别名映射: {len(herb_aliases)} 组\n")
|
||
|
||
# ===================== 药材标准化匹配 =====================
|
||
# 合并药材名+别名集合,按长度排序(最长优先)
|
||
all_herb_names = sorted(set(all_herbs.keys()), key=lambda x: -len(x))
|
||
# 别名去标准名重复
|
||
pure_aliases = []
|
||
for alias, targets in herb_aliases.items():
|
||
if alias not in all_herbs:
|
||
pure_aliases.append((alias, targets))
|
||
# 按长度降序
|
||
pure_aliases.sort(key=lambda x: -len(x[0]))
|
||
|
||
def match_herbs(text):
|
||
"""从文本中匹配药材(标准名优先,别名次之)"""
|
||
found = set()
|
||
for name in all_herb_names:
|
||
if len(name) >= 2 and name in text:
|
||
found.add(name)
|
||
for alias, targets in pure_aliases:
|
||
if len(alias) >= 2 and alias in text:
|
||
found.update(targets)
|
||
return found
|
||
|
||
# ===================== Step 1: 药材频次与覆盖分析 =====================
|
||
print("=" * 60)
|
||
print("Step 1: 药材使用频次与覆盖度分析")
|
||
print("=" * 60)
|
||
|
||
herb_in_diet_count = Counter()
|
||
herb_diet_pairs = defaultdict(list) # 药材→使用该药材的药膳列表
|
||
|
||
for diet in all_diets:
|
||
pei_fang = diet.get('配方', '')
|
||
matched = match_herbs(pei_fang)
|
||
for h in matched:
|
||
herb_in_diet_count[h] += 1
|
||
herb_diet_pairs[h].append(diet['名称'])
|
||
|
||
used_herb_count = len(herb_in_diet_count)
|
||
coverage = used_herb_count / len(all_herbs) * 100
|
||
|
||
print(f"\n药材库总数: {len(all_herbs)}")
|
||
print(f"在药膳中出现: {used_herb_count} ({coverage:.1f}%)")
|
||
print(f"未出现在药膳中: {len(all_herbs) - used_herb_count}")
|
||
|
||
print(f"\n>>> TOP 30 高频药材 <<<")
|
||
for herb, cnt in herb_in_diet_count.most_common(30):
|
||
# 获取药材功效(字段名是'功能')
|
||
h_data = all_herbs.get(herb, {})
|
||
gongxiao = ""
|
||
if isinstance(h_data.get('功效作用'), dict):
|
||
gongxiao = h_data['功效作用'].get('功能', '')
|
||
else:
|
||
gongxiao = str(h_data.get('功效作用', ''))
|
||
gongxiao_short = (gongxiao[:30] + '..') if len(gongxiao) > 30 else gongxiao
|
||
print(f" {herb:6s}: {cnt:3d}次 | 药材功效: {gongxiao_short}")
|
||
|
||
# ===================== Step 2: 双向映射 =====================
|
||
print(f"\n{'=' * 60}")
|
||
print("Step 2: 药膳-药材双向映射")
|
||
print("=" * 60)
|
||
|
||
# 药膳→药材映射
|
||
diet_to_herbs = {}
|
||
for diet in all_diets:
|
||
pei_fang = diet.get('配方', '')
|
||
matched = match_herbs(pei_fang)
|
||
diet_to_herbs[diet['名称']] = matched
|
||
|
||
# 药材→药膳 已在上一步构建
|
||
|
||
# 每味药材关联的药膳数量分布
|
||
pair_dist = Counter()
|
||
for herb, diets in herb_diet_pairs.items():
|
||
pair_dist[len(diets)] += 1
|
||
|
||
print(f"\n药材→药膳关联数分布:")
|
||
for k in sorted(pair_dist.keys())[:10]:
|
||
print(f" 关联{k}个药膳: {pair_dist[k]}味药材")
|
||
if len(pair_dist) > 10:
|
||
tail_sum = sum(v for k, v in pair_dist.items() if k > 10)
|
||
print(f" 关��>10个药膳: {tail_sum}味药材")
|
||
|
||
# 药膳→药材关联数分布
|
||
diet_herb_dist = Counter()
|
||
for diet_name, herbs in diet_to_herbs.items():
|
||
diet_herb_dist[len(herbs)] += 1
|
||
|
||
print(f"\n药膳→药材关联数分布:")
|
||
for k in sorted(diet_herb_dist.keys())[:8]:
|
||
print(f" 含{k}味药材: {diet_herb_dist[k]}个药膳")
|
||
if len(diet_herb_dist) > 8:
|
||
tail_sum = sum(v for k, v in diet_herb_dist.items() if k > 8)
|
||
print(f" 含>8味药材: {tail_sum}个药膳")
|
||
|
||
# 核心枢纽药材(同时出现在药膳和药材库,且频次最高的)
|
||
hub_herbs = []
|
||
for herb, cnt in herb_in_diet_count.most_common(10):
|
||
h_data = all_herbs[herb]
|
||
gx = str(h_data.get('功效作用', ''))
|
||
hub_herbs.append((herb, cnt, gx))
|
||
|
||
print(f"\n>>> 10大枢纽药材 <<<")
|
||
for herb, cnt, gx in hub_herbs:
|
||
print(f" {herb}: 出现{cnt}次, 药材功效: {str(gx)[:50]}")
|
||
|
||
# ===================== Step 3: 功效交叉验证 =====================
|
||
print(f"\n{'=' * 60}")
|
||
print("Step 3: 功效交叉验证分析")
|
||
print("=" * 60)
|
||
|
||
# 提取药膳功效标签
|
||
diet_gx_labels = []
|
||
for diet in all_diets:
|
||
gx = diet.get('功效', '')
|
||
diet_gx_labels.append((diet['名称'], gx))
|
||
|
||
# 提取药材功效标签
|
||
herb_gx_labels = {}
|
||
for name, hdata in all_herbs.items():
|
||
gx = str(hdata.get('功效作用', ''))
|
||
herb_gx_labels[name] = gx
|
||
|
||
# 药膳功效大类分布
|
||
gx_categories = Counter()
|
||
GX_CAT_MAP = {
|
||
'补气': '补益', '补血': '补益', '补阴': '补益', '滋阴': '补益', '补肾': '补益',
|
||
'补脾': '补益', '健脾': '补益', '补肝': '补益', '益精': '补益', '壮阳': '补益',
|
||
'温阳': '补益', '扶正': '补益', '养心': '补益', '养阴': '补益', '益气': '补益',
|
||
'清热': '清热', '解毒': '清热', '泻火': '清热', '凉血': '清热', '滋阴': '滋阴',
|
||
'活血': '活血', '化瘀': '活血', '祛瘀': '活血', '行气': '理气', '理气': '理气',
|
||
'止痛': '止痛', '安神': '安神', '止咳': '止咳化痰', '化痰': '止咳化痰', '润肺': '止咳化痰',
|
||
'祛风': '祛风除湿', '除湿': '祛风除湿', '散寒': '散寒解表', '解表': '散寒解表',
|
||
'利水': '利水渗湿', '通便': '通淋', '明目': '明目'
|
||
}
|
||
|
||
for diet_name, gx_text in diet_gx_labels:
|
||
matched_cats = set()
|
||
for kw, cat in GX_CAT_MAP.items():
|
||
if kw in gx_text:
|
||
matched_cats.add(cat)
|
||
if not matched_cats:
|
||
matched_cats.add('其他')
|
||
gx_categories.update(matched_cats)
|
||
|
||
print(f"\n药膳功效大类分布:")
|
||
for cat, cnt in gx_categories.most_common():
|
||
print(f" {cat}: {cnt}条 ({cnt/len(all_diets)*100:.1f}%)")
|
||
|
||
# 交叉验证:药膳功效 vs 组成药材功效一致性
|
||
# 检查药膳功效描述中的关键词是否在药材功效中也有体现
|
||
consistency_count = 0
|
||
total_checked = 0
|
||
|
||
for diet in all_diets:
|
||
diet_name = diet['名称']
|
||
diet_gx = diet.get('功效', '')
|
||
herbs_used = diet_to_herbs.get(diet_name, set())
|
||
if not herbs_used:
|
||
continue
|
||
|
||
# 提取药膳功效中的 2-3 字词
|
||
gx_words = re.findall(r'[\.。,.、,]*(.{2,3})[\.。,.、,]?', diet_gx)
|
||
if not gx_words:
|
||
gx_words = [diet_gx[:4]] # fallback
|
||
|
||
# 检查药材功效是否包含相同关键词
|
||
consistent_words = 0
|
||
for word in gx_words:
|
||
if len(word) < 2:
|
||
continue
|
||
for h_name in herbs_used:
|
||
h_gx = herb_gx_labels.get(h_name, '')
|
||
if word in h_gx:
|
||
consistent_words += 1
|
||
break # 只要有一个药材匹配就算
|
||
|
||
total_checked += 1
|
||
if consistent_words > 0:
|
||
consistency_count += 1
|
||
|
||
consistency_pct = consistency_count / total_checked * 100 if total_checked > 0 else 0
|
||
print(f"\n功效一致性验证:")
|
||
print(f" 参与验证药膳: {total_checked}")
|
||
print(f" 功效可追溯至组成药材: {consistency_count} ({consistency_pct:.1f}%)")
|
||
|
||
# ===================== Step 4: 特殊分析 — 药食同源药材统计 =====================
|
||
print(f"\n{'='*60}")
|
||
print("Step 4: 药食同源药材深度分析")
|
||
print("="*60)
|
||
|
||
# 常见药食同源药材名单(国家卫健委公布的)
|
||
med_food_herbs = {
|
||
'山药', '枸杞子', '枸杞', '茯苓', '甘草', '生姜', '大枣', '红枣',
|
||
'薏苡仁', '百合', '桂圆肉', '山楂', '陈皮', '菊花', '蜂蜜', '花椒',
|
||
'丁香', '八角茴香', '小茴香', '胡椒', '砂仁', '肉桂', '八角',
|
||
'杏仁', '核桃仁', '桃仁', '莲子', '芡实', '白果', '桑葚', '黑芝麻',
|
||
'黑芝麻', '黑芝麻', '黑豆', '黄豆', '黑芝麻', '黑芝麻', '黑芝麻',
|
||
'地黄', '玉竹', '黄精', '决明子', '罗汉果', '桑叶', '洛神花',
|
||
'蜂蜜', '党参', '黄芪', '红枣', '当归', '何首乌', '杜仲', '骨碎补',
|
||
'白果', '核桃', '杏仁', '桃仁', '花椒', '胡椒', '香薷', '高良姜',
|
||
'砂仁', '肉桂', '草果', '白芷', '白豆蔻', '肉豆蔻', '草豆蔻',
|
||
'紫苏', '薄荷', '胖大海', '鱼腥草', '姜黄', '荜澄茄', '青果', '橘皮',
|
||
'罗汉果', '乌梅', '陈皮', '大枣', '甘草', '丁香', '八角茴香', '山奈',
|
||
'阴香', '草果', '肉蔻', '草豆蔻', '砂仁', '白豆蔻', '肉豆蔻', '高良姜',
|
||
'香薷', '花椒', '小茴香', '八角', '桂花'
|
||
}
|
||
|
||
# 统计药食同源药材在药膳中的使用
|
||
yf_usage = Counter()
|
||
for diet in all_diets:
|
||
pei_fang = diet.get('配方', '')
|
||
for hname in med_food_herbs:
|
||
if hname in pei_fang and hname in herb_in_diet_count:
|
||
yf_usage[hname] += 1
|
||
|
||
print(f"\n药食同源药材使用 TOP 15:")
|
||
for herb, cnt in yf_usage.most_common(15):
|
||
print(f" {herb}: {cnt}次")
|
||
|
||
non_yf = herb_in_diet_count - yf_usage
|
||
print(f"\n非药食同源药材使用 TOP 10:")
|
||
for herb, cnt in non_yf.most_common(10):
|
||
print(f" {herb}: {cnt}次")
|
||
|
||
# ===================== Step 5: 药膳配伍规律分析 =====================
|
||
print(f"\n{'='*60}")
|
||
print("Step 5: 药膳配伍规律分析")
|
||
print("="*60)
|
||
|
||
# 药材对共现分析
|
||
herb_pair_freq = Counter()
|
||
for diet in all_diets:
|
||
pei_fang = diet.get('配方', '')
|
||
herbs = match_herbs(pei_fang)
|
||
herb_list = sorted(list(herbs))
|
||
for i in range(len(herb_list)):
|
||
for j in range(i+1, len(herb_list)):
|
||
pair = (herb_list[i], herb_list[j])
|
||
herb_pair_freq[pair] += 1
|
||
|
||
print(f"\n药材共现TOP 15:")
|
||
for (h1, h2), cnt in herb_pair_freq.most_common(15):
|
||
print(f" {h1}+{h2}: {cnt}次")
|
||
|
||
# ===================== 保存索引 =====================
|
||
# 双向映射索引
|
||
bidirectional = {
|
||
"药膳→药材": {name: sorted(list(herbs)) for name, herbs in diet_to_herbs.items()},
|
||
"药材→药膳": {herb: sorted(diets) for herb, diets in herb_diet_pairs.items()}
|
||
}
|
||
|
||
# 只保存TOP部分(全量太大)
|
||
top_herb_diet = {herb: sorted(diets) for herb, diets in herb_diet_pairs.items() if len(diets) >= 5}
|
||
top_diet_herb = {name: sorted(list(herbs)) for name, herbs in diet_to_herbs.items() if len(herbs) >= 2}
|
||
|
||
# 保存
|
||
with open(os.path.join(TOP_DIR, "索引_药膳药材双向映射.json"), "w", encoding='utf-8') as f:
|
||
json.dump({
|
||
"统计": {
|
||
"药材在药膳中使用数": len(herb_in_diet_count),
|
||
"药膳含药材数": sum(1 for h in diet_to_herbs.values() if h),
|
||
"覆盖率": round(coverage, 2)
|
||
},
|
||
"高频药材→药膳": {k: sorted(v) for k, v in sorted(herb_diet_pairs.items(), key=lambda x: -len(x))[:50]},
|
||
"药材共现TOP": herb_pair_freq.most_common(30)
|
||
}, f, ensure_ascii=False, indent=2)
|
||
|
||
# 功效交叉验证索引
|
||
with open(os.path.join(TOP_DIR, "索引_功效交叉验证.json"), "w", encoding='utf-8') as f:
|
||
json.dump({
|
||
"药膳功效大类": dict(gx_categories.most_common()),
|
||
"功效一致率": round(consistency_pct, 2),
|
||
"药食同源使用TOP": yf_usage.most_common(30),
|
||
"非药食同源使用TOP": [(h, c) for h, c in sorted(non_yf.items(), key=lambda x: -x[1])[:20]]
|
||
}, f, ensure_ascii=False, indent=2)
|
||
|
||
print(f"\n索引文件保存完毕")
|
||
print(f"\n{'='*60}")
|
||
print("分析完成!")
|
||
print("="*60)
|