你有没有遇到过这种场面👇
领导甩过来一个 PPT,说:“这个内容你整理一下。”
你打开一看:
几十页,全是文本,没有原稿,没有备注,没有 Word。
那一刻你是不是也在想:👉 “难道只能一页一页复制粘贴吗?”👉 “我都写代码了,还要干这种体力活?”
别慌,这事儿真的可以自动搞定。
背景:我为什么要“读”PPT?
事情的起因其实很简单。
我需要把 PPT 里的所有文本一次性拷出来,用于后续处理,比如:
但问题来了:
PPT 不是纯文本文件,Obsidian 也不能直接读。
那怎么办?
答案只有一个:
👉 让 Python 出手
整体思路,用一句话说清楚
Obsidian 负责“选文件 + 执行命令”,Python 负责“真正读取 PPT 内容”,最后结果直接进剪贴板。
全程无界面、无手动复制,体验就是一句话:丝滑。
//js //templaterletPYTHON = ['/home/jushen1/miniconda3/envs/py3.10/bin/python'].filter(x=>ea.ns.fsEditor.isfile(x))[0]if(!PYTHON){return}asyncfunctionselect_outlink_file(cfile,extensions){let files = ea.nc.chain.get_outlinks( cfile,false ).filter(x=>extensions.contains(x.extension));let file = null;if(files.length==1){ file = files[0] }else{ file = await ea.nc.chain.sugguster_note(files); }return file;}let pyfile = awaitselect_outlink_file( tp.config.template_file, ['py'])if(!pyfile){return}let pypath = ea.ns.fsEditor.abspath(pyfile);let pptfile = awaitselect_outlink_file( tp.config.active_file, ['ppt','pptx'])if(!pptfile){return}let pptpath = ea.ns.fsEditor.abspath(pptfile);functionexecAsync(cmd){returnnewPromise((resolve, reject) => {exec(cmd, (err, stdout, stderr) => {if (err) {reject(err);return; }resolve(stdout); // ✅ 这是字符串 }); });}let stdout = awaitexecAsync(`${PYTHON}${pypath}${pptpath}`);await navigator.clipboard.writeText(stdout);newNotice(stdout.slice(0,100))
第一步:在 Obsidian 里选对文件
这里用的是 Obsidian 的 Templater + 扩展能力。
逻辑很简单:
找到一个 .py 文件(负责读 PPT)
找到当前笔记关联的 .ppt / .pptx
你不用记路径,不用复制文件名,
点几下就行。
第二步:异步执行 Python(重点来了)
这里最容易踩坑的是:
👉 Node.js 执行外部命令,返回的不是你想象的“字符串”。
所以我们用一个 execAsync 包一层 Promise,
确保拿到的是真正的 stdout 文本。
这一步做对了,后面就全是快乐。
第三步:直接写进剪贴板
拿到 Python 输出的文本后,
干三件事:
写入系统剪贴板
弹个 Notice 提示
到这里,你已经可以:
👉 Ctrl + V,粘到任何地方
Word、Obsidian、微信编辑器,
想贴哪贴哪。
那 Python 文件干了什么?
一句话版解释:
👉 用 Python 把 PPT 里的每一页、每一个文本框都读出来,拼成纯文本。
具体实现我放在了这个文件里:
#!/usr/bin/env python3import argparseimport osfrom pptx import Presentationtry: import pyperclip CLIPBOARD_AVAILABLE = Trueexcept ImportError: CLIPBOARD_AVAILABLE = Falsedef extract_ppt_text(ppt_path, include_notes=True): prs = Presentation(ppt_path) texts = [] for slide_idx, slide in enumerate(prs.slides, start=1): texts.append(f"\n=== Slide {slide_idx} ===") for shape in slide.shapes: # 文本框 if shape.has_text_frame: for p in shape.text_frame.paragraphs: if p.text.strip(): texts.append(p.text) # 表格 if shape.has_table: for row in shape.table.rows: for cell in row.cells: if cell.text.strip(): texts.append(cell.text) # 备注页 if include_notes and slide.has_notes_slide: notes = slide.notes_slide.notes_text_frame.text if notes.strip(): texts.append("[Notes]") texts.append(notes) return "\n".join(texts)def main(): parser = argparse.ArgumentParser( description="Extract all text from a PPTX file" ) parser.add_argument( "ppt", help="Path to .pptx file" ) parser.add_argument( "-o", "--output", help="Output text file path (optional)" ) parser.add_argument( "--no-notes", action="store_true", help="Do not include notes pages" ) parser.add_argument( "--clipboard", action="store_true", help="Copy extracted text to clipboard" ) args = parser.parse_args() ppt_path = os.path.abspath(args.ppt) if not os.path.isfile(ppt_path): raise FileNotFoundError(f"PPT file not found: {ppt_path}") text = extract_ppt_text( ppt_path, include_notes=not args.no_notes ) # 输出到文件 if args.output: out_path = os.path.abspath(args.output) with open(out_path, "w", encoding="utf-8") as f: f.write(text) print(f"✅ Text written to: {out_path}") # 复制到剪贴板 if args.clipboard: if not CLIPBOARD_AVAILABLE: raise RuntimeError( "pyperclip not installed, run: pip install pyperclip" ) pyperclip.copy(text) print("✅ Text copied to clipboard") # 如果既不输出文件也不复制,就直接打印(可重定向) if not args.output and not args.clipboard: print(text)if __name__ == "__main__": main()
(你可以按自己的需求改格式、加页码、加分隔符,完全自由)
为什么我强烈推荐你这么干?
因为这套方案解决的不是“读 PPT”,
而是一个更大的问题:
如何把“封闭格式”的信息,变成可再利用的知识。
一旦你打通了这条链路:
你会发现,
很多以前看起来很麻烦的事,其实都能自动化。