Python处理Excel:批量替换内容与合并多个文件(附完整代码)

Python处理Excel:批量替换内容与合并多个文件(附完整代码)

一、知识点简介

日常工作中,Excel几乎是每个职场人的必备工具:报表汇总、数据清洗、模板填充……但当你要把1000行数据里的"北京市"全部改成"北京",或者把10个部门的Excel合并成一张总表时,手动操作简直是噩梦。

本文教你用 Python 搞定这两件事:

  • 替换内容:按关键词/正则/条件精准替换,支持整表或指定列
  • 合并内容:把多个Excel合并成一个,支持按 sheet、按列去重

全程零门槛,代码复制就能跑,不装任何付费插件。


二、适用场景

场景 用 Python 解决什么问题
数据清洗 把"北京市"/"北京"/"Beijing"统一替换成"北京"
报表合并 月末把10个分公司的Excel合并成一张总表
模板填充 批量把模板中的占位符 {name} 替换成真实姓名
字段统一 把"男/女"改成"1/0",把"是/否"改成布尔值
数据脱敏 把手机号、身份证号中间几位打码
去重合并 把多张表合并后按某列(如工号)去重

💡 为什么用 Python 而不是 Excel 内置功能?

  • Excel 替换不支持正则(Python 的 re 模块支持),批量处理多个文件要写 VBA,Python 一行命令搞定
  • 合并时自动去重、多 sheet 处理,VBA 写30行,Python 10行搞定

三、核心原理

3.1 用什么库?

Python 操作 Excel 主要两个库,选哪个看情况:

特点 适用场景
openpyxl 读写字.xlsx/.xlsm,功能全 99%的场景用它
xlrd/xlwt 只读/只写老格式 .xls 老文件(.xls)才用

本文全部用 openpyxl,安装一行命令:

pip install openpyxl

3.2 Excel 文件在你电脑上是什么结构?

一个Excel文件(workbook)
├── Sheet1(工作表)
│   ├── A列   B列   C列
│   ├── 张三  北京   28
│   └── 李四  上海   35
├── Sheet2
│   └── ...
└── Sheet3

Python 操作的核心逻辑就是:

  1. 打开文件 → 拿到 workbook 对象
  2. 定位 sheet → 拿到 worksheet 对象
  3. 读写单元格sheet['A1']sheet.cell(row=1, column=1)
  4. 保存文件wb.save('output.xlsx')

3.3 替换的原理

Python 里字符串替换就是一行代码的事:

# 基础替换
text = "北京市朝阳区"
text = text.replace("北京", "天津")  # "天津市朝阳区"

# 正则替换(支持复杂规则)
import re
text = re.sub(r"1[3-9]\d{9}", "1XXXXXXXXXX", text)  # 手机号脱敏

Excel 替换就是把表格里每个单元格都遍历一遍,满足条件的就改掉。

3.4 合并的原理

合并多个文件的本质是把数据都读到内存里,然后用 extend()pandas.concat() 拼接成一张大表

# 伪代码逻辑
all_rows = []
for file in [file1, file2, file3]:       # 遍历每个文件
    rows = 读取该文件的所有行
    all_rows.extend(rows)                  # 追加到列表
合并结果 = 去掉all_rows里的重复行
写入新的Excel文件

四、完整实操步骤

环境准备

pip install openpyxl pandas

pandas 用来合并去重,比纯 openpyxl 方便很多。


五、代码实战

5.1 实战一:批量替换内容

场景:把"北京市"替换成"北京","男/女"替换成"1/0"

import openpyxl
import re
from typing import Union, List, Dict


def replace_cell_value(cell, old_text=None, new_text=None,
                        regex_pattern=None, regex_repl=None,
                        replace_map: Dict[str, str] = None):
    """
    替换单个单元格的值(核心替换逻辑)
    
    参数(至少传一种):
        old_text / new_text      : 普通文本替换
        regex_pattern / regex_repl: 正则替换(支持复杂规则)
        replace_map              : 字典映射替换(适合多对一场景)
    """
    if cell.value is None:
        return

    val = str(cell.value)

    if replace_map:
        # 字典映射替换:{"北京市":"北京", "男":"1", "女":"0"}
        for k, v in replace_map.items():
            val = val.replace(k, v)

    elif regex_pattern is not None:
        # 正则替换:支持复杂匹配规则
        val = re.sub(regex_pattern, regex_repl, val)

    elif old_text is not None:
        # 普通文本替换
        val = val.replace(old_text, new_text if new_text else "")

    cell.value = val


