ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

xlrd与xlwt读写Excel:从.xls读取到样式保留的完整实践

xlrd与xlwt读写Excel:从.xls读取到样式保留的完整实践 在 Python 自动化办公的任务清单里读写 Excel 是很日常的能力。xlrd和xlwt是两个出现得比较早的库分别负责读取和写入.xls格式的 Excel 文件当项目里不打算引入重量级数据分析组件时它们仍然值得掌握。这篇文章是《100 天精通 Python》系列第 41 天的内容目标是围绕xlrd的读取参数、xlwt的写入参数、xlutils的复制修改这一条完整链路做一次代码级梳理。读完可以独立处理“读取老 Excel、生成新 Excel、模板回填并保留样式”这类任务也能够在遇到格式不支持、单元格覆盖失败、日期显示成数字等问题时快速定位。1. 先搞清楚 xlrd 和 xlwt 的定位与版本边界1.1 Excel 文件格式决定了 xlrd/xlwt 只能处理 xlsExcel 文件常见有两种形态旧版.xls和新版.xlsx。.xls本质是二进制的 BIFF 文件.xlsx则是一个基于 ZIP 压缩的 XML 包。xlrd与xlwt诞生于.xls时代所以它们对.xlsx的支持很有限xlwt更是从一开始就是只能写.xls不能写.xlsx。这一点必须放在选型之前理解因为网上很多旧教程的示例代码都基于较早的xlrd 1.2.0版本那个版本可以直接读取.xlsx。后来xlrd在2.0.0版本中移除了对.xlsx的支持只保留.xls读取能力。如果直接安装最新版xlrd去读一个.xlsx会得到类似下面的报错xlrd.biffh.XLRDError: Excel xlsx file; not supported所以在开始写代码之前第一件事是确认手里要处理的文件到底是.xls还是.xlsx。库默认支持格式能读能写常见需求xlrd.xls支持不支持读取老 Excel 内容xlwt.xls不支持支持创建 .xls 文件xlutils.xls基于 xlrd 读基于 xlwt 写复制原文件后再修改openpyxl.xlsx支持支持处理新格式pandas.xls/.xlsx支持支持数据分析与转换1.2 xlrd 2.0 版本变化带来的常见误区很多老教程会写import xlrd wb xlrd.open_workbook(data.xlsx) sheet wb.sheet_by_index(0)这套代码在xlrd 1.2.0里能跑在xlrd 2.0.x里却会报格式不支持。这里有一个很容易让人迷惑的点网上资料可能同时存在“xlrd 能读 xlsx”和“xlrd 不能读 xlsx”两种说法它们都没有错差别在版本。如果目标只是读取.xls使用当前最新版没有任何问题。如果项目里确实既要用xlrd又要读.xlsx那只能选择低于2.0.0的版本例如pip install xlrd1.2,2.1但我不建议因为老教程而把整个项目都降级。更合理的做法是.xlsx交给openpyxl或pandas.xls才交给xlrd。版本隔离比牺牲兼容性更安全。1.3 什么时候选 xlrd/xlwt什么时候换 openpyxl判断标准不复杂手头全是遗留系统导出的.xls文件只需要做内容提取。公司内部模板固定为.xls并且要求不改变格式。学习阶段想理解底层单元格读写的细节。需要读写的文件是.xlsx。需要做透视、筛选、分组、聚合等数据分析操作。文件量很大或者需要严格控制内存。前三种情况可以继续使用xlrd/xlwt这套方案后三种建议直接切换。它们不是同一个时代下的替代品而是针对不同格式和场景的工具。2. 环境准备用虚拟环境隔离避免全局依赖污染2.1 创建项目目录和虚拟环境无论做自动化小脚本还是正式项目都不建议直接往系统 Python 环境里装包。这里先创建一个独立目录mkdir python-excel-demo cd python-excel-demo然后创建虚拟环境。Linux 和 macOS 上执行python3 -m venv venv source venv/bin/activateWindows 上执行python -m venv venv venv\Scripts\activate激活后看到命令行前面出现(venv)说明当前已经在虚拟环境里。接下来安装三个库pip install xlrd xlwt xlutils安装完成后确认版本pip show xlrd pip show xlwt pip show xlutils在常见的新版本 Python 环境中安装下来的xlrd通常是2.0.1xlwt通常是1.3.0xlutils通常是2.0.0。具体版本以自己的输出为准重点是确认它们已经进入当前虚拟环境而不是安装在系统环境里。2.2 准备测试文件并验证依赖可用没有测试文件时可以先写一个最小脚本确认三个库能正常导入import xlrd import xlwt import xlutils print(xlrd:, xlrd.__version__) print(xlwt:, xlwt.__version__)如果没有任何报错说明依赖可用。随后可以准备一个测试用的.xls文件。最直接的方式是用xlwt现场生成一个import xlwt wb xlwt.Workbook(encodingutf-8) sheet wb.add_sheet(成绩单) sheet.write(0, 0, 姓名) sheet.write(0, 1, 语文) sheet.write(0, 2, 数学) sheet.write(1, 0, 张三) sheet.write(1, 1, 90) sheet.write(1, 2, 95) wb.save(test.xls)这段代码会生成一个最简单的.xls文件。后面所有读取示例都可以基于这个文件运行。注意测试文件一定不要直接从.xlsx重命名成.xls。改名只是改变文件后缀内部结构仍然是 ZIP XML不能解决问题。遇到.xlsx时要么用 Excel 另存为.xls要么换用openpyxl读取。3. xlrd 读取 Excel核心 API、参数说明和验证方法3.1 open_workbook 常用参数的含义xlrd的入口是open_workbook它的常用参数如下参数含义默认值使用注意filename要打开的 .xls 文件路径无路径不存在时抛 FileNotFoundErrorfile_contents文件字节内容None适合需要先读入内存的场景formatting_info是否加载格式信息False做样式复制时一般要设为 Trueon_demand是否按需加载 sheetFalse对超大文件可以降低启动耗时ragged_rows是否允许每行长度不一致False遇到空行较多的表格可设为 True最简单的读取方式import xlrd wb xlrd.open_workbook(test.xls) print(sheet 名称列表:, wb.sheet_names()) sheet wb.sheet_by_index(0) print(sheet 名称:, sheet.name) print(行数:, sheet.nrows) print(列数:, sheet.ncols)如果清楚 sheet 的名字也可以直接用sheet wb.sheet_by_name(成绩单)这里要注意sheet_names()返回的是列表sheet 名称需要精确匹配包含空格和中文时不要额外处理。3.2 单行、单列、单个单元格的读取方式拿到 sheet 对象后常用方法有四个row_values、col_values、cell_value、cell。row_data sheet.row_values(1) print(第 2 行数据:, row_data) col_data sheet.col_values(1) print(第 2 列数据:, col_data) name sheet.cell_value(1, 0) print(第 2 行第 1 列:, name) cell sheet.cell(1, 1) print(单元格类型:, cell.ctype) print(单元格内容:, cell.value)整表遍历可以这样写for row in range(sheet.nrows): values sheet.row_values(row) print(row, values)row_values返回的是列表适合直接展示或写入其他容器。cell返回的是Cell对象多一个ctype属性用于判断数据类型。ctype对应的类型如下ctype含义0空单元格1文本2数字3日期4布尔值5错误6空白单元格有格式但没有内容3.3 日期、公式、合并单元格的处理方式很多初学者读取日期时拿到的是一串数字这是因为 Excel 日期本质上是一个数值只是通过单元格格式显示成了日期。xlrd读到日期时需要通过工作簿的datemode进行转换import xlrd from xlrd import xldate_as_datetime wb xlrd.open_workbook(test.xls) sheet wb.sheet_by_index(0) cell sheet.cell(2, 2) if cell.ctype 3: dt xldate_as_datetime(cell.value, wb.datemode) print(日期:, dt.strftime(%Y-%m-%d)) else: print(非日期单元格)也可以使用xldate_as_tuple得到年月日时分秒元组from xlrd import xldate_as_tuple if cell.ctype 3: date_tuple xldate_as_tuple(cell.value, wb.datemode) print(date_tuple)关于公式要纠正一个常见认知xlrd不是 Excel 引擎它不会重新计算公式。它读取公式单元格时更多时候拿到的是该单元格上次被 Excel 保存时缓存的计算结果。如果源文件来自程序生成而且没有经过 Excel 打开计算公式对应的缓存值可能是空值。不要依赖xlrd去完成动态公式计算。合并单元格的情况也要单独处理。sheet.merged_cells会返回一段区域信息例如(start_row, end_row, start_col, end_col)的元组print(sheet.merged_cells) for rlow, rhigh, clow, chigh in sheet.merged_cells: value sheet.cell_value(rlow, clow) print(合并区域: 行 %d - %d, 列 %d - %d, 左上角值: %s % (rlow, rhigh, clow, chigh, value))合并单元格通常只有左上角有实际值其他区域读出来是空字符串遍历时要注意。3.4 读取结果不正确时先查这几个地方读不到内容或者读出来不符合预期可以先按顺序检查。文件名是否写错特别是后缀.xls和.xlsx是完全不同的文件。文件路径是否在当前工作目录下建议使用绝对路径或pathlib.Path。sheet 名称是否精确匹配Excel 允许 sheet 名带空格禁止重复。单元格是数字还是文本身份证号、结算单号很容易被 Excel 存成文本但文本型数字在row_values中会以字符串形式出现。公式单元格是否已经缓存值程序生成的 xls 文件如果未用 Excel 打开公式结果可能为空。4. xlwt 写入 Excel从零生成一份带样式的报表4.1 Workbook、add_sheet、write、save 的完整流程xlwt的写入流程比xlrd更直观四步可以完成基础写入创建Workbook。使用add_sheet添加 sheet。使用write写入单元格。使用save保存文件。import xlwt wb xlwt.Workbook(encodingutf-8) ws wb.add_sheet(报表, cell_overwrite_okTrue) ws.write(0, 0, 姓名) ws.write(0, 1, 分数) ws.write(1, 0, 张三) ws.write(1, 1, 89.5) wb.save(result.xls)Workbook(encodingutf-8)中的编码决定的是工作簿内部的文本编码存储方式包含中文内容时建议统一使用utf-8。add_sheet的第二个参数cell_overwrite_ok默认是False。如果同一个单元格被写入两次会抛出异常Exception: Attempt to overwrite cell: sheetname... rowx0 colx0如果写循环时没有把握不会重复可以在创建 sheet 时设置cell_overwrite_okTrue。但在正式代码里我更推荐先规划好行列不要开全局覆盖否则写错位置也可能不报错。write方法不是只能写字符串它会根据 Python 类型自动处理ws.write(0, 0, 文本) ws.write(0, 1, 100) ws.write(0, 2, 3.14) ws.write(0, 3, True) ws.write(0, 4, None) # 会写空值等价于不写内容save只能保存为.xls如果文件路径写成.xlsx实际内容仍然是.xls这会在后续读取时造成误解。为了文件格式严谨建议路径后缀始终使用.xls。4.2 表格宽度、行高、字体、对齐和边框设置如果只是导出程序数据不设置样式也可以。但做报表时表头加粗、居中对齐、列宽合适、边框完整会让文件更容易阅读。这里使用XFStyle对象组合样式。import xlwt wb xlwt.Workbook(encodingutf-8) ws wb.add_sheet(成绩单) header_style xlwt.XFStyle() font xlwt.Font() font.name 微软雅黑 font.height 220 # 11 磅height 的单位是 1/20 磅 font.bold True header_style.font font alignment xlwt.Alignment() alignment.horz xlwt.Alignment.HORZ_CENTER alignment.vert xlwt.Alignment.VERT_CENTER header_style.alignment alignment borders xlwt.Borders() borders.left xlwt.Borders.THIN borders.right xlwt.Borders.THIN borders.top xlwt.Borders.THIN borders.bottom xlwt.Borders.THIN header_style.borders borders pattern xlwt.Pattern() pattern.pattern xlwt.Pattern.SOLID_PATTERN pattern.pattern_fore_colour 22 # 一种浅色背景 header_style.pattern pattern ws.write(0, 0, 姓名, header_style) ws.write(0, 1, 语文, header_style) ws.write(0, 2, 数学, header_style) ws.col(0).width 256 * 12 ws.col(1).width 256 * 10 ws.col(2).width 256 * 10 ws.row(0).height_mismatch True ws.row(0).height 20 * 20 wb.save(styled.xls)列宽的默认单位是“1/256 个字符宽度”。256 * 12表示大约 12 个字符宽度。行高的默认单位是 1/20 磅所以20 * 20表示 20 磅同时要设置height_mismatch True否则某些 Excel 版本可能不会应用自定义行高。如果只想快速写个表头不关心对象化定义也可以用easyxf字符串简写header_style xlwt.easyxf( font: bold on; align: horiz centre, vert centre; borders: left thin, right thin, top thin, bottom thin; )easyxf适合小段样式但可读性和复杂度都不如对象方式只在场景简单时使用。4.3 数字格式、日期和公式的写入写入小数金额时如果希望 Excel 里显示千分位和两位小数需要设置num_format_strmoney_style xlwt.XFStyle() money_style.num_format_str #,##0.00 ws.write(1, 2, 1234567.8, money_style)日期需要使用 Python 的date或datetime类型并配合日期格式import datetime date_style xlwt.XFStyle() date_style.num_format_str YYYY-MM-DD ws.write(2, 0, datetime.date(2024, 1, 15), date_style)这样写出来的单元格在 Excel 中是日期格式不会显示成奇怪的数字。xlwt还可以写入公式ws.write(3, 0, xlwt.Formula(SUM(B2:B10)))公式不会在xlwt中计算结果它只是写入公式字符串。最终文件被 Excel 或 WPS 打开后软件会进行计算用xlrd再读这个文件读到的可能是空值或旧缓存这一点和前面读取部分强调的内容一致。5. 用 xlutils.copy 做“读改写”避免破坏已有格式5.1 为什么需要 copy 模式只读用xlrd只写用xlwt那“打开旧文件改几个单元格再另存为新文件”应该怎么做如果直接用xlwt重新写一个文件原来 Excel 里的样式、页眉、列宽、合并单元格都得重新写一遍成本很高。这时可以使用xlutils.copy它的思路是把xlrd读取的工作簿复制成一个xlwt可以操作的Workbook再在这个副本上修改单元格。import xlrd from xlutils.copy import copy as xlcopy rb xlrd.open_workbook(source.xls, formatting_infoTrue) wb xlcopy(rb) ws wb.get_sheet(0) ws.write(1, 1, 修改后的值) wb.save(target.xls)打开原文件时设置formatting_infoTrue是关键。xlutils.copy需要读取原文件的样式相关信息如果为False复制出来的目标文件可能丢失格式或者在部分版本上抛异常。5.2 最小读改写案例模板回填实际项目里经常有这样的场景有一个设计好的 Excel 空白模板程序只需要往指定单元格里填数据然后另存为最终文件。这种需求非常适合xlutils.copy。先生成一个模板import xlwt wb xlwt.Workbook(encodingutf-8) ws wb.add_sheet(模板, cell_overwrite_okTrue) title_style xlwt.easyxf(font: bold on, height 240; align: horiz centre) ws.write(0, 0, 销售统计, title_style) ws.write(2, 0, 区域) ws.write(2, 1, 销售额) ws.write(3, 0, 华东) ws.write(3, 1, 1000) ws.write(4, 0, 华南) ws.write(4, 1, 2000) ws.col(0).width 256 * 12 ws.col(1).width 256 * 15 wb.save(source.xls)再执行回填import xlrd from xlutils.copy import copy as xlcopy rb xlrd.open_workbook(source.xls, formatting_infoTrue) wb xlcopy(rb) ws wb.get_sheet(0) # 修改“华南”销售额 ws.write(4, 1, 2500) # 在下一行插入一条新记录 ws.write(5, 0, 西南) ws.write(5, 1, 1800) wb.save(target.xls)执行后打开target.xls可以看到原有的结构和样式被保留只有被修改和新增的单元格发生变化。5.3 xlutils.copy 的限制xlutils.copy也有明显限制。它建立在xlrd和xlwt之上所以只能处理.xls不能处理.xlsx。其次它并不是对 Excel 文件的逐字节复制某些图表、宏、图片等复杂元素可能无法完整保留。如果模板中包含大量复杂元素建议先用简单副本测试一遍格式是否完整再进入正式流程。xlutils是一个较早的项目在较新的 Python 版本上如果遇到依赖安装问题可以先确认当前 Python 版本是否受支持。对于生产环境如果业务已经大量使用.xlsx不建议在xlutils这条路上继续深入直接切换到openpyxl会更稳。6. 一个完整小工具把多个 xls 文件汇总成一个报表6.1 需求拆解结合前面的读取和写入知识可以做一个值得收藏的小工具读取指定目录下的所有.xls文件把每个文件第一个 sheet 的非空行汇总到一个大的结果表并在结果表中标注来源文件。这个工具适合处理“每天导出一个小表月末合并成一个大表”的场景。它不依赖pandas只用xlrd和xlwt就能跑通逻辑足够清晰也方便改成其他样式。目录结构merge_excel/ ├── data/ │ ├── 一月.xls │ ├── 二月.xls │ └── 三月.xls ├── merge.py └── output.xls6.2 核心代码import xlrd import xlwt from pathlib import Path def read_all_rows(filename): book xlrd.open_workbook(filename) sheet book.sheet_by_index(0) rows [] for row in range(sheet.nrows): values [sheet.cell_value(row, col) for col in range(sheet.ncols)] # 跳过整行都为空的记录避免把空行导进汇总表 if not any(str(v).strip() for v in values): continue rows.append(values) return rows def main(): data_dir Path(data) out_path Path(output.xls) all_rows [] for xls_file in sorted(data_dir.glob(*.xls)): rows read_all_rows(xls_file) for row in rows: # 第一列右侧追加来源文件名便于追溯 all_rows.append([xls_file.name] row) if not all_rows: print(没有读取到任何数据) return wb xlwt.Workbook(encodingutf-8) ws wb.add_sheet(汇总, cell_overwrite_okTrue) header_style xlwt.easyxf( font: bold on; align: horiz centre, vert centre; borders: left thin, right thin, top thin, bottom thin; ) # 根据原始第一行数据的长度生成表头 header [来源文件] [列%d % (i 1) for i in range(len(all_rows[0]) - 1)] for col, name in enumerate(header): ws.write(0, col, name, header_style) ws.col(col).width 256 * 14 for r_idx, row in enumerate(all_rows, start1): for c_idx, value in enumerate(row): ws.write(r_idx, c_idx, value) wb.save(str(out_path)) print(汇总完成共写入 %d 行数据到 %s % (len(all_rows), out_path)) if __name__ __main__: main()6.3 运行验证在merge_excel目录下激活虚拟环境后执行python merge.py正常输出汇总完成共写入 6 行数据到 output.xls打开output.xls可以看到每个来源文件的数据都按行排列并且第一列标记了来源文件表头加粗列宽也统一设置过。如果某个源文件包含公式单元格cell_value读取到的可能是缓存值这一点在汇总前要先检查和确认。7. 高频报错与排查从现象到根因按顺序查7.1 高频报错对照表问题现象常见原因检查方式处理建议Excel xlsx file; not supportedxlrd 2.x 不支持 xlsx看文件后缀改用 openpyxl 或转存为 xlsAttempt to overwrite cell同一单元格被 write 两次检查行列是否重复打开 cell_overwrite_ok 或修正循环逻辑sheet_by_name 找不到sheet 名称大小写、空格不一致打印 sheet_names()用准确名称重新获取日期显示成数字数字单元格没有转换为日期检查 cell.ctype 是否为 3使用 xldate_as_datetime 转换打开文件提示文件已损坏原文件是 xlsx 改名成 xls用文本工具查看文件头不要改名使用 openpyxl中文乱码Workbook 未设置 utf-8或写入时系统编码不一致检查开头 Workbook 创建参数统一使用 encodingutf-8xlutils.copy 后格式丢失open_workbook 未开 formatting_info查看复制前代码打开时设置 formatting_infoTrue7.2 三个最容易踩的坑第一个坑直接用最新xlrd读.xlsx。很多从老教程复制代码的人手里是.xlsx文件然后又pip install xlrd装了当前最新版。读到报错后会以为安装包坏了实际是格式和版本不匹配。排查时先打印文件后缀再决定用哪个库。第二个坑开着cell_overwrite_okTrue把所有单元格写入逻辑混在一起。它会掩盖重复写入问题导致最终数据的值取决于执行顺序而不是业务逻辑。正确做法是先维护好二维数组再统一写入重复写入应当通过异常暴露出来。第三个坑把公式结果当成必然存在的内容。程序生成的.xls如果写入的是公式没有经过 Excel 计算并保存xlrd再次读取时可能拿不到结果。需要公式计算结果的场景要么先在 Excel 中打开并保存一次要么在生成端计算后写入纯文本值。7.3 排查顺序清单遇到读写 Excel 的异常按下面顺序排查效率最高确认文件后缀到底是什么不要相信文件名颜色。确认读取还是写入xlrd读不了.xls以外的新格式。确认路径存在使用Path对象打印当前工作目录。确认 sheet 名称和行列索引正确。确认关键参数formatting_info、cell_overwrite_ok、encoding。读取数值时确认是否要做类型转换。公式、日期、空值等特殊内容单独走调试分支。最后再考虑是否为xlutils等旧库的兼容性问题。8. 最佳实践与后续学习方向8.1 在 xls 遗留项目里的可落地建议如果项目确实还不能从.xls迁移到.xlsx下面几条实践建议值得保留所有文件操作使用pathlib.Path不要用字符串拼路径。读取文件时使用with open配合file_contents传入尽早释放文件句柄。from pathlib import Path import xlrd path Path(test.xls) with open(path, rb) as f: book xlrd.open_workbook(file_contentsf.read())把“测试文件和临时文件”和“正式输入文件”分目录管理不要覆盖原始数据。统一封装一个read_cell(sheet, row, col)函数内部处理空值和类型避免业务代码到处判断。写文件时新增一条记录使用新增行号不推荐在已有数据行上原地覆盖。8.2 什么时候转换到 openpyxl 或 pandas如果你发现自己写的代码主要在处理.xlsx或者开始频繁做分组、去重、排序、多 sheet 合并这时xlrd/xlwt已经不是效率最优方案。可以直接用openpyxl处理.xlsxpip install openpyxl如果还需要进一步的数据处理使用pandas会更适合import pandas as pd df pd.read_excel(input.xlsx, sheet_name0) print(df.head()) df[总分] df[语文] df[数学] df.to_excel(output.xlsx, indexFalse)单个 Excel 写入场景建议先把数据整理成 DataFrame 或二维列表再一次性写入避免在循环里频繁打开和保存文件。数据量超过万行时需要对内存占用有预期文件体积过大时不要一条条写入可以考虑分批生成或改用 CSV。8.3 一条可以复用的学习路径把这篇文章涉及的知识拆开练习可以按下面顺序走用xlwt生成一个带表头的.xls文件。用xlrd读取刚才生成的文件并打印每一行。在文件中写入日期、公式、小数和空单元格再读取验证ctype和value。做一个模板文件用xlutils.copy回填数据。把多个文件合并成汇总文件并加入来源列。再用同样的需求使用openpyxl实现一次对比两种思路差异。到第 6 步时你会清楚xlrd/xlwt解决了什么也清楚什么时候需要迁移到新库。自动化读写 Excel 的核心难点不是 API 背熟而是对文件格式、版本差异、公式缓存、日期类型这些“应用层看不见的信息”有足够判断力。把本文的示例运行两遍再自己改造一遍这份技能就能直接用到真实报表任务里。
返回列表