FEATURED · 精选文章

用Python pandas批量删除Excel空白行列的完整指南

发布时间 / 2026/9/15 2:53:18
来源 / 创域科博编辑部
栏目 / 资讯中心
用Python pandas批量删除Excel空白行列的完整指南 处理Excel数据的时候最烦人的一件事就是表格里那些大片大片的空白行列。数据从系统导出来、报表从同事手里传过来经常头部几行空着、尾部十几行空着、中间还夹着几条全空的行列那边也偶尔冒出几列“幽灵列”。要是小表手动删几下还能忍到了几百行几百列甚至多个工作表的时候手工操作既费时间又容易漏更别提那种动不动就上万的明细表人眼根本盯不过来。我当时遇到这个需求是在整理一份跨部门的月度数据。Excel文件有八九个工作表每个表结构还不一样有的表前面有标题行有的表中间空着五行。领导要求把整个工作簿里所有“全空白”的行和列一次性清掉再合并导出。用Excel内置的定位条件、筛选、删除组合拳试了一遍遇到多表时就变成体力活了。后来干脆写了段Python脚本用pandas读取所有工作表、逐表过滤空白行列、重新写回文件整个过程从半小时缩短到十几秒。这篇文章就是把这段处理思路完整拆开讲清楚包括pandas的dropna配套参数、openpyxl的逐格判断、常见的“假空白”问题以及能直接拿去改的完整代码。适合刚接触Python、但每天要跟Excel表格纠缠的办公族也可以作为pandas批量清洗表格的入门案例参考。1. 动手之前先把“空白行列”这件事掰扯清楚1.1 两类空白需求处理逻辑完全不同很多人在网上搜“删除空白行列”其实背后藏着两种完全不同的需求搞混了代码就会写错方向。第一类需求是删除“全空行”或“全空列”。也就是说这一行所有单元格都是空的这一列所有单元格也都是空的。这种空白通常是表格格式化时留下的“预留区域”或者从系统导出后末尾多出的无用行。删掉它们对数据内容没有任何影响纯粹是清理版面、缩小文件体积。第二类需求是删除“含空值的行/列”。数据表中某一行里的几个关键字段是空的比如客户的姓名、联系电话缺失这行数据就算不完整或者某列90%以上的单元格都是空的这个字段基本没有分析价值。这种情况下删除的标准不再是“全空”而是“有多少空值就判断为无效”。从Excel手动操作的角度看前者对应定位条件里的“空值”然后删除行/列后者对应筛选出空白单元格再删行。从Python的角度看pandas里这两个需求都是通过dropna()实现的区别只在参数配置上。howall处理全空howany处理存在任意空值的行/列再用thresh可以做成“空值超过N个才删除”的弹性规则。1.2 技术选型为什么优先用pandas而不是openpyxl处理Excel的Python库主流就是pandas和openpyxl两个再加上一个xlrd只能读xls。我在这个场景里优先推荐pandas原因有三个。第一pandas把表格抽象成DataFrame之后行、列的判断逻辑和统计逻辑非常自然。比如判断哪些行是全空一行df.isnull().all(axis1)就能搞定但要统计空值比例、按条件筛选openpyxl就得自己嵌套循环遍历单元格代码量和出错概率都上升不少。第二pandas删除行/列之后的数据整合能力太方便了。删完空白行可以马上接fillna()、astype()、groupby()做后续处理数据清洗和分析是一条流水线。openpyxl更偏底层适合保留格式的精细操作但它没有DataFrame这种高级数据结构。第三pandas可以一行代码处理多个工作表。pd.read_excel(file, sheet_nameNone)直接返回一个包含所有工作表的DataFrame字典批量处理天然顺手openpyxl则需要手动管理Workbook、Worksheet这些对象。那openpyxl是不是完全没用也不是。如果你用pandas读完再写回原Excel的格式、图表、公式、批注基本都会丢失这时候openpyxl就派上用场了。它可以直接在Excel文件上删除行和列保留原有格式。我最后的方案就是pandas处理“纯数据清洗”openpyxl处理“需要保留格式的页签”后面第4节我会专门讲openpyxl的做法。2. 环境准备与pandas判断空值的底层逻辑2.1 装好环境别在第一步卡住如果你电脑上还没有Python环境先去官网装一个Python 3.9以上版本安装的时候记得勾选“Add Python to PATH”。装完打开命令行或者终端确认一下python --version然后装两个库pip install pandas openpyxlpandas负责数据读取、清洗、写回openpyxl在这里是两个角色一是作为pandas读写xlsx的引擎二是后面单独处理格式保留的场景。注意pandas读写xlsx文件本身依赖openpyxl所以这个库必须一起装。装完随便打开一个Python交互环境输入import pandas不报错环境就算准备好了。2.2 isna、isnull、NaN、None、空字符串到底谁算“空”这是新手最容易踩坑的地方。pandas判断空值有一套自己的逻辑和Excel里“单元格看起来是空的”并不是一回事。pandas里有一个专门的空值标记叫NaNNot a Numberfloat类型。当你用pd.read_excel()读取Excel文件时Excel里真正空的单元格会被读成NaN。pd.isna()和pd.isnull()是同一个函数都会把NaN识别为空。但有一种情况会让“空”失效如果单元格里输入了空格或者导出的数据里带上了\t、换行符pandas读进来就不是NaN而是一个看起来是空的字符串。还有一个更隐蔽的情况Excel单元格里的公式返回空字符串比如IF(A1,,A1)读进来同样是空字符串而不是NaN。这两种“假空白”用isna()是筛不出来的后面第5节的排查表里我会给出解决方案。另外Python自带的Nonepandas也会识别为空。但直接从Excel读数据时几乎不会产生None更多是在内存里手动构造DataFrame时出现。核心记一句话默认情况下pandas只把NaN和None当作空值空字符串不代表空全角/半角空格更不代表空。2.3 dropna参数详解一张表看懂核心配置dropna()是pandas里删除空值行的核心方法参数不多但每个都影响判断规则。我用一张表列清楚。参数作用常见取值典型用法axis删除方向0行、1列axis1表示删列how删除条件any、allhowall只删全空的行/列thresh保留条件整数N非空值数量小于N的行/列删除subset限定检查范围列名列表只检查指定的几列是否为空inplace是否原地修改True、False默认False返回新对象how和thresh不能同时使用。这两个参数都是用来描述“空到什么程度算空”的how是定性描述任意空就算还是全部空才算thresh是定量描述至少要有N个非空值才算有效。代码里如果同时传了这两个参数pandas会直接抛TypeError。inplace参数我几乎不会用尤其是处理从Excel读出来的DataFrame。因为很多情况下DataFrame是切片或筛选后得到的视图inplaceTrue可能触发SettingWithCopyWarning但数据并没有真正被修改。更稳妥的做法是重新赋值df df.dropna(...)。3. 核心实操完整代码删除全空行列3.1 最基础的版本读入、删除、写回先来一个能直接跑的完整脚本处理单个Excel文件中所有工作表的全空行和全空列。import pandas as pd file_path 待处理文件.xlsx sheet_dict pd.read_excel(file_path, sheet_nameNone) clean_dict {} for sheet_name, df in sheet_dict.items(): df_clean df.dropna(axis0, howall) df_clean df_clean.dropna(axis1, howall) clean_dict[sheet_name] df_clean output_path 清理后文件.xlsx with pd.ExcelWriter(output_path, engineopenpyxl) as writer: for sheet_name, df_clean in clean_dict.items(): df_clean.to_excel(writer, sheet_namesheet_name, indexFalse)这段代码做了四件事用sheet_nameNone把文件里所有工作表读成一个字典循环每个工作表先删全空行再删全空列把清理后的DataFrame存回字典用ExcelWriter统一写回新文件。几个细节要解释清楚。dropna(axis0, howall)的意思是逐行检查如果某一行所有值都是NaN就把这一行删掉。axis1同理逐列检查全空列。为什么先删行再删列其实顺序在绝大多数情况下不影响结果因为判断全空行时只关心每行自身的数据判断全空列时只关心每列自身的数据两个操作互不干扰。只是从逻辑上先说行再说列更好理解。to_excel(..., indexFalse)里的indexFalse非常关键。如果不加这个参数pandas会把行号0、1、2、3当成一列写进Excel等于新文件里凭空多出来一列序号。我有一次深夜处理报表忘了加这个参数第二天看到多了一列“Unnamed: 0”排查半天才发现是这个原因。3.2 为什么用howall而不是howany在这个场景里我们处理的是“全空白”行/列所以必须用howall。如果用howany只要某一行有任何一列是空的整行都会被删除。如果你的数据本身就允许某些字段为空比如备注列大部分是空的用howany会删掉几乎全部数据。这属于“代码没错但意思完全变了”的典型情况。如果你的需求是“删除所有存在缺失值的行”那么确实要用howany。但实际操作中我更推荐用thresh来精确控制。举例说明一张表有10列你希望“一行数据只要少于7个非空值就删除”写成df.dropna(thresh7)就行。这比howany更接近业务判断逻辑不会因为某一个非关键字段的缺失就误删整条记录。3.3 只删除部分工作表里的空白有时候需求没这么粗暴。可能工作簿里有好几个工作表但只有其中两个表需要清理其它表要认真保留。改造一下加个白名单import pandas as pd file_path 待处理文件.xlsx sheet_dict pd.read_excel(file_path, sheet_nameNone) target_sheets [Sheet1, 数据明细] # 只处理这些工作表 clean_dict {} for sheet_name, df in sheet_dict.items(): if sheet_name in target_sheets: df_clean df.dropna(axis0, howall) df_clean df_clean.dropna(axis1, howall) else: df_clean df clean_dict[sheet_name] df_clean output_path 清理后文件.xlsx with pd.ExcelWriter(output_path, engineopenpyxl) as writer: for sheet_name, df_clean in clean_dict.items(): df_clean.to_excel(writer, sheet_namesheet_name, indexFalse)白名单列表可以按自己的需求调整。还有一种常见情况是删除“第一个工作表之前的所有空白行”因为很多系统导出时会在真正的表头之前留几行说明文字。这种场景用header参数更合适df pd.read_excel(file_path, sheet_name0, header2) # 跳过前两行第3行作为表头这里header2表示从0开始计数的第2行也就是物理上的第3行作为表头。比先读取再过滤要干净得多。前提是你清楚目标表头在文件里的具体位置。3.4 进阶场景删除部分空值的行/列再往深走一步。真实数据里“部分空值”才是常态全部数据整整齐齐反而少见。我常用的thresh方案能覆盖大部分业务需求。import pandas as pd file_path 成绩单.xlsx df pd.read_excel(file_path, sheet_name0) # 总共有5列要求一行至少有4个非空值否则删除 df_clean df.dropna(thresh4) # 单独检查关键列姓名和学号为空的行直接删除 df_clean2 df.dropna(subset[姓名, 学号])两个场景对应不同的业务判断一个是整体完整性判断一个是关键字段判断。subset适合那种“其他列空了可以补但主键不能空”的场景。比如员工信息表里工号和身份证号就是主键这两列有缺失整行直接删掉比回头补录更省事。删除列方向上的“部分空值”我很少直接用dropna(thresh...)因为列是否有保留价值更多要看空值占比而不是绝对数量。100行的表某列空了30行占比30%可能还有分析价值10000行的表某列空了30行占比0.3%完全不影响。这种场景我通常先计算空值比例再决定删不删import pandas as pd df pd.read_excel(客户明细.xlsx, sheet_name0) null_ratio df.isnull().mean() cols_to_drop null_ratio[null_ratio 0.5].index.tolist() df_clean df.drop(columnscols_to_drop)df.isnull().mean()是pandas里一个很优雅的写法mean()作用于布尔值DataFrame时True被当作1False被当作0结果直接就是每列的空值占比。这个逻辑非常实用建议收藏。4. 应用场景延伸openpyxl方案与批量处理4.1 需要保留格式时改用openpyxl逐格判断pandas方案有个硬伤读进来的DataFrame丢掉了格式信息写回去之后所有单元格的样式、列宽、合并单元格、批注都会变成默认状态。如果你的Excel表格有复杂的格式比如表头有颜色、报表有固定列宽、有合并单元格那pandas方案就不可用了。这时候用openpyxl直接在原文件上操作。核心原理是遍历工作表的每一行/列判断该行/列的所有单元格是否均为None是则删除。from openpyxl import load_workbook file_path 待处理文件.xlsx wb load_workbook(file_path) for ws in wb.worksheets: # 删除全空行从最后一行往前删避免索引变化 for row in range(ws.max_row, 0, -1): all_empty all(ws.cell(rowrow, columncol).value is None for col in range(1, ws.max_column 1)) if all_empty: ws.delete_rows(row, 1) # 删除全空列从最后一列往前删 for col in range(ws.max_column, 0, -1): all_empty all(ws.cell(rowrow, columncol).value is None for row in range(1, ws.max_row 1)) if all_empty: ws.delete_cols(col, 1) wb.save(清理后保留格式.xlsx)两个细节要特别说明。第一个是为什么从最后一行/列往前遍历。delete_rows(row, 1)删除某一行后后续所有行的索引会往前移动。如果从前往后遍历删除第2行后原来的第3行变成了新的第2行循环继续到第3行时就会跳过这行没检查。从后往前遍历可以避免这种索引错位问题这是所有“边遍历边删除”操作的通用技巧openpyxl里尤其重要。第二个是用is None而不是not value。如果单元格的值是数字0not 0结果为True就会被误判为空。用is None只判断None这一种情况openpyxl中真正为空的单元格读取到的就是None不会误伤。openpyxl方案的缺点是删除列对公式的影响比较大。如果表格里有单元格引用被删除列中的某个地址删除后公式可能变成#REF!错误。我的经验是如果表格里公式密集删之前先检查一下有没有公式引用了将被删除的区域或者干脆用pandas方案处理数据副本。4.2 批量处理整个文件夹里的Excel文件实际工作里很少只处理一个文件更多是拿到一个文件夹里面几十个Excel都要清理。这种批量需求其实就是在基础脚本外面套一层文件遍历。import pandas as pd from pathlib import Path input_dir Path(原始数据) # 存放待处理文件的文件夹 output_dir Path(清理结果) # 存放处理后文件的文件夹 output_dir.mkdir(exist_okTrue) for file_path in input_dir.glob(*.xlsx): print(f正在处理: {file_path.name}) try: sheet_dict pd.read_excel(file_path, sheet_nameNone) except Exception as e: print(f读取失败: {file_path.name}, 错误: {e}) continue clean_dict {} for sheet_name, df in sheet_dict.items(): df_clean df.dropna(axis0, howall) df_clean df_clean.dropna(axis1, howall) clean_dict[sheet_name] df_clean output_path output_dir / file_path.name with pd.ExcelWriter(output_path, engineopenpyxl) as writer: for sheet_name, df_clean in clean_dict.items(): df_clean.to_excel(writer, sheet_namesheet_name, indexFalse) print(f完成: {file_path.name})这段代码里用了pathlib.Path来管理路径它是Python 3.6之后推荐的标准库比os.path拼接路径更简洁、跨平台兼容性更好。glob(*.xlsx)会匹配所有以.xlsx结尾的文件但不会递归匹配子文件夹如果数据分了多层目录可以用rglob。批量处理前一定要先备份原始文件。我见过不止一次因为源数据格式太乱脚本把有效数据也误删的情况。备份文件夹拷一份脚本跑出结果后先随机抽查几个文件的处理前后行数确认无误再清理备份。4.3 清理后的数据校验清单脚本跑完不要急着把结果发出去。养成一个“清洗后校验”的习惯能帮你避免在领导面前翻车。我的校验流程一般是固定的四步。第一步对比处理前后的行列总数。用df.shape打印元组(行数, 列数)前后差异应该正好等于删除的空白行列数量如果有意外偏差就要警惕。第二步检查空值分布。df.isnull().sum()能列出每一列还有多少个空值如果是删除全空行列的场景清理后整个DataFrame里不应该再有任何空值除非原始表有部分空单元格那不是这个脚本的职责范围。第三步抽查关键行和关键列。用df.head()看前几行用df.tail()看末几行确认表头和表尾数据没有被误删。原始表如果有标题行清理后第一行应该还是标题如果表尾有合计行也要确保它没被当作空白行删掉。第四步检查索引是否连续。如果用pandas删除中间的行DataFrame的行索引会保留原来的编号出现类似0、1、2、5、7、8这样的跳号情况。直接df.reset_index(dropTrue)重置一下就干净了。5. 常见问题与排查技巧实录5.1 常见问题速查表现象可能原因解决方案明明是全空行但dropna没删掉单元格里有空格、换行符或公式返回的空字符串用df.replace(r^\s*$, pd.NA, regexTrue)先把假空白替换为NA删除后Excel多出一列序号列写回文件时没加indexFalseto_excel时设置indexFalse输出文件里所有格式都丢了pandas方案本身就不保留格式改用openpyxl方案直接对原文件操作删除中间行后索引变成跳号DataFrame删除行后索引默认保留原值df.reset_index(dropTrue)重置索引一个工作表删除行导致另一个表数据错位多个表结构不一致统一处理导致误判为每个表单独配置删除条件不要共用一套规则列没删干净只剩名义上的空列但有格式单元格里有样式但无值openpyxl读出来为None检查ws.cell().value is None确认是否带样式判断文件很大脚本跑得很慢表格有几万行逐格遍历太耗时pandas方案几乎秒级openpyxl避免逐格读用ws.iter_rows()批量迭代5.2 经典坑空字符串和空格导致的“假空白”我在第2节提到了“假空白”问题这里展开说。当你用Excel打开一个文件肉眼看起来确实是空白的单元格但pandas读进来后isna()返回False导致dropna(howall)怎么都删不掉那一行。这种“假空白”一般来自两个源头一是单元格里确实有空格用户输入时不小心敲了空格键二是公式返回了空字符串比如VLOOKUP(...)查不到结果时配合IFERROR会返回这个是一个真实存在的字符串值。解决方法分两步。先“清洗”数据里的假空白再执行删除import pandas as pd df pd.read_excel(带假空白的文件.xlsx, sheet_name0) # 把所有只包含空白字符的单元格统一替换为pd.NA df df.replace(r^\s*$, pd.NA, regexTrue) # 此时df里的假空白已经被识别为缺失值dropna才能正常工作 df_clean df.dropna(axis0, howall)r^\s*$这个正则表达式匹配“从开头到结尾只有空白字符”的字符串\s代表空白字符*代表零个或多个。regexTrue告诉replace按正则规则匹配。替换为pd.NA后空字符串和纯空格单元格就和真正的空单元格一视同仁了。注意pd.NA和pd.NaT的区别NA是pandas 1.0后引入的通用缺失值标记NaT是专门用于时间类型的缺失值。这里的场景用pd.NA更合适。5.3 经典坑inplace不生效与链式赋值警告很多pandas初学者喜欢写df.dropna(inplaceTrue)觉得这样省一次赋值。但我在第2节说过不建议用inplace现在具体说说原因。当DataFrame来自read_excel这样的函数时它是独立的对象inplaceTrue可以正常工作。但如果DataFrame是切片、筛选、groupby操作的结果它可能是原DataFrame的视图View或副本CopyinplaceTrue实际上是试图修改一个临时副本pandas会抛出SettingWithCopyWarning警告而且修改结果并不生效。我排查过很多次类似问题最终方案统一改成“重新赋值”风格# 不推荐 df.dropna(howall, inplaceTrue) # 推荐 df df.dropna(howall)重新赋值避免了视图/副本的语义混乱代码可读性也更好。唯一的代价是多一次内存分配但处理Excel文件的数据量级这个开销可以忽略不计。5.4 实战排错删除后数据对不上账最后分享一个我真实踩过的坑。有一张财务报表原始表有1000行数据脚本运行后删除空白行结果变成950行但手动数了一下真正的空白行只有10行还有40行凭空消失了。排查过程大概如下先打印第0列的所有非空值发现sheet里第一列是空的真正的数据从第二列开始。这就意味着每一行数据的第一列都是NaNhowall判断“这一行是否全空”时因为第一列缺失该行会被判定为“非全空”按道理不会误删。但我在操作时先执行了dropna(axis1, howall)删除了第一列之后数据就变成全是有效值了这时候再删行并没有问题。问题出在另一个地方原始表行号是从第5行开始有数据的前4行是标题和说明文字而我的脚本里没有header参数pandas默认把第1行当表头剩下的标题和说明文字被当成数据了其中有两行因为“文字列恰好非空”就被保留下来导致后面的分析全部偏移。正确的做法是先print(df.head())看前几行确认表头在哪一行用header参数指定正确的表头行再执行删除。一句话总结任何数据清洗的第一步都是“先了解数据长什么样”不要上来就写清洗逻辑。6. 最后再说点实在的这套脚本我后来一直留在工具库里遇到类似需求改改路径就能直接用。过程中最大的体会是删除空白行列这件事真正难的从来不是代码怎么写而是搞清楚“什么才算空白”。同一张表做数据统计的人和做报表呈现的人给出的空白定义可能完全不同有的人认为空字符串也算空白有的人认为只要有一个关键字段非空就不能删。建议你在动手之前先花两分钟把这个问题问清楚再决定用howall、howany还是thresh。另外一个实用建议是把清理逻辑封装成一个函数以后每次调用都只需要传文件路径和参数。我自己的封装大致长这样import pandas as pd def clean_blank_rows_cols( input_file, output_file, howall, threshNone, sheet_nameNone, ): sheet_dict pd.read_excel(input_file, sheet_namesheet_name) clean_dict {} for name, df in sheet_dict.items() if isinstance(sheet_dict, dict) else [(Sheet1, sheet_dict)]: if thresh is not None: df_clean df.dropna(axis0, threshthresh) else: df_clean df.dropna(axis0, howhow) df_clean df_clean.dropna(axis1, howall) clean_dict[name] df_clean.reset_index(dropTrue) with pd.ExcelWriter(output_file, engineopenpyxl) as writer: for name, df_clean in clean_dict.items(): df_clean.to_excel(writer, sheet_namename, indexFalse) return output_file函数里保留了how和thresh两个入口因为不同业务场景判断标准不一样。默认情况下处理全空行需要更精细控制时传thresh。这里axis0的删除规则加入了重置索引axis1的删除规则依然保持howall因为“删除全空列”这个需求在所有场景下基本一致。最后再分享一个小技巧清洗完的数据如果只是用来做后续分析不一定非要写回Excel文件。直接df.to_csv(clean.csv, indexFalse)保存成CSV后续读取和处理的效率比Excel高不少文件体积也小得多。只有需要把结果交回给业务同事、他们还要在Excel里继续操作的时候才写回xlsx。这个选择对工作流的影响长期用下来你会感受到差别。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