如何用Python2.7自动检测ArcGIS/xlsxwriter/SpatiaLite生成的损坏Excel文件
自动检测异常Excel文件的方案
问题背景
在ArcGIS 10.6中通过Python工具箱,使用xlsxwriter从SpatiaLite数据库生成带数据和图片的Excel报表,运行环境为Excel 10 + Python 2.7。多数报表正常,但部分文件打开时会触发错误,提示需移除/xl/workbook.xml中的命名范围,Excel需恢复数据才能打开,修复后内容无异常。
已做排查:
- 对比正常/异常报表数据,未发现差异;
- 解压Excel查看XML,肉眼未发现问题;
- 注释打印区域、分页符相关代码后,错误仍存在;
- 基于xlrd的检测脚本(如下)显示所有文件正常,但实际部分打开报错:
def test_book(filename): try: book = open_workbook(filename) try: sheet = book.sheet_by_index(0) b6 = sheet.cell_value(rowx=5, colx=1) #b6 an internal code print "'"+ str(sheet.name) + "'," return True except XLRDError: return False print "error" except Exception as e: return False print "error"
补充异常文件的workbook.xml内容:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><fileVersion appName="xl" lastEdited="4" lowestEdited="4" rupBuild="4505"/><workbookPr defaultThemeVersion="124226"/><bookViews><workbookView xWindow="240" yWindow="15" windowWidth="16095" windowHeight="9660"/></bookViews><sheets><sheet name="R7MB22" sheetId="1" r:id="rId1"/></sheets><definedNames><definedName name="_xlnm.Print_Area" localSheetId="0">R7MB22!$A$1:$H$137</definedName><definedName name="_xlnm.Print_Titles" localSheetId="0">R7MB22!$1:$2</definedName></definedNames><calcPr calcId="124519" fullCalcOnLoad="1"/></workbook>
核心需求:批量检测300+周期性生成的报表,自动识别打开会报错的文件。
问题根源
从报错提示和异常XML来看,问题出在命名范围的localSheetId与工作表sheetId不匹配:
- 异常文件中,命名范围的
localSheetId="0",但工作表的sheetId="1",Excel 10对这种ID映射不兼容的情况判定为文件损坏; - xlrd仅校验文件是否可读取,不会检查XML结构的逻辑合法性,因此无法检测出这类问题。
自动检测方案
方案1:解析Excel的XML结构校验
Excel本质是ZIP压缩包,直接读取xl/workbook.xml,校验命名范围的localSheetId是否存在对应的工作表sheetId:
import zipfile import xml.etree.ElementTree as ET def check_excel_named_ranges(filename): # 定义XML命名空间 ns = {'ss': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'} try: with zipfile.ZipFile(filename, 'r') as zip_file: # 读取workbook.xml文件 with zip_file.open('xl/workbook.xml') as xml_file: tree = ET.parse(xml_file) root = tree.getroot() # 收集所有工作表的sheetId sheet_ids = [] sheets = root.findall('.//ss:sheet', ns) for sheet in sheets: sheet_id = sheet.get('sheetId') sheet_ids.append(sheet_id) # 检查每个命名范围的localSheetId是否有效 defined_names = root.findall('.//ss:definedName', ns) for dn in defined_names: local_sheet_id = dn.get('localSheetId') if local_sheet_id and local_sheet_id not in sheet_ids: print(f"[异常] {filename}:命名范围localSheetId={local_sheet_id}无对应工作表") return False return True except Exception as e: print(f"[错误] {filename}:读取失败 - {str(e)}") return False
使用时遍历所有Excel文件调用该函数,返回False的即为异常文件。该方法速度快,无需依赖Excel环境。
方案2:利用Excel COM组件检测(Windows环境适用)
通过Excel自身的COM接口打开文件,模拟实际用户打开场景,捕获是否需要修复文件,准确性最高:
import win32com.client import os def check_excel_with_com(filename): excel_app = None try: # 初始化Excel应用 excel_app = win32com.client.Dispatch("Excel.Application") excel_app.Visible = False excel_app.DisplayAlerts = False # 以修复模式打开文件 workbook = excel_app.Workbooks.Open(filename, CorruptLoad=1) # 检查是否进入修复模式 if workbook.RepairMode: print(f"[异常] {filename}:Excel检测到损坏并修复") result = False else: result = True workbook.Close(SaveChanges=False) return result except Exception as e: print(f"[错误] {filename}:打开失败 - {str(e)}") return False finally: # 确保退出Excel进程 if excel_app: excel_app.Quit()
注意:需要安装pywin32库(执行pip install pywin32),批量检测时速度较慢,但结果最贴近实际打开情况。
额外修复建议
若要从根源解决问题,修改xlsxwriter生成报表的代码:
- 确保设置打印区域/标题时,
localSheetId与工作表的sheetId一致; - 直接通过xlsxwriter的工作表对象设置打印区域(如
worksheet.set_print_area('A1:H137')),库会自动处理ID映射,避免手动设置导致的不匹配。
内容的提问来源于stack exchange,提问作者nanunga
相关产品推荐
相关产品推荐

