python
import pandas as pd
import os
from typing import Tuple, Dict, List
import warnings
warnings.filterwarnings('ignore')
def validate_file_and_columns(file_path: str, key_col: str, compare_cols: List[str]) -> Tuple[pd.DataFrame, bool]:
"""
验证文件是否存在以及指定列是否存在
:param file_path: Excel文件路径
:param key_col: 检索列(用于匹配行的列)
:param compare_cols: 要比较的目标列列表
:return: DataFrame和验证结果(True/False)
"""
# 验证文件是否存在
if not os.path.exists(file_path):
print(f"错误:文件 {file_path} 不存在!")
return pd.DataFrame(), False
# 读取Excel文件
try:
df = pd.read_excel(file_path)
except Exception as e:
print(f"错误:读取文件 {file_path} 失败 - {str(e)}")
return pd.DataFrame(), False
# 验证检索列是否存在
all_cols = df.columns.tolist()
if key_col not in all_cols:
print(f"错误:文件 {file_path} 中不存在检索列 '{key_col}'")
print(f"该文件包含的列:{all_cols}")
return pd.DataFrame(), False
# 验证所有比较列是否存在
missing_cols = [col for col in compare_cols if col not in all_cols]
if missing_cols:
print(f"错误:文件 {file_path} 中缺少以下比较列:{missing_cols}")
print(f"该文件包含的列:{all_cols}")
return pd.DataFrame(), False
# 检查检索列是否有重复值
if df[key_col].duplicated().any():
duplicate_values = df[df[key_col].duplicated(keep=False)][key_col].unique()
print(f"警告:文件 {file_path} 的检索列 '{key_col}' 存在重复值:{duplicate_values[:5]}...")
return df, True
def compare_excel_columns(
file1_path: str,
file2_path: str,
key_col: str,
compare_cols: List[str],
output_result: bool = True,
output_path: str = "comparison_result.xlsx"
) -> Dict[str, List]:
"""
比较两个Excel文件的指定列
:param file1_path: 第一个Excel文件路径
:param file2_path: 第二个Excel文件路径
:param key_col: 检索列(用于匹配行的列)
:param compare_cols: 要比较的目标列列表
:param output_result: 是否保存比较结果到Excel
:param output_path: 结果文件保存路径
:return: 比较结果字典
"""
print("="*50)
print("开始比较Excel文件...")
print(f"文件1:{file1_path}")
print(f"文件2:{file2_path}")
print(f"检索列:{key_col}")
print(f"比较列:{compare_cols}")
print("="*50)
# 验证并读取两个文件
df1, df1_valid = validate_file_and_columns(file1_path, key_col, compare_cols)
df2, df2_valid = validate_file_and_columns(file2_path, key_col, compare_cols)
if not df1_valid or not df2_valid:
print("文件验证失败,终止比较!")
return {"error": "文件验证失败"}
# 以检索列为索引,方便匹配
df1_indexed = df1.set_index(key_col)
df2_indexed = df2.set_index(key_col)
# 获取所有检索键
keys1 = set(df1_indexed.index)
keys2 = set(df2_indexed.index)
# 分析键的匹配情况
common_keys = sorted(keys1 & keys2) # 共同存在的键(用于比较)
only_in_file1 = sorted(keys1 - keys2) # 只在文件1中存在的键
only_in_file2 = sorted(keys2 - keys1) # 只在文件2中存在的键
print(f"\n检索列匹配情况:")
print(f"共同存在的记录数:{len(common_keys)}")
print(f"仅文件1存在的记录数:{len(only_in_file1)}")
print(f"仅文件2存在的记录数:{len(only_in_file2)}")
# 比较共同键的目标列
match_results = []
mismatch_results = []
print(f"\n正在比较 {len(common_keys)} 条共同记录...")
for key in common_keys:
row1 = df1_indexed.loc[key]
row2 = df2_indexed.loc[key]
row_result = {key_col: key}
is_mismatch = False
for col in compare_cols:
val1 = row1[col]
val2 = row2[col]
# 处理NaN值的比较(NaN == NaN 结果为False,这里视为相等)
if pd.isna(val1) and pd.isna(val2):
match_status = "一致"
elif pd.isna(val1) or pd.isna(val2):
match_status = "不一致"
is_mismatch = True
elif str(val1) == str(val2): # 转为字符串比较,避免数值类型差异(如int和float)
match_status = "一致"
else:
match_status = "不一致"
is_mismatch = True
row_result[f"{col}_文件1"] = val1
row_result[f"{col}_文件2"] = val2
row_result[f"{col}_比较结果"] = match_status
if is_mismatch:
mismatch_results.append(row_result)
else:
match_results.append(row_result)
# 统计结果
total_common = len(common_keys)
match_count = len(match_results)
mismatch_count = len(mismatch_results)
print(f"\n比较结果统计:")
print(f"完全一致的记录数:{match_count}")
print(f"存在不一致的记录数:{mismatch_count}")
print(f"一致率:{match_count/total_common*100:.2f}%" if total_common > 0 else "无共同记录可比较")
# 整理最终结果
final_results = {
"共同记录_完全一致": match_results,
"共同记录_存在不一致": mismatch_results,
"仅文件1存在的记录": [{key_col: key} for key in only_in_file1],
"仅文件2存在的记录": [{key_col: key} for key in only_in_file2],
"统计信息": {
"文件1总记录数": len(df1),
"文件2总记录数": len(df2),
"共同记录数": total_common,
"完全一致记录数": match_count,
"不一致记录数": mismatch_count,
"仅文件1记录数": len(only_in_file1),
"仅文件2记录数": len(only_in_file2),
"一致率(%)": match_count/total_common*100 if total_common > 0 else 0
}
}
# 保存结果到Excel
if output_result:
try:
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
# 写入各个结果表
if match_results:
pd.DataFrame(match_results).to_excel(writer, sheet_name="完全一致", index=False)
if mismatch_results:
pd.DataFrame(mismatch_results).to_excel(writer, sheet_name="存在不一致", index=False)
if only_in_file1:
pd.DataFrame(only_in_file1, columns=[key_col]).to_excel(writer, sheet_name="仅文件1存在", index=False)
if only_in_file2:
pd.DataFrame(only_in_file2, columns=[key_col]).to_excel(writer, sheet_name="仅文件2存在", index=False)
# 写入统计信息
stats_df = pd.DataFrame(final_results["统计信息"].items(), columns=["统计项", "数值"])
stats_df.to_excel(writer, sheet_name="统计信息", index=False)
print(f"\n比较结果已保存到:{output_path}")
except Exception as e:
print(f"警告:保存结果文件失败 - {str(e)}")
print("\n" + "="*50)
print("比较完成!")
print("="*50)
return final_results
# ------------------- 示例使用 -------------------
if __name__ == "__main__":
# 配置参数(请根据实际需求修改)
FILE1_PATH = "file1.xlsx" # 第一个Excel文件路径
FILE2_PATH = "file2.xlsx" # 第二个Excel文件路径
KEY_COLUMN = "ID" # 检索列(用于匹配行的唯一标识列)
COMPARE_COLUMNS = ["姓名", "年龄", "成绩"] # 要比较的目标列列表
OUTPUT_PATH = "comparison_result.xlsx" # 结果输出路径
# 执行比较
results = compare_excel_columns(
file1_path=FILE1_PATH,
file2_path=FILE2_PATH,
key_col=KEY_COLUMN,
compare_cols=COMPARE_COLUMNS,
output_result=True,
output_path=OUTPUT_PATH
)
代码核心功能说明:
-
文件验证:
- 检查文件是否存在
- 验证检索列和比较列是否存在于两个文件中
- 检测检索列是否有重复值(避免一对多匹配问题)
-
行匹配逻辑:
- 以指定的「检索列」(如 ID)作为唯一标识,匹配两个文件中的对应行
- 自动识别三种情况:共同存在的行、仅在文件 1 存在的行、仅在文件 2 存在的行
-
值比较规则:
- 支持多列同时比较
- 处理 NaN / 空值(两个空值视为一致)
- 转为字符串比较,避免数值类型差异(如 100 和 100.0 视为一致)
- 详细记录每列的比较结果
-
结果输出:
- 控制台显示统计信息(匹配率、各类记录数)
- 生成 Excel 结果文件,包含 5 个工作表:
- 完全一致:所有比较列都匹配的记录
- 存在不一致:至少有一列不匹配的记录(显示具体差异)
- 仅文件 1 存在:只在第一个文件中出现的记录
- 仅文件 2 存在:只在第二个文件中出现的记录
- 统计信息:详细的数量统计
使用方法:
-
安装依赖:
bash
pip install pandas openpyxl -
修改配置参数:
FILE1_PATH和FILE2_PATH:改为你的两个 Excel 文件路径KEY_COLUMN:改为用于匹配行的列名(如 ID、编号等唯一标识)COMPARE_COLUMNS:改为需要比较的列名列表(可多个)OUTPUT_PATH:结果文件保存路径(默认即可)
-
运行代码:
- 直接运行脚本,会自动完成比较并生成结果文件
注意事项:
- 检索列建议使用唯一标识(如 ID),避免重复值导致匹配混乱
- 支持.xlsx 和.xls 格式(.xls 需要额外安装 xlrd 库:
pip install xlrd==1.2.0) - 对于日期类型,会转为字符串比较(确保两个文件的日期格式一致)
- 结果文件会覆盖同名文件,请确保输出路径正确