You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用openpyxl或底层XML快速检测含文本的Excel形状?

快速检测Excel工作簿中的文本形状(文本框/自选图形)

问题背景

手里有数千个Excel工作簿,需要筛选出包含文本框、自选图形这类带文本形状的文件,但用xlwings和pywin32遍历形状的速度太慢,想通过openpyxl或直接解析XML来提速。

原慢代码(中文注释版):

for shape in ws_xlwings.shapes:
    if shape.type == "text_box" or shape.type == "auto_shape":
        try:
            str_cell_value = shape.text.lower()
            str_cell_value_upper = shape.text
        except: # 形状可能没有文本内容
            continue

方案一:用openpyxl快速检测

openpyxl处理xlsx/xlsm格式文件时,可通过只读模式减少内存占用,直接读取工作表的形状相关XML节点,大幅提升检测速度。

实现步骤

  • 遍历目标文件夹下的所有xlsx/xlsm文件
  • 以只读模式加载工作簿,避免加载不必要的格式信息
  • 检查每个工作表是否存在绘图对象(包含形状),若存在则解析形状的文本内容
  • 只要检测到带非空文本的文本框/自选图形,立即标记该文件符合条件,无需继续遍历

示例代码

import os
from openpyxl import load_workbook

def has_text_shape(file_path):
    try:
        # 只读模式加载,降低内存占用并提升速度
        wb = load_workbook(file_path, read_only=True, data_only=True)
        for ws in wb.worksheets:
            # 跳过无绘图对象的工作表
            if ws.drawing is None:
                continue
            # 获取形状对应的XML元素
            drawing_root = ws.drawing._element
            # 遍历所有自选图形节点
            for shape in drawing_root.findall(".//{http://schemas.openxmlformats.org/drawingml/2006/main}sp"):
                # 检查形状是否包含文本内容节点
                tx_body = shape.find("{http://schemas.openxmlformats.org/drawingml/2006/main}txBody")
                if tx_body is not None:
                    # 检测是否存在非空文本
                    text_nodes = tx_body.findall(".//{http://schemas.openxmlformats.org/drawingml/2006/main}t")
                    if any(node.text and node.text.strip() for node in text_nodes):
                        return True
        return False
    except Exception as e:
        print(f"处理文件 {file_path} 出错: {e}")
        return False

# 遍历目标文件夹生成清单
target_folder = "你的目标文件夹路径"
result_list = []

for root, _, files in os.walk(target_folder):
    for file in files:
        if file.lower().endswith((".xlsx", ".xlsm")):
            full_path = os.path.join(root, file)
            if has_text_shape(full_path):
                result_list.append(full_path)

# 保存结果到文本文件
with open("带文本形状的工作簿清单.txt", "w", encoding="utf-8") as f:
    f.write("\n".join(result_list))

方案二:直接解析底层XML(最快)

xlsx/xlsm本质是压缩包,可直接读取内部的XML文件,跳过Excel库的上层封装,速度是三种方案中最快的,适合处理海量文件。

实现步骤

  • 用zipfile直接读取压缩包内的XML文件,无需完全解压
  • 检查工作表XML是否引用绘图文件,若引用则读取对应的绘图XML
  • 在绘图XML中查找带文本内容的sp(自选图形)和txbx(文本框)节点
  • 只要找到非空文本的形状,立即标记文件符合条件

示例代码

import os
import zipfile
from xml.etree import ElementTree as ET

# 定义XML命名空间,避免节点查找出错
ns_map = {
    "dml": "http://schemas.openxmlformats.org/drawingml/2006/main",
    "ss": "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
    "r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
}

def has_text_shape_xml(file_path):
    try:
        with zipfile.ZipFile(file_path, "r") as zf:
            # 获取所有工作表XML文件
            sheet_files = [f for f in zf.namelist() if f.startswith("xl/worksheets/sheet") and f.endswith(".xml")]
            for sheet_file in sheet_files:
                # 读取工作表XML,检查是否有绘图引用
                with zf.open(sheet_file) as f:
                    sheet_tree = ET.parse(f)
                    drawing_ref = sheet_tree.getroot().find(".//ss:drawing", ns_map)
                    if drawing_ref is None:
                        continue
                    # 获取绘图文件的关系ID
                    drawing_rel_id = drawing_ref.attrib[f"{{{ns_map['r']}}}id"]
                    # 读取工作表的关系文件,找到绘图文件路径
                    rels_file = sheet_file.replace("worksheets", "worksheets/_rels") + ".rels"
                    with zf.open(rels_file) as rels_f:
                        rels_tree = ET.parse(rels_f)
                        drawing_elem = rels_tree.getroot().find(f".//*[@Id='{drawing_rel_id}']")
                        if drawing_elem is None:
                            continue
                        drawing_path = drawing_elem.attrib["Target"]
                        # 拼接绘图文件的完整路径
                        full_drawing_path = f"xl/worksheets/{drawing_path}" if ".." not in drawing_path else f"xl/{drawing_path}"
                        # 读取绘图XML,查找带文本的形状
                        with zf.open(full_drawing_path) as drw_f:
                            drw_tree = ET.parse(drw_f)
                            # 遍历自选图形和文本框节点
                            for shape in drw_tree.getroot().findall(".//dml:sp", ns_map) + drw_tree.getroot().findall(".//dml:txbx", ns_map):
                                tx_body = shape.find("dml:txBody", ns_map)
                                if tx_body is not None:
                                    text_nodes = tx_body.findall(".//dml:t", ns_map)
                                    if any(node.text and node.text.strip() for node in text_nodes):
                                        return True
        return False
    except Exception as e:
        print(f"处理文件 {file_path} 出错: {e}")
        return False

# 生成结果清单
target_folder = "你的目标文件夹路径"
result_list = []

for root, _, files in os.walk(target_folder):
    for file in files:
        if file.lower().endswith((".xlsx", ".xlsm")):
            full_path = os.path.join(root, file)
            if has_text_shape_xml(full_path):
                result_list.append(full_path)

with open("带文本形状的工作簿清单.txt", "w", encoding="utf-8") as f:
    f.write("\n".join(result_list))

提速关键说明

  • 只读模式:openpyxl的只读模式会跳过格式渲染,只加载必要数据,降低内存占用
  • 提前终止:只要在某工作表中找到符合条件的形状,立即返回结果,无需遍历所有内容
  • 直接解析XML:完全绕开Excel库的封装,直接操作原始数据,速度最优

内容的提问来源于stack exchange,提问作者Hooded 0ne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 23:47:36