吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 410|回复: 4
收起左侧

[经验求助] Python怎么读取表格图片,并替换原表格

[复制链接]
人心所向 发表于 2026-4-5 01:03
150吾爱币
本帖最后由 人心所向 于 2026-4-5 01:04 编辑

众所周知,WPS表格如果将图片嵌入到单元格内,再用Excel打开表格,会提示dispimg,这个是WPS内置函数,Excel是没有这个函数的,所以无法显示图片,函数的第一个参数为图片ID,另外xlsx表格文件更改扩展名为zip后可以解压看到表格文件的大概结构:
图片位置:xl\media
cellimages.xml.rels位置:xl\_rels
cellimages.xml位置:xl
根据互联网的信息,大概意思就是:
先从cellimages.xml内获取ID,比如
[C++] 纯文本查看 复制代码
<etc:cellImage>
<xdr:pic>
<xdr:nvPicPr>
<xdr:cNvPr id="7" name="ID_75BC16877A1847729B2E098D0D27FECE"/>
此处为dispimg内的ID
<xdr:cNvPicPr>
<a:picLocks noChangeAspect="1"/>
</xdr:cNvPicPr>
</xdr:nvPicPr>
<xdr:blipFill>
<a:blip r:embed="rId1"/>
此处为ID对应的名称
<a:stretch>
<a:fillRect/>
</a:stretch>
</xdr:blipFill>
<xdr:spPr>
<a:xfrm>
<a:off x="8472805" y="1887855"/>
<a:ext cx="1296670" cy="914400"/>
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst/>
</a:prstGeom>
<a:noFill/>
<a:ln w="9525">
<a:noFill/>
</a:ln>
</xdr:spPr>
</xdr:pic>
接着在cellimages.xml.rels里根据ID找到图片地址,比如
[mw_shl_code=cpp,true]<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.png"/></Relationships>

名称对应的图片位置
用deepseek+Python写了一堆屎山代码,图片倒是能提取出来,但是对应的图片不知道怎么替换对应的dispimg函数,意思就是把dispimg删掉,替换成正确的图片,我的目的是,用Python写个GUI程序,表格文件拖入到窗口后,程序自动根据以上原理,在原表格同目录下复制一个表格文件,文件名为:原表格名_结果.原表格扩展名(例如程序测试_结果.xlsx),接着里面的dispimg函数自动替换成正确的图片,描述的可能不清楚,如果需要明白具体意思的我可以回复。
</etc:cellImage>[/mw_shl_code]

最佳答案

查看完整内容

import os import re import zipfile import io import tkinter as tk from tkinter import filedialog, messagebox, ttk from xml.etree import ElementTree as ET from PIL import Image as PIL_Image import openpyxl from openpyxl.drawing.image import Image def get_dispimg_mapping(xlsx_path): """解析 WPS 特有的 cellimages.xml,得到 {ID: PIL_Image} 映射""" mapping = {} try: ...

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

老梁不说话 发表于 2026-4-5 01:03
import os
import re
import zipfile
import io
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from xml.etree import ElementTree as ET
from PIL import Image as PIL_Image
import openpyxl
from openpyxl.drawing.image import Image

