第352讲:从Excel自动化到RPA——完整工作流串联
- 2026-09-22 08:52:36
场景:从数据采集→清洗→报表生成→邮件发送→消息推送的全链路自动化
Python:串联所有环节,形成完整Pipeline
VBA:在Excel内完成全流程,或调用外部程序
对照要点:Python适合构建完整工作流 vs VBA适合Excel内闭环
一、为什么要聊"全链路自动化"?
很多读者学完VBA的字典、数组,学完Python的pandas、requests之后,都会遇到同一个瓶颈:
单个环节能写,串起来就乱了。
比如老板的需求是:"每天早上9点,从系统导出销售数据,清洗后生成报表,发给5个区域经理,同时在群里通知大家报表已出。"
这不是一个函数能解决的问题,而是一个工作流(Pipeline)的设计问题。
今天这一讲,我们就用同一个业务场景,分别用Python和VBA实现全链路自动化,重点对比两者的设计思路和适用边界。
二、场景拆解:一个完整的自动化工作流长什么样?
先把业务场景拆成5个环节,这是任何RPA(机器人流程自动化)工具设计的第一步:
环节 | 做什么 | 技术难点 |
|---|---|---|
① 数据采集 | 从数据库/API/网页/CSV获取原始数据 | 数据源多样,格式不统一 |
② 数据清洗 | 去重、补全缺失值、类型转换、异常值处理 | 规则复杂,需可复用 |
③ 报表生成 | 按维度汇总,生成Excel/PDF报表 | 格式美化、多Sheet、图表 |
④ 邮件发送 | 将报表作为附件发送给指定收件人 | 定时、批量、HTML正文 |
⑤ 消息推送 | 企业微信/钉钉/Slack通知相关人员 | API调用、Webhook |
关键认知:这5个环节不是独立的脚本,而是一个有依赖关系的管道——上一步的输出是下一步的输入。
三、Python方案:构建完整Pipeline

