自动生成财务分析报告:从原始数据到 PDF 报告——全流程自动化实战
前面我们学会了数据处理(Pandas)、格式排版(Openpyxl)和数据可视化(Matplotlib)。今天,我们要把这些技能串联起来,打造一条完整的自动化流水线:
原始数据 → 数据清洗 → 分析计算 → 图表生成 → 格式排版 → 输出PDF/Excel报告这就是财务自动化的终极形态——一键生成专业级财务分析报告。

一、 报告自动化框架设计
一个完整的财务报告自动生成系统包含以下模块:
import pandas as pd
import numpy as np
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
from openpyxl.chart import BarChart, LineChart, PieChart, Reference
from openpyxl.drawing.image import Image as XLImage
import matplotlib.pyplot as plt
from datetime import datetime
import os
classFinancialReportGenerator:
"""财务分析报告自动生成器"""
def__init__(self, company_name="星辰科技", report_period="2024年Q1"):
self.company_name = company_name
self.report_period = report_period
self.generated_at = datetime.now().strftime("%Y-%m-%d %H:%M")
self.charts_created = []
self.wb = None
# ===== 核心方法将在下面逐一实现 =====
pass二、 模块一:数据读取与清洗
classFinancialReportGenerator:
# ... (前面的 __init__ 保持不变)
defload_and_clean_data(self, filepath):
"""
步骤1: 读取并清洗原始数据
"""
print(f"\n📂 [步骤1/5] 读取并清洗数据...")
# 读取数据
if filepath.endswith(".xlsx"):
df = pd.read_excel(filepath)
else:
df = pd.read_csv(filepath, encoding="utf-8-sig")
print(f" 原始数据: {len(df)} 行 × {len(df.columns)} 列")
# 基础清洗
# 删除空行
df = df.dropna(how="all")
# 删除重复行
before_dedup = len(df)
df = df.drop_duplicates()
iflen(df) < before_dedup:
print(f" 删除重复行: {before_dedup - len(df)} 行")
# 数值列清理
for col in df.columns:
if df[col].dtype == "object":
try:
df[col] = pd.to_numeric(
df[col].astype(str).str.replace(",", "").replace(" ", ""),
errors="coerce"
)
except:
pass
self.raw_data = df
print(f" ✅ 清洗后: {len(df)} 行有效数据")
return df三、 模块二:数据分析引擎
classFinancialReportGenerator:
# ... (延续上面的类)
defanalyze_data(self):
"""
步骤2: 执行多维度分析
"""
print(f"\n🔍 [步骤2/5] 执行数据分析...")
df = self.raw_data
self.analysis_results = {}
# ===== 基础统计 =====
numeric_cols = df.select_dtypes(include=[np.number]).columns
self.analysis_results["summary"] = {
"总记录数": len(df),
"数值列": list(numeric_cols),
}
# 如果有金额列,做金额分析
amount_col = None
for col in ["金额", "金额", "收入", "支出", "销售额", "费用"]:
if col in df.columns:
amount_col = col
break
if amount_col:
self.analysis_results["amount"] = {
"总计": df[amount_col].sum(),
"均值": df[amount_col].mean(),
"最大值": df[amount_col].max(),
"最小值": df[amount_col].min(),
"中位数": df[amount_col].median(),
}
print(f" 💰 金额列 '{amount_col}': 总计 {self.analysis_results['amount']['总计']:,.0f}")
# ===== 分组分析 =====
group_cols = [c for c in df.columns if c != amount_col and df[c].nunique() <= 20]
self.analysis_results["groupby"] = {}
for gcol in group_cols[:3]: # 最多3个分组维度
grouped = df.groupby(gcol)[amount_col].agg(["sum", "count", "mean"]) \
.round(2) if amount_col else df.groupby(gcol).size()
grouped.columns = ["合计", "笔数", "均值"] if amount_col else ["计数"]
grouped = grouped.sort_values("合计", ascending=True) if"合计"in grouped.columns else grouped
self.analysis_results["groupby"][gcol] = grouped
print(f" 📊 按 '{gcol}' 分组完成 ({len(grouped)} 个类别)")
# ===== 趋势分析(如果有日期列)=====
date_cols = [c for c in df.columns if"日期"in c or"时间"in c or"date"in c.lower()]
if date_cols:
date_col = date_cols[0]
try:
df[date_col] = pd.to_datetime(df[date_col])
df["月份"] = df[date_col].dt.to_period("M")
monthly = df.groupby("月份")[amount_col].sum() if amount_col else df.groupby("月份").size()
self.analysis_results["trend"] = monthly
print(f" 📈 月度趋势分析完成 ({len(monthed)} 个月)")
except:
pass
print(f" ✅ 分析完成!")
returnself.analysis_results四、 模块三:图表自动生成
classFinancialReportGenerator:
# ... (延续)
defgenerate_charts(self, output_dir="./charts"):
"""
步骤3: 自动生成分析图表
"""
print(f"\n📊 [步骤3/5] 生成可视化图表...")
os.makedirs(output_dir, exist_ok=True)
# 中文配置
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False
charts = []
# ===== 图表1: TOP N 排名图 =====
if"groupby"inself.analysis_results:
for gcol, gdata inlist(self.analysis_results["groupby"].items())[:2]:
if"合计"in gdata.columns:
fig, ax = plt.subplots(figsize=(10, max(4, len(gdata) * 0.4)))
colors = plt.cm.Blues(np.linspace(0.4, 0.9, len(gdata)))
bars = ax.barh(gdata.index, gdata["合计"], color=colors, edgecolor="white")
# 数值标签
for bar, val inzip(bars, gdata["合计"]):
ax.text(val + max(gdata["合计"]) * 0.01,
bar.get_y() + bar.get_height()/2,
f"{val:,.0f}", va="center", fontsize=9)
ax.set_title(f"按{gcol}排名({self.report_period})",
fontsize=14, fontweight="bold")
ax.set_xlabel("金额") if"金额"notin gcol.lower() elseNone
ax.grid(axis="x", linestyle="--", alpha=0.4)
chart_path = os.path.join(output_dir, f"chart_{gcol}_ranking.png")
plt.tight_layout()
plt.savefig(chart_path, dpi=150, bbox_inches="tight", facecolor="white")
plt.close()
charts.append({"path": chart_path, "title": f"{gcol}排名"})
print(f" ✓ {gcol}排名图")
# ===== 图表2: 趋势折线图 =====
if"trend"inself.analysis_results:
trend = self.analysis_results["trend"]
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(range(len(trend)), trend.values, marker="o", linewidth=2,
color="#4472C4", markersize=8)
ax.fill_between(range(len(trend)), trend.values, alpha=0.15, color="#4472C4")
ax.set_xticks(range(len(trend)))
ax.set_xticklabels([str(m) for m in trend.index], rotation=45)
ax.set_title(f"月度趋势({self.report_period})", fontsize=14, fontweight="bold")
ax.grid(True, linestyle="--", alpha=0.4)
chart_path = os.path.join(output_dir, "chart_trend.png")
plt.tight_layout()
plt.savefig(chart_path, dpi=150, bbox_inches="tight", facecolor="white")
plt.close()
charts.append({"path": chart_path, "title": "月度趋势"})
print(f" ✓ 趋势折线图")
# ===== 图表3: 占比饼图 =====
if"groupby"inself.analysis_results:
first_group = list(self.analysis_results["groupby"].values())[0]
if"合计"in first_group.columns andlen(first_group) <= 10:
fig, ax = plt.subplots(figsize=(8, 8))
colors = plt.cm.Set3(np.linspace(0, 1, len(first_group)))
wedges, texts, autotexts = ax.pie(
first_group["合计"], labels=first_group.index,
autopct="%1.1f%%", colors=colors, startangle=90,
textprops={"fontsize": 10}
)
ax.set_title(f"结构占比({self.report_period})", fontsize=14, fontweight="bold")
chart_path = os.path.join(output_dir, "chart_pie.png")
plt.tight_layout()
plt.savefig(chart_path, dpi=150, bbox_inches="tight", facecolor="white")
plt.close()
charts.append({"path": chart_path, "title": "结构占比"})
print(f" ✓ 结构占比饼图")
self.charts_created = charts
print(f" ✅ 共生成 {len(charts)} 张图表")
return charts五、 模块四:生成 Excel 报告
classFinancialReportGenerator:
# ... (延续)
defgenerate_excel_report(self, output_file=None):
"""
步骤4: 生成格式化的 Excel 报告
"""
print(f"\n📝 [步骤4/5] 生成 Excel 报告...")
if output_file isNone:
output_file = f"{self.company_name}_{self.report_period}_财务分析报告.xlsx"
wb = Workbook()
ws = wb.active
ws.title = "报告首页"
# ===== 封面区域 =====
ws.merge_cells("A1:H1")
ws["A1"] = f"{self.company_name}"
ws["A1"].font = Font(name="微软雅黑", size=24, bold=True, color="1F4E79")
ws["A1"].alignment = Alignment(horizontal="center", vertical="center")
ws.row_dimensions[1].height = 50
ws.merge_cells("A2:H2")
ws["A2"] = f"财务分析报告"
ws["A2"].font = Font(name="微软雅黑", size=18, color="2F5496")
ws["A2"].alignment = Alignment(horizontal="center")
ws.row_dimensions[2].height = 35
ws.merge_cells("A3:H3")
ws["A3"] = f"报告期间: {self.report_period}"
ws["A3"].font = Font(size=12, italic=True, color="666666")
ws["A3"].alignment = Alignment(horizontal="center")
ws.merge_cells("A4:H4")
ws["A4"] = f"生成时间: {self.generated_at}"
ws["A4"].font = Font(size=10, color="888888")
ws["A4"].alignment = Alignment(horizontal="center")
# ===== 关键指标卡片 =====
row = 6
if"amount"inself.analysis_results:
metrics = [
("总金额", self.analysis_results["amount"]["总计"], "#4472C4"),
("平均值", self.analysis_results["amount"]["均值"], "#ED7D31"),
("最大值", self.analysis_results["amount"]["最大值"], "#70AD47"),
("记录数", self.analysis_results["summary"]["总记录数"], "#FFC000"),
]
ws.cell(row=row, column=1, value="关键指标").font = Font(bold=True, size=12)
row += 1
for i, (label, value, color) inenumerate(metrics):
col = i * 2 + 1
cell_val = ws.cell(row=row, column=col, value=value)
cell_val.font = Font(bold=True, size=16, color=color)
cell_val.alignment = Alignment(horizontal="center")
cell_val.number_format = '#,##0.00'ifisinstance(value, float) else'#,##0'
cell_label = ws.cell(row=row+1, column=col, value=label)
cell_label.font = Font(size=10, color="666666")
cell_label.alignment = Alignment(horizontal="center")
row += 3
# ===== 插入图表 =====
for chart_info inself.charts_created[:4]: # 最多插入4张图
if os.path.exists(chart_info["path"]):
img = XLImage(chart_info["path"])
img.width = 500
img.height = 300
ws.add_image(img, f"A{row}")
row += 20
# ===== 数据明细 Sheet =====
ws_data = wb.create_sheet("数据明细")
for r_idx, row_data inenumerate(dataframe_to_rows(self.raw_data, index=False, header=True), 1):
for c_idx, value inenumerate(row_data, 1):
cell = ws_data.cell(row=r_idx, column=c_idx, value=value)
if r_idx == 1:
cell.font = Font(bold=True, color="FFFFFF")
cell.fill = PatternFill("4472C4", fill_type="solid")
# ===== 分析结果 Sheet =====
if"groupby"inself.analysis_results:
ws_analysis = wb.create_sheet("分组分析")
start_row = 1
for gname, gdata inself.analysis_results["groupby"].items():
ws_analysis.cell(row=start_row, column=1, value=f"按 {gname} 分组").font = Font(bold=True, size=12)
start_row += 1
for c_idx, col_name inenumerate(gdata.columns, 1):
ws_analysis.cell(row=start_row, column=c_idx, value=col_name).font = Font(bold=True)
start_row += 1
for r_idx, (idx, row_data) inenumerate(gdata.iterrows(), 0):
ws_analysis.cell(row=start_row + r_idx, column=1, value=str(idx))
for c_idx, val inenumerate(row_data, 2):
ws_analysis.cell(row=start_row + r_idx, column=c_idx, value=val)
start_row += len(gdata) + 3
wb.save(output_file)
print(f" ✅ Excel 报告已保存: {output_file}")
return output_file注意: 需要在文件头部添加
from openpyxl.utils.dataframe import dataframe_to_rows。
六、 完整执行流程
defrun_full_report_pipeline(data_file, company_name="星辰科技",
report_period="2024年Q1", output_dir="./"):
"""
一键运行完整报告生成流水线
"""
print("=" * 70)
print(f"🚀 财务分析报告自动生成系统")
print(f" 公司: {company_name}")
print(f" 期间: {report_period}")
print("=" * 70)
start_time = datetime.now()
# 初始化生成器
generator = FinancialReportGenerator(company_name, report_period)
# 步骤1: 数据读取与清洗
generator.load_and_clean_data(data_file)
# 步骤2: 数据分析
generator.analyze_data()
# 步骤3: 生成图表
charts_dir = os.path.join(output_dir, "charts")
generator.generate_charts(charts_dir)
# 步骤4: 生成 Excel 报告
excel_file = generator.generate_excel_report(
os.path.join(output_dir, f"{company_name}_{report_period}_报告.xlsx")
)
elapsed = (datetime.now() - start_time).total_seconds()
print(f"\n{'='*70}")
print(f"✨ 全部完成! 总耗时: {elapsed:.1f} 秒")
print(f" 📁 Excel报告: {excel_file}")
print(f" 📊 图表目录: {charts_dir}")
print(f"{'='*70}")
return generator
# ===== 使用示例 =====
# run_full_report_pipeline(
# data_file="费用明细_2024Q1.xlsx",
# company_name="星辰科技有限公司",
# report_period="2024年第一季度",
# output_dir="./输出报告/"
# )七、 进阶:输出为 PDF
如果需要生成 PDF 版本的报告,可以使用以下方案:
# 方案1: Excel 转 PDF(最简单)
# 在 Excel 中:文件 → 导出 → 创建 PDF/XPS 文档
# 或使用 win32com 库自动化操作 Excel
# 方案2: 使用 ReportLab 生成专业 PDF
# pip install reportlab
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
defgenerate_pdf_report(analysis_results, charts_created, output_file="财务报告.pdf"):
"""生成 PDF 格式的财务报告"""
doc = SimpleDocTemplate(output_file, pagesize=A4,
rightMargin=2*cm, leftMargin=2*cm,
topMargin=2*cm, bottomMargin=2*cm)
styles = getSampleStyleSheet()
story = []
# 标题
title_style = ParagraphStyle("Title", parent=styles["Heading1"],
fontSize=24, spaceAfter=30, alignment=1)
story.append(Paragraph("财务分析报告", title_style))
# 关键指标表格
if"amount"in analysis_results:
data = [["指标", "数值"]]
for key, val in analysis_results["amount"].items():
data.append([key, f"{val:,.2f}"])
table = Table(data, colWidths=[6*cm, 8*cm])
table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#4472C4")),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("ALIGN", (0, 0), (-1, -1), "CENTER"),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, 0), 12),
("BOTTOMPADDING", (0, 0), (-1, 0), 12),
("BACKGROUND", (0, 1), (-1, -1), colors.HexColor("#F2F2F2")),
("GRID", (0, 0), (-1, -1), 1, colors.white),
]))
story.append(table)
story.append(Spacer(1, 20))
# 插入图表
for chart in charts_created[:3]:
if os.path.exists(chart["path"]):
story.append(Paragraph(f"<b>{chart['title']}</b>", styles["Heading2"]))
img = Image(chart["path"], width=16*cm, height=10*cm)
story.append(img)
story.append(Spacer(1, 15))
doc.build(story)
print(f"✅ PDF 报告已生成: {output_file}")
# 使用
# generate_pdf_report(generator.analysis_results, generator.charts_created)八、 总结
今天我们构建了一个完整的财务报告自动化流水线:
这套系统的核心价值在于可复用性——每月只需替换数据文件,点击运行即可获得一份全新的完整报告。
下一篇文章,我们将学习 财务预测入门——如何用 Python 做销售趋势预测。
夜雨聆风