WPS表格图片批量处理:Python自动化提取、重命名与回写
简介这份源码面向WPS表格用户与办公自动化开发者提供基于WPS JS宏的图片批量处理方案解决表格中图片逐个手动导入、导出、清除效率低下的问题。包内共3个文件以html页面、inscode配置与gitignore为主压缩包约6KB轻量易加载可直接在WPS宏编辑器中运行。已有161人学习下载。源码完整实现三大核心函数CommandButton1_Click遍历Shapes集合并严格判断图片对象后批量删除避免误删文本框与图表CommandButton2_Click借助FileSystemObject读取本地路径用AddPicture将JPG、PNG、GIF、BMP等图片按单元格坐标与宽高比动态缩放锚定CommandButton3_Click反向遍历图片按“SheetName_Row_序号”规则导出并支持分辨率设置。代码注释详尽含路径校验、try-catch异常捕获与StatusBar进度反馈兼容WPS 2019及以上双平台适合人事照片归集、商品图库同步、教学资料整理等场景也可作为学习WPS JS宏对象模型与图形属性操控的范例。1. 批量处理表格图片从手动三百次到一次点击的工程化思路如果你经常跟 WPS 表格打交道大概率遇到过这种场景一张表里嵌了几十上百张产品图、员工照片或者物料截图现在需要统一改尺寸、统一压缩、统一重命名甚至按单元格内容导出成独立文件。手动一张张右键、另存、改名三百张图能干掉一整个下午而且改到后面手一抖就出错。这个标题讲的正是把这类重复劳动工程化——用脚本读取 WPS 表格文件批量提取、处理、回写图片。它解决的不是“会不会修图”的问题而是“能不能把表格当图片数据库来用”的问题。适合两类人一类是运营、行政、电商从业者手里有大量带图表格要交付另一类是刚接触办公自动化的开发者想找一个真实场景练手文件解析与图像处理。下面我按自己实际做过的路径把选型、代码、参数和踩过的坑一次讲清。2. 拆解 WPS 表格的图片存储结构为什么不能直接当 zip 读2.1 表格文件本质是一个压缩包WPS 表格保存为.et或.xlsx格式时底层遵循的是 OOXML 规范本质上就是一个 ZIP 压缩包。把后缀改成.zip解压你会看到xl/media/目录下躺着所有嵌入的图片文件命名通常是image1.png、image2.jpeg这种。这意味着批量提取图片最直接的办法不是调用 WPS 的 COM 接口而是直接解压文件、遍历media目录。这样做的好处是速度快、不依赖 WPS 是否安装、跨平台也能跑。但代价是你拿到的只是裸图片丢失了“哪张图属于哪一行哪一列”的映射关系。如果只是批量压缩或改格式这就够了如果要按单元格内容重命名或回写就必须再解析表格结构。2.2 图片与单元格的锚定关系藏在 drawing 里图片在表格中的位置信息不在media目录而在xl/drawings/drawing1.xml这类文件里。这个 XML 描述了每个图片锚定到哪个单元格区域包含from和to的行列索引。解析它需要用到 XML 解析库把xdr:from下的xdr:row和xdr:col读出来再跟xl/worksheets/sheet1.xml里的单元格值做关联。常见做法是先用openpyxl加载工作簿遍历worksheet._images每个图片对象自带anchor属性能直接拿到锚定单元格的行列号。这比手动解 XML 省事得多也是我一般会优先选的路子。下面这段代码演示如何读取一个表格里所有图片及其锚定位置import openpyxl # 加载工作簿注意 keep_linksFalse 可避免外部链接报错 wb openpyxl.load_workbook(demo.xlsx, keep_linksFalse) ws wb.active # 遍历当前工作表所有图片对象 for idx, img in enumerate(ws._images): # anchor._from 包含锚定起始行列0 基索引 row img.anchor._from.row 1 col img.anchor._from.col 1 # 读取该单元格的值作为重命名依据 cell_value ws.cell(rowrow, columncol).value print(f图片{idx}: 锚定单元格({row},{col}), 单元格值{cell_value})这段代码的关键在img.anchor._from它返回一个包含row和col的对象索引从 0 开始所以加 1 才是人类习惯的行列号。cell_value可能是 None说明图片浮在空白单元格上这种情况要单独处理不能直接拿来做文件名。参数上keep_linksFalse能避免一些带外部引用的表格加载失败属于血泪经验。2.3 选 openpyxl 还是直接解压一张对比表维度openpyxl 解析直接解压 zip获取图片与单元格映射支持直接读 anchor不支持需额外解析 XML处理速度中等大文件较慢快几乎瞬间依赖 WPS/Excel不需要不需要回写图片支持不支持适用场景需重命名、按行导出仅批量压缩、改格式如果你的需求只是把表格里所有图抠出来统一转成 WebP直接解压最快如果要按“A列是姓名、B列是照片”导出成张三.jpg那就老老实实用 openpyxl。我一般会先解压看一眼 media 目录的图片数量和格式再决定走哪条路。3. 用 Python 跑通批量提取与回写的最小闭环3.1 环境准备与依赖安装动手前先把环境搭好。Python 3.8 以上即可核心依赖三个openpyxl负责读写表格Pillow负责图像处理pathlib是标准库不用装。安装命令如下pip install openpyxl Pillow如果你还要处理.et格式openpyxl 默认不支持需要先用 WPS 另存为.xlsx。这是第一个容易翻车的地方很多人直接拿.et文件喂给 openpyxl报InvalidFileException以为是代码问题其实是格式没转。批量转换可以用 WPS 的批量另存功能或者用libreoffice --headless --convert-to xlsx命令行处理。3.2 批量提取图片并按单元格值重命名下面这段代码是我实际项目里精简出来的功能是遍历表格所有图片按锚定单元格的值重命名并保存到输出目录。如果单元格值为空就用row_col兜底。import os from pathlib import Path import openpyxl from PIL import Image from io import BytesIO def extract_images(xlsx_path, out_dir): # 确保输出目录存在 Path(out_dir).mkdir(parentsTrue, exist_okTrue) wb openpyxl.load_workbook(xlsx_path, keep_linksFalse) ws wb.active count 0 for img in ws._images: # 获取锚定行列转为 1 基 row img.anchor._from.row 1 col img.anchor._from.col 1 cell_val ws.cell(rowrow, columncol).value # 构造文件名过滤非法字符 if cell_val: name str(cell_val).strip().replace(/, _).replace(\\, _) else: name frow{row}_col{col} # img._data() 返回原始字节用 Pillow 打开 pil_img Image.open(BytesIO(img._data())) # 统一转为 RGB避免 PNG 透明通道存 JPEG 报错 if pil_img.mode in (RGBA, P): pil_img pil_img.convert(RGB) save_path os.path.join(out_dir, f{name}.jpg) pil_img.save(save_path, quality85) count 1 print(f共提取 {count} 张图片到 {out_dir}) extract_images(demo.xlsx, output_images)逻辑说明img._data()是 openpyxl 图片对象内部方法返回图片原始字节流比先存临时文件再读快得多。Image.open(BytesIO(...))直接在内存里打开省去磁盘 IO。convert(RGB)这步很关键PNG 带透明通道直接存 JPEG 会抛OSError: cannot write mode RGBA as JPEG这是新手必踩的坑。quality85是压缩质量85 在肉眼几乎无损的前提下能把体积压到原图 30% 左右如果对体积敏感可以降到 70但低于 60 会出现明显块状伪影。3.3 批量压缩与格式转换的参数怎么设如果不需要重命名只是把表格里所有图统一压到指定宽度可以加一个缩放逻辑。下面这段在提取基础上增加了最大宽度限制def resize_and_save(pil_img, save_path, max_width800, quality80): # 按最大宽度等比缩放不放大只缩小 if pil_img.width max_width: ratio max_width / pil_img.width new_size (max_width, int(pil_img.height * ratio)) pil_img pil_img.resize(new_size, Image.LANCZOS) pil_img.save(save_path, qualityquality, optimizeTrue) # 调用时替换原来的 save # resize_and_save(pil_img, save_path)Image.LANCZOS是重采样算法缩小时比默认的NEAREST平滑得多代价是稍慢。optimizeTrue会让 Pillow 尝试无损优化编码体积能再小 5% 到 10%。max_width800适合网页展示场景如果是打印物料建议设 1500 以上。这里有个边界如果原图宽度小于max_width代码不会放大避免糊图。很多人写缩放忘了判断结果小图被拉大交付时被吐槽模糊。3.4 回写图片到新表格提取之后有时需要把处理过的图重新塞回表格。openpyxl 支持通过ws.add_image()添加图片但定位需要用到openpyxl.drawing.spreadsheet_drawing里的锚定类。下面是一个最小回写示例from openpyxl.drawing.image import Image as XLImage from openpyxl.drawing.spreadsheet_drawing import OneCellAnchor, AnchorMarker from openpyxl.utils.units import pixels_to_EMU def write_image(ws, img_path, row, col): xl_img XLImage(img_path) # 锚定到指定单元格偏移设为 0 marker AnchorMarker(colcol-1, rowrow-1, colOff0, rowOff0) xl_img.anchor OneCellAnchor(_frommarker, extNone) ws.add_image(xl_img) # 用法把 output_images/张三.jpg 写回第 2 行第 2 列 wb openpyxl.Workbook() ws wb.active write_image(ws, output_images/张三.jpg, row2, col2) wb.save(rewritten.xlsx)AnchorMarker的col和row是 0 基所以传参时要减 1。OneCellAnchor表示图片锚定在单个单元格上随单元格移动。如果想让图片固定大小不随单元格缩放用TwoCellAnchor指定起止单元格。回写时图片路径必须真实存在否则XLImage初始化就报错。这个环节的坑在于图片尺寸如果原图是 4000 像素宽直接塞进表格会撑爆单元格显示最好先用前面的resize_and_save压到合理尺寸再回写。4. 避坑与排查图片批量处理里最容易翻车的五件事4.1 提取出来的图片全是乱序对不上行现象解压media目录拿到的image1.png到image50.png跟表格里从上到下的顺序完全不一致。原因media目录的编号是按图片插入时间或内部引用顺序生成的跟视觉位置无关。解决不要用解压方式做映射改用 openpyxl 遍历ws._images按anchor._from.row排序后再处理。如果必须解压就得解析drawing1.xml里的锚定关系工作量大且容易出错。4.2 保存 JPEG 时报 cannot write mode RGBA现象代码跑到pil_img.save(save_path)时抛OSError: cannot write mode RGBA as JPEG。原因PNG 图片带透明通道JPEG 不支持透明Pillow 拒绝直接转换。解决保存前判断pil_img.mode如果是RGBA或P就convert(RGB)。注意转换后透明区域会变成黑色如果介意可以先铺一层白底再转换。4.3 大表格加载慢到怀疑人生现象一个 50MB 的表格load_workbook跑了三分钟还没结束。原因openpyxl 默认会解析所有工作表、所有样式和公式内存占用高。解决加载时加read_onlyTrue和data_onlyTrue只读模式跳过样式解析速度能提升数倍。但注意read_only模式下ws._images可能不可用需要先判断场景只提取图片用解压要映射关系就用普通模式但接受慢。4.4 单元格值带换行或特殊字符导致文件名非法现象用单元格值做文件名时Windows 报OSError: Invalid argument。原因单元格里可能有\n、:、*、?等字符这些在文件系统里非法。解决重命名前做一次清洗把非法字符替换成下划线并截断过长的名称。我一般会写一个sanitize_filename函数把\/:*?|和换行都替换掉再限制长度 100 字符以内。4.5 回写后图片显示为红叉或空白现象用 openpyxl 回写图片后打开表格图片位置显示红叉。原因图片路径是相对路径而表格保存后被移动了位置或者图片文件被删除。解决回写前把图片转成绝对路径或者先把图片嵌入到工作簿同目录下再引用。另一个常见原因是图片格式不被 WPS 识别比如 WebP 格式建议统一转成 PNG 或 JPEG 再回写。5. 进阶技巧把批量处理做成可复用的命令行工具前面都是脚本片段实际交付时我习惯把它封装成一个命令行工具用argparse接收参数这样非开发同事也能用。核心参数就四个输入文件、输出目录、最大宽度、压缩质量。下面是一个精简版入口import argparse def main(): parser argparse.ArgumentParser(descriptionWPS表格图片批量处理) parser.add_argument(input, help输入表格路径) parser.add_argument(-o, --output, defaultoutput, help输出目录) parser.add_argument(-w, --width, typeint, default800, help最大宽度) parser.add_argument(-q, --quality, typeint, default85, helpJPEG质量1-100) args parser.parse_args() extract_images(args.input, args.output, args.width, args.quality) if __name__ __main__: main()封装后调用就是python tool.py demo.xlsx -o out -w 1200 -q 75同事不用改代码就能调参。这里有个细节quality参数要做范围校验小于 1 或大于 100 直接报错退出避免生成损坏图片。另外输出目录如果已存在同名文件我一般会加时间戳后缀而不是覆盖防止误删原始素材。验证处理结果是否可靠我通常做两件事一是随机抽三张图用图片查看器打开确认没有花屏或截断二是对比处理前后的文件体积如果压缩后反而变大说明原图可能已经是高压缩比格式这时候应该跳过压缩直接复制。这个判断逻辑可以写成if os.path.getsize(new) os.path.getsize(old): shutil.copy(old, new)。说个我自己的教训早期做批量重命名时没做文件名清洗结果一张图对应的单元格值是“产品/规格A”保存时直接报错中断前面处理的两百张全白干。从那以后我养成了习惯——任何拿外部数据做文件名的操作先过一遍清洗函数再包一层 try-except 记录失败项而不是直接崩。这个习惯帮我省了无数次后悔药。希望帮到你。本文还有配套的精品资源点击获取