3.1 设计思路
Python的优势在于生态完整、各库之间天然兼容。我们用以下库来串联:
pandas:数据采集+清洗openpyxl:报表生成与格式美化smtplib + email:邮件发送requests:企业微信Webhook推送
3.2 完整代码实现
import pandas as pdfrom openpyxl import load_workbookfrom openpyxl.styles import Font, Alignment, Border, Sideimport smtplibfrom email.mime.multipart import MIMEMultipartfrom email.mime.base import MIMEBasefrom email.mime.text import MIMETextfrom email import encodersimport requestsimport osfrom datetime import datetime# ========== 配置区 ==========CONFIG = {"raw_data_path": "./data/raw_sales.csv","report_path": f"./output/销售日报_{datetime.now().strftime('%Y%m%d')}.xlsx","recipients": ["manager_a@company.com", "manager_b@company.com"],"smtp_server": "smtp.company.com","smtp_port": 587,"sender": "reports@company.com","password": "your_password","wecom_webhook": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"}# ========== ① 数据采集 ==========def collect_data():"""支持从CSV、数据库、API多种来源采集这里以CSV为例,实际可替换为SQL查询或requests.get()"""df = pd.read_csv(CONFIG["raw_data_path"], encoding="utf-8-sig")print(f"[采集] 读取原始数据 {len(df)} 行")return df# ========== ② 数据清洗 ==========def clean_data(df):"""清洗规则:1. 删除完全重复的行2. 销售额缺失值填充为03. 日期列统一转为datetime4. 剔除销售额为负数的异常记录"""df = df.drop_duplicates()df["销售额"] = df["销售额"].fillna(0)df["日期"] = pd.to_datetime(df["日期"], errors="coerce")df = df[df["销售额"] >= 0]df = df.dropna(subset=["日期"])print(f"[清洗] 清洗后剩余 {len(df)} 行")return df# ========== ③ 报表生成 ==========def generate_report(df, output_path):"""按区域汇总,生成带格式的Excel报表"""# 按区域汇总summary = df.groupby("区域").agg(订单数=("订单ID", "count"),总销售额=("销售额", "sum"),平均客单价=("销售额", "mean")).reset_index()summary["平均客单价"] = summary["平均客单价"].round(2)# 写入Excelwith pd.ExcelWriter(output_path, engine="openpyxl") as writer:summary.to_excel(writer, sheet_name="区域汇总", index=False)df.to_excel(writer, sheet_name="明细数据", index=False)# 格式美化wb = load_workbook(output_path)ws = wb["区域汇总"]header_font = Font(bold=True, color="FFFFFF")header_fill = openpyxl.styles.PatternFill(fill_type="solid", fgColor="4472C4")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 = header_fontcell.fill = header_fillcell.alignment = Alignment(horizontal="center")cell.border = thin_borderwb.save(output_path)print(f"[报表] 已生成: {output IPath}")return output_path# ========== ④ 邮件发送 ==========def send_email(report_path):"""发送带附件的HTML邮件"""msg = MIMEMultipart()msg["From"] = CONFIG["sender"]msg["To"] = ", ".join(CONFIG["recipients"])msg["Subject"] = f"销售日报 {datetime.now().strftime('%Y-%m-%d')}"body = """<html><body><p>各位经理,您好:</p><p>附件为今日销售日报,请查收。</p><p>如有疑问请联系数据组。</p></body></html>"""msg.attach(MIMEText(body, "html"))with open(report_path, "rb") as f:part = MIMEBase("application", "octet-stream")part.set_payload(f.read())encoders.encode_base64(part)part.add_header("Content-Disposition", f"attachment; filename={os.path.basename(report_path)}")msg.attach(part)with smtplib.SMTP(CONFIG["smtp_server"], CONFIG["smtp_port"]) as server:server.starttls()server.login(CONFIG["sender"], CONFIG["password"])server.send_message(msg)print("[邮件] 发送成功")# ========== ⑤ 消息推送 ==========def push_wecom_notification(report_path):"""企业微信机器人Webhook推送"""content = f"📊 今日销售日报已生成并发送邮件。\n报表文件:{os.path.basename(report_path)}"payload = {"msgtype": "text","text": {"content": content}}resp = requests.post(CONFIG["wecom_webhook"], json=payload)if resp.status_code == 200:print("[推送] 企业微信通知成功")else:print(f"[推送] 失败: {resp.text}")# ========== Pipeline入口 ==========def run_pipeline():print("=" * 50)print(f"Pipeline启动 {datetime.now()}")print("=" * 50)df = collect_data()df = clean_data(df)report_path = generate_report(df, CONFIG["report_path"])send_email(report_path)push_wecom_notification(report_path)print("=" * 50)print("Pipeline执行完毕")print("=" * 50)if __name__ == "__main__":run_pipeline()
3.3 Python方案的关键设计点
1. 配置与逻辑分离。CONFIG字典集中管理所有可变参数,换环境只改配置不改代码。
2. 函数粒度控制。 每个环节一个函数,单一职责,方便单独测试和复用。
3. 错误处理留口。 实际生产环境应在每个函数中加入try-except,并记录日志到文件(推荐logging模块),这里为保持代码清晰做了简化。
4. 可扩展性强。 如果明天要增加"上传到FTP服务器"这个环节,只需新增一个函数并在run_pipeline()中调用即可,不影响已有代码。
四、VBA方案:Excel内闭环

