ARTICLE DETAIL

资讯详情

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

Python嵌套列表转Excel表格并自动合并单元格实操指南

Python嵌套列表转Excel表格并自动合并单元格实操指南 手头有一堆列表嵌套的数据想导成表格还要把相同类别的单元格合并起来看着专业又整洁——这种需求我估计做过数据处理或报表开发的人都遇过。今天这篇就把“列表嵌套”到“表格合并”这条链路完整捋一遍从Python里嵌套列表的基本功到实际把数据写进Excel并自动合并单元格最后再分享几个我踩过的坑保证你下次拿到这种需求能一次做对。这篇适合谁我先说清楚正在用Python写脚本处理表格数据的人项目里需要自动生成报表、导Excel给业务看的人以及听说过pandas和openpyxl但一直没上手的初学者都可以直接对着操作。不需要太高深的基础嵌套列表的语法我会用最直白的方式讲透。1. 嵌套列表和表格合并为什么要放在一起讲1.1 嵌套列表到底是什么先把它看顺眼嵌套列表说白了就是“列表套列表”。Python里面它是这个样子的data [ [一班, 张三, 88], [一班, 李四, 92], [一班, 王五, 75], [二班, 赵六, 90], [二班, 孙七, 85], ]外层是一个列表里面的每个元素又是一个列表。每个内层列表代表一行记录你把它平着看就是一个二维表格第一列是班级第二列是姓名第三列是成绩。我习惯把这个过程叫“列表平铺成表格”——内层列表的索引位置对应列外层列表的顺序对应行。嵌套列表其实到处都是。你在网页上勾选一堆筛选条件提交给后端时往往就是一个嵌套结构爬虫解析JSON接口解析出来也经常是这种“大列表套小字典/小列表”的形状数据库查询工具导出的CSV读进来一样是嵌套列表。所以这个知识点不只为写Excel服务它是所有数据处理工作的基本功。1.2 合并单元格到底解决了什么问题一个普通表格直接写出来也能用但视觉上很“碎”。拿上面的数据举例班级列里“一班”连续出现三行每行都写一遍“一班”看的人眼睛要扫好几遍才能形成分组概念。合并单元格本质上就是在做同类信息压缩同一列里连续相同的值合并成一个区域信息只保留一次分组关系一目了然地强调出来。这里最关键的是“连续”两个字。合并单元格不是简单的“值相同就合并”它要求相同的值必须是相邻的。如果一班的数据分散在两段中间插了几条二班那就不能盲目合并。所以所有合并操作的第一步都是排序把同一分类的值排到一起去。这一点我后面会重点讲也是最多人忽略的地方。有人可能觉得合并单元格是不是多此一举数据自己看时确实可以不合并表结构干净一些分析起来也方便。但要是做成报表给领导看、给客户看、打印出来签字合并后的表格明显更专业信息层级也更清晰。这个场景差异要分清别为了合并而合并也不能该合并不合并。1.3 什么时候适合代码自动合并什么时候千万别用按我的经验下面这些场景适合用代码做合并报表导出部门汇总、班级汇总、订单分类都希望同类单元格合并模板填充用EasyExcel、POI这类库往模板里填数据填完还要合并单元格多级表头表头本身就是合并单元格的重灾区比如月份下再分“计划”和“实际”数据看板导出把统计后的分组结果导成Excel给非技术人员看不适合用合并的场景也有后续还要做数据分析、透视表、程序再次读取或者要交给别人二次加工时尽量别合并。因为合并单元格在程序读取时极其不友好数据只保留在左上角后面单元格都是空值。我后面会专门讲这个坑很多人在这上面白费过时间。2. 动手前先搞清楚嵌套列表的读取、排序与清洗这个环节先别急着打开Excel把数据本身玩明白。合并表格如果出错多半不是openpyxl的问题而是嵌套列表在写入前就没准备好。2.1 创建嵌套列表你会哪几种写法最简单的方式是手写像上面那样一行一个列表。但实际项目里数据通常不是手写的而是代码生成的。最常见的两种# 方式一循环append data [] for item in raw_rows: if item[score] 60: data.append([item[class_name], item[name], item[score]]) # 方式二列表推导式 data [ [item[class_name], item[name], item[score]] for item in raw_rows if item[score] 60 ]列表推导式是这种场景里效率最高的写法一行顶一个循环可读性也不差。如果要在嵌套列表中再嵌套一层那就是三层列表比如一个班级下面挂了多个小组每个小组有几个成员结构就成了班级列表套小组列表套成员列表。不过写Excel时三层列表通常要先压平回两层否则表格不好表示。2.2 读取嵌套列表索引、遍历和切片这块是你后面调试代码的武器花两分钟巩固一下data[0] # 取第一行 data[0][1] # 取第一行的姓名也就是张三 len(data) # 有几行 len(data[0]) # 第一行有几列遍历更常用尤其是要逐行写入Excel的时候for row in data: for cell in row: print(cell, end\t) print()嵌套列表切片我多说一句它和普通列表一样但取出来的是新子列表的引用关系修改时要小心。比如 data[0:2] 取的是前两行你要是改这个子切片里的内容原始数据可能跟着变。用 copy 模块做深拷贝能避免这类问题。这个坑虽然离表格有点远但实际处理数据时很常见值得注意。2.3 写入表格前数据必须处理干净的三个点我自己做报表时写入前一定会先做三次检查。第一列维度必须一致。嵌套列表里如果某一行少了一个值写入Excel后整列就会错位张三的名字跑到了成绩列成绩变成了别人的。排查方法很简单写一行代码检查lengths {len(row) for row in data} print(lengths) # 如果输出多个数字说明有不齐的行第二要合并的列必须提前排序。这一点很容易被忽略。比如按“班级”合并那 data 必须按班级排好序一班的数据放一起二班的数据放一起否则相同班级被不同班级隔开合并逻辑会乱。排序用Python内置的 sort 就行data.sort(keylambda x: x[0]) # 按第一列排序第三空值要认真处理。如果某一行的分类字段是 None 或空字符串它也会参与合并结果会出现一个很大的空洞区域。我一般会在预处理时把空值填充成“未分类”之类的默认值宁可让它们合并在一起也比留空强。2.4 从嵌套列表到Excel两条技术路线怎么选写Excel有两条主流路线你根据需求选pandas ExcelWriter数据量中等主要目的是快速把DataFrame导出来顾不上细粒度样式openpyxl 直接操作能做样式、能合并单元格、能设置列宽行高细粒度控制最强其实还有一个组合方案最能打先用pandas把嵌套列表转成DataFrame再交给openpyxl去处理合并和样式。或者反过来用openpyxl读Excel转成嵌套列表后用pandas分析。这一段先有个印象下一个章节我直接上完整代码。3. 手把手实操openpyxl把嵌套列表写入表格并自动合并到了正题。我拿学生成绩表当例子数据还是开头那个嵌套列表。目标导出一份Excel第一列“班级”自动合并表头加粗所有单元格加边框、居中列宽和行高调到刚好合适。3.1 第一步用openpyxl写基础数据有人一开始就想着合并其实顺序不能乱先写数据再合并再调样式。合并操作不会自动生成内容它的作用是把已有内容的区域框起来。import openpyxl data [ [一班, 张三, 88], [一班, 李四, 92], [一班, 王五, 75], [二班, 赵六, 90], [二班, 孙七, 85], ] data.sort(keylambda x: x[0]) # 按班级排序确保合并前连续 wb openpyxl.Workbook() ws wb.active ws.title 成绩表 headers [班级, 姓名, 成绩] ws.append(headers) for row in data: ws.append(row) wb.save(成绩表_基础版.xlsx)是不是很简单ws.append 每次追加一行后面跟一个列表自然就写入对应列了。到这里你已经完成“列表到表格”这一步。有人会问为什么不直接用 pandas.DataFrame.to_excel可以但后续合并单元格时还是要转回openpyxl操作。我的习惯是如果一开始就知道要合并就直接用openpyxl全程处理省得绕一圈。3.2 第二步自动合并连续相同的单元格合并的核心逻辑是找到“同一列中连续相同值的范围”。以班级列为例从第2行开始往下数发现第2到第4行都是“一班”第5到第6行都是“二班”于是分别调用 merge_cells 把对应行范围合并起来。def merge_consecutive_same_values(ws, column_index, start_row): 合并某一列中连续相同的单元格返回合并后的下一行位置 merge_start start_row while merge_start ws.max_row: merge_end merge_start current_value ws.cell(rowmerge_start, columncolumn_index).value while merge_end ws.max_row and ws.cell(rowmerge_end 1, columncolumn_index).value current_value: merge_end 1 if merge_end merge_start: ws.merge_cells( start_rowmerge_start, start_columncolumn_index, end_rowmerge_end, end_columncolumn_index ) merge_start merge_end 1这段代码我为什么单独抽成一个函数因为实际报表里你要合并的列可能不止一列产品分类列要合并、区域列要合并、月份列要合并。抽成函数后直接循环调用就行# 合并班级列 merge_consecutive_same_values(ws, column_index1, start_row2)特别说明一下这个函数依赖一个前提要合并的列已经排好序。如果数据本身就是乱的相同值不在连续区域函数会把这些值当成不同区域合并效果就是一段一段的看起来像没合并一样。所以每次调用前我都会确认数据源已经按这个列排过序。3.3 第三步给合并后的表格加边框、居中、调列宽合并完的单元格如果不加样式视觉上非常难看合并区域里的内容默认还是靠左靠上的。这一步不能省。from openpyxl.styles import Alignment, Font, Border, Side thin_border Border( leftSide(stylethin), rightSide(stylethin), topSide(stylethin), bottomSide(stylethin) ) header_font Font(boldTrue) for row in ws.iter_rows(min_row1, max_rowws.max_row, min_col1, max_collen(headers)): for cell in row: cell.alignment Alignment(horizontalcenter, verticalcenter) cell.border thin_border if cell.row 1: cell.font header_font from openpyxl.utils import get_column_letter column_widths {A: 12, B: 12, C: 10} for col_letter, width in column_widths.items(): ws.column_dimensions[col_letter].width width ws.row_dimensions[1].height 22 for row_idx in range(2, ws.max_row 1): ws.row_dimensions[row_idx].height 20 wb.save(成绩表_合并单元格.xlsx)细心的朋友可能已经注意到我边框加在整个表格区域包括合并区域。merge_cells 之后合并区域内部如果出现残留边框线会影响视觉。我的经验是先不加边框把数据写好、合并区域合并完最后再统一遍历加边框这样整张表的边框最干净。如果你已经加过一次边框又执行了合并有时候需要手动清除旧边框再重加。3.4 完整代码和最终效果把上面几段拼起来就是这个需求的最小完整方案。我通常还会加一步合并列的数量不止班级一列时用列表控制columns_to_merge [1] # 想合并哪几列就在这里写对应列号 for col_idx in columns_to_merge: merge_consecutive_same_values(ws, column_indexcol_idx, start_row2)最终生成的Excel效果是第一列“一班”占据连续的三行区域垂直居中“二班”占据两行区域姓名和成绩正常显示整表有边框表头加粗。打开文件看一眼比不合并的纯数据表专业了不止一个档次。4. 合并单元格老出问题常见报错与排查技巧这部分是我被问得最多的内容基本每个做报表的人都踩过。4.1 合并出来的区域不对或者明明相同值却没合并这大概率是排序问题。排查方法很简单先把嵌套列表按需要合并的列排好序再写入。如果数据源本身是数据库查询出来的很可能已经按主键排序但主键排序不等于按分类字段排序所以不要想当然写入前应确认排序顺序。还有一种隐藏情况值看起来一样其实类型不一样。一个是字符串“一班”一个是数字1或者字符串里带了个不可见空格。openpyxl比较值时用的是 判断不会做类型自动转换。建议在数据清洗阶段统一把分类字段强制转成字符串并 strip 掉首尾空格。4.2 合并单元格之后读取数据时其他格子全是None这是openpyxl最典型的坑。merge_cells合并后除了左上角的单元格仍然保留值其余单元格的值会被置为 None。你人工在Excel里看到的是合并后的整体显示但程序读出来就是残缺的。这个问题的处理要看场景。如果只是给人看的报表不用处理显示没影响。但如果这个表还要被程序读回来继续加工我建议合并前先把数据保存一份副本或者干脆不要在源数据表上合并用一份新表来做展示。这样既能满足人看的视觉要求又不破坏原始数据。4.3 合并后样式错乱、边框断断续续边框问题我在3.3里提到过原则就是“最后统一加样式”。另外给合并区域内的单元格加背景色或字体时openpyxl只对左上角生效除非你遍历整个合并区域的每个单元格去设置。openpyxl里有 MergedCell 的限制不能直接给 MergedCell 赋值内容但样式属性是可以设置的。实操中我一般让左上角显示内容其余单元格不重复设置值样式则整块区域统一遍历。4.4 嵌套列表长度不一致写入后数据错位这个用第2章里的 lengths 检查法可以在写入前拦截。如果发现某一行长度不对我会先打印这一行的原始内容看一下基本是数据源漏字段或者解析逻辑有 bug。定位到是哪条数据之后再修别指望 openpyxl 帮你容错它只会把所有行依次写入多出来的值往下一列塞少了的直接空缺视觉上看不出来但数据已经错了。4.5 pandas写的表要再去合并怎么办如果你一开始是用 pandas.DataFrame.to_excel 导出的文件现在想补充合并单元格不要重新写一遍逻辑。直接用 openpyxl 读取已生成的文件再执行合并即可。两种工具并不互斥游戏规则是pandas负责快速落地数据openpyxl负责精细化修饰。from openpyxl import load_workbook wb load_workbook(pandas导出的表.xlsx) ws wb.active merge_consecutive_same_values(ws, column_index1, start_row2) wb.save(pandas导出的表_合并后.xlsx)4.6 问题排查速查表现象原因处理办法同类数据没合并未按分类排序写入前排好序合并区域不完整值看似相同实则不同类型或带空格统一转字符串并strip读取时其他值为Nonemerge_cells的特性保留数据副本或用副本展示边框断裂先合并后加边框顺序不对最后统一加边框数据错位嵌套列表行长度不一致事先用set检查lengths合并后无法保存合并区域与其他操作冲突先保存备份再逐步调试5. 再拓展一下HTML、Markdown、EasyExcel里的表格合并5.1 HTML表格、Markdown表格、WPS的合并逻辑如果你做的不是Excel而是网页上的表格思路是一样的只是语法不同。HTML用 rowspan 表示纵向合并colspan 表示横向合并Markdown原生不支持合并单元格但很多笔记软件通过插件或者自定义渲染方式实现了合并WPS图形界面里点工具栏的“合并单元格”就行本质和Excel一致。核心思想都一样先想清楚哪些行或列属于同一个分组再决定合并范围。5.2 企业报表导出场景EasyExcel和POI在Java技术栈里阿里EasyExcel和Apache POI是两个绕不开的库。EasyExcel模板填充场景下可以用自定义拦截器在导出时动态合并某些列POI里的 CellRangeAddress 类专门用来表示合并区域。原理和openpyxl完全一样底层都是把一段连续的单元格区域标记为合并。所以你要是把这些库的合并规则和openpyxl的 merge_cells 对照着看会发现它们就是同一个概念的两种实现会一种之后上手另一种很快。5.3 注意合并单元格和多表合并是两码事这里顺带提醒一下“表格合并”还有一个完全不同的理解方向把多个表格文件合并成一个表。比如你手头有12个月的数据表需要拼成一张年度总表。这时候用 pandas.concat 或 pd.merge 才是对的它们合的是数据行和数据列不是单元格样式。很多初学者把这两个“合并”搞混接到需求也不确认对着Excel一顿操作。写代码之前一定先跟需求方确认清楚到底是合并单元格还是合并表。从我自己的实际使用体验来说处理“嵌套列表到表格合并”这类需求最关键的其实不是背API而是先把数据顺序整理好再决定合并策略。你把这两步想清楚后面用openpyxl、EasyExcel还是POI都是换汤不换药。要是手头的需求复杂到一堆层级表头加各种分组我建议先用小数据导出来人工核对一遍合并规则再放大到全量数据跑这样最稳。最后再分享一个小技巧合并单元格后的Excel如果想打印记得把打印方向改成横向、缩放比例调到“将所有列调整为一页”不然合并出来的宽表格打印出来经常被裁断这是我踩了很多次坑才注意到的细节。
返回列表