【操作记录】Python提取WPS内嵌单元格图片

背景
最近项目测试中,需要处理一枇图片数据。但是其他同事提供的是内嵌到WPS单元格内的图片,直接提取单元格内容,只能获取到形如“=DISPIMG(“ID_8646994B7F0E4E2CBDEF2BBD89791AC2”,1)”的数据,拉不到实际的图片数据。
一番搜索,发现这是wps才有的配置,将我们的.xlsx文件转成.zip后,可以发现,其中文件格式如下:
.xlsx文件内部结构
如上图所示,我们只要取出三个文档,就可以获得 单元格图片id->图片id->image图片
需要的三个文档分别为:
xl/cellimages.xml
xl/_rels/cellimages.xml.rels
xl/media/[image_name]
cellimages.xml
cellimages.xml.rels
故,此处可以通过下述代码完成id的转换。

import zipfile
import os
import xml.etree.ElementTree as ET
import openpyxl
import logging
import pictUpload

# 设置日志记录
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
pictcow = '变更后图片'
travelcow = '游历卡ID'
def extract_image_id(formula):
    """
    从公式中提取 image_id。
    支持提取多个 image_id,并处理异常情况。
    """
    image_ids = []
    start = 0
    while True:
        start_pos = formula.find('=_xlfn.DISPIMG(', start)
        if start_pos == -1:
            break
        end_pos = formula.find(')', start_pos)
        if end_pos == -1:
            break
        substring = formula[start_pos:end_pos]
        quote_start = substring.find('"')
        if quote_start == -1:
            start = end_pos + 1
            continue
        quote_end = substring.find('"', quote_start + 1)
        if quote_end == -1:
            start = end_pos + 1
            continue
        image_id = substring[quote_start + 1:quote_end]
        image_ids.append(image_id)
        start = end_pos + 1
    return image_ids


def read_excel_data(filename_path, sheet_name=None):
    try:
        workbook = openpyxl.load_workbook(filename_path, data_only=False)
    except FileNotFoundError:
        logging.error(f"Error: The file '{filename_path}' was not found.")
        return [], []
    except openpyxl.utils.exceptions.InvalidFileException:
        logging.error(f"Error: The file '{filename_path}' is not a valid Excel file.")
        return [], []

    try:
        if sheet_name is not None:
            sheet = workbook[sheet_name]
        else:
            sheet = workbook.active
    except KeyError:
        logging.error(f"Error: The sheet '{sheet_name}' does not exist in the workbook.")
        return [], []

    image_list = []
    data = []
    image_id_map = {}

    # 获取列索引
    header_row = next(sheet.iter_rows(min_row=1, max_row=1, values_only=True))
    try:
        image_col_index = header_row.index(pictcow)
        id_col_index = header_row.index(travelcow)
    except ValueError as e:
        logging.error(f"Error: Column not found in the sheet. {e}")
        return [], []

    for row in sheet.iter_rows(min_row=2, values_only=True):
        row_data = []
        image_ids = []
        card_id = None

        for col_index, cell_value in enumerate(row):
            if col_index == image_col_index and isinstance(cell_value, str) and '=_xlfn.DISPIMG(' in cell_value:
                image_ids.extend(extract_image_id(cell_value))
            elif col_index == id_col_index:
                card_id = cell_value
            row_data.append(cell_value)

        data.append(row_data)
        image_list.extend(image_ids)
		#存储一个 card_id 和image_id的map,方便后面处理数据
        if card_id and image_ids:
            image_id_map[card_id] = image_ids[0]  # 假设每个游历卡ID对应一个image_id

    return data, image_list, image_id_map

#获取excel文件中图片id和图片路径的映射关系
def get_xml_id_image_map(xlsx_file_path):
    try:
        with zipfile.ZipFile(xlsx_file_path, 'r') as zfile:
            with zfile.open('xl/cellimages.xml') as file:
                xml_content = file.read()
            with zfile.open('xl/_rels/cellimages.xml.rels') as file:
                relxml_content = file.read()
    except (zipfile.BadZipFile, FileNotFoundError) as e:
        logging.error(f"Error: {e}")
        return {}

    root = ET.fromstring(xml_content)
    namespaces = {
        'xdr': 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing',
        'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'
    }

    name_to_embed_map = {}
    for pic in root.findall('.//xdr:pic', namespaces=namespaces):
        name = pic.find('.//xdr:nvPicPr/xdr:cNvPr', namespaces=namespaces).attrib['name']
        embed = pic.find('.//xdr:blipFill/a:blip', namespaces=namespaces).attrib[
            '{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed']
        name_to_embed_map[name] = embed

    root1 = ET.fromstring(relxml_content)
    namespaces = {'r': 'http://schemas.openxmlformats.org/package/2006/relationships'}

    id_target_map = {child.attrib['Id']: child.attrib.get('Target', 'No Target Found') for child in
                     root1.findall('.//r:Relationship', namespaces=namespaces)}

    name_to_target_map = {name: id_target_map.get(embed, 'No Target Found') for name, embed in
                          name_to_embed_map.items()}
    return name_to_target_map

