news 2026/10/20 16:21:57

Python学习第49天:数据清洗到报表自动化完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python学习第49天:数据清洗到报表自动化完整实战

今天是我 pythonstudy 的第49天。坚持将近50天,坦白说中间有几天想放弃,尤其是看到教程里“代码能跑”和“自己能写”之间那道坎的时候。第40天左右我开始接触 pandas 和 matplotlib,发现一个很尴尬的情况:库里每个函数单独看都认识,一到真处理数据就不知道从哪下手。所以 Day49 我没有继续往后学新知识点,而是做了一件事——把前面学的所有东西串起来,完整走一遍“原始数据 → 清洗 → 汇总 → 可视化 → 自动报表”的小项目。

这篇文章就是第49天的完整记录。包括我为什么选这个方向、三个库怎么分工、每一步代码怎么写、中间踩过的坑,以及给同样在坚持学习的小伙伴一些实用建议。如果你是学Python一个月以上、准备开始做点小东西却不知道怎么开头的人,这应该能给你一个非常具体的参考路径。

1. 第49天为什么我突然决定做一个“完整小项目”

1.1 前48天都干了什么

我的前半程其实很普通:变量、字符串、列表、字典、元组,然后函数、类、文件读写,再往后学了点异常处理和常用标准库。大概30天左右,语法层面的东西基本过了一遍,但要说能解决一个实际问题,心里完全没底。于是从第35天开始啃 pandas,接着是 matplotlib、openpyxl,越学越发现自己陷入“什么都听过、什么都不熟”的状态。

这种状态很典型:每次打开文档都觉得懂了,关上又两眼一黑。真正让我下决心做项目,是因为我发现自己写代码时总在一个地方卡住——不是不会用某个函数,而是不知道整个流程该先做什么、后做什么。比如拿到一份数据,是先处理空值还是先去重?日期格式乱了是直接改字符串还是转时间类型?这些问题教程里不会统一告诉你,只能靠完整跑一遍项目来体会。

1.2 项目目标:让报表自动化

所以 Day49 我给自己定的目标非常具体:造一份比较乱的销售明细表,用 pandas 读进来,把里面所有能想到的脏数据问题都处理掉,然后按区域汇总金额,画出柱状图,最后生成一个排版还不错的 Excel 报表。

选择“报表自动化”这个方向,是因为它离真实工作场景最近。几乎每个公司都有类似需求:定时把一堆流水表统计成日报、周报、月报。而且这个项目能把 pandas、matplotlib、openpyxl 三个库全部用上,覆盖了从数据读取到结果输出的完整闭环。做完之后,你会发现自己对“工具链”的理解完全不同,不再是零散学了一个函数,而是知道每个工具在整个流程里的位置。

2. 技术选型:pandas、matplotlib、openpyxl到底怎么分工

2.1 pandas是核心但不是全部

一开始我想过用 csv 内置模块直接读文件,但很快放弃。原因很简单:csv 模块要一行一行手动处理,遇到列对不齐、字段里有逗号、数据量稍微大一点,代码就会变得很长。pandas 最大的优势是它的 DataFrame 概念,可以让你把数据当成一张表格看待,然后直接用函数操作整张表,而不是写循环去遍历每个单元格。

Day49 这个项目里,读文件、看数据概况、清洗、分组汇总,这些都是 pandas 一条龙搞定的。它承担了整个流程最重的工作。但要注意,pandas 不是万能的,它擅长“处理数据”,不擅长“把数据做得美观”。如果你只想快速把数据导出成 Excel,DataFrame.to_excel() 确实够用,可导出的效果往往是黑底白字、列宽挤在一起、没有任何排版美感,所以我还需要别的工具来收尾。

2.2 matplotlib画图如果不配中文字体,会当场翻车

matplotlib 是Python最经典的绘图库,功能很强,但有个老毛病:默认设置下,图表里的中文会全部显示成方块。这个问题几乎每个用中文做图的人都会遇到,第49天我也没能幸免。我第一次运行画图代码时,标题和坐标轴标签全是□□□,当时还以为代码写错了。

解决办法其实就两行配置:

import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei'] plt.rcParams['axes.unicode_minus'] = False

