简介《中医食疗学课程.ppt》是一份面向中医药专业学生、食疗养生爱好者及健康管理从业者的教学课件系统讲解中医食疗学的核心理论与应用方法涵盖药食同源、整体观念、辨证施膳、药膳分类及常见病食疗方案等模块适合课堂讲授与自学入门。压缩包内为单个PPT文件大小675KB内容结构完整从中医食疗的历史源流如《黄帝内经》、唐代《食疗本草》到明清食疗专著延伸至临床辨证施膳并结合心脏、肝脾肺肾等脏腑证型给出莲子龙眼汤、参杞羊头汤、当归猪心汤等具体药膳示例信息密度较高。已有110人学习下载适合希望快速了解中医食疗逻辑并获取课件式知识框架的读者通过这份PPT可掌握食物性味、五色入五脏、因时因地因人施膳等要点以及常见药膳的制作分类与适用人群便于后续进一步研读食疗专著或开展饮食调理实践。1. 拿到一份中医食疗学课件先别急着翻页很多人把《中医食疗学课程.ppt》当展示材料翻一翻、截个图就完事。但如果你正要做中医食疗知识库、食疗推荐小程序、课程资源网站或者只是想把课件里的食材功效表整理成能查询的结构化数据这个 PPT 就是一个待挖掘的知识源。问题在于PPT 里的信息是给人看的不是给程序用的——文字散落在文本框、表格、图片里章节关系藏在版式和跳转里。这篇文章要做的就是把这套课件从能翻页变成能检索、能组装、能二次开发。全程用本地脚本和开源库搞定不需要额外平台适合做医学教育信息化的工程师、知识库构建者和课程数字化项目成员照着做。2. 解析层用 python-pptx 读取中医食疗课件的三类内容载体2.1 为什么先处理 PPT 而不是直接去找 PDF 或网页一份中医食疗学课件的常见存在形式是 .ppt 或 .pptx。如果是 .pptxpython-pptx 可以直接解析如果是老式 .ppt先另存为 .pptx 再用脚本处理这是最省事的路径不要试图手工去解 OLE 复合文档。相比 PDF 或扫描图片PPTX 的底层是 XML文本、表格、图片是分离存储的解析准确率高得多。PDF 里经常出现文本块错位、表格线丢失的问题而 PPTX 里的表格单元格是独立节点行列关系不会因为渲染而丢失。所以第一步永远是把文件统一成 .pptx然后用结构化方式提取。课件里的信息载体无非三类文本框标题、正文、要点、表格食材功效对比、食物性味归经表、图片食材照片、古籍书影、扫描的讲义页。三类载体的处理方式完全不同文本直接取字符串表格要重建行列关系图片得走 OCR。搞清楚这个分类后面才不会把表格内容当普通文本拼在一起。2.2 用 python-pptx 提取文本和表格的基础脚本先装依赖python-pptx 是当前处理 .pptx 最主流的开源库pip install python-pptx写一个最小提取脚本把每一页的文本和表格内容输出为带页码的 Markdownfrom pptx import Presentation from pptx.enum.shapes import MSO_SHAPE_TYPE def extract_ppt_text(path): prs Presentation(path) for slide_index, slide in enumerate(prs.slides, start1): print(f\n## 第 {slide_index} 页) for shape in slide.shapes: # 文本框和占位符统一走 text_frame if shape.has_text_frame: for para in shape.text_frame.paragraphs: text .join(run.text for run in para.runs) if text.strip(): print(f- {text}) # 表格单独处理保留行列结构 if shape.has_table: table shape.table for row_index, row in enumerate(table.rows): cells [cell.text.strip() for cell in row.cells] print(f [表] 行{row_index}: { | .join(cells)}) if __name__ __main__: extract_ppt_text(中医食疗学课程.pptx)这段脚本的逻辑分两部分has_text_frame分支负责普通文本框has_table分支负责表格。para.runs这个细节值得注意——PPT 里一段文字可能被拆成多个 run每个 run 有自己的字体格式如果只取para.text在某些场景会丢内容用join(run.text for run in para.runs)更稳妥。表格部分按行输出单元格文本行号和竖线分隔能直观看到表格结构。跑完脚本你会得到一份按页码组织的纯文本。但这份文本离可用还差一步讲义里同一页可能同时有标题、正文、表格、备注它们混在输出里没有区分信息类型。下一步要做的是给内容打标签区分这是章节标题这是食材功效表的表头这是图片里的文字。2.3 python-pptx 的三个参数坑第一个坑是占位符。课件模板里的大标题、副标题、页脚都是占位符shape.has_text_frame对它们同样为 True提取出来的内容会带有模板默认文案。判断方式是检查shape.is_placeholder如果是占位符可以只保留shape.placeholder_format.idx在 1 到 3 之间的标题类占位符其余过滤掉。第二个坑是组合形状。很多食疗课件的版式是左侧食材图 右侧功效说明放在一个组合里slide.shapes默认只遍历顶层形状组合内部的子形状不会被访问到。需要递归遍历def iter_shapes(shapes): for shape in shapes: if shape.shape_type MSO_SHAPE_TYPE.GROUP: yield from iter_shapes(shape.shapes) else: yield shape第三个坑是表格单元格里的空行。python-pptx 读取的单元格文本经常以\n结尾直接打印会多出空白行用cell.text.strip()处理。表格提取出来后还有一个容易被忽略的问题合并单元格。python-pptx 的row.cells返回的是该行所有列位置的对象合并单元格时多个位置指向同一个 cell 对象提取出来的文本会重复出现。解决方法是做一个去重只输出 cell 第一次出现的位置。3. 结构化层把食疗功效表从二维表格还原成键值关系3.1 从单元格坐标重建合并单元格中医食疗课件里最常见的表格结构是第一列是食材名称第二列是性味第三列是归经第四列是功效有时还有推荐食谱和注意事项。这类表在 PPT 里往往是跨页的表头只在第一页出现后面几页只有内容行。直接按页提取得到的是一堆断裂的行数据没法查询。常见做法是手工维护一个表头锚点——把第一行的单元格文本作为表的列名遇到新表时先检查表头如果表头和上一个表头一致就继续往同一个表里追加行from pptx import Presentation def extract_tables_with_header(path): prs Presentation(path) current_table None for slide in prs.slides: for shape in slide.shapes: if not shape.has_table: continue table shape.table header [cell.text.strip() for cell in table.rows[0].cells] # 表头看起来像数据行时跳过这一行 if not any(keyword in h for h in header for keyword in [食材, 食物, 品名, 名称]): header [fcol_{i} for i in range(len(header))] rows [] for row in table.rows[1:]: seen set() row_data [] for cell in row.cells: # 同一个 cell 对象出现多次说明是合并单元格只在第一次记录 if id(cell) not in seen: seen.add(id(cell)) row_data.append(cell.text.strip()) rows.append(row_data) if current_table and header current_table[header]: current_table[rows].extend(rows) else: if current_table: yield current_table current_table {header: header, rows: rows} if current_table: yield current_table for table_info in extract_tables_with_header(中医食疗学课程.pptx): print(table_info[header]) print(f共 {len(table_info[rows])} 行)这段代码的关键点是表头匹配用了列表相等判断要求 PPT 里跨页表格的表头完全一致实际情况中经常出现细微差异比如某一页写成食物名称另一页写成品名。遇到这种情况先人工统一课件里的表头措辞或者做一个别名字典映射比如食物名称 - 食材, 品名 - 食材再做归一化比较。合并单元格的去重是一个重要细节。python-pptx 对水平合并的处理是多个单元格引用同一个_Cell对象用id(cell)判断是否已经处理过能避免同一段文本在输出里出现多次。3.2 扫描版/图片型课件的 OCR 兜底不是所有课件都能从 XML 里直接抽文本。有些老课件是整页截图贴进去的has_text_frame返回 Falsehas_table也返回 False整页就是一张大图。这时只能 OCR。如果图片来源是扫描的古籍书影或打印讲义直接 OCR 的效果很差先做图像预处理tesseract input.png output -l chi_sim --psm 6-l chi_sim是简体中文语言包没有的话先apt install tesseract-ocr-chi-sim或从 tessdata 仓库下。--psm 6表示把整页当成一个统一的文本块处理适合版式规整的讲义页如果是图文混排的页面--psm 3自动分页检测更合适。OCR 精度不足时配合 OpenCV 做二值化和去噪import cv2 img cv2.imread(page.jpg, cv2.IMREAD_GRAYSCALE) _, thresh cv2.threshold(img, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) cv2.imwrite(page_clean.jpg, thresh)OTSU 自适应阈值处理能显著提高扫描件的文字对比度尤其适合纸张泛黄、文字发灰的课件扫描页。处理完再重新跑 tesseract。OCR 出来的文本没有版式信息表格线、列对齐全部丢失返回的是一段顺序排列的文本。要恢复表格结构可以在预处理阶段用 OpenCV 检测水平线和垂直线按线条交点切分单元格再把每个区域的文本对应回去。这个操作成本较高如果整个课件只有两三个扫描页建议直接人工录入把精力花在后面的知识组织上。3.3 重建后的数据校验无论文本提取还是 OCR结果都必须做一次校验。一个低成本的办法是把提取结果和原始 PPT 的页数做交叉比对如果某页提取出来只有标题、没有正文优先怀疑是不是图片型页面。另一个验证是表内数值的完整性比如归经列应该至少包含一个经络名如果某行归经为空可能是合并单元格的去重逻辑误伤了内容。校验脚本里加一条规则把所有列缺失超过一半的行单独输出人工看一遍。格式层面的校验可以更进一步性味列应该符合温/寒/凉/平/热 加 甘/酸/苦/辛/咸 的组合模式归经列应该是十二经络名之一。写个正则做合规检查能把 OCR 识别错的内容自动标记出来——比如肝被识别成月干这一类问题。4. 领域建模面向查询的食材—性味—功效数据模型4.1 数据模型设计把课件里的表格提出来后下一步是把它们组织成可查询的结构。直接存 Markdown 或 Excel 都能用但如果要做的是知识库、问答机器人或者推荐系统数据模型就决定了后续开发的边界。中医食疗学的核心知识结构是食材 → 属性 → 应用三层食材是实体属性包括性味、归经、功效、宜忌应用是食谱、主治、配伍。模型设计成 JSON 对象每个食材一个条目{ food: 山药, aliases: [薯蓣, 淮山, 怀山药], nature: 平, flavor: 甘, meridian: [脾, 肺, 肾], effects: [补脾养胃, 生津益肺, 补肾涩精], suitable_for: [脾虚食少, 久泻不止, 肺虚喘咳], avoid_for: [湿盛中满, 有实邪者], recipes: [ { name: 山药粥, ingredients: [山药, 粳米], method: 山药切片与粳米同煮为粥 } ], source_slide: 12 }为什么按字段建模而不是按第几页讲了什么存全文因为查询意图是哪些食材入脾经性平又能止咳的食材有什么湿热体质不能吃什么这些意图对应的都是字段级过滤不是全文检索。课件原文里一句话可能同时包含多个属性不拆开就没办法精准匹配。4.2 从提取结果映射 JSON 的转换脚本上一节提取出来的表格行字段顺序基本对应模型字段。写一个转换脚本把表格行映射成 JSONimport json, re FIELD_MAP { 食材: food, 食物: food, 品名: food, 名称: food, 性味: nature_flavor, 归经: meridian, 功效: effects, 主治: suitable_for, 禁忌: avoid_for } def parse_meridian(text): meridians [心, 肝, 脾, 肺, 肾, 胃, 胆, 大肠, 小肠, 膀胱, 三焦, 心包] result [] for m in meridians: if m in text: result.append(m 经) return result def parse_nature_flavor(text): natures [温, 热, 寒, 凉, 平] flavors [甘, 酸, 苦, 辛, 咸, 淡, 涩] nature flavor [] for ch in text: if ch in natures and nature : nature ch if ch in flavors: flavor.append(ch) return nature, .join(flavor) def row_to_food_item(header, row): data dict(zip(header, row)) item {food: data.get(food, ), aliases: [], source_slide: 0} nf data.get(性味, ) if nf: item[nature], item[flavor] parse_nature_flavor(nf) item[meridian] parse_meridian(data.get(归经, )) effects data.get(功效, ) item[effects] [e for e in re.split(r[;,\n], effects) if e] return item映射逻辑里最容易出错的是性味字段。课件里性味一列常常写成一个长字符串比如甘平也可能写成味甘性平。parse_nature_flavor逐字扫描把寒热温凉平归到nature把甘酸苦辛咸归到flavor能兼容大多数课程 PPT 的写法。parse_meridian的难点是肝和心这些单字容易误判比如开心会被匹配成心经所以参数里只精确匹配经络字并且把三焦、心包这类双字经络放在前面优先匹配避免心包被拆成心经和包。转换完的数据存成food_items.json后续所有查询、问答、推荐都基于这个文件。4.3 基于 JSON 的检索示例有了结构化数据最简单的查询用 Python 列表推导就能做import json with open(food_items.json, encodingutf-8) as f: food_items json.load(f) def search_by_condition(items, natureNone, meridianNone, effect_keywordNone): results [] for item in items: if nature and item.get(nature) ! nature: continue if meridian and meridian not in item.get(meridian, []): continue if effect_keyword and not any(effect_keyword in e for e in item.get(effects, [])): continue results.append(item[food]) return results print(search_by_condition(food_items, nature平, meridian脾经, effect_keyword补))这个示例展示了数据模型的价值一次遍历完成多条件过滤查询条件任意组合都可以扩展。实际项目中数据量到几千条时这个写法依然够用没必要上数据库。但如果要做更复杂的关联查询比如找出所有和山药配伍出现在食谱里的食材就需要导入 SQLite 或直接遍历 recipes 数组。5. 问答增强给课件接一个中医食疗语义检索5.1 切块策略当知识库不只包含结构化 JSON还要把课件原文也纳入检索范围时会遇到怎么切块的问题。按页切是最直观的但效果不好——一页 PPT 可能讲了 3 个食材用户问山药有什么功效时召回的是整页内容其中只有三分之一相关。常见的做法是按语义单元切块具体到食疗课件就是按食材条目切一个食材连同它的性味、归经、功效、食谱、注意事项作为一个块。每块长度控制在 200 到 500 字既保证上下文完整又避免无关信息干扰。切块工具可以用 LangChain 的RecursiveCharacterTextSplitter也可以自己写分页逻辑。重点是把切好的块和对应的食材名、页码关联起来方便溯源chunks [] for item in food_items: chunk { content: f{item[food]}性{item.get(nature,)}味{item.get(flavor,)} f归{、.join(item.get(meridian, []))}。 f功效{.join(item.get(effects, []))}。, metadata: {food: item[food], slide: item.get(source_slide, 0)} } chunks.append(chunk)5.2 向量检索与关键词召回的组合策略向量检索对语义近似的召回效果好比如问吃啥补脾胃能匹配到含补脾养胃的食材块。但中医食疗术语有大量近似表达养胃和和胃、补气和益气向量模型能抓语义却能不保证术语精准。关键做法是混合召回BM25 做关键词精确匹配向量做语义扩展两者结果取并集后再按得分融合排序。本地跑 BM25 可以用rank_bm25库向量化用text2vec或bge-small-zh这类中文 embedding 模型量化后大约 100MB普通开发机跑得动。融合策略简单点就行BM25 命中的结果给权重 0.4向量命中的给 0.6两者都命中的取加权和。这块不做太复杂因为课件规模通常只有几百个食材块简单线性融合已经超过大多数内部知识库的检索质量。5.3 术语归一化山药、薯蓣与淮山混合检索方案上线后会暴露一个典型问题课件原文用薯蓣用户搜山药BM25 召回不到向量召回也不稳定。中医食疗学里这个情况非常普遍——同一个食材有学名、别名、商品名、炮制品名课件里混合使用。解决方案是维护一个食材别名表在检索前做查询词扩展。分词后拿扩展词同时做 BM25 和向量检索而不是只拿原始词去搜。直接在aliases字段上构建倒排索引即可alias_index {} for item in food_items: for alias in item.get(aliases, []) [item[food]]: alias_index.setdefault(alias, []).append(item[food])用户搜淮山时先映射成山药再走检索流程。这个细节直接决定检索系统在真实语境下好不好用。到这里从一份能翻页的 PPT到一套带结构化数据、语义检索和术语归一化的知识库的链路就通了。如果课件的表格足够规整全程跑完大概半天遇到扫描页多的版本把 OCR 和时间成本算进去一天到一个半工作日。最后留一个值得试的技巧把课件里的食谱步骤整理成语义三元组存进图数据库或 JSON当数据量超过三百条时你能获得比纯文本检索直观得多的食材配伍关系视图。本文还有配套的精品资源点击获取