
上周三下午运营部的小夏抱着一杯珍珠奶茶来找我的时候,我正对着满屏的bug发愁。她把奶茶往我桌上一放,苦着脸说:“哥,能不能帮我个忙?每周五的销售报表我实在做吐了,三个平台的数据导出来七八个Excel,复制粘贴加算环比,每次要花三个多小时,上周还因为算错一个数被领导骂了。”
我当时拍胸脯说这还不简单,半天给你搞定。结果真动手做的时候才发现,现实里的需求根本不像LeetCode题那样规整,前前后后踩了四五个坑,折腾了快两天才做出来。不过现在小夏每周五打开软件点一下按钮,10秒钟就能出完整报表,昨天还说这个月不用加班省下来的钱准备请我吃火锅。
特意把整个开发过程和踩过的坑整理出来,给同样经常要处理Excel的朋友们做个参考,完整源码我放在文末了,需要的可以自取。
先跟小夏坐下来捋了整整半小时,才把她平时做报表的完整流程搞清楚,以前我总觉得自动化就是写代码读文件算数据就行,真对接业务才发现,80%的时间都在处理那些“不规范”的问题。
我把手动处理和自动化的效果做了个简单对比:
对比项 | 手动处理 | 自动化工具 |
|---|---|---|
单次耗时 | 2.5-3小时/周 | 10秒以内 |
错误率 | 5%-10%(经常看错行、公式拉错) | 0(逻辑固定) |
学习成本 | 新人要教1周才能独立做 | 双击运行,选文件就行,1分钟学会 |
可维护性 | 新增统计维度要改所有表格 | 改几行代码即可 |
附加功能 | 做图表还要手动拉选数据 | 自动生成趋势图和汇总表 |
具体要实现的功能其实不复杂:
一开始也考虑过几个方案,毕竟给非技术人员用,稳定好用比技术炫酷重要多了:
用到的库都是非常成熟的,没有用什么冷门依赖:
pandas==1.5.3 # 数据处理
openpyxl==3.1.2 # Excel读写和格式设置
matplotlib==3.7.1 # 生成图表
pyinstaller==5.13.0 # 打包成exe
tkinter # 做个简单的选文件界面,Python自带不用装本来以为几十行代码就能搞定,结果真写的时候坑一个接一个,说多了都是泪,给大家看看我踩过的那些坑:
一开始我直接写pd.concat([pd.read_excel(f) for f in file_list]),结果跑出来列全乱了。后来才发现三个平台导出的Excel表头根本不一样:淘宝叫“订单金额”,京东叫“实付金额”,拼多多更绝,叫“买家实付(元)”,甚至同一个平台不同月份导出的文件,列顺序还会变,偶尔还有隐藏的空列。
解决方案:不要按列位置取数,统一做列名映射,不管原始列叫什么,先转成统一的字段名:
# 列名映射字典,不管原始列叫啥,统一重命名
column_mapping = {
'订单编号': 'order_id',
'订单金额': 'amount',
'实付金额': 'amount',
'买家实付(元)': 'amount',
'支付时间': 'pay_time',
'下单时间': 'pay_time',
'商品品类': 'category',
'品类名称': 'category'
}
def read_and_clean(file_path):
df = pd.read_excel(file_path)
# 只保留我们需要的列,顺便重命名
df = df.rename(columns=column_mapping)
df = df[['order_id', 'amount', 'pay_time', 'category']]
return df合并完数据算周度汇总的时候,发现pay_time列全是45123、45130这种数字,一开始以为是数据类型错了,转了半天字符串都不对,折腾了快一个小时才想起来,Excel里的日期本质是从1900年开始算的序列号,openpyxl读取的时候如果没做处理,就会直接读成数字。
解决方案:用pandas自带的转换函数,注意origin要设成1899-12-30,别问为什么,Excel自己有个千年虫bug,少算两天:
# 转换Excel日期序列号
df['pay_time'] = pd.to_datetime(df['pay_time'], unit='D', origin='1899-12-30')
# 过滤掉退款单和测试单
df = df[df['amount'] > 0]
df = df[~df['order_id'].str.contains('测试|退款', na=False)]用matplotlib生成趋势图的时候,插进去一看所有中文标签都变成了□□□,这个是老问题了,matplotlib默认字体不支持中文。
解决方案:直接指定系统里的中文字体,不用手动下载字体文件,Windows系统自带微软雅黑:
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei'] # 解决中文显示
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 画周度销量趋势图
weekly_sales = df.groupby('week')['amount'].sum()
plt.figure(figsize=(10, 4))
plt.plot(weekly_sales.index, weekly_sales.values, marker='o', color='#2E86C1')
plt.title('近8周销售额趋势', fontsize=12)
plt.xlabel('周次', fontsize=10)
plt.ylabel('销售额(元)', fontsize=10)
plt.grid(alpha=0.3)
plt.savefig('trend.png', dpi=100, bbox_inches='tight')我把遇到的所有坑整理成了表格,大家做类似项目的时候可以直接避坑:
坑点描述 | 根本原因 | 解决方案 |
|---|---|---|
合并Excel时数据错位 | 不同文件列顺序不一致、有隐藏列 | 统一做列名映射,按列名取数而非位置 |
日期读成45xxx数字 | Excel日期为序列号存储 |
|
图表中文显示方块 | matplotlib默认无中文字体 | 指定sans-serif为系统自带中文字体 |
打包exe后运行闪退 | 相对路径问题,打包后工作目录变了 | 用 |
写入Excel时原有格式全丢 | pandas默认会覆盖整个sheet | 用openpyxl加载原有工作簿,只写入数据区域 |
踩完所有坑之后,核心逻辑其实就几十行代码,我加了详细的注释,大家可以直接改改用:
import pandas as pd
from openpyxl import load_workbook
from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
from openpyxl.drawing.image import Image
import datetime
def generate_report(input_files, output_path):
# 1. 读取并合并所有文件
all_dfs = []
for f in input_files:
all_dfs.append(read_and_clean(f))
df = pd.concat(all_dfs, ignore_index=True)
# 2. 数据计算
df['pay_time'] = pd.to_datetime(df['pay_time'], unit='D', origin='1899-12-30')
df['week'] = df['pay_time'].dt.isocalendar().week
# 本周和上周数据
current_week = datetime.date.today().isocalendar().week
last_week = current_week - 1
current_data = df[df['week'] == current_week].groupby('category').agg(
销量=('order_id', 'count'),
销售额=('amount', 'sum'),
客单价=('amount', 'mean')
).reset_index()
last_data = df[df['week'] == last_week].groupby('category')['销售额'].sum().reset_index()
last_data.columns = ['category', '上周销售额']
# 合并计算环比
result = pd.merge(current_data, last_data, on='category', how='left')
result['环比增长'] = (result['销售额'] - result['上周销售额']) / result['上周销售额']
result['客单价'] = result['客单价'].round(2)
result['环比增长'] = result['环比增长'].apply(lambda x: f"{x*100:.1f}%")
# 3. 写入Excel并设置格式
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
result.to_excel(writer, sheet_name='周度汇总', index=False)
# 插入趋势图
wb = writer.book
ws = wb['周度汇总']
img = Image('trend.png')
ws.add_image(img, 'A10')
# 设置表头格式(灰色背景、加粗、居中)
header_fill = PatternFill(start_color='D9D9D9', end_color='D9D9D9', fill_type='solid')
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'),
top=Side(style='thin'), bottom=Side(style='thin'))
for cell in ws[1]:
cell.font = Font(bold=True)
cell.fill = header_fill
cell.alignment = Alignment(horizontal='center')
# 给所有单元格加边框
for row in ws.iter_rows(min_row=1, max_row=len(result)+1, max_col=5):
for cell in row:
cell.border = thin_border
cell.alignment = Alignment(horizontal='center')为了让同事用着方便,我用tkinter做了个极其简单的界面,丑是丑了点但是够用:就两个按钮,一个选文件,一个点生成,连路径都不用手动输。毕竟给非技术人员用,按钮越少越好,选项多了他们反而不会用。
(图1 工具运行界面,真的只有两个按钮,选完三个平台的Excel点生成就行)
最后用pyinstaller打包成单个exe文件,命令也给大家贴出来:
pyinstaller -F -w -i icon.ico main.py-F是打包成单个exe文件,不用带一堆依赖-w是运行的时候不弹黑色的命令行窗口-i是给exe加个图标,看起来正规一点打包完生成的exe大概30M左右,直接发给小夏,她桌面上双击就能打开,不用装任何环境。现在她每周五早上到公司,选三个平台导出的文件,点一下生成按钮,喝口水的功夫报表就做好了,格式都是调好的,打印出来直接就能给领导交差。
(图2 最终生成的报表效果,前半部分是分品类汇总数据,环比增长超过10%的自动标成绿色,低于-10%的标成红色,下面是自动生成的近8周趋势图)
上周小夏跟我说,她们部门现在三个人都在用这个工具,以前每周五一整个下午都在做报表,现在10分钟搞定,剩下的时间都能摸鱼了。
其实这个工具真的没什么高深的技术,用的都是Pandas最基础的功能,写代码的过程也没什么技术难点,大部分时间都在处理那些业务上的“小问题”:比如平台导出的文件偶尔会有合并单元格、同事下载文件的时候喜欢改名字、不同电脑上字体显示不一样这些细碎的问题,这些都是看书和看教程学不到的东西。
以前我总觉得要做分布式、高并发、大模型这种“高大上”的技术才叫厉害,这次做完这个小工具才发现,能实实在在解决身边人的麻烦,让他们不用做重复枯燥的劳动,写代码的价值其实一点都不小。
对了,还有几个小功能还在迭代:比如自动从邮箱拉取附件不用手动下载、生成完报表自动发给领导,等做完了再给大家更新。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。