这里第一行指定了中文字体,第二行是让坐标轴上的负号正常显示。因为默认字体不支持中文,但负数符号又会被某种字体影响变成乱码,所以一定要两条都写上。不同操作系统选的字体会不一样,Windows 上微软雅黑和黑体通常可用,Mac 上可以用 PingFang SC,Linux 上常见的是 Noto Sans CJK。事前统一配置一下,能省后面很多事。

2.3 openpyxl:pandas的to_excel不够用的补充

项目最后要把结果输出成 Excel 报表,这里我选 openpyxl 而不是 pandas 自带的 to_excel,是因为 to_excel 只能把数据老老实实写进去,不能做复杂排版。准确的说是:pandas 的 ExcelWriter 底层虽然也能调用 openpyxl,但你在上面很难方便地设置列宽、合并单元格、改字体颜色、插入图片。

openpyxl 是一个专门操作 Excel 文件的库,它把工作簿、工作表、单元格都变成了对象,你可以像操作一个真正的表格一样去设置样式。比如我想让表头加粗、背景色变成浅灰色、列宽根据内容自动调整、把柱状图图片插到指定位置,这些用 openpyxl 都很顺手。所以我最终的分工是:pandas 负责数据,matplotlib 负责图,openpyxl 负责呈现。

3. 从脏数据到干净表格:完整清洗流程实录

3.1 先造一份“够脏”的数据

为了把问题暴露出来,我写了个小脚本生成了100行模拟销售数据,文件名是 sales_data.csv。字段包括订单编号、日期、区域、产品名称、数量、金额。里面的问题我特意埋了一堆:日期有“2024/01/05”“20240105”“2024-01-05”三种格式;金额有的带“元”字,有的带千分位逗号;30%的行有缺失值;还有几条完全重复的记录;另外区域列里混了空格,比如“华南区 ”和“华南区”其实是同一个地方。

这种脏数据不是凭空瞎编,现实中从系统导出的表经常就是这副模样。在多系统对接的时候尤其常见:一个系统导出的是“2024/1/5”,另一个导出的是“20240105”,合并在一起就是灾难。造完数据后,我先用一行命令看整体情况:

import pandas as pd df = pd.read_csv('sales_data.csv', encoding='utf-8') print(df.head()) print(df.info())

info() 会告诉你每一列有多少个非空数据、是什么类型、总行数多少。这一步非常关键,因为清洗的第一步不是动手改数据,而是先知道数据到底“脏”在哪里。

3.2 日期、金额、空值、重复——四类脏数据怎么一步步收拾干净

第一类是日期。我先把日期列统一转成 pandas 的时间类型,遇到解析不了的会变成 NaT:

df['日期'] = pd.to_datetime(df['日期'], errors='coerce')

这里 errors='coerce' 的意思是:能转就转,不能转就置为 NaT,而不是让程序报错中断。转完之后,再把 NaT 的行过滤掉。因为日期都解析不了的话,这条记录基本没法用于时间相关的统计。

第二类是金额。金额列读进来之后大概率是字符串,因为里面有“元”和逗号。我的处理办法是先把单位去掉,再删掉逗号,最后转为 float:

df['金额'] = df['金额'].astype(str).str.replace('元', '') df['金额'] = df['金额'].str.replace(',', '') df['金额'] = pd.to_numeric(df['金额'], errors='coerce')

用 astype(str) 是为了防止某些单元格已经是数字类型时报错,稳妥起见先统一转成字符串再做替换。pd.to_numeric 同样用 errors='coerce',保证遇到异常值不会让程序死掉。

第三类是空值。空值不能一刀切,要看业务含义。数量列如果为空,说明这笔订单可能没成交,但金额还在,所以数量用中位数填充比用0填充更合理;产品名称如果为空,我选择填充“未知”,因为直接删掉可能会丢掉金额信息;金额为空则会影响汇总,如果数量有、金额没有,我选择用0填充,并在后面过滤掉成交金额为0的明细。

第四类是重复。去重时要注意:不是所有列都完全一样才算重复,订单编号可能因为生成方式不同,但日子、区域、金额都一模一样,这时候就该认定为重复。我用 subset 指定列:

