Python 玩转 Excel 案例【第 4 期】
Python 玩转 Excel 案例【第 4 期】
大家好!今天我们来学习一个实用的Python小案例:用Python自动合并Excel多工作表的公司数据并去重。
一、案例背景
想象一下:你有一个Excel文件,里面有5个公司的工作表,每个表记录了4个季度的冠亚季军名单。领导让你统计每个公司出现过哪些人(去重合并)。手动复制粘贴不仅繁琐,还容易出错。
我们今天要学的Python脚本,就是专门解决这个问题的自动化工具。
二、整体功能预览
这个脚本主要完成以下任务:
-
• ✅ 读取一个Excel文件(.xlsx格式)中的所有工作表 -
• ✅ 每个工作表代表一个公司 -
• ✅ 提取每个工作表中指定列(比如B、C、D列)的数据 -
• ✅ 去重后合并成一个字符串 -
• ✅ 输出一个新的Excel文件,包含两列:”公司名”和”名单”
三、准备工作
在开始之前,请确保你已经安装了必要的库:
pip install pandas openpyxl
-
• pandas:强大的数据处理库 -
• openpyxl:用于读写Excel文件的引擎
四、代码整体结构
import logging
from pathlib import Path
from typing import List, Optional, Union
import pandas as pd
# 配置日志
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
# 三个核心函数
def read_company_data(...): ...
def save_company_result(...): ...
def process_excel_companies(...): ...
if __name__ == "__main__":
# 主程序入口
process_excel_companies(...)
关于模块导入
-
• from ... import ...:只导入模块中的特定部分 -
• import ... as ...:导入整个模块并起别名
记忆要点
-
• 只用模块中的1-2个功能 → from ... import ...
例如:from math import pi -
• 要用模块的很多功能 → import ... as ...
例如:import numpy as np -
• 模块名本身就很短/清晰 → import ...
例如:import json -
• 避免命名冲突 → import ... as ...
例如:import statistics as stats
五、逐个函数详解
1. 日志配置模块
# 配置日志
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
知识点讲解:
-
• logging模块:Python内置的日志记录工具,可以让我们清楚地看到程序运行的过程 -
• basicConfig():配置日志的基本设置 -
• level=logging.INFO:设置日志级别为INFO -
• format:定义日志的输出格式(时间 – 级别 – 具体信息) -
• getLogger(name):创建一个以当前模块名命名的日志记录器
2. read_company_data 函数(核心处理函数)
这个函数是整个程序的核心,负责读取Excel文件并处理数据。
def read_company_data(
input_path: Union[str, Path],
columns: Optional[List[Union[int, str]]] = None,
skip_rows: int = 1,
) -> pd.DataFrame:
参数说明
-
• input_path ( str或Path,必填):输入的Excel文件路径 -
• columns ( List[int或str],默认None,函数内单独设置了默认值,若为None则设为[1,2,3]):要读取的列(列号或列名) -
• skip_rows ( int,默认1):跳过前几行(通常是表头)
Union与Optional
def read_company_data(
columns: Optional[List[Union[int, str]]] = None, # 嵌套使用
)
拆解分析:
-
• Optional[...]:表示这个参数可以传,也可以不传(不传时默认为None) -
• List[Union[int, str]]:表示列表里的元素可以是int,也可以是str -
• 整体意思: columns参数要么是None,要么是一个包含int或str的列表
💡 记忆技巧
-
• 这个参数可以是int或str → Union[int, str] -
• 这个参数可以传int,也可以不传 → Optional[int] -
• 这个参数可以是int或str,也可以不传 → Optional[Union[int, str]]
代码详解
第一步:参数初始化
if columns is None:
columns = [1, 2, 3]
如果调用函数时没指定columns,就默认读取B、C、D列。
第二步:路径处理和文件存在性检查
input_path = Path(input_path)
if not input_path.exists():
raise FileNotFoundError(f"文件不存在: {input_path}")
-
• Path(input_path):将字符串路径转换成Path对象 -
• exists():检查文件是否存在
第三步:读取Excel文件
try:
sheet_dict = pd.read_excel(
input_path,
sheet_name=None,
header=None,
usecols=columns,
skiprows=skip_rows
)
logger.info(f"读取文件: {input_path},工作表: {list(sheet_dict.keys())}")
except Exception as e:
logger.error(f"无法读取 Excel 文件: {e}")
raise
关键参数解读:
-
• sheet_name=None:一次性读取所有工作表,返回字典(键=工作表名,值=DataFrame) -
• header=None:不把任何行当作列名(后面会详细解释为什么) -
• usecols=columns:只读取指定列 -
• skiprows=skip_rows:跳过前n行(通常是表头)
重要:header=None 与 skiprows 的配合使用
原始数据示例(蜀中集团)
季度 冠军 亚军 季军 ← 第1行(标题行,需跳过)
0 1季度 张无忌 赵敏 杨逍 ← 第2行(数据)
1 2季度 张无忌 杨逍 赵敏 ← 第3行(数据)
2 3季度 杨逍 张无忌 周芷若 ← 第4行(数据)
3 4季度 张无忌 赵敏 杨逍 ← 第5行(数据)
✅ 正确用法:header=None, skiprows=1
pd.read_excel(..., header=None, skiprows=1, usecols=[1,2,3])
读取结果:
0 1 2
0 张无忌 赵敏 杨逍
1 张无忌 杨逍 赵敏
2 杨逍 张无忌 周芷若
3 张无忌 赵敏 杨逍
原理:
-
• skiprows=1:跳过第1行标题 -
• header=None:确保剩下的全是数据,没有列名干扰
❌ 常见错误
错误1:只用header=None(不跳行)
pd.read_excel(..., header=None, skiprows=0, usecols=[1,2,3])
读取结果❌:
0 1 2
0 冠军 亚军 季军 ← 标题被当成数据!
1 张无忌 赵敏 杨逍
2 张无忌 杨逍 赵敏
3 杨逍 张无忌 周芷若
4 张无忌 赵敏 杨逍
问题:标题”冠军/亚军/季军”被当成数据处理
错误2:只用skiprows=1(无header=None)
pd.read_excel(..., header=0, skiprows=1, usecols=[1,2,3])
读取结果❌:
张无忌 杨逍 张无忌 ← 原第2行数据被当成了列名
0 杨逍 张无忌 周芷若 ← 原第3行数据
1 张无忌 赵敏 杨逍 ← 原第4行数据
2 张无忌 赵敏 杨逍 ← 原第5行数据
问题:原第2行数据”张无忌/杨逍/张无忌”变成列名
错误3:header=0, skiprows=0(默认)
pd.read_excel(..., header=0, skiprows=0, usecols=[1,2,3])
读取结果❌:
冠军 亚军 季军
0 张无忌 赵敏 杨逍
1 张无忌 杨逍 赵敏
2 杨逍 张无忌 周芷若
3 张无忌 赵敏 杨逍
问题:虽然显示正确,但后面的df.stack()会把列名也堆叠进去
第四步:遍历每个工作表并处理数据
result_rows = []
for sheet_name, df in sheet_dict.items():
try:
# 数据处理流水线:堆叠 → 去重 → 去空 → 合并
unique_values = df.stack().drop_duplicates().dropna()
combined_str = "、".join(map(str, unique_values))
result_rows.append({"公司名": sheet_name, "名单": combined_str})
logger.info(f"处理完成: {sheet_name},共 {len(unique_values)} 条唯一值")
数据处理流程详解:
以蜀中集团数据为例:
原始DataFrame:
0 1 2
0 张无忌 赵敏 杨逍
1 张无忌 杨逍 赵敏
2 杨逍 张无忌 周芷若
3 张无忌 赵敏 杨逍
-
1. df.stack():堆叠所有列(只显示行号,省略了列号)0 张无忌
0 赵敏
0 杨逍
1 张无忌
1 杨逍
1 赵敏
2 杨逍
2 张无忌
2 周芷若
3 张无忌
3 赵敏
3 杨逍 -
2. drop_duplicates():去重0 张无忌
0 赵敏
0 杨逍
2 周芷若
dtype: object -
3. dropna():去除空值(如有) -
4. "、".join(map(str, unique_values)):将unique_values中的每个元素转成字符串,并用顿号连接成一个完整的文本。
拆解看:
map(str, …):把每个元素转成字符串(防数字等类型报错)
"、".join(...):用顿号把这些字符串串起来
最终效果:
["张无忌", "赵敏", "杨逍"] → "张无忌、赵敏、杨逍"
第五步:返回结果
except Exception as e:
logger.error(f"处理工作表 {sheet_name} 时出错: {e}")
result_rows.append({"公司名": sheet_name, "名单": f"处理失败: {e}"})
return pd.DataFrame(result_rows)
📌 为什么要转换成DataFrame?
原始result_rows(列表套字典):
result_rows = [
{"公司名": "蜀中集团", "名单": "张无忌、赵敏、杨逍、周芷若"},
{"公司名": "姑苏慕容", "名单": "慕容复、邓百川、包不同、风波恶"},
# ...
]
转换成DataFrame后:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
为什么要转换?
-
• ✅ 后续 save_company_result()需要DataFrame才能用to_excel() -
• ✅ 表格化展示更清晰 -
• ✅ 便于后续数据处理
3. save_company_result 函数(保存结果)
def save_company_result(
df: pd.DataFrame,
output_path: Union[str, Path],
sheet_name: str = "统计结果",
) -> None:
参数讲解
-
• df ( pd.DataFrame,必填):包含处理结果的DataFrame -
• output_path ( str或Path,必填):输出文件路径 -
• sheet_name ( str,默认"统计结果"):Excel工作表名称
代码详解:
output_path = Path(output_path)
try:
df.to_excel(output_path, sheet_name=sheet_name, index=False, engine="openpyxl")
logger.info(f"结果已保存至: {output_path}")
except Exception as e:
logger.error(f"保存文件失败: {e}")
raise
代码详解:
-
• Path(output_path):把输出路径转换成Path对象 -
• df.to_excel():把DataFrame直接写入Excel文件 -
• output_path:输出文件路径 -
• sheet_name=sheet_name:指定工作表名称 -
• index=False:不保存DataFrame的行索引(就是左边那个默认的数字序号) -
• engine="openpyxl":指定使用openpyxl引擎处理.xlsx格式
4. process_excel_companies 函数(主流程)
def process_excel_companies(
input_path: Union[str, Path] = "Chapter-4.xlsx",
output_path: Union[str, Path] = "Chapter-4-统计结果.xlsx",
columns: Optional[List[Union[int, str]]] = None,
skip_rows: int = 1,
) -> None:
logger.info("开始处理 Excel 公司数据...")
try:
result_df = read_company_data(input_path, columns, skip_rows)
save_company_result(result_df, output_path)
logger.info("处理完成。")
except Exception as e:
logger.critical(f"程序执行失败: {e}")
raise
这个函数是整个程序的”总指挥”,流程清晰:
-
1. 记录开始日志 -
2. 调用 read_company_data读取数据 -
3. 调用 save_company_result保存结果 -
4. 记录完成日志
5. 主程序入口
if __name__ == "__main__":
process_excel_companies()
说明:只有当这个文件被直接运行时,才会执行下面的代码(而不是被导入时)。
六、运行效果展示
输入文件内容
蜀中集团工作表:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
姑苏慕容工作表:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
太湖帮工作表:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
西湖会工作表:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
金陵帮工作表:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
运行程序
python company_processor.py
控制台输出
2026-03-09 20:15:23 - INFO - 开始处理 Excel 公司数据...
2026-03-09 20:15:24 - INFO - 读取文件: Chapter-4.xlsx,工作表: ['蜀中集团', '姑苏慕容', '太湖帮', '西湖会', '金陵帮']
2026-03-09 20:15:24 - INFO - 处理完成: 蜀中集团,共 4 条唯一值
2026-03-09 20:15:24 - INFO - 处理完成: 姑苏慕容,共 4 条唯一值
2026-03-09 20:15:24 - INFO - 处理完成: 太湖帮,共 4 条唯一值
2026-03-09 20:15:24 - INFO - 处理完成: 西湖会,共 4 条唯一值
2026-03-09 20:15:24 - INFO - 处理完成: 金陵帮,共 3 条唯一值
2026-03-09 20:15:24 - INFO - 结果已保存至: Chapter-4-统计结果.xlsx
2026-03-09 20:15:24 - INFO - 处理完成。
输出结果
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
七、知识点总结
通过这个实战项目,我们学到了:
|
|
|
|
|---|---|---|
| pathlib | Path对象 |
|
| logging |
|
|
| pandas | read_excel() |
|
| pandas | sheet_name=None |
|
| pandas | header=None |
|
| pandas | skiprows |
|
| pandas | stack() |
|
| pandas | drop_duplicates() |
|
| pandas | dropna() |
|
| pandas | to_excel() |
|
| 异常处理 | try-except |
|
八、应用场景拓展
这个脚本可以广泛应用于:
-
• 📊 人力资源:整理各公司员工名单 -
• 📈 销售管理:汇总各团队业绩数据 -
• 💰 财务统计:统计各部门费用明细 -
• 📋 项目管理:合并各项目成员信息 -
• 🔍 数据分析:从多工作表中提取并汇总数据
希望这篇教程能帮助你掌握Python处理Excel数据的技巧,提升工作效率!
📦 资源获取提示
关注「码农自习室」,后台回复关键词 PythonExcel案例,即可获取本文完整代码及配套练习数据集,一起动手掌握高效数据操作的核心技巧!
❤️ 支持我们
如果本文对你有帮助,欢迎点赞 + 关注 + 点亮小红心,你的支持就是我们持续创作的最大动力~
夜雨聆风