ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用 python-pptx 将 78 页吹风造型教材变成可检索课件

用 python-pptx 将 78 页吹风造型教材变成可检索课件 简介这是一份面向美容美发从业者与美发培训学员的吹风造型专业培训教材以78页PPT课件形式系统讲解吹风造型的技术要点与审美逻辑适合希望夯实基本功、提升造型完成度的初、中级发型师也可用于门店内部培训与技能考核前的集中复习。压缩包内共1个PPT文件大小约1.99MB结构轻便便于在手机或电脑上随时翻阅。内容围绕吹风造型的十大重点展开涵盖自然生长方向、手法与站位方向、发片直拉方向、出风摆位方向、加热与冷风定型时间、角度提升距离、力度构成等核心维度并延伸至定点吹风与移动吹风的位置调整、高角度细腻纹理与低角度堆积层次的差别以及发根、发中、发尾的分区处理逻辑同时配有梳子收放练习与站姿配合等基础训练提示。目前已有89人学习适合作为日常练习与授课备课的参考素材。1. 78 页吹风造型教材为什么先要当数据源看门店里最常被翻烂的那份材料往往是《美容美发业-吹风造型知识培训教材》这类 PPT风温分几档、集风嘴和散风嘴分别用在哪、圆滚梳从发根带向发尾是 45 度还是 90 度、发旋和鬓角怎么处理、内扣和蓬松分别在什么发质上做。内容本身不复杂麻烦在于它是一份 78 页的 PPT——新人只能从头翻到尾讲师改一版要重新对齐版式店长想知道内扣讲在第几页只能肉眼找区域培训想抽查知识点覆盖度也没有抓手。我一般会把这份教材当成一份待处理的数据源先用 python-pptx 把 78 页里的标题、正文、讲师备注、示意图全抽出来再把吹风造型的知识点拆成带字段的结构化记录最后用模板加数据回写成新版课件顺手让它可检索。这套路径适合维护门店培训体系的 IT 同学也适合想把课件做成知识库的技术负责人。2. 用 python-pptx 拆开 78 页吹风造型培训教材2.1 动手前先统计每页的形状类型很多人拿到 PPT 的第一反应是for slide in prs.slides: print(slide.shapes[0].text_frame.text)然后在第 30 页吃一个 IndexError。原因很简单78 页培训教材通常不是一套版式走到底。封面用标题版式步骤讲解用标题和内容示意图页可能整页就是一张图加几个散落文本框最后的考核页干脆是一张表格。先做一次分布统计成本不到一分钟但它决定了后面的抽取逻辑是写一套还是写三套。from pptx import Presentation from collections import Counter prs Presentation(吹风造型培训教材.pptx) # 注意.ppt 旧格式读不了先转 pptx print(总页数:, len(prs.slides)) layout_counter, shape_counter Counter(), Counter() for idx, slide in enumerate(prs.slides, start1): layout_counter[slide.slide_layout.name] 1 for shp in slide.shapes: shape_counter[str(shp.shape_type)] 1 if shp.is_placeholder: # 只有占位符才有 idx shape_counter[fPH:{shp.placeholder_format.idx}] 1 for name, cnt in layout_counter.most_common(): print(f{name}: {cnt} 页) print(shape_counter)slide_layout.name用来判断这一页属于哪类内容shp.shape_type是枚举值文本框、图片、表格、占位符各有取值is_placeholder配合placeholder_format.idx决定你能否用标题固定是 idx 0这种稳定规则取字段。参数上没有可调项唯一要忍住的是别跳过这一步直接写抽取。提示python-pptx 只能读 .pptx。手上是 .ppt 的话先用 LibreOffice 无头模式转一遍页数和图片一般不会丢字体和艺术字效果可能变转完记得抽查首页。形状类型在吹风造型教材里的典型形态抽取建议PLACEHOLDER(0)页面标题如圆滚梳内扣手法直接作为title字段PLACEHOLDER(1)知识点正文段落按段落拆进body列表TEXT_BOX图上标注如风口 45 度需按位置排序后再拼接PICTURE吹风机握法、梳子走向示意图抽 blob 存盘并去重TABLE风温风速对照表、考核评分表逐格读取保留行列结构NOTES讲师口播稿单独成列量化参数常在这里2.2 抽取标题、正文与讲师备注的稳定字段文本有三个来源缺一不可。占位符里的标题和正文是给学员看的散落文本框是图上标注讲师备注是口播稿而吹风造型的量化参数——风温几档、风口离发片多少厘米、冷风定型停几秒——经常只写在备注里页面上只有一张示意图加一句如图所示。所以抽取函数必须把这三路都收进来并且保留段落列表而不是拼成一整串方便后面做规则匹配。import json, re def clean(t): return re.sub(r\s, , t or ).strip() def dump_slide(slide, idx): title, body_parts , [] for shp in slide.shapes: if not shp.has_text_frame: continue text clean(shp.text_frame.text) if not text: continue if shp.is_placeholder and shp.placeholder_format.idx 0: title text # 标题占位符绝大多数模板固定为 0 else: body_parts.append(text) notes if slide.has_notes_slide: # 备注页可能不存在必须判空 notes clean(slide.notes_slide.notes_text_frame.text) return {page: idx, title: title, body: body_parts, notes: notes} prs Presentation(吹风造型培训教材.pptx) records [dump_slide(s, i) for i, s in enumerate(prs.slides, start1)] json.dump(records, open(slides.json, w, encodingutf-8), ensure_asciiFalse, indent1)clean()把换行、制表符、连续空格压成单空格否则同一句话在不同页会呈现出不同的空白形态后面正则匹配会莫名其妙漏掉。has_notes_slide这个判断别省没有备注页的 slide 直接访问会抛异常。输出成 JSON 而不是直接入库是为了中途可以人工看一眼抽出来的 78 条记录里如果有十几条title是空的那多半是版式判断出了问题回头改 idx 规则比改数据库快得多。2.3 图片与示意图归类顺手建一个造型图库78 页里通常有几十张示意图吹风机握法、圆滚梳进入发片的角度、分区夹法、发旋处理前后对比。这些图不抽出来等重排课件时就得回到原文件里一页页右键另存。抽的时候顺手做两件事按内容摘要去重因为同一张握法图经常被复制到好几页生成缩略图给后面的预览页和检索结果卡片用。import hashlib, os from io import BytesIO from PIL import Image os.makedirs(assets, exist_okTrue) seen {} for i, slide in enumerate(prs.slides, start1): for j, shp in enumerate(slide.shapes): if str(shp.shape_type) ! PICTURE: continue blob shp.image.blob digest hashlib.md5(blob).hexdigest()[:8] if digest in seen: # 跨页复用图只落盘一份 continue seen[digest] fp{i}_{j} path fassets/p{i:02d}_{j}_{digest}.{shp.image.ext} open(path, wb).write(blob) img Image.open(BytesIO(blob)).convert(RGB) img.thumbnail((320, 320)) # 等比缩放不裁剪 img.save(path.replace(assets/, assets/thumb_), quality82)shp.image.blob是图片原始字节用 md5 前 8 位做去重键足够shp.image.ext给出 png、jpeg 等真实后缀别自己猜。thumbnail只缩不放小图不会被拉伸变形。落盘之后一定要把page → path的映射写回slides.json否则第 4 章回写课件时图片和知识点对不上号78 页要重新手工配一遍。3. 把吹风造型知识点拆成可检索的结构化字段3.1 一份吹风造型知识卡应该有哪些列抽完文本只是原料。教材正文是给人读的句子——取圆滚梳从发根插入风口与发片保持 45 度中温中风吹至八成干再转冷风定型。要让它可检索、可生成新课件得先定义一张吹风造型知识卡。我一般按动作要素 适用条件 错误示范三段来切字段宁可多留几个空列也别等到要用的时候再去补抽。字段含义取值示例备注style_name造型名称内扣、蓬松、外翻、纹理卷多数出现在标题里hair_type适用发质细软、粗硬、自然卷、染后受损常出现在正文首段tool工具组合圆滚梳、九排梳、集风嘴、散风嘴一页可能有多个heat_level风温档位低温、中温、高温、冷风与风速分开存speed_level风速档位弱风、中风、强风教材里叫法不统一angle_deg风口或发片角度45、90统一成阿拉伯数字distance_cm风口到发片距离15、20教材混用厘米公分technique手法提拉、内卷、外翻、打散、顺吹动词短语finish_product定型产品定型喷雾、发蜡、摩丝收尾环节source_page来源页码37溯源必备这张表的价值在于它同时服务三个下游检索、生成课件、培训考核。发型师问细软发质做蓬松用几档风查的是style_name hair_type讲师要出新版课件用的是整张卡考核出题用的是common_error这一列。3.2 用正则把教材句子映射到字段规则版先跑起来别一上来就上模型。吹风造型教材的表述相当程式化风温、风速、角度、距离这四类参数用正则能覆盖七八成。import re PATTERNS { heat_level: r(低温|中温|高温|冷风), speed_level: r(弱风|低风|中风|高风|强风), angle_deg: r(\d{2,3})\s*度, distance_cm: r(\d{1,2})\s*(?:厘米|公分|cm|CM), technique: r(内卷|外翻|提拉|打散|顺吹|逆吹), } def parse_card(text, page): card {source_page: page} for field, pat in PATTERNS.items(): hit re.search(pat, text) if hit: card[field] hit.group(1) # 命中即写不命中留空 for name in (内扣, 蓬松, 外翻, 纹理, 刘海, 波波头): if name in text: card[style_name] name # 造型名用关键词兜底 break return card每个字段一条独立正则、命中即写、不命中留空这是关键。78 页教材里参数写全的段落是少数很多页只有造型名称和一张图如果因为一条没匹配上就丢掉整张卡最后能用的记录会少一大半。角度正则写\d{2,3}是为了避开分三股这类手数描述距离统一归一到厘米。规则跑完后抽 10 页人工核对准确率能到八成以上再考虑上抽取模型否则你调的是模型错的是字段定义。3.3 落到 SQLite FTS5让内扣怎么吹直接查到页码存储用一个文件型数据库就够78 页的量级远没到需要独立搜索引擎。SQLite 的 FTS5 全文索引是这里最省事的选择但中文要处理一下分词口径。CREATE VIRTUAL TABLE cards USING fts5( style_name, tool, technique, body, source_page, tokenize unicode61 -- 中文按连续汉字整块切需入库前自行分词 );import sqlite3, jieba conn sqlite3.connect(blowdry.db) conn.execute(CREATE VIRTUAL TABLE IF NOT EXISTS cards USING fts5( style_name, tool, technique, body, source_page, tokenizeunicode61)) def seg(s): return .join(jieba.cut_for_search(s or )) for r in records: text .join(r[body] [r[notes]]) card parse_card(text, r[page]) conn.execute(INSERT INTO cards VALUES (?,?,?,?,?), ( seg(card.get(style_name, )), seg(card.get(tool, )), seg(card.get(technique, )), seg(text[:800]), # 单页正文截断防止备注过长撑爆索引 r[page], )) conn.commit() q seg(内扣 圆滚梳 角度) for row in conn.execute( SELECT source_page, snippet(cards,3,[,],...,12) FROM cards WHERE cards MATCH ? ORDER BY rank LIMIT 5, (q,)): print(row)说明三个点。unicode61分词器会把一串连续汉字当成一个 token所以入库和查询两端都必须先过jieba口径不一致就会出现入库能搜到、查询搜不到的诡异现象。snippet()的最后一个参数 12 是片段长度token 数用来在检索结果里带出命中词前后的上下文做门店端问答时这比整段返回更实用。ORDER BY rank走的是 BM25 排序不需要额外建索引列。source_page存成独立列是为了任何检索结果都能一键跳回原教材页码——这一点在培训场景里比排序精度还重要。4. 用母版加 JSON 数据把 78 页课件重新生成一遍4.1 先定母版约定再写生成代码见过不少人上来就写slide.shapes.add_textbox(...)坐标全靠手写生成出来的 78 页每页位置都不一样讲师看一眼就退回来。正确的顺序反过来先做一份 4 到 5 个版式的模板 pptx每种版式留好占位符然后记住每个占位符的 idx代码只负责填内容位置交给母版。版式名占位符 idx 与用途使用场景封面0 标题 / 1 副标题 / 2 版本与讲师整套教材第一页知识点0 造型名 / 1 发质与工具 / 2 参数行 / 3 示意图每个造型一页步骤图0 标题 / 1 步骤文字 / 2 大图手法拆解分步讲考核0 标题 / 1 题目表格章节末小测模板改一次78 页的排版全部跟着变。这个约定的成本只有半小时但省下的是每次改版都要重排一遍的时间。4.2 按知识卡回写每一页from pptx import Presentation from pptx.util import Cm TPL {style: 1, step: 2, quiz: 3} # 版式索引对应模板中的顺序 tpl Presentation(template.pptx) def add_page(kind, card): layout tpl.slide_layouts[TPL[kind]] # 传版式不传空版式 slide tpl.slides.add_slide(layout) ph {p.placeholder_format.idx: p for p in slide.placeholders} ph[0].text card.get(style_name, 未命名造型) ph[1].text f适用发质{card.get(hair_type,通用)}工具{card.get(tool,—)} ph[2].text (f风温 {card.get(heat_level,—)} 风速 {card.get(speed_level,—)} f角度 {card.get(angle_deg,—)} 度 距离 {card.get(distance_cm,—)} cm) pic ph[3].insert_picture(card[image_path]) # 占位符插图片自动落位 pic.width, pic.height Cm(12), Cm(8) slide.notes_slide.notes_text_frame.text f来源原教材第 {card[source_page]} 页 for card in cards: add_page(style, card) tpl.save(吹风造型培训教材_v2.pptx)add_slide(layout)传的是版式对象位置尺寸全由母版决定这是 78 页排版一致的根本。ph[idx]用字典推导按 idx 建索引比slide.placeholders[3]按顺序取更稳因为不同版本模板里占位符顺序未必一样。insert_picture是占位符专有方法会按占位符预留的框自动落位和缩放比add_picture手算坐标省事得多。把来源页码写进备注页是为了下一轮迭代还能追回原教材也方便讲师在答疑时快速定位。4.3 无头导出 PDF 与缩略图预览生成完 pptx 不能直接发群讲师手机上打不开字体也可能缺。加一步自动导出。# pptx 导 PDF--headless 不弹窗口适合放进 CI 或定时任务 soffice --headless --convert-to pdf --outdir dist 吹风造型培训教材_v2.pptx # 抽前 3 页做缩略图用于门店端预览和课程封面 pdftoppm -jpeg -r 60 -f 1 -l 3 dist/吹风造型培训教材_v2.pdf dist/preview--outdir指向的目录必须提前存在否则转换会静默失败只留下一个空的 dist。-r 60是渲染分辨率60 到 72 之间最省时间又不会糊做缩略图不要用 150 以上。这两条命令接在生成脚本后面自动跑讲师拿到的是能直接转发的 PDF课件原文件仍然保留用于二次编辑。5. 教材巡检与检索问答让 78 页课件保持可用5.1 巡检脚本空页、参数缺失、缺图一次扫出来课件最怕的不是做得差是错得静默。参数正则没匹配上的页占位符会留下—如果没人检查这一页就这么发下去了。写个巡检脚本把生成结果反向扫一遍。from pptx import Presentation prs Presentation(吹风造型培训教材_v2.pptx) bad [] for i, slide in enumerate(prs.slides, start1): texts [s.text_frame.text.strip() for s in slide.shapes if s.has_text_frame] joined .join(texts) has_pic any(str(s.shape_type) PICTURE for s in slide.shapes) if not any(texts): bad.append((i, 整页无文字)) if — 度 in joined or — cm in joined: bad.append((i, 参数缺失需回查原教材)) if not has_pic: bad.append((i, 缺示意图)) pages sorted({b[0] for b in bad}) print(f总页数 {len(prs.slides)}问题页 {len(pages)} 页{pages})这个脚本的价值在于它跑得比人快也能进 CI生成、导出、巡检三步串起来任何一次内容改动后问题页超过阈值就报警讲师不用逐页翻。缺图那一类尤其要盯吹风造型的手法讲解高度依赖示意图一页没有图的步骤页基本等于废页。5.2 把检索接到课件上一个问题的定位路径检索的用法有两条。一条是给讲师用的输入细软发质 蓬松 风温直接返回原教材第 41 页的片段答疑时不用翻。另一条是给门店端用的把知识点卡做成问答卡片答完显示来源页码学员可以自己回去核对。问题类型主要命中字段命中示例某造型怎么吹style_name technique内扣 → 圆滚梳内卷、冷风定型某发质用什么档位hair_type heat_level细软 → 低温、中风、距离 20cm工具怎么选tool technique集风嘴 → 顺吹压毛躁常见错误common_error高温近距离 → 发丝毛躁再补一个同义词扩展能明显提命中率。教材里同一件事有好几种叫法内扣也叫内弯收尾往里蓬松也叫发根立起来风口有时写风嘴有时写出风口。把这些同义词提前灌进一张synonyms表检索前先做一次查询扩展比去调 BM25 的 k1、b 参数见效快得多而且改起来是改数据不是改算法——这一条在门店这种没人专门维护检索效果的场景里比任何排序优化都实用。本文还有配套的精品资源点击获取
返回列表