
简介这份资源面向需要批量处理Word文档的Python开发者与办公自动化爱好者聚焦于从.docx文件中提取图片与嵌入对象这一常见却不易实现的场景。包内共2033个文件以908个py源码与880个pyc字节码为主辅以C扩展头文件、依赖元数据、说明文档及少量示例docx压缩包约12.42MB整体呈现一个可直接运行的完整工程目录。核心逻辑围绕docx库遍历文档部件、解析关系文件定位图片并借助oletools等工具解析OLE结构导出嵌入的表格、PDF等文件脚本分工清晰便于二次修改与集成。已有1197人学习下载适合希望掌握文档深度解析、构建自动化导出流程的读者参考也可作为数据分析与内容迁移场景下的工具脚本基础。1. 从一份 Word 里批量抠图为什么我劝你别手动另存上周帮一个做工程文档的朋友处理一批验收资料三十多份.docx每份里嵌了几十张现场照片和若干附件表格。他原本的做法是逐个打开、右键图片另存为、再翻到附件位置手动拖出来干到第十份就崩溃了。这类需求其实非常典型Word 文档本质上是个 ZIP 压缩包图片、嵌入的 Excel、PDF、甚至音频都躺在包内的特定目录里用 Python 的zipfile加xml解析就能一次性全导出来。这篇笔记就围绕「Python 批量导出 Word 文档中所有图片与嵌入文件」这个场景把原理、代码、参数和踩过的坑讲透。适合手里有大量 docx 需要做素材归档、内容迁移或数据清洗的从业者新手照着代码能跑通熟手能看清 OOXML 结构的边界在哪。2. 先搞懂 docx 的 OOXML 结构图片和嵌入文件到底藏在哪动手写代码之前得先明白.docx不是二进制黑匣子。从 Office 2007 开始Word 文档改用 OOXMLOffice Open XML格式本质是一个遵循 ZIP 规范的容器里面按目录组织着 XML 描述文件和媒体资源。你把任意一个.docx后缀改成.zip再解压就能看到它的内部结构。理解这套结构后面写导出逻辑才不会靠猜。2.1 解压一个 docx 看看目录长什么样随便找一个 docx用 Python 打印它的内部文件列表这是排查一切问题的起点import zipfile # 打开 docx 本质就是打开一个 zip with zipfile.ZipFile(sample.docx, r) as z: for name in z.namelist(): print(name)跑完你会看到类似这样的输出不同文档略有差异[Content_Types].xml _rels/.rels word/document.xml word/_rels/document.xml.rels word/media/image1.png word/media/image2.jpeg word/embeddings/oleObject1.bin word/embeddings/Microsoft_Excel_Worksheet1.xlsx word/styles.xml ...这里有几个关键位置必须记住。word/media/目录存放文档里插入的所有图片命名通常是image1.png、image2.jpeg这种流水号。word/embeddings/目录存放嵌入的 OLE 对象比如嵌进来的 Excel 表格、PDF、Visio 图文件名可能是oleObject1.bin或直接是原始文件名。word/document.xml是正文内容图片和嵌入对象在正文里的引用关系记录在word/_rels/document.xml.rels这个关系文件里。2.2 关系文件才是图片与位置的映射表光把word/media/里的文件全导出来你拿到的是「一堆没有上下文的图」。如果文档里有几十张图你根本不知道哪张对应正文哪一段。真正的映射关系在document.xml.rels里它用rId把正文中的引用和实际文件关联起来。看一段典型的关系文件内容Relationships xmlns... Relationship IdrId5 Type.../image Targetmedia/image1.png/ Relationship IdrId6 Type.../oleObject Targetembeddings/oleObject1.bin/ /Relationships正文document.xml里插入图片时会写成a:blip r:embedrId5/这个rId5就指向media/image1.png。所以如果你需要「按正文顺序导出并重命名」就得先解析关系文件建立rId → 文件路径的字典再按document.xml里出现的顺序去取。只做「全量导出」的话直接遍历word/media/和word/embeddings/就够了这也是大多数场景够用的方案。2.3 图片格式的坑emf、wmf 这些矢量图怎么办word/media/里不只有 png、jpg还可能是.emf、.wmf这类 Windows 矢量图格式尤其在从其他 Office 文档复制粘贴过来的图里很常见。这些格式在 Linux 或 Mac 上没法直接预览Pillow 也读不了。常见做法是导出时保留原格式后续需要转换再用libreoffice --headless --convert-to png或专门的转换工具处理。别在导出脚本里硬转容易把矢量图转糊。另外还有一种情况图片是以「链接」而非「嵌入」方式插入的这种图不在word/media/里导出脚本拿不到需要单独处理链接关系这是很多人第一次写脚本会翻车的地方。3. 用 zipfile 加 xml 解析写一个通用导出脚本原理清楚了直接上可复用的脚本。我一般把导出逻辑拆成三层解包、识别资源、落盘。这样每层都能单独调试出问题好定位。下面这份代码处理了图片、嵌入文件、以及按关系文件重命名的增强逻辑。3.1 基础版全量导出 media 和 embeddings先写一个最小可用版本把两类资源全导出来import zipfile import os import shutil def export_docx_assets(docx_path, out_dir): 导出 docx 中所有图片和嵌入文件 # 用文档名建子目录避免多文档导出时互相覆盖 base os.path.splitext(os.path.basename(docx_path))[0] target os.path.join(out_dir, base) img_dir os.path.join(target, images) emb_dir os.path.join(target, embeddings) os.makedirs(img_dir, exist_okTrue) os.makedirs(emb_dir, exist_okTrue) with zipfile.ZipFile(docx_path, r) as z: for name in z.namelist(): # 图片资源 if name.startswith(word/media/): fname os.path.basename(name) with z.open(name) as src, \ open(os.path.join(img_dir, fname), wb) as dst: shutil.copyfileobj(src, dst) # 嵌入的 OLE 对象 elif name.startswith(word/embeddings/): fname os.path.basename(name) with z.open(name) as src, \ open(os.path.join(emb_dir, fname), wb) as dst: shutil.copyfileobj(src, dst) return target逻辑说明zipfile.ZipFile以只读方式打开文档namelist()返回包内所有条目路径。用startswith前缀匹配筛出word/media/和word/embeddings/两类。z.open(name)返回一个类文件对象配合shutil.copyfileobj流式写入避免大文件一次性读进内存。参数上out_dir是导出根目录函数内部会按文档名再建一层子目录这样批量处理时不会串。os.makedirs(..., exist_okTrue)保证目录已存在时不报错。3.2 增强版按正文顺序重命名图片基础版导出的图是image1.png、image2.png如果文档里图很多归档时很难对应。增强版解析关系文件和正文按图片在正文中出现的顺序重命名import zipfile import os import re from xml.etree import ElementTree as ET NS { w: http://schemas.openxmlformats.org/wordprocessingml/2006/main, a: http://schemas.openxmlformats.org/drawingml/2006/main, r: http://schemas.openxmlformats.org/officeDocument/2006/relationships, rel: http://schemas.openxmlformats.org/package/2006/relationships, } def export_images_ordered(docx_path, out_dir): 按正文出现顺序导出并重命名图片 os.makedirs(out_dir, exist_okTrue) with zipfile.ZipFile(docx_path, r) as z: # 1. 解析关系文件建立 rId - media 路径映射 rels ET.fromstring(z.read(word/_rels/document.xml.rels)) rid_map {} for rel in rels.findall(rel:Relationship, NS): rid rel.get(Id) target rel.get(Target) if media/ in target: # Target 可能是相对路径统一成包内绝对路径 rid_map[rid] word/ target.lstrip(./) # 2. 解析正文按出现顺序收集 rId doc ET.fromstring(z.read(word/document.xml)) ordered_rids [] for blip in doc.iter({%s}blip % NS[a]): rid blip.get({%s}embed % NS[r]) if rid and rid in rid_map: ordered_rids.append(rid) # 3. 按顺序导出用序号原扩展名命名 for idx, rid in enumerate(ordered_rids, start1): src_path rid_map[rid] ext os.path.splitext(src_path)[1] out_name fimg_{idx:03d}{ext} with z.open(src_path) as src, \ open(os.path.join(out_dir, out_name), wb) as dst: dst.write(src.read()) return len(ordered_rids)逻辑说明第一步读document.xml.rels遍历所有Relationship节点只保留Target里含media/的建立rId → 包内路径字典。第二步用doc.iter遍历正文里所有a:blip节点取它的r:embed属性拿到rId按遍历顺序即正文出现顺序收集。第三步按序号命名导出img_001.png这种格式排序友好。参数上命名格式fimg_{idx:03d}{ext}里的03d表示补零到三位超过 999 张图会自动扩展位数不用改代码。3.3 批量处理整个目录实际场景往往是一个文件夹里几十份文档套一层循环就行import glob def batch_export(folder, out_dir): results {} for path in glob.glob(os.path.join(folder, *.docx)): # 跳过 Word 打开时生成的临时文件 if os.path.basename(path).startswith(~$): continue try: target export_docx_assets(path, out_dir) results[path] target except Exception as e: results[path] f失败: {e} return results这里有个血泪经验glob会把~$xxx.docx这种 Word 打开文档时生成的临时锁文件也匹配进来这种文件不是合法 ZIP直接打开会抛BadZipFile。所以循环里先过滤掉~$开头的文件。另外用try/except包住单个文档的处理一份失败不影响整批最后把结果字典打出来看哪些没成功。4. 避坑与排查导出脚本最容易翻车的五个地方脚本能跑通不代表能稳定跑。下面这几条是我在不同文档上反复踩出来的按「现象 → 原因 → 解决」整理遇到问题对着查。4.1 报 BadZipFile文档打不开现象执行脚本时抛zipfile.BadZipFile: File is not a zip file。原因通常有三种一是文件根本不是 docx可能是老的.doc二进制格式或者被改过后缀二是文件损坏或下载不完整三是碰到了~$临时文件。解决先用file命令或读文件头判断真实格式.doc需要先用 LibreOffice 转成.docx再处理批量场景务必过滤~$前缀对单份文档可以先zipfile.ZipFile(path).testzip()验证完整性。4.2 图片导出来了但全是 emf/wmf 打不开现象word/media/里导出的文件扩展名是.emf或.wmf双击无法预览。原因这些是从其他 Office 文档复制过来的矢量图Word 保留了原始格式。解决导出脚本保留原格式不做转换后续单独用libreoffice --headless --convert-to png --outdir out/ *.emf批量转或者用inkscape处理。别在导出环节硬转容易丢矢量信息。4.3 嵌入的 Excel 导出后打不开现象word/embeddings/里导出的oleObject1.bin用 Excel 打开报错。原因OLE 嵌入对象的外层是 OLE 复合文档容器真正的 Excel 数据被包在里面直接改后缀成.xlsx不一定能打开。解决优先找word/embeddings/里已经是.xlsx、.pdf这种带真实扩展名的文件它们通常是「以文件形式嵌入」的能直接用对于.bin结尾的需要用olefile库解析容器再提取或者干脆在 Word 里双击对象另存。判断依据是看关系文件里Target的扩展名。4.4 图片顺序和正文对不上现象按image1、image2顺序导出后发现和正文里的图片顺序不一致。原因word/media/里的文件名是 Word 内部维护的不保证和正文出现顺序一致尤其是文档经过多次编辑后。解决用 3.2 节的增强版脚本以document.xml里a:blip节点的遍历顺序为准而不是文件名顺序。如果文档里有浮动图片wp:anchor遍历逻辑要相应调整a:blip依然能抓到但位置信息在wp:anchor里。4.5 中文文件名乱码现象导出的嵌入文件名里中文变成乱码。原因ZIP 条目名在不同编码下解析不一致Python 的zipfile对非 UTF-8 标志的条目会按 CP437 解码。解决读取时用name.encode(cp437).decode(gbk)尝试还原或者用zipfile.ZipFile(path, metadata_encodinggbk)Python 3.11 支持指定编码。更稳妥的做法是导出时统一用序号命名把原始名记录到一个manifest.txt里避免编码问题影响文件系统。5. 进阶技巧把导出结果做成可检索的归档清单导出只是第一步真正省时间的是让导出结果可检索。我现在的习惯是每次导出后生成一份manifest.json记录每张图的来源文档、在正文中的顺序、原始 rId 和导出文件名。这样后续要按文档、按顺序、按类型筛选直接读 JSON 就行不用再翻文件夹。下面这段代码在 3.2 节基础上加了清单输出import json def export_with_manifest(docx_path, out_dir): manifest {source: os.path.basename(docx_path), images: []} with zipfile.ZipFile(docx_path, r) as z: rels ET.fromstring(z.read(word/_rels/document.xml.rels)) rid_map {} for rel in rels.findall(rel:Relationship, NS): if media/ in rel.get(Target): rid_map[rel.get(Id)] word/ rel.get(Target).lstrip(./) doc ET.fromstring(z.read(word/document.xml)) idx 0 for blip in doc.iter({%s}blip % NS[a]): rid blip.get({%s}embed % NS[r]) if rid and rid in rid_map: idx 1 src_path rid_map[rid] ext os.path.splitext(src_path)[1] out_name fimg_{idx:03d}{ext} with z.open(src_path) as src, \ open(os.path.join(out_dir, out_name), wb) as dst: dst.write(src.read()) manifest[images].append({ order: idx, file: out_name, rid: rid, origin: src_path, }) with open(os.path.join(out_dir, manifest.json), w, encodingutf-8) as f: json.dump(manifest, f, ensure_asciiFalse, indent2) return manifest这份清单的价值在于可追溯。比如客户说「第三份文档里第二张现场照片要换」你直接读对应文档的manifest.jsonorder为 2 的那条记录就是目标文件不用再打开 Word 数图。参数上ensure_asciiFalse保证中文正常写入indent2让 JSON 可读。批量处理时把每份文档的 manifest 汇总成一个总表还能做跨文档的图片去重——同一张图在多个文档里重复出现时origin路径和文件哈希能帮你识别出来。从那以后我每次处理 Word 素材归档都强制先跑一遍导出加清单生成再动手做后续的迁移或清洗。这套流程不复杂但省下的重复劳动是实打实的。希望帮到你。本文还有配套的精品资源点击获取