def get_dispimg_mapping(xlsx_path):
    """解析 WPS 特有的 cellimages.xml,得到 {ID: PIL_Image} 映射"""
    mapping = {}
    try:
        with zipfile.ZipFile(xlsx_path) as zf:
            if 'xl/cellimages.xml' not in zf.namelist() or 'xl/_rels/cellimages.xml.rels' not in zf.namelist():
                return mapping

            # ==================== 1. 解析 cellimages.xml ====================
            ns = {
                'xdr': 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing',
                'a': 'http://schemas.openxmlformats.org/drawingml/2006/main',
            }
            xml_data = zf.read('xl/cellimages.xml')
            root = ET.fromstring(xml_data)

            id_to_rid = {}
            for pic in root.findall('.//xdr:pic', namespaces=ns):
                cnv_pr = pic.find('.//xdr:cNvPr', namespaces=ns)
                if cnv_pr is not None:
                    img_id = cnv_pr.get('name')          # 如 ID_75BC16877A1847729B2E098D0D27FECE
                    if img_id and img_id.startswith('ID_'):
                        blip = pic.find('.//a:blip', namespaces=ns)
                        if blip is not None:
                            rid = blip.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed')
                            if rid:
                                id_to_rid[img_id] = rid

            # ==================== 2. 解析 cellimages.xml.rels ====================
            rels_ns = {'': 'http://schemas.openxmlformats.org/package/2006/relationships'}
            rels_data = zf.read('xl/_rels/cellimages.xml.rels')
            rels_root = ET.fromstring(rels_data)

            rid_to_target = {}
            for rel in rels_root.findall('Relationship', namespaces=rels_ns):
                rid = rel.get('Id')
                target = rel.get('Target')
                if rid and target and target.startswith('media/'):
                    rid_to_target[rid] = target

            # ==================== 3. 构建最终映射 ====================
            for img_id, rid in id_to_rid.items():
                if rid in rid_to_target:
                    media_path = f"xl/{rid_to_target[rid]}"
                    if media_path in zf.namelist():
                        img_bytes = zf.read(media_path)
                        pil_img = PIL_Image.open(io.BytesIO(img_bytes))
                        mapping[img_id] = pil_img
                        print(f"&#10003; 映射成功: {img_id} → {rid_to_target[rid]}")
    except Exception as e:
        print(f"解析图片映射出错: {e}")
    return mapping


def replace_dispimg(input_path):
    """核心处理函数:替换 dispimg 为真实图片"""
    if not os.path.exists(input_path):
        raise FileNotFoundError("文件不存在")

    mapping = get_dispimg_mapping(input_path)
    if not mapping:
        print("&#9888; 未找到 dispimg 图片,直接复制原文件")
        dir_name = os.path.dirname(input_path)
        base = os.path.basename(input_path)
        name, ext = os.path.splitext(base)
        output_path = os.path.join(dir_name, f"{name}_结果{ext}")
        import shutil
        shutil.copy2(input_path, output_path)
        return output_path

    # 加载工作簿(必须 data_only=False 才能读到公式)
    wb = openpyxl.load_workbook(input_path, data_only=False)

    replaced_count = 0
    for ws in wb.worksheets:
        print(f"处理工作表: {ws.title}")
        for row in ws.iter_rows():
            for cell in row:
                if not isinstance(cell.value, str):
                    continue
                formula = cell.value.strip()

                # ==================== 关键正则(已适配真实 WPS 公式) ====================
                # 支持: =DISPIMG("ID_xxx",1)   =DISPIMG("ID_xxx";1)   带空格、带@ _xlfn. 等
                match = re.search(
                    r'DISPIMG\s*\(\s*["\']?([^"\',;)]+)["\']?\s*[,;]?\s*\d*\s*\)',
                    formula,
                    re.IGNORECASE
                )
                if match:
                    img_id = match.group(1).strip()
                    print(f"&#128269; 发现 dispimg: {cell.coordinate} → {img_id}")

                    if img_id in mapping:
                        # 1. 清空原公式(防止 Excel 显示 #NAME?)
                        cell.value = None

                        # 2. 插入真实图片(自动成为 Excel 标准浮动图片)
                        pil_img = mapping[img_id]
                        img_obj = Image(pil_img)

                        # 可选:让图片自动适应单元格大小(推荐打开)
                        # img_obj.width = int(ws.column_dimensions[cell.column_letter].width * 7.5) if ws.column_dimensions[cell.column_letter].width else 100
                        # img_obj.height = int(ws.row_dimensions[cell.row].height * 1.3) if ws.row_dimensions[cell.row].height else 100

                        ws.add_image(img_obj, cell.coordinate)
                        replaced_count += 1
                        print(f"&#9989; 已替换: {cell.coordinate}")
                    else:
                        print(f"&#9888; 未找到图片: {img_id}")

    # 保存结果文件
    dir_name = os.path.dirname(input_path)
    base = os.path.basename(input_path)
    name, ext = os.path.splitext(base)
    output_path = os.path.join(dir_name, f"{name}_结果{ext}")

    wb.save(output_path)
    print(f"\n&#127881; 处理完成!共替换 {replaced_count} 张图片\n结果文件:{output_path}")
    return output_path