def replace_in_excel(input_path: str,
                     output_path: str,
                     sheet_names: Union[str, List[str]] = None,
                     columns: List[int] = None,
                     **kwargs):
    """
    替换Excel文件内容
    
    参数:
        input_path   : 输入文件路径
        output_path  : 输出文件路径
        sheet_names  : 要处理的工作表名,None表示全部sheet
                     传字符串表示单个,传列表表示多个
        columns      : 要处理的列索引(从1开始),None表示全部列
                     例如:[1, 3, 5]表示只处理第1、3、5列
        **kwargs    : 透传给 replace_cell_value(见上方函数参数)
    """
    wb = openpyxl.load_workbook(input_path)

    # 确定要处理哪些 sheet
    if sheet_names is None:
        sheets = wb.worksheets
    elif isinstance(sheet_names, str):
        sheets = [wb[sheet_names]]
    else:
        sheets = [wb[name] for name in sheet_names]

    for sheet in sheets:
        print(f"  📄 正在处理 Sheet: {sheet.title}")

        # 遍历所有行
        for row in sheet.iter_rows():
            for cell in row:
                # 如果指定了列号,跳过不在范围内的单元格
                if columns and cell.column not in columns:
                    continue
                replace_cell_value(cell, **kwargs)

    wb.save(output_path)
    print(f"✅ 完成!已保存至: {output_path}")


# ====================== 调用示例 ======================

# 【示例1】普通文本替换:把"北京市"改成"北京"
replace_in_excel(
    input_path="员工信息表.xlsx",
    output_path="员工信息表_替换后.xlsx",
    old_text="北京市",
    new_text="北京"
)

# 【示例2】多对一映射替换:男→1,女→0
replace_in_excel(
    input_path="员工信息表.xlsx",
    output_path="员工信息表_性别编码.xlsx",
    replace_map={"男": "1", "女": "0"}
)

# 【示例3】正则替换:手机号中间4位打码
replace_in_excel(
    input_path="客户列表.xlsx",
    output_path="客户列表_脱敏.xlsx",
    regex_pattern=r"(1[3-9]\d)(\d{4})(\d{4})",
    regex_repl=r"\1****\3"
)

# 【示例4】只处理指定 sheet 和指定列(第1列和第3列)
replace_in_excel(
    input_path="数据.xlsx",
    output_path="数据_已处理.xlsx",
    sheet_names=["销售数据", "退货数据"],
    columns=[1, 3],
    old_text="2023",
    new_text="2024"
)

# 【示例5】把多个关键词统一替换(字典法)
replace_in_excel(
    input_path="地址表.xlsx",
    output_path="地址表_统一.xlsx",
    replace_map={
        "北京市": "北京",
        "上海市": "上海",
        "广州市": "广州",
        "深圳市": "深圳",
        "北京市朝阳区": "北京-朝阳区",
        "北京市海淀区": "北京-海淀区",
    }
)

运行效果

  📄 正在处理 Sheet: 员工信息
  📄 正在处理 Sheet: 部门信息
✅ 完成!已保存至: 员工信息表_替换后.xlsx

替换前后对比(员工信息表.xlsx):

姓名 城市 性别
张三 北京市朝阳区
李四 北京市海淀区

↓ 替换后 ↓

姓名 城市 性别
张三 北京朝阳区 1
李四 北京海淀区 0

5.2 实战二:合并多个Excel文件

场景:把多个分公司月报合并成一张总表,按"工号"列去重

import openpyxl
import pandas as pd
import os
from typing import List, Optional


def merge_excel_files(file_paths: List[str],
                      output_path: str,
                      sheet_name: str = 0,
                      drop_duplicates_by: Optional[List[str]] = None,
                      ignore_sheets: List[str] = None):
    """
    合并多个Excel文件(支持去重)
    
    参数:
        file_paths        : 要合并的文件路径列表
        output_path       : 合并后的输出文件路径
        sheet_name        : 读取哪个工作表(名字或索引,默认第0个)
        drop_duplicates_by: 按哪些列去重,传None表示不去重
                           例如:["工号"] 或 ["姓名", "部门"]
        ignore_sheets     : 忽略的工作表名列表(这些sheet不合并)
    """
    all_dataframes = []

    for file_path in file_paths:
        if not os.path.exists(file_path):
            print(f"  ⚠️  文件不存在,跳过: {file_path}")
            continue

        try:
            # 用 pandas 一次性把指定 sheet 读成 DataFrame
            df = pd.read_excel(file_path, sheet_name=sheet_name)
            df["来源文件"] = os.path.basename(file_path)  # 标记来源
            all_dataframes.append(df)
            print(f"  ✓ 读取: {os.path.basename(file_path)} ({len(df)} 行)")
        except Exception as e:
            print(f"  ✗ 读取失败: {file_path} - {e}")

    if not all_dataframes:
        print("❌ 没有成功读取任何文件!")
        return

    # 合并所有 DataFrame
    merged_df = pd.concat(all_dataframes, ignore_index=True)
    print(f"\n📊 合并完成,总计 {len(merged_df)} 行(去重前)")

    # 去重处理
    if drop_duplicates_by:
        before_count = len(merged_df)
        merged_df = merged_df.drop_duplicates(subset=drop_duplicates_by, keep="first")
        removed = before_count - len(merged_df)
        print(f"🗑️  去重完成,删除了 {removed} 条重复数据,剩余 {len(merged_df)} 行")

    # 保存结果
    merged_df.to_excel(output_path, index=False)
    print(f"✅ 已保存至: {output_path}")
    return merged_df


