资讯动态

Python批量提取Word文档图片与嵌入文件:docx解压与OOXML解析实战

发布时间:2026/10/18 10:01:55 来源:尧图企业网站定制
简介这份资源面向需要用Python自动化处理Word文档的开发者与办公自动化爱好者聚焦于从.docx文件中批量导出内嵌图片与OLE嵌入文件这一实际需求。包内共2033个文件以908个py源码与880个pyc字节码为主辅以C头文件、HTML说明、exe工具、pxd声明及docx示例文档等压缩包约12.42MB整体构成一个可直接运行的完整项目环境。核心逻辑围绕docx库遍历段落、表格等元素提取图片并借助oletools等工具解析OLE结构导出嵌入的Excel、PDF等对象配套脚本与虚拟环境配置齐全便于读者理解文档解析与资源提取的完整链路。目前已有1197人学习下载适合希望掌握Word深度解析、批量文档内容迁移与自动化整理技巧的读者参考实践。1. 批量提取 Word 文档里的图片和嵌入文件Python 到底能省多少事上周同事丢过来一个压缩包里面塞了三十多份产品需求文档全是.docx。他想要的是把每份文档里的插图单独导出来做素材库再把里面嵌入的 Excel 附件按原文件名归位。手动右键另存三十份文档、上百张图干到下班也弄不完。这种重复劳动正是python msword这类操作最该出场的地方——用 Python 把 Word 文档当成一个压缩包来拆图片、嵌入的 OLE 对象、附件全都能批量捞出来。这篇笔记就围绕「导出 Word 文档中所有图片与嵌入文件」这个具体需求把技术选型、目录结构、核心代码、运行步骤和二次改造一次讲透。适合手头有大量文档要处理、又不想装 Office 插件的开发者新手照着跑能出结果熟手能直接拿去改成自己的批处理流水线。2. 技术选型与文档结构为什么拆 docx 比调 Word 接口更靠谱2.1 两条技术路线COM 自动化 vs 直接解析 OOXML处理 Word 文档绕不开两条路。第一条是调用本机安装的 Word 程序通过 COM 接口Windows 上常见的是pywin32里的win32com.client打开文档再用InlineShapes、Shapes这些对象把图片一张张另存出来。这条路的好处是兼容.doc老格式坏处也明显必须装 Office、必须跑在 Windows 上、进程一多就容易卡死服务器上根本没法用。第二条路是把.docx当成一个 ZIP 压缩包直接拆。从 Office 2007 开始.docx本质上就是一个遵循 OOXML 规范的 zip 容器图片、嵌入对象、样式、正文 XML 全都分门别类放在固定目录里。用 Python 标准库zipfile就能打开配合xml.etree.ElementTree解析关系文件不依赖 Office、跨平台、速度快。这个项目场景下我一般会选第二条路因为需求是「批量导出」稳定和可脚本化比什么都重要。注意.doc老二进制格式不是 zipzipfile打不开。如果素材里混了.doc得先用别的方式转成.docx这一点后面避坑章节会细说。2.2 一个 docx 解压后长什么样把任意一个.docx后缀改成.zip再解压你会看到这样的目录结构理解它是后面所有代码的基础example.docx (解压后) ├── [Content_Types].xml # 声明包内各部分的 MIME 类型 ├── _rels/ │ └── .rels # 包级关系指向 document.xml 等主部件 ├── word/ │ ├── document.xml # 正文内容图片/对象在这里被引用 │ ├── _rels/ │ │ └── document.xml.rels # 正文的关系表rId 到实际文件的映射 │ ├── media/ # 所有图片实体image1.png、image2.jpeg... │ └── embeddings/ # 嵌入的 OLE 对象如 oleObject1.xlsx └── docProps/ # 文档属性关键点在于word/media/目录下就是文档里所有图片的原始文件word/embeddings/下就是所有嵌入对象。理论上你只要把这两个目录整个拷出来就完事了。但现实没这么简单——图片文件名是image1.png这种无意义编号嵌入对象是oleObject1.bin你根本不知道它对应文档里哪段内容、原始文件名是什么。要还原这些信息就得读document.xml.rels关系表。2.3 关系表把 rId 翻译成人话word/_rels/document.xml.rels是一份 XML结构大致如下Relationships xmlns... Relationship IdrId4 Type.../image Targetmedia/image1.png/ Relationship IdrId5 Type.../oleObject Targetembeddings/oleObject1.xlsx/ /Relationships正文document.xml里通过r:embedrId4这样的属性引用图片通过r:idrId5引用嵌入对象。所以完整链路是正文 XML 找到 rId → 关系表把 rId 映射到media/或embeddings/下的真实路径 → 从 zip 里读出字节流写盘。这套映射逻辑就是这个项目要写的核心代码。2.4 项目目录结构建议我一般会把这类工具组织成下面这样方便后面加功能word_extractor/ ├── extractor.py # 核心提取逻辑 ├── cli.py # 命令行入口支持批量目录 ├── requirements.txt # 依赖清单 └── output/ # 默认导出目录依赖其实很轻核心只用标准库可选装lxml提升大文档解析速度# requirements.txt lxml4.9.0 # 可选文档很大时解析更快3. 核心代码拆解从 zip 里把图片和嵌入文件捞干净3.1 打开文档并定位关系表第一步是把 docx 当 zip 打开读出关系表建立 rId 到目标路径的字典。这里用命名空间处理因为 OOXML 的 XML 带默认命名空间直接按标签名找会找不到。import zipfile import os import xml.etree.ElementTree as ET # OOXML 关系表的命名空间 REL_NS {http://schemas.openxmlformats.org/package/2006/relationships} def load_relationships(zf): 读取 word/_rels/document.xml.rels返回 {rId: (type, target)} rels {} rel_path word/_rels/document.xml.rels if rel_path not in zf.namelist(): return rels with zf.open(rel_path) as f: tree ET.parse(f) for rel in tree.getroot().findall(f{REL_NS}Relationship): rid rel.get(Id) rtype rel.get(Type, ) target rel.get(Target, ) rels[rid] (rtype, target) return rels逻辑说明zf.namelist()列出压缩包内所有条目先判断关系表是否存在避免空文档报错。ET.parse解析后遍历每个Relationship节点把Id、Type、Target三个属性取出来。Type里包含image、oleObject、hyperlink等关键字后面靠它区分类型。Target是相对word/的路径比如media/image1.png。参数说明zf是zipfile.ZipFile对象返回的字典 key 是rId4这种字符串value 是元组方便后续按类型过滤。3.2 遍历正文按出现顺序提取图片光有 media 目录还不够因为文档里同一张图可能被引用多次而且我们想知道图片在正文里的先后顺序。所以遍历document.xml按r:embed出现的顺序收集 rId。# 正文里图片引用的命名空间 WP_NS {http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing} A_NS {http://schemas.openxmlformats.org/drawingml/2006/main} R_NS {http://schemas.openxmlformats.org/officeDocument/2006/relationships} def iter_image_rids(zf): 按正文出现顺序产出所有图片的 rId with zf.open(word/document.xml) as f: tree ET.parse(f) root tree.getroot() # 图片引用藏在 a:blip 的 r:embed 属性里 for blip in root.iter(f{A_NS}blip): rid blip.get(f{R_NS}embed) if rid: yield rid逻辑说明root.iter()会递归遍历所有后代节点a:blip是 DrawingML 里承载图片引用的元素它的r:embed属性就是 rId。用生成器yield是为了大文档时不必一次性把所有 rId 塞进内存。这里只处理了内联图片浮动图片锚定在段落上的同样走a:blip所以一并覆盖到了。参数说明A_NS是 DrawingML 主命名空间R_NS是关系命名空间两者拼在属性名前才能取到值这是 OOXML 解析最容易翻车的地方。3.3 写盘图片按序号命名嵌入文件还原原名拿到 rId 后从关系表查到真实路径再从 zip 读出字节写文件。嵌入对象OLE的原始文件名往往藏在docProps或对象本身的属性里简单场景下按扩展名还原即可。def extract_all(docx_path, out_dir): os.makedirs(out_dir, exist_okTrue) img_dir os.path.join(out_dir, images) emb_dir os.path.join(out_dir, embeddings) os.makedirs(img_dir, exist_okTrue) os.makedirs(emb_dir, exist_okTrue) with zipfile.ZipFile(docx_path) as zf: rels load_relationships(zf) img_idx 0 for rid in iter_image_rids(zf): if rid not in rels: continue rtype, target rels[rid] if image not in rtype: continue # target 形如 media/image1.png需拼上 word/ 前缀 zip_path word/ target if zip_path not in zf.namelist(): continue ext os.path.splitext(target)[1] or .png img_idx 1 out_name fimage_{img_idx:03d}{ext} with zf.open(zip_path) as src, \ open(os.path.join(img_dir, out_name), wb) as dst: dst.write(src.read()) # 嵌入对象直接扫 embeddings 目录 for name in zf.namelist(): if name.startswith(word/embeddings/) and not name.endswith(/): base os.path.basename(name) with zf.open(name) as src, \ open(os.path.join(emb_dir, base), wb) as dst: dst.write(src.read()) return img_dir, emb_dir逻辑说明图片部分严格按正文顺序编号image_001.png这种命名方便和文档内容对照。嵌入对象部分直接扫word/embeddings/目录因为 OLE 对象在正文里的引用关系比图片复杂简单场景下按目录整体导出最省事。os.makedirs(..., exist_okTrue)保证重复运行不报错。参数说明docx_path是单个文档路径out_dir是导出根目录函数内部再分images和embeddings两个子目录。img_idx用三位补零超过 999 张图时位数自动扩展不影响正确性。3.4 批量入口一次处理整个目录单文档跑通后加一层目录遍历就能批量处理这也是这个工具真正的价值所在。import glob def batch_extract(src_dir, out_root): 遍历 src_dir 下所有 docx逐个导出到 out_root/文档名/ for docx in glob.glob(os.path.join(src_dir, *.docx)): name os.path.splitext(os.path.basename(docx))[0] target os.path.join(out_root, name) try: extract_all(docx, target) print(f[OK] {name}) except Exception as e: # 单个文档失败不影响整体批处理 print(f[FAIL] {name}: {e}) if __name__ __main__: batch_extract(./docs, ./output)逻辑说明glob匹配所有.docx每个文档单独建子目录避免不同文档的同名图片互相覆盖。try/except包住单个文档的处理一份文档损坏不会中断整批任务这在处理来源杂乱的素材时特别重要。参数说明src_dir是待处理文档目录out_root是导出根目录。实际用的时候把这两个路径换成自己的即可。4. 跑起来环境、命令与结果验证4.1 环境准备这个工具对 Python 版本要求不高3.8 以上都能跑核心逻辑只用标准库。如果文档特别大几百页、上千张图装个lxml会明显快一些。# 建议用虚拟环境避免污染全局 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 核心无第三方依赖lxml 可选 pip install lxml4.2 运行与输出把待处理文档放进docs/直接执行入口脚本python cli.py --src ./docs --out ./output跑完后output/下会按文档名分目录每个目录里是images/和embeddings/。验证是否完整最直接的办法是打开原文档数一数图片张数和images/里的文件数对一下。嵌入的 Excel、PDF 附件则看embeddings/里的文件能不能正常打开。4.3 常见失败信号如果images/是空的先确认文档里是不是只有浮动图片或图表Chart图表数据不在media/而在charts/目录需要单独处理。如果embeddings/里出现.bin后缀说明 OLE 对象的原始扩展名没被记录得靠读对象内部头字节判断类型这属于进阶场景。5. 避坑与排查那些让我返工过的细节5.1 现象代码报KeyError: There is no item named word/document.xml原因拿到的文件其实是.doc老格式只是后缀被改成了.docxzipfile打开后里面根本没有 OOXML 结构。解决先用file命令或读文件头判断真实格式.doc的魔数是D0 CF 11 E0遇到这种先转格式再处理。5.2 现象图片导出来了但顺序和文档里对不上原因直接遍历word/media/目录是按文件名排序而image10.png会排在image2.png前面且无法反映正文顺序。解决坚持走document.xml的a:blip遍历按 rId 出现顺序编号这才是文档真实阅读顺序。5.3 现象同一张图在文档里出现两次导出却只有一张原因Word 对重复图片做了去重多个 rId 可能指向同一个media/image1.png。解决如果业务需要「按出现次数导出」就在写盘时对每个 rId 都写一份文件名带上出现序号如果只需要素材去重保持现状即可。这个取舍要在需求阶段想清楚。5.4 现象嵌入的 Excel 打开提示文件损坏原因OLE 嵌入对象在embeddings/里存的是复合文档二进制直接改后缀成.xlsx不一定能打开尤其是老版本嵌入方式。解决优先用olefile库解析 OLE 容器提取内部流再还原简单场景下先按原扩展名导出能打开就够用。5.5 现象批量处理到一半卡死原因某个文档体积过大src.read()一次性读进内存导致占用飙升。解决改成shutil.copyfileobj(src, dst)分块拷贝或者给单个文档加超时控制避免一份坏文档拖垮整批任务。6. 二次开发把它改成你自己的文档处理流水线跑通基础版之后这个工具真正的价值在于改造。我一般会从三个方向动手。第一是加过滤只导出特定类型的嵌入文件比如只留.xlsx和.pdf在extract_all里按扩展名白名单过滤即可KEEP_EXT {.xlsx, .pdf, .docx} if os.path.splitext(base)[1].lower() not in KEEP_EXT: continue第二是接 OCR 或图片去重。导出的图片如果要做素材库重复图很常见可以算感知哈希pHash去重把相似度高于阈值的图归为一组只留分辨率最高的一张。第三是输出结构化清单把「文档名 → 图片列表 → 嵌入文件列表」写成 JSON方便下游系统消费import json manifest {doc: name, images: img_files, embeddings: emb_files} with open(os.path.join(target, manifest.json), w, encodingutf-8) as f: json.dump(manifest, f, ensure_asciiFalse, indent2)验证改造是否成功我习惯用一份「已知答案」的测试文档手动数好 5 张图、2 个附件跑完对比数量、文件名、顺序三项全对才算过。这里有个血泪经验——别拿生产文档直接测先复制一份到临时目录因为早期版本我写盘路径拼错把导出目录覆盖成了源目录差点把原始素材搞丢。从那以后我每次跑批处理前都强制走一遍「源目录只读、输出目录独立」的检查再动手。希望这个拆解能帮到你把手里那堆文档的图片和附件一次性理干净。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