前四节学了 Excel 处理、Word/PDF 操作、数据分析可视化、邮件消息推送。这一节把它们全部串起来------从原始数据到周报文档,从图表分析到邮件发送,一条命令搞定整个周报流程。
1. 我们要做什么
一条命令,自动完成:
scss
原始数据(Excel) → 数据清洗 → 统计分析 → 生成图表 → 填充Word模板 → 导出PDF → 发送邮件 → 推送通知
| 步骤 | 用的技术 | 前几节学的 |
|---|---|---|
| 读取数据 | pandas | 第1节 |
| 数据清洗 | pandas | 第1节 |
| 统计分析 | pandas + matplotlib | 第3节 |
| 生成周报 | python-docx | 第2节 |
| 导出 PDF | docx2pdf | 第2节 |
| 发送邮件 | smtplib | 第4节 |
| 推送通知 | 企业微信 Webhook | 第4节 |
2. 项目结构
bash
weekly_report/
├── main.py # 主入口:一键生成
├── config.py # 配置文件
├── data/
│ └── 原始数据.xlsx # 各部门提交的工作记录
├── templates/
│ └── 周报模板.docx # Word 周报模板
├── output/
│ ├── 周报_20240108.docx
│ ├── 周报_20240108.pdf
│ └── charts/
│ ├── 任务完成率.png
│ └── 部门对比.png
└── requirements.txt
依赖安装
bash
pip install pandas openpyxl matplotlib python-docx docx2pdf requests
3. 配置文件
python
# config.py
import os
from datetime import datetime, timedelta
# ===== 路径配置 =====
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(BASE_DIR, "data")
TEMPLATE_DIR = os.path.join(BASE_DIR, "templates")
OUTPUT_DIR = os.path.join(BASE_DIR, "output")
CHART_DIR = os.path.join(OUTPUT_DIR, "charts")
# 自动创建目录
for d in [DATA_DIR, TEMPLATE_DIR, OUTPUT_DIR, CHART_DIR]:
os.makedirs(d, exist_ok=True)
# ===== 邮件配置 =====
SMTP_SERVER = "smtp.qq.com"
SMTP_PORT = 465
SENDER_EMAIL = "your_email@qq.com"
EMAIL_PASSWORD = "your_auth_code"
# 收件人
REPORT_RECIPIENTS = [
"boss@company.com",
"manager@company.com",
]
# ===== 企业微信配置 =====
WECHAT_WEBHOOK = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY"
# ===== 日期计算 =====
TODAY = datetime.now()
LAST_MONDAY = TODAY - timedelta(days=TODAY.weekday() + 7)
LAST_FRIDAY = LAST_MONDAY + timedelta(days=4)
WEEK_RANGE = f"{LAST_MONDAY.strftime('%m月%d日')}-{LAST_FRIDAY.strftime('%m月%d日')}"
REPORT_DATE = LAST_FRIDAY.strftime("%Y%m%d")
4. 第一步:数据读取与清洗
python
# data_processor.py
import pandas as pd
from config import DATA_DIR, WEEK_RANGE
def load_and_clean_data():
"""读取原始数据并清洗"""
print(f"📊 正在读取数据...")
# 读取原始数据
df = pd.read_excel(f"{DATA_DIR}/原始数据.xlsx")
# 数据清洗
# 1. 删除空行
df = df.dropna(subset=["姓名", "部门"])
# 2. 去重
df = df.drop_duplicates(subset=["姓名", "任务名称", "日期"])
# 3. 字符串清洗
df["姓名"] = df["姓名"].str.strip()
df["部门"] = df["部门"].str.strip()
df["任务名称"] = df["任务名称"].str.strip()
# 4. 日期处理
df["日期"] = pd.to_datetime(df["日期"])
# 5. 状态标准化
status_map = {"已完成": "完成", "完成": "完成", "done": "完成"}
df["状态"] = df["状态"].replace(status_map)
# 6. 添加优先级标记
df["是否超时"] = df["状态"].apply(
lambda x: "超时" if x != "完成" else "正常"
)
print(f" 共 {len(df)} 条记录, {df['部门'].nunique()} 个部门")
return df
5. 第二步:统计分析
python
# analyzer.py
import pandas as pd
def analyze_data(df):
"""统计分析"""
print("📈 正在分析数据...")
# 部门汇总
dept_summary = df.groupby("部门").agg(
总任务数=("任务名称", "count"),
完成数=("状态", lambda x: (x == "完成").sum()),
超时数=("是否超时", lambda x: (x == "超时").sum()),
参与人数=("姓名", "nunique"),
).reset_index()
dept_summary["完成率"] = (
(dept_summary["完成数"] / dept_summary["总任务数"] * 100)
.round(1).astype(str) + "%"
)
# 人员排名
person_summary = df.groupby(["部门", "姓名"]).agg(
任务数=("任务名称", "count"),
完成数=("状态", lambda x: (x == "完成").sum()),
).reset_index()
person_summary["完成率"] = (
(person_summary["完成数"] / person_summary["任务数"] * 100)
.round(1).astype(str) + "%"
)
person_summary = person_summary.sort_values(
["部门", "任务数"], ascending=[True, False]
)
# 超时任务清单
overdue = df[df["是否超时"] == "超时"][
["部门", "姓名", "任务名称", "日期", "状态"]
].sort_values("部门")
print(f" 部门汇总: {len(dept_summary)} 行")
print(f" 人员明细: {len(person_summary)} 行")
print(f" 超时任务: {len(overdue)} 条")
return dept_summary, person_summary, overdue
6. 第三步:生成图表
python
# chart_generator.py
import matplotlib
matplotlib.use("Agg") # 无界面模式
import matplotlib.pyplot as plt
from config import CHART_DIR, REPORT_DATE
# 设置中文字体
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False
def generate_charts(dept_summary):
"""生成统计图表"""
print("🎨 正在生成图表...")
charts = {}
# 图1:部门任务完成率柱状图
fig, ax = plt.subplots(figsize=(8, 4))
rates = dept_summary["完成率"].str.rstrip("%").astype(float)
bars = ax.bar(dept_summary["部门"], rates, color="#366092")
# 颜色标记
for bar, rate in zip(bars, rates):
if rate >= 80:
bar.set_color("#4CAF50")
elif rate >= 60:
bar.set_color("#FF9800")
else:
bar.set_color("#F44336")
ax.set_ylabel("完成率 (%)")
ax.set_title("各部门任务完成率", fontsize=14, fontweight="bold")
ax.set_ylim(0, 105)
for bar, rate in zip(bars, rates):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
f"{rate}%", ha="center", va="bottom")
chart_path = f"{CHART_DIR}/完成率_{REPORT_DATE}.png"
plt.savefig(chart_path, dpi=150, bbox_inches="tight")
plt.close()
charts["completion"] = chart_path
# 图2:任务分布饼图
fig, ax = plt.subplots(figsize=(6, 6))
ax.pie(
dept_summary["总任务数"],
labels=dept_summary["部门"],
autopct="%1.1f%%",
colors=["#366092", "#4CAF50", "#FF9800", "#9C27B0", "#F44336"],
startangle=90
)
ax.set_title("各部门任务分布", fontsize=14, fontweight="bold")
chart_path = f"{CHART_DIR}/任务分布_{REPORT_DATE}.png"
plt.savefig(chart_path, dpi=150, bbox_inches="tight")
plt.close()
charts["distribution"] = chart_path
print(f" 已生成 {len(charts)} 张图表")
return charts
7. 第四步:生成 Word 周报
python
# report_generator.py
from docx import Document
from docx.shared import Inches, Pt, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from config import OUTPUT_DIR, REPORT_DATE, WEEK_RANGE
def generate_report(dept_summary, person_summary, overdue, charts):
"""生成 Word 周报"""
print("📝 正在生成周报...")
doc = Document()
# 标题
title = doc.add_heading(f"工作周报 ({WEEK_RANGE})", level=0)
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
doc.add_paragraph("") # 空行
# 一、总体概况
doc.add_heading("一、总体概况", level=1)
total_tasks = int(dept_summary["总任务数"].sum())
total_done = int(dept_summary["完成数"].sum())
total_overdue = int(dept_summary["超时数"].sum())
overall_rate = round(total_done / total_tasks * 100, 1) if total_tasks else 0
doc.add_paragraph(f"本周共完成任务 {total_tasks} 项,"
f"已完成 {total_done} 项,"
f"超时 {total_overdue} 项,"
f"整体完成率 {overall_rate}%。")
# 二、部门汇总表
doc.add_heading("二、部门汇总", level=1)
table = doc.add_table(rows=1, cols=6, style="Table Grid")
headers = table.rows[0].cells
headers[0].text = "部门"
headers[1].text = "总任务"
headers[2].text = "已完成"
headers[3].text = "超时"
headers[4].text = "参与人数"
headers[5].text = "完成率"
for _, row in dept_summary.iterrows():
cells = table.add_row().cells
cells[0].text = str(row["部门"])
cells[1].text = str(int(row["总任务数"]))
cells[2].text = str(int(row["完成数"]))
cells[3].text = str(int(row["超时数"]))
cells[4].text = str(int(row["参与人数"]))
cells[5].text = str(row["完成率"])
# 三、图表分析
doc.add_heading("三、图表分析", level=1)
doc.add_paragraph("各部门任务完成率:")
doc.add_picture(charts["completion"], width=Inches(5.5))
doc.add_paragraph("")
doc.add_paragraph("各部门任务分布:")
doc.add_picture(charts["distribution"], width=Inches(4.5))
# 四、人员明细
doc.add_heading("四、人员明细", level=1)
table2 = doc.add_table(rows=1, cols=4, style="Table Grid")
headers2 = table2.rows[0].cells
headers2[0].text = "部门"
headers2[1].text = "姓名"
headers2[2].text = "任务数"
headers2[3].text = "完成率"
for _, row in person_summary.iterrows():
cells = table2.add_row().cells
cells[0].text = str(row["部门"])
cells[1].text = str(row["姓名"])
cells[2].text = str(int(row["任务数"]))
cells[3].text = str(row["完成率"])
# 五、超时任务
if len(overdue) > 0:
doc.add_heading("五、超时任务清单", level=1)
table3 = doc.add_table(rows=1, cols=4, style="Table Grid")
headers3 = table3.rows[0].cells
headers3[0].text = "部门"
headers3[1].text = "姓名"
headers3[2].text = "任务名称"
headers3[3].text = "状态"
for _, row in overdue.iterrows():
cells = table3.add_row().cells
cells[0].text = str(row["部门"])
cells[1].text = str(row["姓名"])
cells[2].text = str(row["任务名称"])
cells[3].text = str(row["状态"])
# 保存
output_path = f"{OUTPUT_DIR}/周报_{REPORT_DATE}.docx"
doc.save(output_path)
print(f" 周报已保存: {output_path}")
return output_path
8. 第五步:导出 PDF 并发送
python
# sender.py
import smtplib
import requests
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
import os
from config import *
def export_pdf(docx_path):
"""导出 PDF"""
print("📄 正在导出 PDF...")
try:
from docx2pdf import convert
pdf_path = docx_path.replace(".docx", ".pdf")
convert(docx_path, pdf_path)
print(f" PDF 已保存: {pdf_path}")
return pdf_path
except Exception as e:
print(f" ⚠️ PDF 导出失败: {e}")
return None
def send_report_email(docx_path, pdf_path, summary_text):
"""发送周报邮件"""
print("📧 正在发送邮件...")
msg = MIMEMultipart()
msg["From"] = SENDER_EMAIL
msg["To"] = ", ".join(REPORT_RECIPIENTS)
msg["Subject"] = f"工作周报 ({WEEK_RANGE})"
# HTML 正文
html = f"""
<div style="font-family: 'Microsoft YaHei'; max-width: 600px;">
<h2 style="color: #366092;">工作周报 ({WEEK_RANGE})</h2>
<pre style="background: #f5f5f5; padding: 15px; border-radius: 5px;
font-size: 14px; line-height: 1.6;">{summary_text}</pre>
<p>详细周报见附件(Word + PDF)。</p>
<p style="color: #999; font-size: 12px;">此邮件由周报系统自动发送</p>
</div>
"""
msg.attach(MIMEText(html, "html", "utf-8"))
# 附件
for file_path in [docx_path, pdf_path]:
if file_path and os.path.exists(file_path):
with open(file_path, "rb") as f:
attachment = MIMEApplication(f.read())
attachment.add_header(
"Content-Disposition", "attachment",
filename=os.path.basename(file_path)
)
msg.attach(attachment)
# 发送
with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) as server:
server.login(SENDER_EMAIL, EMAIL_PASSWORD)
server.sendmail(SENDER_EMAIL, REPORT_RECIPIENTS, msg.as_string())
print(f" 邮件已发送至 {len(REPORT_RECIPIENTS)} 人")
def send_wechat_notification(summary_text):
"""企业微信通知"""
print("💬 正在推送企业微信...")
data = {
"msgtype": "markdown",
"markdown": {"content": summary_text}
}
resp = requests.post(WECHAT_WEBHOOK, json=data)
if resp.json().get("errcode") == 0:
print(" 企业微信通知已推送")
else:
print(f" ⚠️ 推送失败: {resp.json()}")
9. 主入口:一键执行
python
# main.py
"""
周报一键生成系统
一条命令完成:数据读取 → 清洗 → 分析 → 图表 → Word → PDF → 邮件 → 通知
"""
from config import WEEK_RANGE
from data_processor import load_and_clean_data
from analyzer import analyze_data
from chart_generator import generate_charts
from report_generator import generate_report
from sender import export_pdf, send_report_email, send_wechat_notification
def main():
print("=" * 50)
print(f" 周报自动生成系统")
print(f" 报告周期: {WEEK_RANGE}")
print("=" * 50)
print()
# 1. 读取数据
df = load_and_clean_data()
# 2. 统计分析
dept_summary, person_summary, overdue = analyze_data(df)
# 3. 生成图表
charts = generate_charts(dept_summary)
# 4. 生成 Word 周报
docx_path = generate_report(dept_summary, person_summary, overdue, charts)
# 5. 导出 PDF
pdf_path = export_pdf(docx_path)
# 6. 生成摘要文本
total = int(dept_summary["总任务数"].sum())
done = int(dept_summary["完成数"].sum())
rate = round(done / total * 100, 1) if total else 0
summary = f"""
📊 周报摘要 ({WEEK_RANGE})
总任务数: {total}
已完成: {done}
超时: {int(dept_summary['超时数'].sum())}
整体完成率: {rate}%
各部门完成率:
{dept_summary[['部门', '完成率']].to_string(index=False)}
详细周报已发送至邮箱,请注意查收。
"""
# 7. 发送邮件
send_report_email(docx_path, pdf_path, summary)
# 8. 推送企业微信
send_wechat_notification(summary)
print()
print("=" * 50)
print(" ✅ 周报生成并发送完成!")
print(f" 文件位置: {docx_path}")
print("=" * 50)
if __name__ == "__main__":
main()
运行效果
erlang
==================================================
周报自动生成系统
报告周期: 01月01日-01月05日
==================================================
📊 正在读取数据...
共 86 条记录, 4 个部门
📈 正在分析数据...
部门汇总: 4 行
人员明细: 23 行
超时任务: 7 条
🎨 正在生成图表...
已生成 2 张图表
📝 正在生成周报...
周报已保存: output/周报_20240105.docx
📄 正在导出 PDF...
PDF 已保存: output/周报_20240105.pdf
📧 正在发送邮件...
邮件已发送至 2 人
💬 正在推送企业微信...
企业微信通知已推送
==================================================
✅ 周报生成并发送完成!
==================================================
10. 结合 WorkBuddy 定时自动执行
创建 Skill
将整个周报系统封装为 WorkBuddy Skill:
yaml
# ~/.workbuddy/skills/weekly-report/SKILL.md
---
name: weekly-report
description: 一键生成并发送工作周报
version: 1.0.0
---
# 周报自动生成技能
## 使用方式
用户说"生成周报"时执行:
1. 运行 `python main.py`
2. 如果报错,检查数据文件是否存在
3. 成功后告知用户周报已发送
## 依赖
- Python 3.10+
- pandas, openpyxl, matplotlib, python-docx, docx2pdf, requests
- 邮箱 SMTP 授权码
- 企业微信 Webhook Key
设置定时自动化
自动化名称:每周周报自动生成
执行时间:每周五 17:30
执行内容:运行周报生成技能,自动读取数据、生成报告、发送邮件
这样每周五下班前,周报自动生成并发送,再也不用手动整理了。
回顾:五节系列总结
| 节次 | 主题 | 核心能力 |
|---|---|---|
| 第1节 | Excel 自动化 | 数据读写、清洗、格式化、批量处理 |
| 第2节 | Word/PDF 文档处理 | 模板填充、PDF 读取、格式转换 |
| 第3节 | 数据分析与可视化 | 统计分析、图表生成、分析报告 |
| 第4节 | 邮件与消息自动化 | 邮件发送、模板批量、企业消息推送 |
| 第5节 | 综合实战 | 全流程串联、一键生成、定时执行 |
学习建议
- 先跑通再优化:把示例代码跑起来,再根据实际需求调整
- 数据格式统一:让各部门用统一模板提交数据,减少清洗工作
- 模板分离:配置和模板独立文件,方便维护修改
- 日志记录:加日志模块,出问题时方便排查
- 错误处理:邮件发送失败、文件不存在等情况要 try-catch
- 逐步自动化:先手动触发,稳定后再加定时任务
💡 核心思想:办公自动化不是一步到位的,而是把日常重复操作逐步用脚本替代。每替代一个操作,就多出一点时间做更有价值的事。
📺 系列文章: 智能办公自动化
- 第1节:Excel 自动化(已发布)
- 第2节:Word/PDF 文档处理(已发布)
- 第3节:数据分析与可视化(已发布)
- 第4节:邮件与消息自动化(已发布)
- 第5节:综合实战:周报一键生成(本文)