def merge_all_sheets_in_file(file_path: str,
                              output_path: str,
                              drop_duplicates_by: Optional[List[str]] = None):
    """
    把单个Excel文件的所有 sheet 合并成一个(保留 sheet 名作为来源标记)
    
    适用场景:一个Excel文件里有很多 sheet,每个 sheet 结构相同,
             想合并到一起分析。
    """
    wb = openpyxl.load_workbook(file_path)
    all_rows = []

    for sheet in wb.worksheets:
        for row in sheet.iter_rows(values_only=True):
            # 追加来源 sheet 名称作为最后一列
            all_rows.append(list(row) + [sheet.title])

    if not all_rows:
        print("❌ 该文件没有内容!")
        return

    # 第一行是表头,后面是数据
    headers = list(all_rows[0]) + ["来源Sheet"]
    data_rows = all_rows[1:]

    df = pd.DataFrame(data_rows, columns=headers)

    if drop_duplicates_by:
        df = df.drop_duplicates(subset=drop_duplicates_by)

    df.to_excel(output_path, index=False)
    print(f"✅ Sheet合并完成,已保存至: {output_path}")
    return df


# ====================== 调用示例 ======================

# 【示例1】合并多个文件,按"工号"列去重
merge_excel_files(
    file_paths=[
        "月报/北京分公司_6月.xlsx",
        "月报/上海分公司_6月.xlsx",
        "月报/广州分公司_6月.xlsx",
        "月报/深圳分公司_6月.xlsx",
    ],
    output_path="月报合并/全国汇总_6月.xlsx",
    sheet_name=0,
    drop_duplicates_by=["工号"]   # 工号相同的数据只保留一条
)

# 【示例2】合并文件,不去重(保留所有数据)
merge_excel_files(
    file_paths=[
        "数据/2024Q1.xlsx",
        "数据/2024Q2.xlsx",
        "数据/2024Q3.xlsx",
        "数据/2024Q4.xlsx",
    ],
    output_path="年度汇总_2024.xlsx",
    drop_duplicates_by=None       # 不去重
)

# 【示例3】把一个文件里的所有 sheet 合并
merge_all_sheets_in_file(
    file_path="销售明细表_2024.xlsx",
    output_path="销售明细表_2024_合并.xlsx",
    drop_duplicates_by=["订单号"]  # 按订单号去重
)

运行效果

  ✓ 读取: 北京分公司_6月.xlsx (128 行)
  ✓ 读取: 上海分公司_6月.xlsx (95 行)
  ✓ 读取: 广州分公司_6月.xlsx (110 行)
  ✓ 读取: 深圳分公司_6月.xlsx (87 行)

📊 合并完成,总计 420 行(去重前)
🗑️  去重完成,删除了 23 条重复数据,剩余 397 行
✅ 已保存至: 月报合并/全国汇总_6月.xlsx

5.3 实战三:替换 + 合并一站式处理

有些场景下需要先替换再合并,或者批量处理整个文件夹下的所有 Excel:

import glob
import os


def batch_replace_and_merge(input_folder: str,
                             output_path: str,
                             replace_map: dict,
                             drop_duplicates_by: list = None):
    """
    一站式处理:把文件夹下所有Excel替换后合并
    
    适用场景:每月汇总前,需要先把各分公司的
    "简称"替换成"全称",再合并成一张大表
    """
    # 找出文件夹下所有 .xlsx 文件
    all_files = glob.glob(os.path.join(input_folder, "*.xlsx"))
    
    if not all_files:
        print(f"❌ 文件夹中没有找到 .xlsx 文件: {input_folder}")
        return

    print(f"🔍 发现 {len(all_files)} 个Excel文件\n")

    for file_path in all_files:
        # 替换内容(生成临时文件)
        temp_path = file_path + ".tmp.xlsx"
        replace_in_excel(file_path, temp_path, replace_map=replace_map)
        
        # 读取替换后的数据备用
        df = pd.read_excel(temp_path, sheet_name=0)
        df["来源"] = os.path.basename(file_path)
        
        # 删除临时文件
        os.remove(temp_path)
        
        # 合并逻辑(这里用内存合并,不用落盘)
        if file_path == all_files[0]:
            merged_df = df
        else:
            merged_df = pd.concat([merged_df, df], ignore_index=True)

    # 去重
    if drop_duplicates_by:
        before = len(merged_df)
        merged_df = merged_df.drop_duplicates(subset=drop_duplicates_by)
        print(f"\n🗑️  去重: {before} → {len(merged_df)} 行")

    merged_df.to_excel(output_path, index=False)
    print(f"\n✅ 全部完成,已保存至: {output_path}")