df = df.drop_duplicates(subset=['日期', '区域', '产品名称', '金额'], keep='first')

keep='first' 是保留第一条,把后面重复的删掉。真正处理业务数据时,重复规则要根据场景调整,没有万能方案,但原则是:去重前一定要想清楚“哪些列相同就意味着重复”。

另外还有一个容易忽略的坑:区域列里的空格。这个我在后面专门说,但清洗顺序上,它应该放在去重之前,否则“华南区 ”和“华南区”会因为一个空格被认为是两条不同记录。

3.3 分组汇总:为什么汇总要同时算三个指标

清洗完之后,我用 groupby 按区域分组,但汇总的时候不只算总金额,还同时算了订单数量和平均金额:

result = df.groupby('区域').agg( 总金额=('金额', 'sum'), 订单数=('订单编号', 'count'), 平均金额=('金额', 'mean') ).reset_index()

很多初学者做汇总只会用一个 sum,但实际报表里,只看总金额很容易误导。比如某个区域总金额很高,可能只是因为它订单数量多;另一个区域平均金额很高,才说明客单价做得好。把三个指标放一起,看一张表就能判断区域质量,而不是单单看一个数。

算出结果后,我按总金额降序排了一下,再用 nlargest 取前10。这里有个细节:排序后取前10,用 sort_values 也可以,但 pandas 提供了 nlargest 可以直接取最大几个,不用先排完整序,性能更好,代码也更简洁。

4. 报表输出与图表落地的关键细节

4.1 绘图环节的代码和输出

汇总表我打印出来确认无误后,就开始画柱状图。目标很直接:用一张图展示各区域总金额对比,高亮第一名。为了让图表更像“报表里的图”,我做了几个设置:图形大小设置为10×6,柱状图颜色用了蓝色系,把数值标签显示在柱子顶部。

核心代码如下:

import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei'] plt.rcParams['axes.unicode_minus'] = False top10 = result.nlargest(10, '总金额') fig, ax = plt.subplots(figsize=(10, 6)) bars = ax.bar(top10['区域'], top10['总金额'], color='#5B9BD5') ax.set_title('各区域销售总金额 Top10') ax.set_xlabel('区域') ax.set_ylabel('总金额(元)') ax.set_xticklabels(top10['区域'], rotation=45) for bar in bars: ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height(), f'{bar.get_height():,.0f}', ha='center', va='bottom', fontsize=9) plt.tight_layout() plt.savefig('region_sales.png', dpi=150, bbox_inches='tight') plt.close()

这里有两个细节值得说。一是保存图片时用了 bbox_inches='tight',不然旋转后的区域名称容易被截掉;二是 plt.close() 一定要写,不然在交互环境里图片会一直占着内存,后面循环画图时容易出各种奇怪问题。

4.2 把数据写进Excel并做好排版

绘图完成后,我用 openpyxl 把汇总表写进 Excel。流程分三步:先创建 Workbook,拿到默认工作表;然后把列名和数据逐行写进去;最后统一调整样式。

from openpyxl import Workbook from openpyxl.styles import Font, Alignment, PatternFill from openpyxl.drawing.image import Image as XLImage wb = Workbook() ws = wb.active ws.title = '区域汇总' headers = ['区域', '总金额', '订单数', '平均金额'] ws.append(headers) for _, row in result.iterrows(): ws.append([row['区域'], row['总金额'], row['订单数'], row['平均金额']]) header_fill = PatternFill(start_color='DDEBF7', end_color='DDEBF7', fill_type='solid') header_font = Font(bold=True) for col in range(1, len(headers) + 1): cell = ws.cell(row=1, column=col) cell.fill = header_fill cell.font = header_font cell.alignment = Alignment(horizontal='center') widths = {'A': 12, 'B': 16, 'C': 12, 'D': 16} for col, width in widths.items(): ws.column_dimensions[col].width = width img = XLImage('region_sales.png') img.width = 600 img.height = 360 ws.add_image(img, 'F2')

为什么用 ws.append 而不是循环写 cell?因为 append 会自动把数据放到下一行,写起来更简洁。设置列宽时,我用字典一次配好,比一个个写更容易维护。这里也验证了选 openpyxl 的价值:pandas 的 to_excel 要做到表头加底纹、调整列宽、插图片,代码会绕很多。

