#!/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🎉 所有分析输出已完成!")