在日常办公中,经常需要将一张包含多类数据的 Excel 总表,按照某个字段(如产品、地区、门店)拆分成多个独立的工作簿。如果手动操作,需要反复筛选、复制、新建、粘贴、保存,不仅效率低下,还容易出错。使用 Python 脚本可以一键完成这一任务,稳定且可复用。本文将基于 xlwings 库,详细讲解如何实现按条件拆分的完整流程,并提供可直接运行的代码。
## 一、业务场景与核心理念
典型场景包括:销售总表按产品拆分给产品负责人,门店数据按门店拆分给店长,区域业绩按地区拆分给区域经理。只要表格中有明确的分类列(如“类别”、“地区”等),就可以用程序自动分组输出。
核心思路是使用 Python 的字典(dict)作为“分类容器”。字典的键(key)是分类字段的值,值(value)是该分类下的所有数据行(列表形式)。例如:
- data = {
- "背包": [
- ["双肩包", "背包", 10, 129, 1290],
- ["登山包", "背包", 5, 199, 995]
- ],
- "行李箱": [
- ["拉杆箱", "行李箱", 8, 299, 2392]
- ]
- }
复制代码
然后遍历字典,对每个 key 生成一个独立的工作簿,将表头和数据行写入后保存。这样,分类字段的值就直接决定了输出文件名和内容。
## 二、完整代码:按指定列拆分总表
以下代码使用 xlwings(操作 Excel 的第三方库),读取源文件,按指定列(默认第 1 列,即 B 列)分组,并输出到目标文件夹。代码已处理文件名非法字符、空分类值、工作表名称长度限制等问题。
- import os
- import re
- import xlwings as xw
- def safe_filename(name):
- """将分类值转换为合法文件名,替换非法字符为下划线"""
- name = str(name).strip()
- name = re.sub(r'[\\/:*?"<>|]', "_", name)
- return name if name else "未分类"
- # ====== 需要根据实际情况修改的参数 ======
- source_file = r"e:\file\总表.xlsx"
- source_sheet = "Sheet1"
- output_dir = r"e:\file\拆分结果"
- group_col_index = 1 # 按哪一列拆分:0 表示 A 列,1 表示 B 列,以此类推
- # =====================================
- os.makedirs(output_dir, exist_ok=True)
- app = xw.App(visible=False, add_book=False)
- try:
- wb = app.books.open(source_file)
- sht = wb.sheets[source_sheet]
- # 读取连续区域(包含表头)
- table = sht.range("A1").expand("table").value
- if not table or len(table) < 2:
- raise ValueError("源表数据为空或只有表头,没有可拆分的数据行。")
- header = table[0]
- rows = table[1:]
-
- data = dict()
- for row in rows:
- key = row[group_col_index]
- if key is None or str(key).strip() == "":
- key = "未分类"
- key = str(key).strip()
- if key not in data:
- data[key] = []
- data[key].append(row)
-
- for key, value in data.items():
- file_name = safe_filename(key) + ".xlsx"
- out_path = os.path.join(output_dir, file_name)
- new_wb = app.books.add()
- new_sht = new_wb.sheets[0]
- new_sht.name = safe_filename(key)[:31] # Excel 工作表名最长 31 字符
- new_sht.range("A1").value = [header] + value # 表头 + 数据行
- new_wb.save(out_path)
- new_wb.close()
- print(f"已生成:{out_path},行数:{len(value)}")
- wb.close()
- finally:
- app.quit()
复制代码
## 三、关键代码解析
- **data = dict()**:创建一个空字典,用于存放分组结果。
- **if key not in data: data[key] = []**:如果该分类尚未出现,则初始化空列表。
- **data[key].append(row)**:将当前行追加到对应分类列表。
- **[header] + value**:将表头与数据行拼接,确保输出文件包含字段名称。
- **safe_filename(key)**:去除分类值中的空格,并将 Windows 非法字符(\ / : * ? " < > |)替换为下划线,避免保存失败。
- **new_sht.name = safe_filename(key)[:31]**:Excel 工作表名称长度不能超过 31 个字符,截断处理。
## 四、常见问题与检查要点
1. **分类字段为空**:脚本中已将 None 或空白行归入“未分类”,但实际业务中可能需要特殊处理(如跳过或报错)。
2. **分类值存在前后空格**:使用 strip() 去除空格,否则“华北”和“华北 ”会被视为两个不同分类。
3. **文件名重复冲突**:若多个分类清洗后得到相同文件名,后生成的会覆盖先前的。建议使用唯一编号避免覆盖:
- def get_unique_path(folder, filename):
- base, ext = os.path.splitext(filename)
- path = os.path.join(folder, filename)
- index = 1
- while os.path.exists(path):
- path = os.path.join(folder, f"{base}_{index}{ext}")
- index += 1
- return path
复制代码
4. **输出目录不存在**:使用 os.makedirs(output_dir, exist_ok=True) 自动创建。
## 五、结果验证:确保拆分正确
运行脚本后,不能只看控制台无报错。应检查以下三点:
- 输出文件数量是否等于分类数量。
- 每个工作簿中的分类列数据是否一致(抽样检查)。
- 输出数据行总数是否等于源表数据行数。
可以使用以下代码快速核对:
- total_output_rows = 0
- for key, value in data.items():
- print(f"{key}:{len(value)} 行")
- total_output_rows += len(value)
- print(f"源数据行数:{len(rows)}")
- print(f"输出数据行数合计:{total_output_rows}")
复制代码
原理:按条件拆分且不删除数据,输出行数总和应等于源表数据行数。若不一致,需排查空值处理、过滤逻辑或数据读取范围。
## 六、实战总结
本文展示的“读取→分组→逐组输出”模型,可广泛应用于销售数据分发、资产清单拆分、人员绩效报表等场景。关键是先确认分类字段的可靠性,在输出前做数据清洗(去空格、处理空值、文件名合法化),最后对结果进行核对。将此脚本进一步封装,可加入图形界面或配置参数,成为稳定的办公自动化工具。 |