4.3 文件命名与路径处理

最后保存文件时,我加了一个时间戳:

from datetime import datetime filename = f'sales_report_{datetime.now().strftime("%Y%m%d_%H%M%S")}.xlsx' wb.save(filename) print(f'报表已生成:{filename}')

加时间戳的原因很简单:同一个自动化脚本可能每天都要跑,如果不加时间戳,之前生成的文件会被覆盖。而且文件名里带上时间,后续查找历史文件会非常方便。这也是实际工作中很常见的一个习惯。

路径处理上,我踩了个小坑:为图省事直接用了相对路径。如果脚本的执行目录和保存图片的目录不是同一个,openpyxl 插入图片时会找不到文件。所以后来我改成用 os.path.join 和绝对路径,或者至少在插入图片前检查一下文件是否存在:

import os if not os.path.exists('region_sales.png'): print('图片不存在,请检查绘图步骤') else: img = XLImage('region_sales.png')

这个检查虽然简单,但能避免生成一个“看似成功、打开却发现没图”的坏文件。

5. 我踩过的坑,希望你一次避开

5.1 坑1:日期格式就像开盲盒

我的原始数据里有“2024/01/05”“20240105”“2024-01-05”三种日期格式。第一次转换时我没加 errors='coerce',程序直接报了 ParserError 中断。这其实不算坏事,但如果你数据量大,一个坏值就让整个脚本停下来,体验非常差。后来我改成 errors='coerce',再用 isna() 查询哪些行没转换成功,对质量就有数了。

处理日期还有一个心得:不要一上来就用字符串切片去拼格式,“20240105”这种字符串看着简单,但写正则或者切片很容易漏掉边界情况,比如月份补零。pandas 的 to_datetime 已经能处理大部分常见格式,优先相信它。

5.2 坑2:中文变方块,负号变方块

这个前面提到过,是我当天遇到的第二个问题。第一次画图,标题、横轴纵轴全是方块,我以为是缺字体,到处装字体折腾了一个多小时。后来才发现,只要在数据可视化之前把 rcParams 配置好就行。加上这段配置:

plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei'] plt.rcParams['axes.unicode_minus'] = False

注意顺序:先设置字体,再设置负号。如果只设字体,负数可能变成一个个方块;如果只设负号,中文还是方块。两个都得写。另外,如果你的系统没有“Microsoft YaHei”这个字体名,可以换成你系统列表里的中文字体名,用下面代码可以查看系统支持哪些字体:

import matplotlib.font_manager as fm font_list = [f.name for f in fm.fontManager.ttflist if 'CJK' in f.name or 'Hei' in f.name] print(font_list)

5.3 坑3:去重没生效的原因是一颗空格

这个问题想单独拿出来说,因为它非常隐蔽。我明明用了 drop_duplicates,结果重复行还在。检查后才发现,区域列里“华南区”和“华南区 ”就差末尾一个空格,pandas 认为它们是两个不同字符串,所以不去重。

解决办法是在清洗的时候对文本列统一做 strip:

df['区域'] = df['区域'].str.strip()

清理操作要放在去重之前。这个坑也提醒我一个通用原则:对文本列做比较、分组、去重之前,先做一次 strip,成本很低,收益很大。如果有复杂的不可见字符,还可以用正则替换把它们清掉。

5.4 坑4:Excel图片“不显示”其实根本没插进去

报表生成完了,打开一看,图片位置是空的。第一反应是 openpyxl 的 bug,后来折腾了一阵才发现,问题出在绘图后我把脚本跑到一半换了个工作目录,然后 Excel 文件保存到了新目录,但图片还是用相对路径读取的。openpyxl 插入图片时,如果找不到图片文件,并不会报严重错误,而是默默生成没有图的表格,所以排查起来特别迷惑。

我的解决办法是:在插入图片前用 os.path.exists 检查文件路径,发现不存在就打印提示。另外,图片路径最好用绝对路径,或者确保整个项目的路径从始至终是统一的。把路径常量定义在脚本开头,后面都用 os.path.join 拼接,可以避免很多类似问题。