# ====================== GUI(支持拖入 + 按钮选择) ======================
class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("WPS dispimg 一键转 Excel 标准图片工具")
        self.geometry("680x480")
        self.resizable(False, False)

        tk.Label(self, text="WPS 嵌入单元格图片修复工具", font=("微软雅黑", 16, "bold")).pack(pady=15)

        self.drop_label = tk.Label(
            self,
            text="拖入 XLSX 文件到这里\n或点击下方「选择文件」",
            font=("微软雅黑", 14),
            bg="#f0f0f0",
            fg="#333",
            width=60,
            height=12,
            relief="solid",
            bd=3
        )
        self.drop_label.pack(pady=10)

        btn_frame = tk.Frame(self)
        btn_frame.pack(pady=10)
        tk.Button(btn_frame, text="选择文件", font=("微软雅黑", 12), width=18, height=2,
                  command=self.select_file).pack(side=tk.LEFT, padx=15)
        tk.Button(btn_frame, text="退出", font=("微软雅黑", 12), width=18, height=2,
                  command=self.quit).pack(side=tk.LEFT, padx=15)

        self.status = ttk.Progressbar(self, mode='indeterminate')
        self.status.pack(fill=tk.X, padx=30, pady=8)

        self.info = tk.Label(self, text="", fg="green", font=("微软雅黑", 11), wraplength=600)
        self.info.pack(pady=5)

        # 尝试启用拖放(可选)
        try:
            from tkinterdnd2 import DND_FILES, TkinterDnD
            self.drop_label.drop_target_register(DND_FILES)
            self.drop_label.dnd_bind('<<Drop>>', self.on_drop)
            self.drop_label.config(text="支持直接拖入 XLSX 文件\n(已启用拖放功能)")
        except ImportError:
            self.drop_label.config(text="拖入功能需额外安装:\npip install tkinterdnd2\n\n或直接点击下方按钮选择")

    def on_drop(self, event):
        file_path = event.data.strip('{}')
        self.process_file(file_path)

    def select_file(self):
        file_path = filedialog.askopenfilename(
            title="选择含 dispimg 的 WPS 文件",
            filetypes=[("Excel 文件", "*.xlsx *.xlsm")]
        )
        if file_path:
            self.process_file(file_path)

    def process_file(self, file_path):
        if not file_path.lower().endswith(('.xlsx', '.xlsm')):
            messagebox.showerror("错误", "请选择 .xlsx 或 .xlsm 文件!")
            return

        self.info.config(text="正在解析并替换 dispimg,请稍候...", fg="blue")
        self.status.start()

        try:
            result_path = replace_dispimg(file_path)
            self.status.stop()
            self.info.config(text=f"&#9989; 处理完成!\n{os.path.basename(result_path)}", fg="green")
            messagebox.showinfo("成功", f"已生成结果文件:\n{os.path.basename(result_path)}\n\n原文件未被修改")
        except Exception as e:
            self.status.stop()
            self.info.config(text="&#10060; 处理失败", fg="red")
            messagebox.showerror("错误", f"处理失败:{str(e)}")


if __name__ == "__main__":
    print("=== WPS dispimg 修复工具 ===")
    print("依赖安装命令:")
    print("pip install openpyxl pillow")
    print("(可选拖放)pip install tkinterdnd2\n")
    app = App()
    app.mainloop()

安装依赖:Bashpip install openpyxl pillow
把上面的代码保存为 wps_dispimg_fix.py,双击运行(或 python wps_dispimg_fix.py)。
把你的 .xlsx 文件拖入窗口灰色区域(或点击「选择文件」)。
程序自动在同目录生成 原文件名_结果.xlsx。
Crows 发表于 2026-4-5 04:52
能提取图片那再提取图片位置遍历找到删除dispimg()然后用openpyxl插入图片应该就行了
 楼主| 人心所向 发表于 2026-4-5 09:14
Crows 发表于 2026-4-5 04:52
能提取图片那再提取图片位置遍历找到删除dispimg()然后用openpyxl插入图片应该就行了

就是这一步有问题,图片映射能找到,但是ai貌似无法识别到dispimg函数,deepseek+豆包都失败了
 楼主| 人心所向 发表于 2026-4-5 10:49
老梁不说话 发表于 2026-4-5 09:58
import os
import re
import zipfile

这个代码,可以,不错,虽然有点小BUG,但是整体能解决
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-24 13:08

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表