Python 玩转 Excel 案例【第 4 期】
大家好!今天我们来学习一个实用的Python小案例:用Python自动合并Excel多工作表的公司数据并去重。
一、案例背景
想象一下:你有一个Excel文件,里面有5个公司的工作表,每个表记录了4个季度的冠亚季军名单。领导让你统计每个公司出现过哪些人(去重合并)。手动复制粘贴不仅繁琐,还容易出错。
我们今天要学的Python脚本,就是专门解决这个问题的自动化工具。
二、整体功能预览
这个脚本主要完成以下任务:
- • ✅ 读取一个Excel文件(.xlsx格式)中的所有工作表
- • ✅ 提取每个工作表中指定列(比如B、C、D列)的数据
- • ✅ 输出一个新的Excel文件,包含两列:"公司名"和"名单"
三、准备工作
在开始之前,请确保你已经安装了必要的库:
pip install pandas openpyxl
- • 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对象
第三步:读取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:不把任何行当作列名(后面会详细解释为什么) - •
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 张无忌 赵敏 杨逍
原理:
- •
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
- 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文件 - •
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
这个函数是整个程序的"总指挥",流程清晰:
- 2. 调用
read_company_data读取数据 - 3. 调用
save_company_result保存结果
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案例,即可获取本文完整代码及配套练习数据集,一起动手掌握高效数据操作的核心技巧!
❤️ 支持我们
如果本文对你有帮助,欢迎点赞 + 关注 + 点亮小红心,你的支持就是我们持续创作的最大动力~