使用openpyxl保存xlsx后Excel显示值与底层XML不一致求助
问题背景
使用openpyxl 3.0.7处理xlsx文件,因系统限制无法升级版本。源文件中单元格为文本格式,内容如Data_x4331_Problem(小写x+4位十六进制代码,前后带下划线),Excel和底层XML显示一致,openpyxl读取值正常。但用openpyxl或Pandas保存文件后,Excel中该内容变为Data䌱Problem(x4331被解析为对应Unicode字符),但底层XML仍保留原始值,openpyxl读取保存后的文件也能得到正确结果。大写X的类似内容(如Data_X4331_Problem)无此问题。需求是通过Python脚本保存文件时不修改数据,让Excel显示值与保存前一致。
原因分析
这是Excel的内置解析特性:当文本中出现_x[四位十六进制]_格式(小写x)时,Excel会将其识别为XML字符实体引用,自动解码为对应的Unicode字符。openpyxl 3.0.7未对这种格式做自动转义处理,导致保存后Excel加载时触发了解码逻辑。而大写X的_X[四位十六进制]_不会被Excel识别为实体引用,因此无异常。
解决方案
通过手动转义字符串中的小写x,让Excel无法将_xXXXX_识别为实体引用,具体操作是将_x替换为_x78_(小写x的十六进制实体编码)。这样Excel解析时会把_x78_还原为小写x,后续的四位十六进制不再构成可解析的实体结构,最终显示原始内容。
用openpyxl处理的代码示例
import re from openpyxl import load_workbook # 加载源文件,data_only=False确保读取单元格原始文本而非计算值 wb = load_workbook("source.xlsx", data_only=False) ws = wb.active # 遍历所有单元格,仅处理文本类型内容 for row in ws.iter_rows(values_only=False): for cell in row: if cell.data_type == "s": # 正则匹配_xXXXX_格式并转义 cell.value = re.sub(r'_x([0-9a-fA-F]{4})_', r'_x78_\1_', cell.value) wb.save("output.xlsx")
用Pandas处理的代码示例
import pandas as pd import re # 读取源文件,指定dtype=str确保文本格式不丢失 df = pd.read_excel("source.xlsx", dtype=str) # 遍历所有字符串列进行转义处理 for col in df.select_dtypes(include=["object"]).columns: df[col] = df[col].apply( lambda x: re.sub(r'_x([0-9a-fA-F]{4})_', r'_x78_\1_', x) if isinstance(x, str) else x ) # 保存文件,index=False避免生成多余索引列 df.to_excel("output.xlsx", index=False)
效果说明
转义后的内容保存后,Excel加载时会自动将_x78_解析为小写x,最终显示Data_x4331_Problem,与源文件一致。同时openpyxl读取保存后的文件时,会自动解析实体编码为原始字符,读取值仍为Data_x4331_Problem,完全符合需求。
内容的提问来源于stack exchange,提问作者DavidA

