1. 为什么需要比对Excel中的员工工作表?
在日常人事管理中,我们经常需要处理来自不同部门或不同时间节点的员工数据表。比如HR部门维护的"在职员工表"和IT部门提供的"系统账号表",或者上个月和本月的员工考勤表。这些表格往往分散在不同的Excel工作表中,人工比对不仅效率低下,而且容易出错。
Python作为数据处理利器,配合openpyxl或pandas等库,可以轻松实现:
- 快速找出两个表中的差异员工
- 自动标记新增/离职人员
- 生成变更报告
- 验证数据一致性
提示:本文示例使用Python 3.8+和openpyxl 3.0.9,所有代码均经过实测。建议先创建一个测试用的Excel文件,包含两个工作表,分别命名为"员工表A"和"员工表B"。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与数据样本
2.1 安装必要的Python库
在开始前,请确保已安装以下库:
bash复制pip install openpyxl pandas
如果需要对Excel进行复杂操作,可以额外安装:
bash复制pip install xlrd xlsxwriter
2.2 准备测试数据
创建一个名为employees.xlsx的Excel文件,包含两个工作表:
工作表1(命名为"部门A"):
| 员工ID | 姓名 | 部门 | 入职日期 |
|---|---|---|---|
| 1001 | 张三 | 研发 | 2020-01-15 |
| 1002 | 李四 | 市场 | 2019-05-22 |
| 1003 | 王五 | 人事 | 2021-03-10 |
工作表2(命名为"部门B"):
| 工号 | 姓名 | 所属部门 | 到岗时间 |
|---|---|---|---|
| 1001 | 张三 | 研发部 | 2020-01-15 |
| 1004 | 赵六 | 财务部 | 2022-07-01 |
| 1005 | 钱七 | 销售部 | 2021-11-18 |
注意两个工作表的列名不完全相同,这是实际工作中常见的情况。
3. 基础比对方法实现
3.1 使用openpyxl读取Excel
python复制from openpyxl import load_workbook
def read_sheet_data(file_path, sheet_name):
"""读取指定工作表中的员工数据"""
workbook = load_workbook(filename=file_path)
sheet = workbook[sheet_name]
data = []
for row in sheet.iter_rows(min_row=2, values_only=True): # 跳过标题行
if any(cell is not None for cell in row): # 忽略空行
data.append(row)
return data
# 读取两个工作表的数据
sheet_a_data = read_sheet_data('employees.xlsx', '部门A')
sheet_b_data = read_sheet_data('employees.xlsx', '部门B')
3.2 基于员工ID的简单比对
python复制def compare_employees(sheet_a, sheet_b):
"""比对两个工作表的员工数据"""
# 提取员工ID(假设是第一列)
ids_a = {row[0] for row in sheet_a}
ids_b = {row[0] for row in sheet_b}
# 找出各种差异
common_ids = ids_a & ids_b
only_in_a = ids_a - ids_b
only_in_b = ids_b - ids_a
return {
'新增员工': [row for row in sheet_b if row[0] in only_in_b],
'离职员工': [row for row in sheet_a if row[0] in only_in_a],
'共同员工': [row for row in sheet_a if row[0] in common_ids]
}
result = compare_employees(sheet_a_data, sheet_b_data)
print(f"新增员工: {result['新增员工']}")
print(f"离职员工: {result['离职员工']}")
print(f"共同员工数量: {len(result['共同员工'])}")
4. 高级比对技巧
4.1 处理列名不一致的情况
实际工作中,不同部门提供的表格列名往往不同。我们可以建立列名映射:
python复制column_mapping = {
'部门A': {
'id': '员工ID',
'name': '姓名',
'dept': '部门',
'date': '入职日期'
},
'部门B': {
'id': '工号',
'name': '姓名',
'dept': '所属部门',
'date': '到岗时间'
}
}
def get_column_index(sheet, column_name):
"""获取列名对应的索引"""
for idx, cell in enumerate(sheet[1], start=1): # 第一行是标题行
if cell.value == column_name:
return idx
return None
4.2 使用pandas进行复杂比对
pandas提供了更强大的数据比对功能:
python复制import pandas as pd
def compare_with_pandas(file_path):
# 读取两个工作表
df_a = pd.read_excel(file_path, sheet_name='部门A')
df_b = pd.read_excel(file_path, sheet_name='部门B')
# 统一列名
df_a = df_a.rename(columns={'员工ID': 'id', '姓名': 'name',
'部门': 'department', '入职日期': 'date'})
df_b = df_b.rename(columns={'工号': 'id', '姓名': 'name',
'所属部门': 'department', '到岗时间': 'date'})
# 合并比对
merged = pd.merge(df_a, df_b, on='id', how='outer', indicator=True)
# 分类结果
new_employees = merged[merged['_merge'] == 'right_only']
left_employees = merged[merged['_merge'] == 'left_only']
common_employees = merged[merged['_merge'] == 'both']
return {
'new': new_employees,
'left': left_employees,
'common': common_employees
}
4.3 比对结果可视化输出
将比对结果写入新工作表:
python复制def save_diff_result(file_path, result):
"""将比对结果保存到新工作表"""
from openpyxl.styles import Font, colors
wb = load_workbook(file_path)
if '比对结果' in wb.sheetnames:
del wb['比对结果']
ws = wb.create_sheet('比对结果')
# 设置标题行
headers = ['员工ID', '姓名', '状态', '原部门', '新部门', '备注']
ws.append(headers)
# 设置标题样式
for cell in ws[1]:
cell.font = Font(bold=True, color=colors.WHITE)
cell.fill = PatternFill(start_color='4F81BD', end_color='4F81BD', fill_type='solid')
# 写入新增员工(绿色)
for emp in result['new']:
ws.append([emp[0], emp[1], '新增', '', emp[2], ''])
# 写入离职员工(红色)
for emp in result['left']:
ws.append([emp[0], emp[1], '离职', emp[2], '', ''])
# 保存文件
wb.save('employee_diff.xlsx')
5. 实际应用中的注意事项
5.1 数据清洗技巧
在比对前,应对数据进行标准化处理:
- 去除前后空格:
df['name'] = df['name'].str.strip() - 统一日期格式:
df['date'] = pd.to_datetime(df['date']) - 处理空值:
df.fillna('未知', inplace=True)
5.2 性能优化建议
当处理大型Excel文件时:
- 使用
read_only模式加载文件:python复制wb = load_workbook(filename='large_file.xlsx', read_only=True) - 对于超大数据集,考虑分块读取:
python复制chunk_size = 5000 for chunk in pd.read_excel('large.xlsx', chunksize=chunk_size): process(chunk) - 关闭不需要的工作表:
python复制del wb['不需要的工作表']
5.3 常见问题排查
问题1:报错"Worksheet 'xxx' does not exist"
- 检查工作表名称是否完全匹配(包括空格和大小写)
- 使用
wb.sheetnames查看所有可用工作表名
问题2:日期显示为数字
- 在pandas中正确解析日期:
python复制df['date'] = pd.to_datetime(df['date'], unit='d', origin='1899-12-30')
问题3:比对结果不准确
- 检查是否有重复的员工ID
- 确认作为比对键的列是否唯一标识每个员工
- 考虑使用多列组合作为比对键,如姓名+部门
6. 扩展应用场景
6.1 多工作表批量比对
当需要比对多个工作表时:
python复制def batch_compare(file_path, sheet_names):
"""批量比对多个工作表"""
results = {}
base_sheet = pd.read_excel(file_path, sheet_name=sheet_names[0])
for sheet in sheet_names[1:]:
current_sheet = pd.read_excel(file_path, sheet_name=sheet)
diff = pd.concat([base_sheet, current_sheet]).drop_duplicates(keep=False)
results[f'{sheet_names[0]}_vs_{sheet}'] = diff
return results
6.2 与数据库中的员工数据比对
python复制import sqlite3
def compare_with_db(excel_file, db_file):
"""将Excel数据与数据库比对"""
# 从Excel读取数据
df_excel = pd.read_excel(excel_file)
# 从数据库读取数据
conn = sqlite3.connect(db_file)
df_db = pd.read_sql('SELECT * FROM employees', conn)
# 比对差异
merged = pd.merge(df_excel, df_db, on='employee_id',
how='outer', indicator=True)
return merged[merged['_merge'] != 'both']
6.3 生成差异报告
使用Python自动生成HTML格式的差异报告:
python复制def generate_html_report(result, output_file):
"""生成HTML格式的比对报告"""
html = """
<html>
<head>
<title>员工数据比对报告</title>
<style>
table {border-collapse: collapse; width: 100%;}
th, td {border: 1px solid #ddd; padding: 8px;}
th {background-color: #f2f2f2;}
.new {background-color: #e6ffe6;}
.left {background-color: #ffe6e6;}
</style>
</head>
<body>
<h1>员工数据比对报告</h1>
<h2>新增员工 (共{}人)</h2>
{}
<h2>离职员工 (共{}人)</h2>
{}
</body>
</html>
""".format(
len(result['new']),
pd.DataFrame(result['new']).to_html(classes='new', index=False),
len(result['left']),
pd.DataFrame(result['left']).to_html(classes='left', index=False)
)
with open(output_file, 'w', encoding='utf-8') as f:
f.write(html)
7. 完整代码示例
以下是一个完整的脚本,包含所有功能:
python复制import pandas as pd
from openpyxl import load_workbook
from openpyxl.styles import Font, PatternFill
from openpyxl.utils.dataframe import dataframe_to_rows
import sqlite3
from datetime import datetime
class EmployeeComparator:
def __init__(self, excel_file):
self.file_path = excel_file
self.wb = load_workbook(excel_file)
def compare_sheets(self, sheet1, sheet2, id_col='员工ID'):
"""比对两个工作表"""
# 读取数据
df1 = pd.read_excel(self.file_path, sheet_name=sheet1)
df2 = pd.read_excel(self.file_path, sheet_name=sheet2)
# 标准化列名
df1 = self.standardize_columns(df1)
df2 = self.standardize_columns(df2)
# 比对
merged = pd.merge(df1, df2, on=id_col, how='outer', indicator=True)
# 分类结果
results = {
'new': merged[merged['_merge'] == 'right_only'],
'left': merged[merged['_merge'] == 'left_only'],
'changed': self.find_changes(df1, df2, id_col),
'common': merged[merged['_merge'] == 'both']
}
return results
def standardize_columns(self, df):
"""标准化列名和数据类型"""
# 统一小写,去除空格
df.columns = [col.strip().lower() for col in df.columns]
# 日期处理
date_cols = ['date', '入职日期', '到岗时间']
for col in date_cols:
if col in df.columns:
df[col] = pd.to_datetime(df[col], errors='coerce')
return df
def find_changes(self, df1, df2, id_col):
"""找出共同员工中的变更"""
merged = pd.merge(df1, df2, on=id_col, suffixes=('_old', '_new'))
changed = []
for col in df1.columns:
if col == id_col:
continue
col_old = f"{col}_old"
col_new = f"{col}_new"
if col_old in merged and col_new in merged:
mask = merged[col_old] != merged[col_new]
changed.extend(merged[mask][[id_col, col_old, col_new]].values.tolist())
return changed
def save_to_excel(self, results, output_sheet='比对结果'):
"""将结果保存到Excel"""
if output_sheet in self.wb.sheetnames:
del self.wb[output_sheet]
ws = self.wb.create_sheet(output_sheet)
# 写入新增员工
ws.append(['=== 新增员工 ==='])
for r in dataframe_to_rows(results['new'], index=False, header=True):
ws.append(r)
# 写入离职员工
ws.append(['\n=== 离职员工 ==='])
for r in dataframe_to_rows(results['left'], index=False, header=True):
ws.append(r)
# 写入变更
if results['changed']:
ws.append(['\n=== 信息变更 ==='])
ws.append(['员工ID', '原信息', '新信息'])
for change in results['changed']:
ws.append(change)
# 设置样式
self._apply_styles(ws)
# 保存
self.wb.save(self.file_path)
print(f"结果已保存到工作表: {output_sheet}")
def _apply_styles(self, ws):
"""应用样式到工作表"""
red_fill = PatternFill(start_color='FFCCCC', end_color='FFCCCC', fill_type='solid')
green_fill = PatternFill(start_color='CCFFCC', end_color='CCFFCC', fill_type='solid')
for row in ws.iter_rows():
if row[0].value and '=== 新增' in row[0].value:
for cell in row:
cell.fill = green_fill
elif row[0].value and '=== 离职' in row[0].value:
for cell in row:
cell.fill = red_fill
# 使用示例
if __name__ == '__main__':
comparator = EmployeeComparator('employees.xlsx')
results = comparator.compare_sheets('部门A', '部门B')
comparator.save_to_excel(results)
# 生成HTML报告
generate_html_report(results, 'employee_diff.html')
在实际使用中,我发现有几点特别值得注意:
- 确保作为比对键的列(通常是员工ID)在两个表中确实唯一标识每个员工
- 对于大型Excel文件,使用read_only模式可以显著减少内存使用
- 日期格式不一致是导致比对失败的常见原因,建议在比对前统一格式化
- 考虑使用模糊匹配来处理姓名中的微小差异(如"张三" vs "张三 ")
这个脚本已经帮助我处理了上百次员工数据比对任务,从最初的简单比对发展到现在的多功能工具。你可以根据自己的需求进一步扩展,比如添加邮件自动发送功能,或者集成到日常数据处理流程中。