# 调用示例:把各月报中的简称统一后合并
batch_replace_and_merge(
    input_folder="月报/2024年",
    output_path="2024年度汇总_统一版.xlsx",
    replace_map={
        "北京": "北京市",
        "上海": "上海市",
        "广深": "广州市+深圳市",
        "总": "汇总",
    },
    drop_duplicates_by=["工号", "月份"]
)

六、常见踩坑问题

❌ 问题1:文件报错"文件已存在"或"被其他程序占用"

现象:保存时提示权限错误。

原因:你打开了这个 Excel 文件还没关,或者上一次 Python 异常退出没释放。

解决方案

  • 先把打开的 Excel 关掉
  • 换个输出文件名(加 _v2 或时间戳后缀)
from datetime import datetime
output_path = f"结果_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"

❌ 问题2:替换后数字变成了字符串

现象:原来单元格是 2024,替换后变成了 "2024"(文本格式),导致无法做 SUM 计算。

原因cell.value 读写时类型丢失了。

解决方案:替换时判断原始类型,保持不变:

def smart_replace(cell, old_text, new_text):
    if cell.value is None:
        return
    # 如果是数字,不做字符串替换,避免类型改变
    if isinstance(cell.value, (int, float)):
        return
    val = str(cell.value).replace(old_text, new_text)
    cell.value = val

❌ 问题3:合并后表头错位

现象:第一个文件的表头是 姓名|城市|工号,第二个文件的表头是 姓名|城市,合并后列对不上。

原因:不同文件的列数/列顺序不一样。

解决方案:用 pandas 读取时指定 header=0,并对所有文件统一补齐列名:

# 统一列名:缺的列补 NaN
all_cols = set()
for df in all_dfs:
    all_cols.update(df.columns)
for df in all_dfs:
    for col in all_cols:
        if col not in df.columns:
            df[col] = None

❌ 问题4:文件很大,读取很慢

现象:一个 50MB 的 Excel,打开要等很久。

原因:Excel 里有很多公式、格式、超链接等额外数据。

解决方案:用 read_only=True 跳过格式只读数据:

wb = openpyxl.load_workbook(input_path, read_only=True)
# 但注意:read_only=True 时不支持写入

如果只需要读数据做分析,用 pandas 比 openpyxl 快很多:

df = pd.read_excel("大文件.xlsx")  # 比 openpyxl 快

❌ 问题5:正则替换不生效

现象:正则表达式写对了,但没匹配到任何内容。

原因:单元格里的内容可能有前后空格或不可见字符

解决方案:先 strip() 去掉首尾空格,或者正则里加 \s*

# 改进版正则:兼容空格
val = re.sub(r"\s*1[3-9]\s*\d{4}\s*\d{4}\s*", "1XXXXXXXXXX", val)

七、总结

核心要点回顾

  1. 替换操作的核心是遍历单元格openpyxl.load_workbook 打开 → sheet.iter_rows() 遍历 → cell.value 读写
  2. 字典映射(replace_map)是最高效的替换方式:一次传多个对应关系,不用写一堆 replace 链式调用
  3. 正则替换解决复杂场景:手机号脱敏、日期格式统一、非标准空格处理都用正则
  4. pandas.concat() 是合并的简洁之道:比手动拼接行列表可靠太多,还自带对齐列的功能
  5. 去重的关键是选对关键列:按业务主键去重(如工号、订单号),不要按整行去重

给你的学习建议

  1. 从替换开始:先跑通 replace_in_excel,换个关键词试试,感受到"代码改数据"的爽感
  2. 理解 DataFrame:pandas 的 DataFrame 就是表格的 Python 表示,学好 df.head() / df.info() / df.describe() 这三个命令,可以随时检查数据长什么样
  3. 结合工作场景:想一想你每月/每周要重复做哪些 Excel 操作,那些"复制粘贴改名称"的活儿,80%都能用脚本自动化
  4. 进阶方向:学会后可以进一步学 xlwings(用 Python 操作 Excel 界面)、python-docx(处理 Word),三件套一起用,你就是办公室自动化高手

工具的价值不在于它有多复杂,而在于它能不能真正替你省时间。跑通一个脚本,解放你每周2小时的重复劳动,这就是技术落地的意义。


代码在 Python 3.8+ / openpyxl 3.1 / pandas 2.0 下测试通过。有问题欢迎评论区留言!

posted @ 2026-06-12 13:57  快乐西西  阅读(5)  评论(0)    收藏  举报