简介这份资源面向需要处理表格数据的Python学习者与开发者聚焦Excel文件的读取与写入这一常见任务帮助解决数据分析、数据清洗与自动化报告中的表格操作问题。压缩包共4个文件全部为py脚本整体约6KB涵盖读取、按条件筛选、写入以及读写结合等不同操作示例便于按需查阅与直接运行。内容围绕pandas与openpyxl两个常用库展开pandas部分演示read_excel读取、条件筛选及to_excel写入openpyxl部分则涉及工作簿加载、单元格遍历与新建保存可帮助读者理解两种方案在简单表格与复杂格式场景下的差异。目前已有702人学习适合希望快速上手Excel自动化处理、积累脚本模板的初中级Python使用者参考。1. 用 Python 读取 Excel 并写入 zip一个被低估的自动化刚需财务同事每周五下午都要把 12 个区域的销售明细表汇总再打包成一个压缩包发给总部。手动操作是打开每个 xlsx复制粘贴到总表另存为右键压缩。一套流程 40 分钟错一个单元格就得重来。这种场景下用 Python 读取 Excel 并写入 zip 不是炫技而是把重复劳动压缩成一条命令的刚需。这个标题拆开看是两件事读 Excelpandas/openpyxl 负责和写 zip标准库 zipfile 负责。难点不在单个库怎么用而在两者衔接时的内存管理、路径处理和编码问题。适合有基础 Python 语法、日常要处理报表打包的运营、财务、数据分析从业者。下面按「读进来 → 处理好 → 压出去」的链路把每一步的参数和坑讲透。2. 读取 Excel 的三种姿势pandas、openpyxl 与 calamine 怎么选2.1 先搞清楚你的 Excel 到底长什么样选库之前先确认三件事文件是 .xlsx 还是 .xls、有没有合并单元格、单表数据量多大。.xls 是旧二进制格式pandas 需要 xlrd 才能读而新版 xlrd 已经放弃了对 xlsx 的支持这个历史包袱经常让人翻车。合并单元格在 pandas 里读出来是 NaN需要额外填充。数据量超过 10 万行时pandas 默认的全量加载会吃掉大量内存。我一般先用一条命令探底python -c import pandas as pd; df pd.read_excel(sample.xlsx, nrows5); print(df.shape); print(df.columns.tolist())nrows5只读前 5 行快速看列名和结构避免大文件全量加载。df.columns.tolist()打印列名列表确认表头在第几行——很多报表前两行是标题和空行直接读会把标题当列名。2.2 pandas 读取的核心参数与适用边界pandas 的read_excel是最省事的选择适合结构规整、单表不超过几十万行的场景。关键参数有四个import pandas as pd df pd.read_excel( sales_2024.xlsx, sheet_nameSheet1, # 指定工作表None 表示读全部 header0, # 表头所在行从 0 开始计数 usecolsA:F, # 只读 A 到 F 列减少内存 dtype{订单号: str}, # 强制订单号为字符串防止前导零丢失 engineopenpyxl # 显式指定引擎 ) print(df.head())sheet_name传字符串读单表传None返回一个字典键是表名、值是 DataFrame适合批量处理多 sheet。usecols用列字母或列名列表限定范围能显著降低内存。dtype是血泪经验订单号、身份证号这类字段如果被 pandas 推断成整数前导零会消失必须显式声明为 str。engine在 xlsx 场景下用 openpyxlxls 场景用 xlrd不指定时 pandas 会自动选但自动选偶尔会选错。读进来的 DataFrame 可以直接做筛选、合并、计算处理完再决定怎么写出去。2.3 openpyxl 直接操作单元格需要保留格式时的选择pandas 读进来的是纯数据原表的字体、颜色、公式全部丢失。如果压缩包里的 Excel 需要保留格式就得用 openpyxl 直接操作工作簿对象from openpyxl import load_workbook wb load_workbook(template.xlsx, data_onlyTrue) ws wb[Sheet1] # 遍历所有行跳过表头 for row in ws.iter_rows(min_row2, values_onlyTrue): order_id, amount, region row[0], row[1], row[2] if amount and amount 10000: print(f大额订单: {order_id} 金额 {amount} 区域 {region}) wb.save(filtered.xlsx)data_onlyTrue表示读取公式的计算结果而不是公式本身这个参数在读取别人发来的报表时特别重要否则你拿到的是SUM(A1:A10)这样的字符串。iter_rows的values_onlyTrue直接返回值元组比逐个访问cell.value快很多。openpyxl 的代价是速度慢10 万行以上会明显卡顿适合小批量、重格式的场景。2.4 大文件场景calamine 引擎的提速效果数据量到几十万行时pandas 默认的 openpyxl 引擎会慢到让人怀疑人生。python-calamine 是一个用 Rust 写的读取引擎pandas 2.2 之后可以直接通过enginecalamine调用import pandas as pd df pd.read_excel( big_data.xlsx, enginecalamine, usecols[日期, 金额, 区域] ) print(f读取完成共 {len(df)} 行)实测在 50 万行、20 列的 xlsx 上calamine 比 openpyxl 快 3 到 5 倍内存占用也更低。代价是需要额外安装python-calamine包且对某些冷门格式支持不如 openpyxl 完善。我的习惯是小文件用 openpyxl大文件先试 calamine遇到格式报错再退回 openpyxl。3. 把处理结果写进 zipzipfile 的四种写入模式3.1 从内存直接写入避免落盘的 BytesIO 方案最直觉的做法是先把 DataFrame 存成 xlsx 文件再压缩。但中间文件多了之后清理是个麻烦。更好的方式是用BytesIO在内存里完成 Excel 的序列化直接写进 zipimport pandas as pd from io import BytesIO from zipfile import ZipFile, ZIP_DEFLATED df pd.read_excel(source.xlsx) # 在内存中生成 xlsx buffer BytesIO() with pd.ExcelWriter(buffer, engineopenpyxl) as writer: df.to_excel(writer, indexFalse, sheet_name汇总) buffer.seek(0) # 关键把指针拨回开头 # 写入 zip with ZipFile(output.zip, w, compressionZIP_DEFLATED) as zf: zf.writestr(汇总表.xlsx, buffer.read()) print(打包完成)BytesIO是一个内存中的二进制流ExcelWriter把 DataFrame 写进去之后指针停在末尾必须seek(0)拨回开头否则read()读到的是空内容——这个坑我踩过不止一次。writestr的第一个参数是压缩包内的文件名可以带路径如2024/汇总表.xlsxzipfile 会自动创建目录结构。ZIP_DEFLATED是压缩算法对 xlsx 这种本身已压缩的格式压缩率有限但聊胜于无。3.2 多文件批量打包循环写入与命名规范实际场景往往是把多个处理结果打进一个包。用循环配合writestr即可import pandas as pd from io import BytesIO from zipfile import ZipFile, ZIP_DEFLATED regions [华东, 华南, 华北, 西南] source pd.read_excel(all_sales.xlsx) with ZipFile(regional_reports.zip, w, compressionZIP_DEFLATED) as zf: for region in regions: subset source[source[区域] region] if subset.empty: continue # 空数据跳过避免生成空文件 buf BytesIO() with pd.ExcelWriter(buf, engineopenpyxl) as writer: subset.to_excel(writer, indexFalse, sheet_nameregion) buf.seek(0) zf.writestr(f{region}_销售明细.xlsx, buf.read()) print(f{region}: {len(subset)} 行已写入)subset.empty判断很关键某些区域可能没有数据生成一个空 xlsx 既占空间又让接收方困惑。文件名用 f-string 拼接区域名保证压缩包内文件可读。循环内每次新建BytesIO不要复用同一个 buffer否则会读到上一次的残留数据。3.3 压缩级别与文件名的三个细节ZipFile的compresslevel参数控制压缩级别取值 0 到 9默认是 None相当于 6from zipfile import ZipFile, ZIP_DEFLATED # 高压缩适合归档 with ZipFile(archive.zip, w, compressionZIP_DEFLATED, compresslevel9) as zf: zf.write(report.xlsx, arcname2024年度报告.xlsx)compresslevel9压缩率最高但耗时最长compresslevel1最快但文件大。对 xlsx 来说因为内部已经是 zip 格式再压缩收益很小我一般用默认值。arcname参数指定文件在压缩包内的名字不指定则用原路径容易带上一长串本地目录。中文文件名在 zipfile 里默认用 UTF-8 编码Windows 自带解压工具可能显示乱码接收方用 7-Zip 或 Bandizip 打开正常。3.4 追加与更新a 模式与重复写入的陷阱ZipFile的a模式可以在已有压缩包上追加文件from zipfile import ZipFile, ZIP_DEFLATED with ZipFile(output.zip, a, compressionZIP_DEFLATED) as zf: zf.write(new_report.xlsx, arcname补充报表.xlsx)注意如果压缩包里已经存在同名文件追加不会覆盖而是出现两个同名条目解压时后者覆盖前者行为取决于解压工具。要更新文件必须先读出全部内容、删掉旧条目、重新写入或者干脆重建整个 zip。我一般不在原包上追加而是每次生成新包避免这种玄学问题。4. 避坑指南编码、内存与路径的五个真实翻车现场4.1 中文文件名在 Windows 解压后变乱码现象Python 生成的 zip 在 Windows 资源管理器里解压中文文件名显示为乱码用 7-Zip 打开却正常。原因是 zipfile 默认用 UTF-8 编码文件名而 Windows 自带解压工具按本地代码页解析。解决方式是在写入时显式指定编码或者干脆用英文文件名加一个中文映射表。我的做法是压缩包内文件名用拼音或英文在包内附一个manifest.txt说明对应关系接收方一看就懂。4.2 BytesIO 忘记 seek(0) 导致写入空文件现象压缩包生成了但里面的 xlsx 打开是空白。原因就是前面提到的ExcelWriter写完后指针在末尾read()返回空字节。解决每次read()之前调用buffer.seek(0)。这个错误不报异常只是静默生成空文件排查起来很费时间。养成习惯凡是 BytesIO 读写切换先 seek。4.3 大文件全量加载导致内存溢出现象处理 100 万行的 xlsx 时进程被系统杀掉。原因是 pandas 默认全量加载100 万行 30 列大约占用 2 到 3 GB 内存。解决用read_excel的usecols限定列用chunksize分块读取注意 read_excel 的 chunksize 返回的是迭代器或者换 calamine 引擎降低内存占用。如果只是筛选后打包根本不需要全量加载分块处理完直接写 zip 即可。4.4 日期列读出来变成数字现象Excel 里的日期2024-01-15读进来变成45306。原因是 Excel 内部用序列号存储日期openpyxl 在某些配置下不自动转换。解决读取后用pd.to_datetime(df[日期], unitD, origin1899-12-30)转换或者在read_excel里指定parse_dates[日期]。注意 origin 是 1899-12-30 而不是 1900-01-01因为 Excel 有一个著名的 1900 闰年 bug。4.5 压缩包内路径用了反斜杠现象在 Windows 上开发压缩包内文件名写成reports\\summary.xlsx在 Linux 上解压变成一个名字里带反斜杠的文件。原因是 zip 规范要求路径分隔符用正斜杠/。解决统一用os.path.join或 pathlib 生成路径后把反斜杠替换成正斜杠或者直接用 f-string 写reports/summary.xlsx。跨平台代码里路径拼接永远用正斜杠。5. 进阶技巧用上下文管理器封装一个可复用的打包函数把前面的逻辑封装成一个函数是让这套方案真正可复用的关键。下面这个函数接收一个 Excel 路径和分组列名按分组拆表并打包全程内存操作不落中间文件import pandas as pd from io import BytesIO from zipfile import ZipFile, ZIP_DEFLATED from pathlib import Path def excel_to_zip_by_group( excel_path: str, group_col: str, output_zip: str, sheet_name: str Sheet1, compress_level: int 6 ) - int: 按指定列分组将每组数据写入独立 xlsx 并打包成 zip。 返回写入的文件数量。 df pd.read_excel(excel_path, sheet_namesheet_name) if group_col not in df.columns: raise ValueError(f列 {group_col} 不存在实际列: {df.columns.tolist()}) count 0 with ZipFile(output_zip, w, compressionZIP_DEFLATED, compresslevelcompress_level) as zf: for name, group in df.groupby(group_col): buf BytesIO() with pd.ExcelWriter(buf, engineopenpyxl) as writer: group.to_excel(writer, indexFalse, sheet_namestr(name)[:31]) buf.seek(0) safe_name str(name).replace(/, _).replace(\\, _) zf.writestr(f{safe_name}.xlsx, buf.read()) count 1 return count if __name__ __main__: n excel_to_zip_by_group(all_sales.xlsx, 区域, regional.zip) print(f共打包 {n} 个文件)几个设计细节值得说。sheet_namestr(name)[:31]是因为 Excel 工作表名上限 31 个字符超长会报错。safe_name把分组名里的斜杠替换掉防止路径穿越或非法文件名。groupby默认会排除 NaN 分组如果分组列有空值这些行会被静默丢弃需要提前用fillna(未知)处理。返回文件数量方便调用方做校验。验证方法很简单生成 zip 后用ZipFile.namelist()打印条目列表再逐个read()出来用 pandas 读一遍确认行数和原数据一致from zipfile import ZipFile import pandas as pd from io import BytesIO with ZipFile(regional.zip) as zf: for name in zf.namelist(): data pd.read_excel(BytesIO(zf.read(name))) print(f{name}: {len(data)} 行, 列: {data.columns.tolist()})这套流程我用了两年多最大的教训是不要相信 Excel 的表头一定在第一行也不要想当然认为分组列没有空值。每次处理新报表先跑一遍探查命令确认结构再写正式逻辑。另一个习惯是压缩包内永远附一个manifest.txt记录生成时间、数据行数和分组依据接收方出问题时能快速定位。希望帮到你。本文还有配套的精品资源点击获取