Python批量提取Word文档图片与嵌入文件实战

发布时间:2026/10/18 10:02:58
Python批量提取Word文档图片与嵌入文件实战
简介这份资源面向需要用Python自动化处理Word文档的开发者与办公自动化爱好者聚焦于从.docx文件中批量导出内嵌图片与OLE嵌入文件这一具体问题。包内共2033个文件以908个py源码、880个pyc字节码为主辅以C扩展头文件、pyd动态库、exe可执行文件及少量docx示例文档压缩包约12.42MB并附带虚拟环境与IDE配置开箱即可运行调试。实现思路上借助docx库遍历段落、表格等元素定位图片解析关系文件获取图片数据并落盘对嵌入的Excel、PDF等对象则通过oletools等库解析OLE结构完成提取脚本分工清晰便于二次改造。已有1197人学习下载适合希望掌握文档深度解析、批量迁移与内容整理技巧的读者参考借鉴。1. 批量提取 Word 文档里的图片和嵌入文件Python 这条路走得通做过文档迁移的同行大概都遇到过这种需求手头几十上百个.docx文件里面散落着产品图、截图、附件表格业务方要你把它们全部抽出来归档。手动右键另存一个文件还能忍一百个就是纯体力活。这个项目的定位很明确——用 Python 把 Word 文档里的图片和嵌入对象批量导出适合需要做文档资产整理、内容迁移或二次开发的开发者。它不依赖 Office 软件本身纯代码解析.docx包结构跑在服务器上也没问题。技术栈就是 Python 标准库zipfile加xml解析配合python-docx处理文档关系映射轻量、可嵌入、好改造。下面从目录结构一路拆到核心代码再讲怎么改。2. 项目结构与技术选型为什么不用 COM 接口2.1 目录布局与模块职责这个项目的代码量不大但分层清晰适合直接嵌进现有工程。典型目录结构如下word-extractor/ ├── extractor/ │ ├── __init__.py │ ├── core.py # 主提取逻辑协调各模块 │ ├── media.py # 图片资源定位与导出 │ ├── embedded.py # 嵌入文件OLE对象处理 │ └── rels.py # 关系映射解析 ├── utils/ │ ├── file_utils.py # 文件读写、目录创建 │ └── logger.py # 日志配置 ├── config.py # 输出路径、命名规则等配置 ├── main.py # 命令行入口 └── requirements.txtcore.py是调度中心负责打开.docx、调用关系解析、分发到media.py或embedded.py。rels.py单独抽出来是因为.docx内部的关系映射word/_rels/document.xml.rels决定了每个资源文件的真实路径和类型这是整个提取流程的关键索引。config.py集中管理输出目录、文件名模板、是否保留原始目录层级等参数改起来不用翻业务代码。2.2 技术选型zipfile lxml 而非 COM很多人第一反应是用win32com调 Word 的 COM 接口让 Office 自己另存。这条路在 Windows 桌面环境下能跑但问题很明显必须装 Office、启动慢、并发差、服务器上基本没法用。这个项目选了另一条路——.docx本质上就是一个 ZIP 包图片放在word/media/下嵌入文件放在word/embeddings/下关系映射写在word/_rels/document.xml.rels里。用zipfile直接读包用lxml解析 XML不依赖任何外部软件。import zipfile from lxml import etree def open_docx(path): 打开 docx 文件返回 zip 对象和关系映射字典 zf zipfile.ZipFile(path, r) # 读取关系映射文件建立 rId - 目标路径的索引 rels_xml zf.read(word/_rels/document.xml.rels) root etree.fromstring(rels_xml) rels_map {} for rel in root: rid rel.get(Id) target rel.get(Target) rel_type rel.get(Type) rels_map[rid] {target: target, type: rel_type} return zf, rels_map这段代码是整个提取流程的入口。zipfile.ZipFile以只读模式打开文档zf.read直接读取内部 XML 而不解压到磁盘。rels_map的键是关系 ID如rId5值包含目标路径和类型。类型字段用来区分是图片、嵌入文件还是超链接——图片的 Type 通常以/image结尾嵌入对象的 Type 包含/oleObject。拿到这个映射后后续就能根据文档正文里引用的rId反查真实文件路径。注意.doc老格式不是 ZIP 包这套方法只适用于.docx。如果手头有.doc需要先批量转成.docx常见做法是用 LibreOffice 的无头模式转换。2.3 依赖清单与版本约束requirements.txt里核心依赖只有两个lxml用于 XML 解析python-docx用于高层文档遍历。python-docx本身也依赖lxml所以实际安装时版本要统一。我一般会锁定lxml4.9和python-docx0.8.11这两个版本组合在 Python 3.8 到 3.11 上都验证过。如果只需要底层提取其实可以完全不用python-docx直接用zipfilelxml就够了少一层依赖少一个坑。3. 核心提取逻辑从关系映射到文件落盘3.1 图片提取定位 media 目录并导出图片提取的关键是两步先从关系映射里筛出所有图片类型的关系再根据 Target 路径从 ZIP 包里读出二进制流写盘。media.py里的核心函数如下import os from utils.file_utils import ensure_dir IMAGE_TYPES (image/png, image/jpeg, image/gif, image/bmp) def extract_images(zf, rels_map, output_dir): 从 docx 中提取所有图片到 output_dir ensure_dir(output_dir) count 0 for rid, info in rels_map.items(): # 只处理图片类型的关系 if not any(t in info[type] for t in IMAGE_TYPES): continue target info[target] # Target 可能是相对路径需要拼上 word/ 前缀 if not target.startswith(word/): target word/ target.lstrip(/) try: data zf.read(target) except KeyError: continue # 关系存在但文件缺失跳过 # 用原始文件名保存避免重名加 rId 前缀 filename f{rid}_{os.path.basename(target)} with open(os.path.join(output_dir, filename), wb) as f: f.write(data) count 1 return countIMAGE_TYPES定义了支持的图片 MIME 类型实际项目中可能还需要加image/tiff或image/webp。info[type]是完整的关系类型 URI用in做子串匹配比精确匹配更稳因为不同 Word 版本生成的 URI 前缀可能略有差异。target字段在关系文件里通常是相对word/的路径比如media/image1.png所以需要补全前缀。zf.read如果遇到关系存在但实际文件缺失的情况会抛KeyError这里直接跳过而不是中断整个流程——批量处理时一个坏文件不应该拖垮全部任务。文件名用rId加原始文件名既保留了顺序信息又避免不同图片重名覆盖。3.2 嵌入文件提取OLE 对象与包对象嵌入文件比图片复杂一些因为 Word 里插入的 Excel 表格、PDF 附件、另一个 Word 文档在底层可能以 OLE 对象或包对象的形式存在。它们的共同点是都放在word/embeddings/目录下但关系类型不同。EMBED_TYPES (oleObject, package) def extract_embedded(zf, rels_map, output_dir): 提取嵌入的 OLE 对象和包对象 ensure_dir(output_dir) results [] for rid, info in rels_map.items(): if not any(t in info[type] for t in EMBED_TYPES): continue target info[target] if not target.startswith(word/): target word/ target.lstrip(/) try: data zf.read(target) except KeyError: continue # 嵌入文件的原始扩展名藏在 oleObject 的 progId 里 # 这里先用 bin 后缀保存后续可根据 progId 重命名 filename f{rid}_{os.path.basename(target)} with open(os.path.join(output_dir, filename), wb) as f: f.write(data) results.append({rid: rid, file: filename, size: len(data)}) return resultsEMBED_TYPES覆盖了oleObject和package两种关系类型。嵌入文件在 ZIP 包里的实际文件名通常是oleObject1.bin、oleObject2.bin这种没有扩展名。要还原成可打开的文件需要进一步解析word/embeddings/下对应的 XML 描述文件从中读取progId如Excel.Sheet.12来推断真实格式。这个项目里先以.bin保存把重命名逻辑留给二次开发按需实现——因为不同业务对嵌入文件的处理策略差异很大有的要还原成 Excel有的只要原始二进制归档。3.3 关系映射解析rId 与真实路径的桥梁rels.py负责把关系映射解析成更易用的结构同时处理一些边界情况比如外部链接Target 是 URL 而非内部路径和图片在页眉页脚中的引用。def build_resource_index(rels_map): 把关系映射按类型分组返回 {type: [资源列表]} index {images: [], embedded: [], others: []} for rid, info in rels_map.items(): target info[target] # 跳过外部链接只处理包内资源 if target.startswith(http) or target.startswith(mailto): continue rel_type info[type] if image in rel_type: index[images].append({rid: rid, target: target}) elif oleObject in rel_type or package in rel_type: index[embedded].append({rid: rid, target: target}) else: index[others].append({rid: rid, target: target}) return index这个索引结构让上层调用更清晰core.py拿到index后直接按类别分发不用在每个提取函数里重复判断类型。跳过http和mailto开头的外部链接很重要否则会尝试从 ZIP 包里读一个不存在的路径而报错。others类别里可能包含超链接、批注引用等当前项目不处理但保留结构方便扩展。4. 避坑与常见问题排查4.1 提取出来图片打不开或只有几 KB现象导出的 PNG 文件大小异常用看图软件打开提示损坏。原因通常是关系映射里的 Target 路径没有正确补全前缀导致读到了错误的文件或者读的是缩略图而非原图。.docx里图片可能同时存在原图和缩略图两个关系缩略图在word/media/下但尺寸很小。解决在extract_images里加一层过滤检查文件大小小于 1KB 的跳过同时打印每个 Target 的实际路径确认补全逻辑正确。4.2 嵌入的 Excel 文件导出后无法打开现象oleObject1.bin改成.xlsx后 Excel 报格式错误。原因是 OLE 对象外面包了一层复合文档结构不是直接的 xlsx 二进制。解决需要解析word/embeddings/下对应的.xml描述文件读取progId和shapeId再用olefile库剥离外层容器。如果业务只要求归档原始数据保持.bin后缀不强行改扩展名反而更安全。4.3 批量处理时某个文件报错导致整个任务中断现象跑一百个文档跑到第 37 个抛异常后面全部没处理。原因是zipfile或lxml遇到损坏文件时抛出的异常没有被捕获。解决在main.py的循环里对每个文件单独 try-except记录失败文件名和异常信息到日志继续处理下一个。批量任务的铁律是单个失败不影响整体最后统一汇总失败列表。4.4 中文文件名乱码现象导出的图片文件名里中文变成乱码。原因是 ZIP 包内文件名编码在不同 Word 版本下不一致zipfile默认用 CP437 解码。解决在ZipFile构造时传入metadata_encodingutf-8Python 3.11 支持或者手动对namelist()里的文件名做编码转换。更稳妥的做法是导出时不用原始文件名统一用rId加序号命名彻底绕开编码问题。4.5 页眉页脚里的图片提取不到现象正文图片都导出了但页眉里的 Logo 没出来。原因是页眉页脚有独立的关系映射文件路径在word/_rels/header1.xml.rels等位置不在主文档的document.xml.rels里。解决遍历word/_rels/下所有.rels文件合并关系映射后再统一提取。这个改动不大但能覆盖更完整的场景。5. 二次开发改造从能跑到好用5.1 按文档结构还原图片位置当前实现是把所有图片平铺到一个目录实际业务里往往需要知道每张图在文档中的位置——比如按章节归档或者生成一份「图片-段落」对照表。改造思路是解析word/document.xml遍历所有w:drawing节点从中提取r:embed属性拿到rId再结合段落文本和标题层级建立映射。from lxml import etree NS { w: http://schemas.openxmlformats.org/wordprocessingml/2006/main, r: http://schemas.openxmlformats.org/officeDocument/2006/relationships, a: http://schemas.openxmlformats.org/drawingml/2006/main } def map_images_to_paragraphs(zf): 返回 [(段落文本, rId), ...] 的映射列表 doc_xml zf.read(word/document.xml) root etree.fromstring(doc_xml) result [] for para in root.iter({http://schemas.openxmlformats.org/wordprocessingml/2006/main}p): text .join(para.itertext()).strip() for drawing in para.iter({http://schemas.openxmlformats.org/wordprocessingml/2006/main}drawing): for blip in drawing.iter({http://schemas.openxmlformats.org/drawingml/2006/main}blip): rid blip.get({http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed) if rid: result.append((text[:50], rid)) return result这段代码遍历文档所有段落对每个段落查找内嵌的drawing节点再从中提取blip元素的r:embed属性。text[:50]截取段落前 50 个字符作为位置标识避免长段落撑爆输出。拿到这个映射后导出图片时就可以按段落文本生成子目录或者输出一份 CSV 索引。5.2 输出格式与命名策略的可配置化不同项目对输出结构要求不同有的要按原始media目录结构保留有的要按扩展名分目录有的要加时间戳前缀。建议在config.py里定义命名策略函数而不是硬编码在提取逻辑里。配置项可选值说明namingrid/original/index文件名生成规则subdir_bynone/ext/type是否按扩展名或类型建子目录keep_structureTrue/False是否保留原始 media 目录层级overwriteTrue/False同名文件是否覆盖我一般会把naming默认设为rid因为原始文件名在批量场景下重名率极高rid加序号最稳。subdir_by设为ext在图片和嵌入文件混在一起时特别有用导出后一眼就能看出哪些是图、哪些是附件。5.3 验证提取完整性的一个笨办法改完代码怎么确认没漏我的习惯是对比 ZIP 包内word/media/和word/embeddings/下的文件总数与导出文件数。写个校验函数def verify_extraction(zf, output_dir): 对比包内资源数与导出数 internal [n for n in zf.namelist() if n.startswith(word/media/) or n.startswith(word/embeddings/)] exported [f for f in os.listdir(output_dir) if os.path.isfile(os.path.join(output_dir, f))] print(f包内资源: {len(internal)}, 已导出: {len(exported)}) if len(internal) ! len(exported): missing set(os.path.basename(n) for n in internal) - set(exported) print(f可能遗漏: {missing})这个校验不判断内容是否正确只保证数量对齐。数量对不上时missing集合能直接指出哪些文件没导出来再去查对应的关系映射就能定位问题。从那以后我每次改完提取逻辑都强制走一遍这个校验比肉眼翻目录靠谱得多。希望帮到你。本文还有配套的精品资源点击获取