# 从聊天到执行：用Python自动化Excel，告别重复劳动

> 豆包能告诉你"怎么处理Excel"，但只有代码能帮你"把Excel处理完"。

## 一、聊天工具 vs 执行工具：一字之差，效率之别

想象一下这个场景：

> 你手里有 30 份销售数据表，格式不统一、空值横飞、日期格式从 "2024/3/1" 到 "Mar 1, 2024" 应有尽有。你需要把它们合并成一张干净的汇总表，再按区域分组算出每季度的销售额。

你打开豆包，问："怎么用 Python 处理 Excel 数据清洗？"

豆包会给你一个完美的答案——pandas 的 `read_excel()`、`fillna()`、`groupby()`……条理清晰、解释到位。**但你拿到答案后，仍然要手动打开编辑器、复制代码、调试、再调试。**

这就是"聊天工具"的本质：它把信息喂到你嘴边，但执行的那一步，永远在你手里断掉。

而 OpenClaw 这类 Agent 平台做的是另一件事：你把需求丢给它，它直接生成脚本、执行脚本、把结果文件放到你桌面上。**从意图到结果，中间零摩擦。**

本文不是来吹工具的。我想做的是：给你一段**能直接跑起来的代码**，让你体会一下"执行"比"知道"爽在哪里。

---

## 二、实战：30份Excel的自动化清洗流水线

### 2.1 需求拆解

我们面对的真实需求通常长这样：

1. **批量读取**：一个文件夹里有 N 个 `.xlsx`，结构相似但不完全相同
2. **数据清洗**：处理缺失值、统一格式、去重、修正异常值
3. **简单分析**：按区域/时间分组聚合，算总和、均值、增长率
4. **自动导出**：生成一张干净的新 Excel，带格式、带汇总表

下面这段代码，一步不落。

---

### 2.2 完整代码

```python
import pandas as pd
import os
from glob import glob
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.utils.dataframe import dataframe_to_rows

# ========== 1. 批量读取 ==========
DATA_DIR = "./sales_data"
files = glob(os.path.join(DATA_DIR, "*.xlsx"))

dfs = []
for f in files:
    df = pd.read_excel(f)
    df["来源文件"] = os.path.basename(f)  # 标记来源，方便追溯
    dfs.append(df)

raw_df = pd.concat(dfs, ignore_index=True)
print(f"合并完成：共 {len(raw_df)} 行，来自 {len(files)} 个文件")

# ========== 2. 数据清洗 ==========
clean_df = raw_df.copy()

# 2.1 统一列名（兼容大小写/空格差异）
clean_df.columns = [c.strip().lower().replace(" ", "_") for c in clean_df.columns]

# 2.2 处理缺失值
# 销售金额缺失 → 标记为0并记录
clean_df["sales_amount"] = clean_df["sales_amount"].fillna(0)
# 区域缺失 → 标记为"未知"，后续人工复核
clean_df["region"] = clean_df["region"].fillna("未知")
# 日期缺失 → 尝试从文件名推断，否则丢弃
clean_df = clean_df.dropna(subset=["date"])

# 2.3 统一日期格式
clean_df["date"] = pd.to_datetime(clean_df["date"], errors="coerce")
clean_df = clean_df.dropna(subset=["date"])  # 无法解析的日期丢弃

# 2.4 去重（同一区域、同日期、同客户且金额相同视为重复）
clean_df = clean_df.drop_duplicates(
    subset=["region", "date", "customer_id", "sales_amount"],
    keep="first"
)

# 2.5 异常值修正（销售金额不可能为负数）
clean_df["sales_amount"] = clean_df["sales_amount"].clip(lower=0)

print(f"清洗后：{len(clean_df)} 行，去重 {len(raw_df) - len(clean_df)} 行")

# ========== 3. 简单分析 ==========
# 3.1 按区域汇总
clean_df["year_quarter"] = clean_df["date"].dt.to_period("Q").astype(str)
region_summary = clean_df.groupby(["region", "year_quarter"]).agg({
    "sales_amount": ["sum", "mean", "count"],
    "customer_id": "nunique"
}).reset_index()
region_summary.columns = ["区域", "季度", "销售额合计", "平均客单价", "订单数", "独立客户数"]

# 3.2 全局趋势
trend = clean_df.groupby("year_quarter")["sales_amount"].sum().reset_index()
trend.columns = ["季度", "总销售额"]
trend["环比增长率"] = trend["总销售额"].pct_change() * 100

# ========== 4. 自动导出 ==========
OUTPUT_FILE = "./销售数据清洗报告.xlsx"

wb = Workbook()

# Sheet 1: 清洗后明细
ws1 = wb.active
ws1.title = "清洗后明细"
for r in dataframe_to_rows(clean_df, index=False, header=True):
    ws1.append(r)

# Sheet 2: 区域汇总
ws2 = wb.create_sheet("区域汇总")
for r in dataframe_to_rows(region_summary, index=False, header=True):
    ws2.append(r)

# Sheet 3: 趋势分析
ws3 = wb.create_sheet("趋势分析")
for r in dataframe_to_rows(trend, index=False, header=True):
    ws3.append(r)

# 美化表头
for ws in [ws1, ws2, ws3]:
    for cell in ws[1]:
        cell.font = Font(bold=True, color="FFFFFF")
        cell.fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
        cell.alignment = Alignment(horizontal="center")

wb.save(OUTPUT_FILE)
print(f"✅ 报告已导出：{OUTPUT_FILE}")
```