#保存图片到指定目录,并获取excel单元格图片id与真实图片路径的映射关系
def save_images(xlsx_file_path, output_directory, new_map):
    final_map = {}
    try:
        with zipfile.ZipFile(xlsx_file_path, 'r') as zfile:
            zip_contents = set(zfile.namelist())

            for key, image_path in new_map.items():
                actual_image_path = f'xl/{image_path}'
                if actual_image_path in zip_contents:
                    try:
                        with zfile.open(actual_image_path) as image_file:
                            image_content = image_file.read()
                            new_file_path = os.path.join(output_directory, f"{key}.png")
                            with open(new_file_path, 'wb') as new_file:
                                new_file.write(image_content)
                        # logging.info(f"Saved image {key} to {new_file_path}")
                        final_map[key] = new_file_path
                    except Exception as e:
                        logging.error(f"Error saving image {key}: {e}")

                else:
                    logging.error(f"File {actual_image_path} (for key {key}) not found in the archive.")
    except (zipfile.BadZipFile, FileNotFoundError) as e:
        logging.error(f"Error: {e}")

    # logging.info(f"Final Image Map: {final_map}")
    return final_map


def output_id_image(xlsx_file_path, output_directory,sheet_name):
    if not os.path.exists(xlsx_file_path):
        logging.error(f"File {xlsx_file_path} does not exist.")
        return

    if not os.path.exists(output_directory):
        os.makedirs(output_directory)

    try:
        data, image_list, image_id_map = read_excel_data(xlsx_file_path, sheet_name)
        name_to_target_map = get_xml_id_image_map(xlsx_file_path)
    except Exception as e:
        logging.error(f"Error occurred while processing Excel or XML data: {e}")
        return

    new_map = {key: name_to_target_map.get(key, 'No Target Found') for key in image_list}
    # logging.info(f"new Image Map: {new_map}")

    final_map = save_images(xlsx_file_path, output_directory, new_map)

    # 生成新的 map{key = image_id_map[key]: value=final_map[value]}
    new_combined_map = {}
    for card_id, image_id in image_id_map.items():
        if image_id in final_map:
            new_combined_map[card_id] = pictUpload.upload_image(final_map[image_id])

    logging.info(f"New Combined Map: {new_combined_map}")
    return new_combined_map

上述代码中,由于我需要获得excel中单独一列card_id和图片上传服务端后返回的图片id对应map表。故,我先调用save_images函数,获取excel单元格图片id与真实图片路径的映射关系,又调用了一个单独的文件上传接口pictUpload.upload_image(),获取图片上传后接口返回的pict_id,生成一个新的map new_combined_map。
over!

### 使用Python脚本读取和编辑WPS云文档 为了实现通过Python脚本来操作WPS在线表格的功能,可以利用已有的C++接口被封装成的Python库来完成这一目标。这使得开发者可以通过简单的Python代码调用来执行复杂的数据处理任务。 #### 准备工作 确保本地计算机上已经安装并配置好了适合版本的Python环境[^3]。对于想要简化依赖管理和项目隔离工作的用户来说,推荐采用Anaconda作为管理工具,并搭配Visual Studio Code这样的集成开发环境进行编码实践。 #### 加载应用程序实例 在Python环境中引入必要的模块之后,创建一个指向WPS应用对象的应用程序实例是首要步骤之一。此过程涉及到使用`comtypes.client.CreateObject()`函数或Pywin32中的相应方法去初始化代表WPS主程序的对象: ```python import comtypes.client app = comtypes.client.CreateObject('kwps.Application') # 或者 'et' 对于仅限电子表格 ``` 这段代码会启动一个新的WPS进程并将控制权交给变量`app`所指代的对象[^4]。 #### 打开现有文件或将新文件上传至云端 假设有一个位于本地磁盘上的.xlsx格式的工作簿需要打开或者打算新建一个空白文档保存到指定位置,则可通过如下方式达成目的: ```python workbook_path = r"C:\path\to\your\file.xlsx" if workbook_exists(workbook_path): wb = app.Workbooks.Open(workbook_path) else: wb = app.Workbooks.Add() wb.SaveAs(workbook_path) def workbook_exists(path): try: with open(path, 'r'): pass return True except FileNotFoundError: return False ``` 这里定义了一个辅助性的判断函数用于检测给定路径下是否存在对应的Excel文件;如果存在则直接加载该文件进入内存以便后续修改;反之则先建立新的Workbook再设定其存储地址。 #### 编辑单元格内容 一旦获得了对特定Sheet页面的操作权限后就可以自由地向其中写入数据或是提取已有信息了。下面的例子展示了怎样设置A1单元格内的文字以及获取B列第2行处的内容: ```python sheet = wb.Sheets['Sheet1'] cell_a1 = sheet.Range("A1") cell_b2 = sheet.Cells(2, "B") cell_a1.Value = "Hello WPS!" print(cell_b2.Text.strip()) ``` 上述片段里不仅示范了基本的文字赋值语法还包含了从选定区域读取出字符串形式的结果展示。 #### 关闭资源释放 当所有的编辑动作完成后记得关闭当前正在使用的WorkBook并且退出整个Office套件以免造成不必要的系统负担: ```python wb.Close(SaveChanges=True) # 如果不需要保留更改可传False参数 app.Quit() # 终止运行中的WPS进程 ``` 以上就是关于如何借助Python编程语言配合官方提供的COM接口来操纵WPS Office内嵌组件的一些基础介绍。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值