6. 下一步怎么继续学

6.1 这49天证明了什么

很多人学编程会陷入“一直学、一直看”的循环,我自己就是典型。直到第49天完整跑通这个项目,我才真正体会到什么叫“会用”。之前总觉得自己基础不牢,不敢动手,实际做完才发现:项目里用到的东西,80%都是基础语法加三个库的常用函数,根本没有想象中的高深。真正难的是流程思路,先读数据、看结构、清洗、检查、汇总、输出,这个思路一旦建立,后面学爬虫、学数据分析、学自动化都会轻松很多。

这次项目也让我对“解决问题”有了新的理解。以前遇到报错就去搜索引擎复制代码,现在我会先看报错信息,再看数据长什么样。数据的问题占了项目里一半以上的时间,但也是最值得花时间的部分。数据干净了,后面的统计和报表就是顺水推舟的事。

6.2 明天Day50我准备做什么

Day49 的脚本虽然能跑,但目前还是一次性的命令行代码。第50天我打算把这个脚本重构成一个类,把清洗、汇总、绘图、输出封装成不同方法,以后只要改一下输入文件路径就能跑别的数据。再往后,我想加上自动读取文件夹里所有 csv 文件的功能,或者用定时任务每天自动跑一遍。

如果你也在学Python,我建议你和我一样,别再往教程列表里加东西了,选一个自己工作/生活中真实存在的数据处理需求,哪怕数据只有几十行,把它做成自动化报表。做完你就会发现,坚持50天换来的是真正能用的能力。这种成就感,比看完第100个视频强得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 16:21:09

AI幻觉八年未解:从概率缺陷到工程防御的实战复盘

AI幻觉,八年祭,血还在流。 这个标题我斟酌了很久才落笔。2017年之前,我们讨论神经网络的时候,"幻觉"这个词还只属于精神科;而今天,任何一个和AI打过交道的人,几乎都被它咬过一口——…

作者头像 李华
网站建设 2026/10/20 16:20:59

信息平台后端实战:REA房源系统的建模、检索与并发设计

REA 这个项目代号,全称是 Real Estate Application,一个面向 C 端的本地房源信息浏览与预约平台。作为这个项目的主力后端,从需求拆解、数据库设计到检索方案落地,整个过程踩过的坑、改过的设计、验证过的方法,我觉得都…

作者头像 李华
网站建设 2026/10/20 16:20:51

OPC DA客户端X64实战:从解压到读写PLC点位

简介:这份资源是面向工业自动化领域开发者的 OPC DA 客户端开发包,基于 COM/DCOM 技术实现,适用于在 64 位 Windows 环境下使用 Visual Studio 2013 构建与 OPC 服务器通信的应用程序。包内包含客户端动态库、头文件、示例工程与迁移说明&…

作者头像 李华
网站建设 2026/10/20 16:20:23

Spring AI + MCP 构建 Java Agent 实战:从架构到踩坑全解析

先说结论:Spring AI MCP 这套组合,是我最近半年用下来觉得最“稳”的 Java 侧 Agent 落地方式。它解决的核心问题不是“把模型接进来”,而是“怎么让模型安全、可控、可扩展地调用真实业务能力”。如果你正在 Java 技术栈里做 AI Agent、智能…

作者头像 李华
网站建设 2026/10/20 16:20:21

ESP-IDF V6.0工具目录迁移:自定义IDF_TOOLS_PATH,彻底解决C盘爆红问题

如果你最近升级到ESP-IDF V6.0,大概率会撞上同一个坑:装完一查,OpenOCD、xtensa工具链、Python环境全都被塞进了用户目录下的.espressif文件夹。我的C盘是256G的SSD,平时装完一堆开发套件已经快满了,再来一个ESP-IDF的…

作者头像 李华
网站建设 2026/10/20 16:19:51

放弃本地虚拟机,用VSCode Remote-SSH实现远程Linux开发

1. 为什么我最终放弃了本地虚拟机,转向远程开发如果你日常在Windows上写代码,但代码最终要跑在Linux服务器上,那你一定经历过这种割裂感:本地用着顺手的编辑器,代码写完却要手动scp上传,或者用Xshell这类终…

作者头像 李华