Files

561 lines
18 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
中华药膳全书资料库 - 深度挖掘分析脚本
生成4个输出:
1. 药膳功效聚类.json
2. 食材药材共现网络.json
3. 药膳-体质关联.json
4. 药膳深度挖掘报告.md
"""
import json
import re
from collections import defaultdict, Counter
BASE = "/home/songyi/Documents/ai_agent_scraper_study/data/中华药膳全书学做药膳不生病"
# ─── 加载数据 ───────────────────────────────────────
def load_json(path):
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
调理药膳 = load_json(f"{BASE}/01_来源数据/调理药膳.json")
配方库 = load_json(f"{BASE}/01_来源数据/药膳配方库_recipes.json")
中药表 = load_json(f"{BASE}/01_来源数据/药膳常用中药功能表.json")
食物表 = load_json(f"{BASE}/01_来源数据/药膳常用食物功能表.json")
print(f"调理药膳: {len(调理药膳)}条")
print(f"配方库: {len(配方库)}条")
print(f"中药表: {len(中药表)}条")
print(f"食物表: {len(食物表)}条")
# ─── 提取名称列表(用于最长前缀匹配) ─────────────
中药名列表 = sorted([h['名称'] for h in 中药表 if h.get('名称')], key=len, reverse=True)
食物名列表 = sorted([f['名称'] for f in 食物表 if f.get('名称')], key=len, reverse=True)
# 统一别名处理:一些常见别名/变体
别名映射 = {
'枸杞': '枸杞子',
'杞子': '枸杞子',
'意薏苡仁': '薏苡仁',
'薏苡仁': '薏苡仁',
'薏仁': '薏苡仁',
'白萝ト': '白萝卜',
'胡萝ト': '胡萝卜',
'芝庥': '芝麻',
'川穹': '川芎',
'有郁李仁': '郁李仁',
'土茯': '土茯苓',
'枣': '大枣',
'红枣': '大枣',
'元肉': '龙眼肉',
'桂圆': '龙眼肉',
'田七': '三七',
'淮山': '山药',
'淮山药': '山药',
'云苓': '茯苓',
'川仲': '杜仲',
}
# 扩充别名
中药别名扩充 = {}
for h in 中药表:
name = h.get('名称','')
if name:
中药别名扩充[name] = name
aliases = h.get('别名','')
if aliases:
for a in re.split(r'[、,,;;。.\s]', aliases):
a = a.strip()
if a and len(a) >= 2:
中药别名扩充[a] = name
食物别名扩充 = {}
for f in 食物表:
name = f.get('名称','')
if name:
食物别名扩充[name] = name
aliases = f.get('别名','')
if aliases:
for a in re.split(r'[、,,;;。.\s]', aliases):
a = a.strip()
if a and len(a) >= 2:
食物别名扩充[a] = name
# 合并别名
中药别名扩充.update(别名映射)
食物别名扩充.update(别名映射)
# 构建最长前缀匹配函数
def 最长前缀匹配(text, name_list, alias_map):
"""在text中查找name_list中的名称(最长前缀匹配)"""
found = set()
if not text:
return found
for name in name_list:
if name in text:
found.add(name)
continue
# 尝试别名
for alias, standard in alias_map.items():
if standard == name and alias in text:
found.add(name)
break
return found
def 匹配中药(text):
return 最长前缀匹配(text or '', 中药名列表, 中药别名扩充)
def 匹配食物(text):
return 最长前缀匹配(text or '', 食物名列表, 食物别名扩充)
# ═══════════════════════════════════════════════════════
# 输出1:药膳功效聚类分析
# ═══════════════════════════════════════════════════════
print("\n=== 输出1:药膳功效聚类分析 ===")
# 功效关键词体系(多级)
功效关键词 = [
'补气', '益气', '补中益气',
'养血', '补血', '活血', '化瘀',
'滋阴', '养阴', '生津',
'健脾', '补脾', '温脾',
'补肾', '益肾', '温肾', '壮阳',
'养肝', '疏肝', '清肝', '平肝',
'润肺', '清肺', '补肺', '止咳',
'安神', '养心', '宁心',
'清热', '解毒', '降火', '泻火',
'祛湿', '利湿', '化湿', '渗湿',
'化痰', '祛痰',
'消食', '开胃', '导滞',
'通便', '润肠',
'祛风', '散寒', '温中',
'明目', '乌发',
'利水', '消肿',
'降压', '降脂', '降糖',
'强筋骨', '壮骨',
'抗衰老', '延年',
'固表', '固涩',
'理气', '行气',
'排毒', '养颜',
]
def 提取功效(text):
"""从文本中提取功效关键词"""
found = set()
if not text:
return found
for kw in 功效关键词:
if kw in text:
found.add(kw)
return found
# 合并所有药膳数据
所有药膳 = []
for item in 调理药膳:
所有药膳.append({
'name': item.get('名称',''),
'source': '调理药膳',
'symptom': item.get('症状',''),
'material': item.get('材料准备',''),
'process': item.get('制作过程',''),
'effect': item.get('功能效用',''),
})
for item in 配方库:
所有药膳.append({
'name': item.get('name',''),
'source': '配方库',
'category': item.get('category',''),
'material': item.get('材料准备',''),
'process': item.get('制作过程',''),
'effect': item.get('功能效用',''),
})
# 统计功效频率
功效频率 = Counter()
功效药膳集 = defaultdict(list) # 功效 -> [(name, source, effect)]
for item in 所有药膳:
effects = 提取功效(item['effect'])
effects |= 提取功效(item['material']) # 材料也可能有功效提示
for kw in effects:
功效频率[kw] += 1
功效药膳集[kw].append({
'name': item['name'],
'source': item['source'],
'effect': item['effect'][:100] + '...' if len(item.get('effect','')) > 100 else item.get('effect','')
})
print(f"提取到 {len(功效频率)} 种功效关键词")
for kw, cnt in 功效频率.most_common(20):
print(f" {kw}: {cnt}")
# 共现分析:哪些功效常同时出现
功效共现 = defaultdict(lambda: defaultdict(int))
功效共现矩阵 = []
for item in 所有药膳:
effects = list(提取功效(item['effect']) | 提取功效(item['material']))
for i in range(len(effects)):
for j in range(i+1, len(effects)):
a, b = sorted([effects[i], effects[j]])
功效共现[a][b] += 1
# 构建共现列表
共现列表 = []
for a in 功效共现:
for b in 功效共现[a]:
cnt = 功效共现[a][b]
if cnt >= 2: # 过滤低频
共现列表.append([a, b, cnt])
共现列表.sort(key=lambda x: x[2], reverse=True)
print(f"功效共现对(>=2次): {len(共现列表)}")
# 每种功效的TOP10药膳
功效TOP10 = {}
for kw in 功效关键词:
items = 功效药膳集.get(kw, [])
# 去重
seen = set()
unique = []
for it in items:
key = it['name'] + it['source']
if key not in seen:
seen.add(key)
unique.append(it)
unique = unique[:10]
if unique:
功效TOP10[kw] = unique
# 组装输出1
输出1 = {
"功效频率统计": {kw: cnt for kw, cnt in 功效频率.most_common()},
"功效共现分析": 共现列表,
"功效关联药膳TOP10": {kw: items for kw, items in 功效TOP10.items()},
"统计": {
"总功效种类数": len(功效频率),
"总药膳数": len(所有药膳),
"高频率功效(>=20)": [{"功效": kw, "频次": cnt} for kw, cnt in 功效频率.most_common() if cnt >= 20]
}
}
with open(f"{BASE}/02_加工数据/药膳功效聚类.json", 'w', encoding='utf-8') as f:
json.dump(输出1, f, ensure_ascii=False, indent=2)
print("✓ 药膳功效聚类.json 写入完成")
# ═══════════════════════════════════════════════════════
# 输出2:食材-药材共现网络
# ═══════════════════════════════════════════════════════
print("\n=== 输出2:食材-药材共现网络 ===")
# 对所有药膳的材料准备字段做匹配
单品频率 = Counter()
共现对 = defaultdict(int)
for item in 所有药膳:
material = item.get('material', '')
herbs = 匹配中药(material)
foods = 匹配食物(material)
# 单品频率
for h in herbs:
单品频率[h] += 1
for f in foods:
单品频率[f] += 1
# 共现对:药材-药材、药材-食物
all_ingredients = list(herbs) + list(foods)
for i in range(len(all_ingredients)):
for j in range(i+1, len(all_ingredients)):
a, b = sorted([all_ingredients[i], all_ingredients[j]])
共现对[(a, b)] += 1
# 转换为输出格式
共现对列表 = [[a, b, cnt] for (a, b), cnt in 共现对.items()]
共现对列表.sort(key=lambda x: x[2], reverse=True)
# 过滤低频
共现对列表_high = [p for p in 共现对列表 if p[2] >= 3]
输出2 = {
"共现对": 共现对列表_high,
"单品频率": {k: v for k, v in 单品频率.most_common()},
"统计": {
"总共现对数": len(共现对),
"高频共现对(>=3次)": len(共现对列表_high),
"总单品数": len(单品频率),
"高频单品(>=10次)": [{"名称": k, "频次": v} for k, v in 单品频率.most_common() if v >= 10]
}
}
with open(f"{BASE}/02_加工数据/食材药材共现网络.json", 'w', encoding='utf-8') as f:
json.dump(输出2, f, ensure_ascii=False, indent=2)
print("✓ 食材药材共现网络.json 写入完成")
# ═══════════════════════════════════════════════════════
# 输出3:药膳-体质关联增强
# ═══════════════════════════════════════════════════════
print("\n=== 输出3:药膳-体质关联增强 ===")
九种体质关键字 = {
"气虚质": ['补气','益气','黄芪','党参','白术','山药','大枣','气虚','乏力'],
"阳虚质": ['温阳','散寒','生姜','肉桂','羊肉','韭菜','干姜','阳虚','畏寒'],
"阴虚质": ['滋阴','润燥','百合','麦冬','沙参','玉竹','枸杞','阴虚','口干'],
"痰湿质": ['化痰','祛湿','陈皮','茯苓','薏米','赤小豆','荷叶','痰湿','肥胖'],
"湿热质": ['清热','利湿','绿豆','冬瓜','苦瓜','薏苡仁','湿热','口苦'],
"血瘀质": ['活血','化瘀','山楂','红花','当归','川芎','玫瑰','血瘀','刺痛'],
"气郁质": ['疏肝','理气','柴胡','香附','薄荷','玫瑰花','气郁','胸闷'],
"特禀质": ['固表','祛风','黄芪','白术','防风','过敏','鼻塞','哮喘'],
"平和质": ['平和','平衡','均衡','保健','养生','五谷杂粮'],
}
# 一些同义词/别名映射用于体质匹配
体质别名 = {
'薏米': '薏苡仁',
'杞子': '枸杞子',
'红枣': '大枣',
'桂圆': '龙眼肉',
}
def 计算体质得分(item, keywords):
"""计算一个药膳与某种体质的匹配得分"""
score = 0
reasons = []
fields = {
'name': item.get('name',''),
'material': item.get('material',''),
'effect': item.get('effect',''),
'symptom': item.get('symptom',''),
}
text_all = ' '.join(fields.values())
for kw in keywords:
# 直接匹配
if kw in text_all:
score += 2
reasons.append(kw)
continue
# 尝试别名
alias = 体质别名.get(kw)
if alias and alias in text_all:
score += 2
reasons.append(kw)
continue
# 模糊匹配(部分匹配,针对复合词)
# 如果关键字是2个字,放宽匹配
if len(kw) == 2:
if kw in text_all.replace(' ','').replace(',','').replace('、',''):
score += 1
reasons.append(kw)
return score, reasons
体质结果 = {}
for 体质, keywords in 九种体质关键字.items():
药膳得分列表 = []
for item in 所有药膳:
score, reasons = 计算体质得分(item, keywords)
if score >= 2 and len(reasons) >= 1:
药膳得分列表.append({
'药膳名': item['name'],
'来源': item['source'],
'得分': score,
'匹配理由': reasons[:6] # 最多6个
})
# 按得分降序,取TOP50
药膳得分列表.sort(key=lambda x: x['得分'], reverse=True)
体质结果[体质] = 药膳得分列表[:50]
print(f" {体质}: {len(药膳得分列表)}条匹配")
输出3 = 体质结果
with open(f"{BASE}/02_加工数据/药膳-体质关联.json", 'w', encoding='utf-8') as f:
json.dump(输出3, f, ensure_ascii=False, indent=2)
print("✓ 药膳-体质关联.json 写入完成")
# ═══════════════════════════════════════════════════════
# 输出4:深度挖掘报告(Markdown)
# ═══════════════════════════════════════════════════════
print("\n=== 输出4:深度挖掘报告 ===")
# 分类类别统计(从配方库)
分类统计 = Counter()
for item in 配方库:
cat = item.get('category', '未分类')
分类统计[cat] += 1
# 体质TOP10
体质TOP10输出 = {}
for 体质 in 九种体质关键字:
items = 输出3.get(体质, [])[:10]
体质TOP10输出[体质] = items
# 功效TOP20
功效TOP20 = 功效频率.most_common(20)
# 食材-药材高频共现TOP30
共现TOP30 = 共现对列表_high[:30]
md = f"""# 中华药膳全书 - 深度挖掘分析报告
> 生成日期: 自动分析
> 数据来源: 中华药膳全书学做药膳不生病资料库
---
## 一、数据概览
| 数据源 | 记录数 |
|--------|--------|
| 调理药膳 | {len(调理药膳)} 条 |
| 药膳配方库 | {len(配方库)} 条 |
| 中药功能表 | {len(中药表)} 种 |
| 食物功能表 | {len(食物表)} 种 |
| 合并药膳总数 | {len(所有药膳)} 条 |
---
## 二、药膳分类类别统计
| 分类 | 数量 |
|------|------|
"""
for cat, cnt in 分类统计.most_common():
md += f"| {cat} | {cnt} |\n"
md += f"""
---
## 三、药膳功效聚类 TOP20
| 排名 | 功效关键词 | 出现频次 |
|------|-----------|---------|
"""
for i, (kw, cnt) in enumerate(功效TOP20, 1):
md += f"| {i} | {kw} | {cnt} |\n"
md += f"""
### 功效共现分析(高频共现对 TOP20)
功效共现分析揭示哪些功效常在同一道药膳中同时出现:
| 排名 | 功效A | 功效B | 共现次数 |
|------|-------|-------|---------|
"""
for i, (a, b, cnt) in enumerate(共现列表[:20], 1):
md += f"| {i} | {a} | {b} | {cnt} |\n"
md += """
---
## 四、食材-药材高频共现 TOP30
| 排名 | 食材/药材1 | 食材/药材2 | 共现次数 |
|------|-----------|-----------|---------|
"""
for i, (a, b, cnt) in enumerate(共现TOP30, 1):
md += f"| {i} | {a} | {b} | {cnt} |\n"
md += """
### 高频单品 TOP20
| 排名 | 食材/药材 | 使用频次 |
|------|-----------|---------|
"""
for i, (k, v) in enumerate(单品频率.most_common(20), 1):
md += f"| {i} | {k} | {v} |\n"
md += """
---
## 五、各类体质推荐药膳 TOP10
"""
for 体质, items in 体质TOP10输出.items():
md += f"""### {体质}
| 序号 | 药膳名称 | 来源 | 匹配得分 | 匹配理由 |
|------|----------|------|---------|---------|
"""
for i, it in enumerate(items, 1):
reasons = '、'.join(it['匹配理由'][:4])
md += f"| {i} | {it['药膳名']} | {it['来源']} | {it['得分']} | {reasons} |\n"
md += "\n"
md += """
---
## 六、分析结论
### 6.1 功效分布特征
"""
# 找最高频的3个功效
top3_eff = 功效频率.most_common(3)
if len(top3_eff) >= 1:
md += f"- 最高频功效关键词为「{top3_eff[0][0]}」({top3_eff[0][1]}次),其次为「{top3_eff[1][0]}」({top3_eff[1][1]}次)、「{top3_eff[2][0]}」({top3_eff[2][1]}次)\n"
md += f"- 共提取到 {len(功效频率)} 种不同功效关键词,{len(共现列表)} 组有效共现对\n"
# 找最高频共现
if 共现列表:
top_co = 共现列表[0]
md += f"- 功效共现组合中最引人注目的是「{top_co[0]}+{top_co[1]}」({top_co[2]}次),反映了药膳配伍中的经典搭配\n"
md += """
### 6.2 食材-药材配伍规律
"""
if 共现对列表_high:
top_pair = 共现对列表_high[0]
md += f"- 最高频食材-药材搭配为「{top_pair[0]}+{top_pair[1]}」({top_pair[2]}次)\n"
if len(共现对列表_high) >= 2:
top_pair2 = 共现对列表_high[1]
md += f"- 其次为「{top_pair2[0]}+{top_pair2[1]}」({top_pair2[2]}次)\n"
if len(单品频率) >= 1:
top_single = 单品频率.most_common(1)[0]
md += f"- 使用频率最高的单品是「{top_single[0]}」({top_single[1]}次)\n"
md += f"- 共发现 {len(共现对)} 组食材-药材共现关系,{len(单品频率)} 种单品\n"
md += """
### 6.3 体质匹配覆盖
"""
for 体质 in 九种体质关键字:
cnt = len(体质结果.get(体质, []))
md += f"- {体质}:匹配到 {cnt} 道药膳\n"
md += """
---
*报告完毕*
"""
with open(f"{BASE}/04_分析报告/药膳深度挖掘报告.md", 'w', encoding='utf-8') as f:
f.write(md)
print("✓ 药膳深度挖掘报告.md 写入完成")
print("\n🎉 所有分析输出已完成!")