---

### 2.3 代码关键节点解读

| 步骤 | 核心操作 | 为什么这么做 |
|------|----------|-------------|
| 批量读取 | `glob` + `pd.concat` | 文件夹新增文件时，脚本无需修改 |
| 列名统一 | `strip().lower().replace()` | 对抗"Sales Amount"和"sales amount"的格式不一致 |
| 缺失值策略 | 分类型填充/丢弃 | 金额填 0 可算汇总，区域填"未知"可追溯 |
| 日期解析 | `pd.to_datetime(errors="coerce")` | 容忍 "2024/3/1"、"Mar 1, 2024"、"03-01-2024" 等多种格式 |
| 去重 | `drop_duplicates` | 同客户同一天两笔完全相同的金额，大概率是重复录入 |
| 导出美化 | `openpyxl` 格式设置 | 交付物看起来像人做的，而不是机器吐的 |

---

## 三、从"会写代码"到"会造流水线"

上面的脚本跑通了，但这只是**单次任务自动化**。真正值钱的思维是：把这段代码变成一条**可重复、可扩展的流水线**。

### 3.1 参数化：让脚本适应不同场景

```python
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--input-dir", default="./sales_data")
parser.add_argument("--output", default="./report.xlsx")
parser.add_argument("--date-column", default="date")
args = parser.parse_args()

# 后续代码中使用 args.input_dir / args.output 替代硬编码路径
```

### 3.2 配置化：把规则从代码里抽出来

```yaml
# config.yaml
columns:
  date: ["date", "日期", "transaction_date"]
  amount: ["sales_amount", "金额", "revenue"]
fill_rules:
  amount: 0
  region: "未知"
drop_threshold:
  amount_negative: true
```

脚本读取 YAML 配置，不同数据源只需改配置、不动代码。

### 3.3 日志化：让每一步可追溯

```python
import logging
logging.basicConfig(
    filename="etl.log",
    level=logging.INFO,
    format="%(asctime)s | %(levelname)s | %(message)s"
)
logging.info(f"读取 {len(files)} 个文件，合并 {len(raw_df)} 行")
```

出问题时，打开日志就知道是哪一步掉了链子。

---

## 四、总结：自动化思维的降维打击

回到开头的对比：

| 维度 | 手动操作 | 聊天问答 | 脚本执行 |
|------|---------|---------|---------|
| 信息获取 | ❌ 自己摸索 | ✅ 即时回答 | ✅ 代码即文档 |
| 执行落地 | ❌ 人来做 | ❌ 人来做 | ✅ 机器自动做 |
| 可重复性 | ❌ 每次都重来 | ❌ 每次重新问 | ✅ 一键复跑 |
| 规模化 | ❌ 人力瓶颈 | ❌ 人力瓶颈 | ✅ 加机器就行 |
| 错误率 | ❌ 人会累会错 | ❌ 人执行会错 | ✅ 逻辑确定则结果确定 |

**豆包告诉你"怎么做"，OpenClaw帮你"做掉它"。** 中间的差距，不是技术能力的差距，而是**思维模式的差距**——从"获取信息"进化到"构建系统"。

这段 Excel 自动化脚本只是一个起点。当你开始把每一个重复的数据处理需求都转化为一段带参数、带配置、带日志的可执行代码时，你就已经从"会写 Python 的人"变成了"能造流水线的人"。

而后者，才是 AI 时代开发者真正的护城河。

---

> **附：环境要求**
> - Python >= 3.8
> - `pip install pandas openpyxl`
> - 示例数据文件夹 `./sales_data` 内放置任意 `.xlsx` 文件即可运行
