简介这份资源面向需要批量处理Word文档的Python学习者与办公自动化从业者聚焦用python-docx库把图片和表格高效写入.docx文件解决手工重复排版、逐份编辑效率低的问题适合具备Python基础、希望提升文档处理效率的读者。压缩包共12个文件以10个docx成品文档为主另含1个py脚本和1张jpg示例图片整体约367KB体积轻便便于直接运行与对照学习。脚本演示了遍历图片目录、按行列数据生成表格、设置图片尺寸并统一保存的完整流程docx文件则给出批量生成后的实际效果方便读者比对输出结果。目前已有181人学习下载可作为办公自动化入门到实践的参考样例帮助读者掌握Document创建、add_picture、add_table及save等核心用法并迁移到工资通知、报表汇总等批量文档场景中。1. 批量把图片和表格写进 Word一个被低估的自动化刚需上周帮同事处理一份季度报告需要把 30 多张截图和 12 张数据表按顺序塞进同一个 Word 文档里。手动操作到第 8 张的时候我就放弃了——每张图要调尺寸、每张表要调列宽、标题和正文的字体还得统一。这种活儿在自动化办公的语境里其实非常典型数据在 Excel 或文件夹里躺着最终交付物却是一份格式规整的 Word。python-docx这个库很多人听过但真正用起来会发现图片插入的尺寸控制、表格样式的继承、段落与表格的交替排列每一个都是独立的坑。这份资源包围绕的就是这个场景用 Python 把图片和表格批量写入 Word省掉重复的复制粘贴和格式调整。适合经常做报告汇总、数据归档、批量生成文档的从业者新手跟着步骤也能跑通熟手可以重点看参数控制和异常处理的部分。2. 环境准备与 python-docx 的核心对象模型2.1 安装与版本选择python-docx是操作 Word 文档最常用的第三方库它不需要本机安装 Office直接读写.docx文件。安装命令很简单pip install python-docx但这里有个版本坑python-docx在 0.8.11 之后有一段时间没更新社区里有人用docx这个包名装到了另一个同名但功能不同的库。我一般会显式确认版本pip show python-docx输出里看到Name: python-docx和Version: 1.1.0以上才算对。如果装错了先pip uninstall docx再重新装python-docx。另外处理图片时底层依赖Pillow通常会自动装上但如果你用的是精简版 Python 环境最好手动补一个pip install Pillow提示不要用pip install docx那个包已经废弃导入时会报ModuleNotFoundError或者行为异常。2.2 Document、Paragraph、Table 的关系理解python-docx的对象模型后面写代码才不会乱。一个Document对象代表整个.docx文件它内部维护一个块级元素序列。块级元素主要有两种Paragraph段落和Table表格。图片本身不是块级元素它必须被放进一个Run对象里而Run又隶属于某个Paragraph。所以插入图片的路径是Document→Paragraph→Run→add_picture()。表格则是直接挂在Document下的通过add_table(rows, cols)创建。表格里的每个单元格又是一个独立的容器可以往里写文字、插图片甚至再嵌套表格。很多人第一次用的时候会困惑为什么表格不能像图片那样直接add_picture因为表格是块级元素图片是行内元素层级不同。下面这段代码展示了基本的结构关系from docx import Document from docx.shared import Inches doc Document() # 添加一个段落段落里加一个 RunRun 里插图片 para doc.add_paragraph() run para.add_run() run.add_picture(chart.png, widthInches(4.5)) # 添加一个 3 行 4 列的表格 table doc.add_table(rows3, cols4) table.style Table Grid # 往单元格里写内容 table.cell(0, 0).text 指标 table.cell(0, 1).text Q1 table.cell(0, 2).text Q2 table.cell(0, 3).text Q3 doc.save(demo.docx)逻辑说明add_paragraph()返回一个Paragraph对象add_run()在段落里创建一个行内容器add_picture()把图片文件写入这个容器。add_table(rows, cols)直接创建表格table.style设置边框样式table.cell(row, col)定位到具体单元格。参数方面width和height接受Inches、Cm、Pt等单位对象只给宽度时高度会按比例缩放两个都给则强制拉伸——这一点后面避坑章节会展开。2.3 批量写入的整体流程设计批量操作的核心思路是把“数据源”和“写入逻辑”分开。数据源可以是一个文件夹里的图片列表也可以是一个 Excel 文件里的表格数据。写入逻辑则是一个循环按顺序往Document里追加内容。我一般会先把要插入的内容整理成一个列表每个元素是一个字典标明类型和路径或数据items [ {type: heading, text: 一、整体概况}, {type: image, path: imgs/overview.png, width: 5.0}, {type: table, data: [[区域, 销量], [华东, 1200], [华南, 980]]}, {type: heading, text: 二、分项明细}, {type: image, path: imgs/detail.png, width: 4.5}, ]然后写一个分发函数根据type调用不同的处理逻辑。这样做的好处是数据源怎么变都不影响写入逻辑后面要加新类型比如页眉、页码也只需要扩展分发函数。这个模式在批量生成报告时非常稳不容易因为某一条数据格式不对就整个崩掉。3. 图片批量插入尺寸、对齐与格式兼容3.1 按文件夹顺序批量插图最常见的需求是一个文件夹里按编号排好了图片要按顺序全部插进 Word。用os.listdir或pathlib拿到文件列表后排序是关键。如果文件名是img1.png、img2.png、img10.png默认字符串排序会把img10排在img2前面。解决办法是用自然排序from pathlib import Path import re def natural_key(path): # 把文件名里的数字提取出来作为排序依据 return [int(s) if s.isdigit() else s.lower() for s in re.split(r(\d), path.name)] img_dir Path(imgs) img_files sorted(img_dir.glob(*.png), keynatural_key) doc Document() for img_path in img_files: doc.add_picture(str(img_path), widthInches(5.0)) doc.add_paragraph() # 图片之间留一个空段落避免挤在一起 doc.save(batch_images.docx)逻辑说明natural_key把文件名拆成数字和非数字片段数字转成int参与排序这样img2就会排在img10前面。doc.add_picture()是Document上的快捷方法内部自动创建段落和 Run。widthInches(5.0)统一宽度高度按原图比例自动算。最后加一个空段落是为了让图片之间有间距否则连续插入的图片会紧贴在一起。参数方面width和height都可以用Inches、Cm、Mm、Pt、Emu。我一般用Inches因为 Word 里默认的页面宽度是 8.5 英寸Letter或 8.27 英寸A4减去左右边距各 1 英寸正文区域大约 6.27 英寸。所以图片宽度设 5.0 到 6.0 英寸比较安全超过 6.27 会被挤到下一页或者被裁切。3.2 控制图片尺寸与保持宽高比只给width时python-docx会按原图比例自动算高度这是最省心的做法。但如果你同时给了width和height它就会强制拉伸图片可能变形。我见过有人为了统一尺寸把截图和照片都设成width4, height3结果截图被压扁照片被拉长。正确的做法是先读原图尺寸再按目标宽度算高度。用Pillow读尺寸from PIL import Image from docx.shared import Inches def add_image_keep_ratio(doc, img_path, target_width_inch): with Image.open(img_path) as im: w_px, h_px im.size ratio h_px / w_px target_height_inch target_width_inch * ratio doc.add_picture(img_path, widthInches(target_width_inch), heightInches(target_height_inch))逻辑说明Image.open()读取图片的像素宽高算出高宽比再用目标宽度乘以比例得到目标高度。这样图片不会变形。参数target_width_inch根据你的页面正文宽度来定A4 纸常用 5.5 到 6.0。注意如果图片本身是竖长图按宽度缩放后高度可能超过一页Word 会自动分页但图片会被截断。这种情况我一般会判断一下如果算出来的高度超过 8 英寸就改用按高度缩放。3.3 图片对齐与段落格式add_picture()默认是左对齐而且图片所在的段落没有额外间距。如果你想让图片居中需要拿到图片所在的段落对象设置对齐方式from docx.enum.text import WD_ALIGN_PARAGRAPH para doc.add_paragraph() para.alignment WD_ALIGN_PARAGRAPH.CENTER run para.add_run() run.add_picture(chart.png, widthInches(5.0))逻辑说明WD_ALIGN_PARAGRAPH.CENTER是段落对齐的枚举值还有LEFT、RIGHT、JUSTIFY。图片本身没有对齐属性对齐是段落级别的。另外如果你想让图片和上下文字有间距可以设置段落的space_before和space_afterpara.paragraph_format.space_before Pt(6) para.paragraph_format.space_after Pt(6)参数单位是Pt磅6 磅大约等于 0.08 英寸视觉上就是一点点空隙。批量插图时我一般会统一设成 6 到 12 磅避免图片和标题粘在一起。4. 表格批量写入数据源、样式与动态行数4.1 从 Excel 或 CSV 读取表格数据表格数据通常来自 Excel 或 CSV。用pandas读进来最方便但如果你不想引入pandas这个重依赖用csv模块也够。下面以 CSV 为例import csv def read_table_data(csv_path): with open(csv_path, r, encodingutf-8-sig) as f: reader csv.reader(f) return [row for row in reader] table_data read_table_data(sales.csv) # table_data 是二维列表第一行通常是表头逻辑说明encodingutf-8-sig是为了处理 Excel 导出的 CSV 带 BOM 头的问题不加的话第一列列名前面会多一个不可见字符。csv.reader返回的是迭代器转成列表后每一行是一个字符串列表。如果数据里有数字读进来也是字符串写进 Word 后如果需要计算得自己转类型。如果数据源是 Excel 的.xlsx文件用openpyxl读from openpyxl import load_workbook wb load_workbook(data.xlsx, data_onlyTrue) ws wb.active table_data [[cell.value for cell in row] for row in ws.iter_rows()]data_onlyTrue表示读公式的计算结果而不是公式本身这个参数在批量生成报告时很关键否则表格里会出现SUM(A1:A10)这种字符串。4.2 创建表格并写入数据拿到二维列表后创建表格并填充def add_table_from_data(doc, data, styleTable Grid): rows len(data) cols len(data[0]) if rows 0 else 0 table doc.add_table(rowsrows, colscols) table.style style for i, row_data in enumerate(data): for j, cell_value in enumerate(row_data): table.cell(i, j).text str(cell_value) if cell_value is not None else return table逻辑说明rows和cols从数据里动态算出来避免硬编码。table.style Table Grid给表格加边框Word 内置的样式名还有Light Shading Accent 1、Medium Shading 1 Accent 1等但不同版本的 Word 对样式名的支持有差异Table Grid是最稳的。table.cell(i, j).text直接赋值如果值是None就写空字符串否则str()转一下。参数方面add_table(rows, cols)的rows和cols必须是整数创建后不能直接增删行列——要增删得用table.add_row()或table.add_column()但这两个方法在旧版本里行为不太一致。我一般是一次性算好行列数再创建。4.3 表格样式与列宽调整Table Grid只给边框列宽是平均分的。如果某一列内容特别长平均分会导致换行很难看。调整列宽需要遍历每一行的单元格设置cell.widthfrom docx.shared import Inches def set_col_widths(table, widths_inch): for row in table.rows: for idx, width in enumerate(widths_inch): row.cells[idx].width Inches(width) set_col_widths(table, [1.2, 2.0, 1.5, 1.5])逻辑说明table.rows遍历每一行row.cells[idx]定位到该行的第 idx 个单元格设置width。注意python-docx的列宽设置是“建议值”Word 打开时会根据内容自动调整除非你同时设置了表格的autofit属性table.autofit False把autofit设为False后列宽才会严格按照你设的值来。这个坑我踩过好几次——明明设了列宽打开 Word 还是被自动调了就是因为没关autofit。提示如果表格列数很多总宽度超过页面正文宽度Word 会强制压缩或者让表格超出页面。A4 纸正文宽度约 6.27 英寸列宽之和不要超过这个值。5. 避坑与常见问题排查5.1 图片插入后显示为红叉或空白现象代码跑完没报错但打开 Word 发现图片位置是一个红叉或者空白框。原因图片路径包含中文或特殊字符python-docx在写入时没有正确处理编码或者图片文件本身损坏Pillow能打开但python-docx读取时失败。解决把图片路径转成绝对路径并且确保路径里没有中文。如果必须有中文用pathlib.Path的resolve()转绝对路径from pathlib import Path img_path Path(图片/图表.png).resolve() doc.add_picture(str(img_path), widthInches(5.0))另外用Pillow先验证图片能否正常打开from PIL import Image try: with Image.open(img_path) as im: im.verify() except Exception as e: print(f图片损坏: {img_path}, {e})5.2 表格样式在打开 Word 后丢失现象代码里设了table.style Table Grid但打开 Word 后表格没有边框。原因python-docx创建表格时样式名必须和 Word 内置样式完全匹配。如果你用的样式名在当前 Word 版本里不存在它会静默失败不报错但也不生效。解决优先用Table Grid这是兼容性最好的。如果确实需要其他样式先在 Word 里手动创建一个带该样式的表格保存为.docx然后用python-docx打开这个文件打印table.style.name看实际样式名是什么。不要凭记忆写样式名。5.3 批量插入大量图片时内存暴涨现象插入几百张图片后Python 进程内存占用超过 2GB甚至被系统杀掉。原因python-docx在add_picture()时会把图片数据读进内存并保留在Document对象里直到save()才写入磁盘。图片越多内存占用越大。解决分批处理。每处理 50 张图片就save()一次然后重新打开继续追加from docx import Document def batch_insert_images(img_files, batch_size50): doc Document() for idx, img_path in enumerate(img_files): doc.add_picture(str(img_path), widthInches(5.0)) if (idx 1) % batch_size 0: doc.save(output.docx) doc Document(output.docx) # 重新打开释放内存 doc.save(output.docx)逻辑说明每 50 张保存一次并重新加载Document对象重建后旧的内存会被回收。代价是每次重新加载会有少量 I/O 开销但比内存溢出强。5.4 中文字体在表格和段落里不生效现象代码里设了run.font.name 宋体但 Word 里中文还是默认字体。原因python-docx的font.name只对西文生效中文字体需要额外设置w:eastAsia属性。解决用qn函数设置 East Asian 字体from docx.oxml.ns import qn run para.add_run(中文内容) run.font.name Times New Roman run._element.rPr.rFonts.set(qn(w:eastAsia), 宋体)逻辑说明run._element.rPr.rFonts拿到字体的 XML 元素qn(w:eastAsia)生成 Word 命名空间下的属性名设置为宋体。这样西文用 Times New Roman中文用宋体。表格里的单元格文字同理需要拿到单元格的段落和 Run 再设置。5.5 插入的表格行数太多导致跨页断行混乱现象表格行数超过一页跨页后表头没有重复第二页的表格看起来像没有表头的新表格。原因python-docx默认不重复表头行。解决设置表格首行为表头重复from docx.oxml.ns import qn from docx.oxml import OxmlElement def set_repeat_header(row): tr row._tr trPr tr.get_or_add_trPr() tblHeader OxmlElement(w:tblHeader) tblHeader.set(qn(w:val), true) trPr.append(tblHeader) set_repeat_header(table.rows[0])逻辑说明table.rows[0]._tr拿到第一行的 XML 元素get_or_add_trPr()获取或创建行属性容器然后插入w:tblHeader元素并设为true。这样 Word 在跨页时会自动重复表头行。这个操作在批量生成多页表格时几乎是必加的否则打印出来很难读。6. 进阶技巧用模板 占位符实现可复用的报告生成批量插入图片和表格只是第一步。实际工作中更常见的是有一份固定的 Word 模板里面用占位符标好了哪里放图片、哪里放表格然后根据数据源替换占位符。这样做的好处是格式完全由模板控制代码只负责填数据不用在代码里写一堆字体、间距、对齐的设置。具体做法是在 Word 里手动做好模板占位符用{{image_1}}、{{table_1}}这种格式。然后用python-docx打开模板遍历所有段落找到占位符所在的 Run替换成图片或表格。from docx import Document from docx.shared import Inches def replace_placeholder_with_image(doc, placeholder, img_path, width_inch): for para in doc.paragraphs: if placeholder in para.text: # 清空占位符所在的 Run for run in para.runs: if placeholder in run.text: run.text run.text.replace(placeholder, ) # 在段落里插入图片 run para.add_run() run.add_picture(img_path, widthInches(width_inch)) return True return False doc Document(template.docx) replace_placeholder_with_image(doc, {{image_1}}, chart1.png, 5.5) doc.save(report.docx)逻辑说明遍历doc.paragraphs找到包含占位符的段落先把占位符文本从 Run 里删掉再在同一个段落里新增一个 Run 来放图片。这样图片就出现在占位符原来的位置。表格的替换稍微复杂一点因为表格不能直接塞进段落需要先找到占位符段落然后在它后面插入表格再删掉占位符段落。def replace_placeholder_with_table(doc, placeholder, data): for para in doc.paragraphs: if placeholder in para.text: # 在占位符段落后面插入表格 table doc.add_table(rowslen(data), colslen(data[0])) table.style Table Grid for i, row_data in enumerate(data): for j, val in enumerate(row_data): table.cell(i, j).text str(val) # 把表格移动到占位符段落的位置 para._element.addnext(table._element) # 删除占位符段落 para._element.getparent().remove(para._element) return True return False逻辑说明doc.add_table()创建的表格默认追加在文档末尾用para._element.addnext(table._element)把表格的 XML 元素移动到占位符段落后面然后删掉占位符段落。这样表格就出现在原来占位符的位置。这个“先创建再移动”的模式在python-docx里很常用因为库本身没有提供“在指定位置插入表格”的 API。模板方案的优势在于格式调整不用改代码改模板就行。我一般会把模板里的样式、页眉页脚、页码都设好代码只负责填内容。这样即使后面报告格式要改也只需要让设计同事改模板不用重新跑代码。从那以后我每次做批量文档生成都会先花十分钟做一个模板把占位符和样式定好再写替换逻辑。这样后面不管数据怎么变格式都不会跑偏。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?