4.1 设计思路
VBA的强项在于与Excel深度绑定。如果你的数据源本身就是Excel文件、清洗逻辑不极端复杂、报表就是Excel格式,VBA可以做到"一个文件解决所有问题"。
VBA的局限也很明显:调用外部API(如企业微信Webhook)需要借助MSXML2.ServerXMLHTTP对象,发送邮件依赖Outlook的COM对象,跨应用调用容易受安全策略限制。
4.2 完整代码实现
Option ExplicitSub RunFullPipeline()'========== 配置区 ==========Dim rawPath As String, reportPath As StringrawPath = ThisWorkbook.Path & "\data\raw_sales.csv"reportPath = ThisWorkbook.Path & "\output\销售日报_" & Format(Date, "yyyymmdd") & ".xlsx"'========== ① 数据采集 ==========Dim rawSheet As WorksheetSet rawSheet = CollectData(rawPath)'========== ② 数据清洗 ==========Dim cleanSheet As WorksheetSet cleanSheet = CleanData(rawSheet)'========== ③ 报表生成 ==========Dim reportWb As WorkbookSet reportWb = GenerateReport(cleanSheet, reportPath)'========== ④ 邮件发送 ==========SendEmail reportPath'========== ⑤ 消息推送 ==========PushWeComNotification reportPathMsgBox "Pipeline执行完毕!", vbInformationEnd Sub'① 数据采集:从CSV导入到工作表Function CollectData(csvPath As String) As WorksheetDim ws As WorksheetSet ws = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))ws.Name = "RawData_" & Format(Now, "hhmmss")With ws.QueryTables.Add(Connection:="TEXT;" & csvPath, Destination:=ws.Range("A1")).TextFileParseType = xlDelimited.TextFileCommaDelimiter = True.TextFileEncoding = 65001 ' UTF-8.Refresh BackgroundQuery:=FalseEnd WithSet CollectData = wsDebug.Print "[采集] 读取完成,共 " & ws.UsedRange.Rows.Count - 1 & " 行数据"End Function'② 数据清洗Function CleanData(rawSheet As Worksheet) As WorksheetDim ws As WorksheetSet ws = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))ws.Name = "CleanData_" & Format(Now, "hhmmss")' 复制数据rawSheet.UsedRange.Copy ws.Range("A1")' 删除重复行ws.UsedRange.RemoveDuplicates Columns:=Array(1, 2, 3, 4, 5), Header:=xlYes' 填充缺失值Dim lastRow As LonglastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).RowDim i As LongFor i = 2 To lastRowIf IsEmpty(ws.Cells(i, "D").Value) Then ws.Cells(i, "D").Value = 0Next i' 剔除异常值For i = lastRow To 2 Step -1If ws.Cells(i, "D").Value < 0 Then ws.Rows(i).DeleteNext iSet CleanData = wsDebug.Print "[清洗] 清洗完成,剩余 " & ws.UsedRange.Rows.Count - 1 & " 行"End Function'③ 报表生成Function GenerateReport(cleanSheet As Worksheet, reportPath As String) As WorkbookDim wb As WorkbookSet wb = Workbooks.Add' 区域汇总Dim wsSummary As WorksheetSet wsSummary = wb.Sheets(1)wsSummary.Name = "区域汇总"cleanSheet.Range("A1").CurrentRegion.CopywsSummary.Range("A1").PasteSpecial xlPasteValues' 插入数据透视表Dim pc As PivotCacheDim pt As PivotTableSet pc = wb.PivotCaches.Create(SourceType:=xlDatabase, SourceData:=wsSummary.Range("A1").CurrentRegion)Set pt = pc.CreatePivotTable(TableDestination:=wsSummary.Range("G1"), TableName:="区域汇总透视")With pt.PivotFields("区域").Orientation = xlRowField.AddDataField .PivotFields("订单ID"), "订单数", xlCount.AddDataField .PivotFields("销售额"), "总销售额", xlSum.AddDataField .PivotFields("销售额"), "平均客单价", xlAverageEnd With' 格式美化wsSummary.Range("A1:G1").Interior.Color = RGB(68, 114, 196)wsSummary.Range("A1:G1").Font.Color = RGB(255, 255, 255)wsSummary.Range("A1:G1").Font.Bold = True' 明细数据cleanSheet.Range("A1").CurrentRegion.Copy wb.Sheets.Add(After:=wsSummary).Range("A1")ActiveSheet.Name = "明细数据"wb.SaveAs reportPathSet GenerateReport = wbDebug.Print "[报表] 已生成: " & reportPathEnd Function'④ 邮件发送(依赖Outlook)Sub SendEmail(reportPath As String)Dim olApp As Object, olMail As ObjectSet olApp = CreateObject("Outlook.Application")Set olMail = olApp.CreateItem(0)With olMail.To = "manager_a@company.com; manager_b@company.com".Subject = "销售日报 " & Format(Date, "yyyy-mm-dd").HTMLBody = "<p>各位经理,您好:</p><p>附件为今日销售日报,请查收。</p>".Attachments.Add reportPath.SendEnd WithSet olMail = NothingSet olApp = NothingDebug.Print "[邮件] 发送成功"End Sub'⑤ 消息推送(企业微信Webhook)Sub PushWeComNotification(reportPath As String)Dim http As ObjectSet http = CreateObject("MSXML2.ServerXMLHTTP")Dim url As Stringurl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"Dim postData As StringpostData = "{""msgtype"":""text"",""text"":{""content"":""📊 今日销售日报已生成并发送邮件。\n报表文件:" & Dir(reportPath) & """}}"http.Open "POST", url, Falsehttp.setRequestHeader "Content-Type", "application/json"http.Send postDataDebug.Print "[推送] 响应: " & http.responseTextSet http = NothingEnd Sub
4.3 VBA方案的关键设计点
1. 工作表即数据库。 VBA把每个中间结果放在一个Sheet中,好处是过程透明、可人工核查;坏处是产生大量临时Sheet,需要在流程结束后清理。
2. 依赖本地应用。 发邮件依赖本机安装Outlook且已登录,推送依赖网络连通性。如果是在服务器上定时运行,需要额外配置。
3. 错误处理缺失。 与Python示例一样,这里省略了On Error GoTo的错误处理,实际生产环境必须加上。
五、Python vs VBA:核心对比
维度 | Python | VBA |
|---|---|---|
数据源支持 | CSV/Excel/SQL/API/网页/JSON等全覆盖 | 主要Excel,其他需借助外部工具 |
清洗能力 | pandas向量化操作,百万行无压力 | 逐行操作,万行以上明显变慢 |
报表格式 | openpyxl/xlsxwriter精细控制 | 原生支持,操作直观 |
邮件发送 | smtplib,不依赖客户端 | 依赖Outlook,需用户登录 |
消息推送 | requests一行搞定 | 需MSXML2对象,写法繁琐 |
定时执行 | 系统级cron/Task Scheduler | 需Windows任务计划+Workbook.Open事件 |
可维护性 | 模块化、版本控制友好 | 代码嵌在Workbook中,版本管理困难 |
部署门槛 | 需安装Python环境 | 只要有Excel就能跑 |
适用场景 | 跨系统、多数据源、复杂逻辑的全链路 | Excel为中心的单文件/单部门自动化 |
一句话总结
Python适合构建完整工作流——它是"串联者",把不同系统的能力粘合在一起。
VBA适合Excel内闭环——它是"深耕者",在Excel这个生态里做到极致。
六、干货补充:如何选择?
6.1 决策树
你的数据源是否主要是Excel?
├── 是 → 你的报表是否也主要是Excel?
│ ├── 是 → 你的流程是否只在Excel内部完成?
│ │ ├── 是 → VBA(Excel内闭环)
│ │ └── 否 → Python(需要调用外部服务)
│ └── 否 → Python(输出为PDF/网页/数据库)
└── 否 → Python(多数据源天然支持)6.2 混合方案:VBA + Python 协同
实际工作中,最强大的方案往往是混合架构:
VBA负责:Excel内的数据录入校验、交互式报表展示、按钮触发
Python负责:后台定时采集、清洗、发送邮件、推送消息
通过VBA调用Python脚本(Shell函数)实现桥接:
Sub TriggerPython()Dim pythonExe As String, scriptPath As StringpythonExe = "C:\Python39\python.exe"scriptPath = ThisWorkbook.Path & "\pipeline.py"Shell pythonExe & " " & scriptPath, vbHideEnd Sub
这样用户只需点一个Excel按钮,后台完整的Python Pipeline就会运行。
6.3 进阶方向
如果你已经掌握了本文的内容,下一步可以关注:
Airflow / Prefect:用DAG(有向无环图)管理复杂Pipeline的依赖关系
Power Automate / UiPath:低代码RPA工具,适合非程序员构建自动化流
Docker容器化:将Python Pipeline打包为容器,实现跨环境一致运行
日志与监控:用
logging模块+异常告警,让自动化流程"可观测"
七、5道选择题(附答案)
Q1. 在构建全链路自动化工作流时,Python相比VBA的最大优势是?
A. 代码更短
B. 生态完整,能串联多种数据源和外部服务
C. 不需要安装任何环境
D. Excel操作更方便
Q2. VBA发送邮件通常依赖什么对象?
A. CDO.Message
B. Outlook.Application
C. SmtpClient
D. MAPI.Session
Q3. Python中用于数据清洗的核心库是?
A. numpy
B. matplotlib
C. pandas
D. requests
Q4. 关于VBA调用外部API(如企业微信Webhook),正确的对象是?
A. ADODB.Connection
B. MSXML2.ServerXMLHTTP
C. Scripting.FileSystemObject
D. Excel.Application
Q5. 在实际企业环境中,VBA+Python混合架构的典型分工是?
A. Python负责Excel内格式美化,VBA负责数据采集
B. VBA负责交互和触发,Python负责后台Pipeline执行
C. 两者完全替代关系,不应混用
D. VBA负责发送HTTP请求,Python负责生成图表
答案
Q1: B — Python的优势在于生态完整,能串联数据库、API、邮件、消息推送等多种服务,形成跨系统的Pipeline。
Q2: B — VBA通常通过CreateObject("Outlook.Application")调用Outlook来发送邮件。CDO.Message也可用于发邮件但不依赖Outlook,不过在办公环境中Outlook方案更常见。
Q3: C — pandas是Python数据清洗的事实标准库,提供DataFrame结构和丰富的清洗方法。
Q4: B — MSXML2.ServerXMLHTTP是VBA中发送HTTP请求的标准对象,用于调用REST API。
Q5: B — 混合架构中,VBA负责Excel内的交互式操作和按钮触发,Python负责后台的数据处理、邮件发送等完整Pipeline执行。


