如何用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
相关产品推荐
相关产品推荐

