我又来分享日常需求使用的软件啦,软件用Python代码生成,功能为多Excel合并 和 EXCEL多Sheet合并功能,有需要的道友可以‘尝鲜’
"
百度网盘:https://pan.baidu.com/s/1Zxo_Za-0cVKAXigE7UQf1w 提取码: 52pj
[Python] 纯文本查看 复制代码import os
import pandas as pd
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttk
import threading
import time
class ExcelMerger:
def __init__(self, root):
self.root = root
self.root.title("Excel文件合并工具——By:QJQ")
self.root.geometry("700x500") # 固定窗口大小
self.root.resizable(False, False) # 禁止调整窗口大小
# 设置全局字体为微软雅黑
self.font = ("微软雅黑", 9)
self.title_font = ("微软雅黑", 9, "bold")
# 应用字体设置
self.style = ttk.Style()
self.style.configure(".", font=self.font)
# 存储Excel文件列表
self.excel_files = []
# 存储Excel文件中的sheet列表
self.sheet_files = []
# 创建界面元素
self.create_widgets()
def create_widgets(self):
# 创建选项卡
self.notebook = ttk.Notebook(self.root)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 创建多Excel合并选项卡
self.frame_excel = ttk.Frame(self.notebook, padding="10")
self.notebook.add(self.frame_excel, text="多Excel合并")
# 创建多Sheet合并选项卡
self.frame_sheet = ttk.Frame(self.notebook, padding="10")
self.notebook.add(self.frame_sheet, text="多Sheet合并")
# 初始化多Excel合并界面
self.create_excel_merge_ui()
# 初始化多Sheet合并界面
self.create_sheet_merge_ui()
# 创建底部状态栏和日志区域(共享)
self.create_shared_ui()
def create_excel_merge_ui(self):
# 待合并路径选择
frame_source = ttk.Frame(self.frame_excel)
frame_source.pack(fill=tk.X, pady=5)
ttk.Label(frame_source, text="待合并路径:", font=self.title_font).grid(row=0, column=0, sticky=tk.W)
self.source_path = tk.StringVar()
source_entry = ttk.Entry(frame_source, textvariable=self.source_path, width=45)
source_entry.grid(row=0, column=1, padx=5)
ttk.Button(frame_source, text="浏览...", command=self.select_source).grid(row=0, column=2)
# 输出文件路径选择
frame_target = ttk.Frame(self.frame_excel)
frame_target.pack(fill=tk.X, pady=5)
ttk.Label(frame_target, text="输出文件路径:", font=self.title_font).grid(row=0, column=0, sticky=tk.W)
self.target_path = tk.StringVar()
target_entry = ttk.Entry(frame_target, textvariable=self.target_path, width=45)
target_entry.grid(row=0, column=1, padx=5)
ttk.Button(frame_target, text="浏览...", command=self.select_target).grid(row=0, column=2)
# 文件列表框架
frame_files = ttk.LabelFrame(self.frame_excel, text="找到的Excel文件", padding="5")
frame_files.pack(fill=tk.BOTH, expand=True, pady=5)
# 文件列表
self.file_listbox = tk.Listbox(frame_files, font=self.font, height=6)
scrollbar = ttk.Scrollbar(frame_files, orient=tk.VERTICAL, command=self.file_listbox.yview)
self.file_listbox.configure(yscrollcommand=scrollbar.set)
self.file_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 按钮框架
frame_buttons = ttk.Frame(self.frame_excel)
frame_buttons.pack(fill=tk.X, pady=5)
ttk.Button(frame_buttons, text="刷新文件列表", command=self.refresh_file_list).pack(side=tk.LEFT, padx=5)
ttk.Button(frame_buttons, text="开始合并", command=self.start_merge).pack(side=tk.RIGHT, padx=5)
def create_sheet_merge_ui(self):
# Excel文件选择
frame_file = ttk.Frame(self.frame_sheet)
frame_file.pack(fill=tk.X, pady=5)
ttk.Label(frame_file, text="Excel文件:", font=self.title_font).grid(row=0, column=0, sticky=tk.W)
self.sheet_file_path = tk.StringVar()
file_entry = ttk.Entry(frame_file, textvariable=self.sheet_file_path, width=45)
file_entry.grid(row=0, column=1, padx=5)
ttk.Button(frame_file, text="浏览...", command=self.select_sheet_file).grid(row=0, column=2)
# 输出文件路径选择
frame_target = ttk.Frame(self.frame_sheet)
frame_target.pack(fill=tk.X, pady=5)
ttk.Label(frame_target, text="输出文件路径:", font=self.title_font).grid(row=0, column=0, sticky=tk.W)
self.sheet_target_path = tk.StringVar()
target_entry = ttk.Entry(frame_target, textvariable=self.sheet_target_path, width=45)
target_entry.grid(row=0, column=1, padx=5)
ttk.Button(frame_target, text="浏览...", command=self.select_sheet_target).grid(row=0, column=2)
# Sheet列表框架
frame_sheets = ttk.LabelFrame(self.frame_sheet, text="找到的Sheet", padding="5")
frame_sheets.pack(fill=tk.BOTH, expand=True, pady=5)
# Sheet列表
self.sheet_listbox = tk.Listbox(frame_sheets, font=self.font, height=6)
scrollbar = ttk.Scrollbar(frame_sheets, orient=tk.VERTICAL, command=self.sheet_listbox.yview)
self.sheet_listbox.configure(yscrollcommand=scrollbar.set)
self.sheet_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 按钮框架
frame_buttons = ttk.Frame(self.frame_sheet)
frame_buttons.pack(fill=tk.X, pady=5)
ttk.Button(frame_buttons, text="刷新Sheet列表", command=self.refresh_sheet_list).pack(side=tk.LEFT, padx=5)
ttk.Button(frame_buttons, text="开始合并", command=self.start_sheet_merge).pack(side=tk.RIGHT, padx=5)
def create_shared_ui(self):
# 进度条和状态
frame_progress = ttk.Frame(self.root)
frame_progress.pack(fill=tk.X, pady=5, padx=10)
self.status_var = tk.StringVar(value="就绪")
ttk.Label(frame_progress, textvariable=self.status_var).pack(side=tk.TOP, anchor=tk.W)
self.progress = ttk.Progressbar(frame_progress, mode='determinate')
self.progress.pack(fill=tk.X, pady=5)
# 日志文本框
frame_log = ttk.LabelFrame(self.root, text="操作日志", padding="5")
frame_log.pack(fill=tk.BOTH, expand=True, pady=5, padx=10)
self.log_text = tk.Text(frame_log, font=self.font, height=6)
scrollbar_log = ttk.Scrollbar(frame_log, orient=tk.VERTICAL, command=self.log_text.yview)
self.log_text.configure(yscrollcommand=scrollbar_log.set)
self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar_log.pack(side=tk.RIGHT, fill=tk.Y)
# 清空日志按钮
frame_bottom = ttk.Frame(self.root)
frame_bottom.pack(fill=tk.X, pady=5, padx=10)
ttk.Button(frame_bottom, text="清空日志", command=self.clear_log).pack(side=tk.RIGHT, padx=5)
def select_source(self):
folder_path = filedialog.askdirectory(title="选择包含Excel文件的文件夹")
if folder_path:
self.source_path.set(folder_path)
self.log(f"已选择待合并路径: {folder_path}")
self.scan_excel_files()
def scan_excel_files(self):
"""扫描待合并路径中的Excel文件"""
source_dir = self.source_path.get()
if not source_dir:
return
self.log("正在扫描Excel文件...")
self.file_listbox.delete(0, tk.END)
self.excel_files = []
try:
# 获取所有Excel文件
files = os.listdir(source_dir)
excel_files = [f for f in files if f.lower().endswith(('.xlsx', '.xls'))]
if not excel_files:
self.log("在待合并路径中未找到Excel文件")
self.status_var.set("未找到Excel文件")
return
self.excel_files = excel_files
self.log(f"找到 {len(excel_files)} 个Excel文件")
self.status_var.set(f"找到 {len(excel_files)} 个Excel文件")
# 显示文件列表
for file in excel_files:
self.file_listbox.insert(tk.END, file)
except Exception as e:
self.log(f"扫描文件夹时出错: {str(e)}")
self.status_var.set("扫描文件夹时出错")
def refresh_file_list(self):
"""刷新文件列表"""
if self.source_path.get():
self.scan_excel_files()
else:
messagebox.showwarning("警告", "请先选择待合并路径")
def select_target(self):
file_path = filedialog.asksaveasfilename(
title="选择保存位置",
defaultextension=".xlsx",
filetypes=[("Excel 文件", "*.xlsx"), ("所有文件", "*.*")]
)
if file_path:
self.target_path.set(file_path)
self.log(f"已选择输出文件路径: {file_path}")
def select_sheet_file(self):
file_path = filedialog.askopenfilename(
title="选择Excel文件",
filetypes=[("Excel 文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
)
if file_path:
self.sheet_file_path.set(file_path)
self.log(f"已选择Excel文件: {file_path}")
self.scan_sheets(file_path)
def scan_sheets(self, file_path=None):
"""扫描Excel文件中的sheet"""
if file_path is None:
file_path = self.sheet_file_path.get()
if not file_path or not os.path.exists(file_path):
self.log("文件路径无效或文件不存在")
return
self.log("正在扫描Excel文件中的sheet...")
self.sheet_listbox.delete(0, tk.END)
self.sheet_files = []
try:
# 根据文件扩展名选择合适的引擎
if file_path.lower().endswith('.xlsx'):
engine = 'openpyxl'
elif file_path.lower().endswith('.xls'):
engine = 'xlrd'
else:
self.log("不支持的文件格式")
return
# 获取所有sheet名称
excel_file = pd.ExcelFile(file_path, engine=engine)
sheet_names = excel_file.sheet_names
if not sheet_names:
self.log("在Excel文件中未找到sheet")
self.status_var.set("未找到sheet")
return
self.sheet_files = sheet_names
self.log(f"找到 {len(sheet_names)} 个sheet")
self.status_var.set(f"找到 {len(sheet_names)} 个sheet")
# 显示sheet列表
for sheet in sheet_names:
self.sheet_listbox.insert(tk.END, sheet)
except ImportError as e:
self.log(f"缺少必要的库: {str(e)}")
self.status_var.set("请安装openpyxl或xlrd库")
except Exception as e:
self.log(f"读取Excel文件时出错: {str(e)}")
self.status_var.set("读取Excel文件时出错")
def refresh_sheet_list(self):
"""刷新sheet列表"""
if self.sheet_file_path.get():
self.scan_sheets()
else:
messagebox.showwarning("警告", "请先选择Excel文件")
def select_sheet_target(self):
file_path = filedialog.asksaveasfilename(
title="选择保存位置",
defaultextension=".xlsx",
filetypes=[("Excel 文件", "*.xlsx"), ("所有文件", "*.*")]
)
if file_path:
self.sheet_target_path.set(file_path)
self.log(f"已选择输出文件路径: {file_path}")
def log(self, message):
timestamp = time.strftime("%H:%M:%S", time.localtime())
self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
self.log_text.see(tk.END)
self.root.update_idletasks()
def clear_log(self):
self.log_text.delete(1.0, tk.END)
def start_merge(self):
source_dir = self.source_path.get()
target_file = self.target_path.get()
if not source_dir or not target_file:
messagebox.showerror("错误", "请先选择待合并路径和输出文件路径")
return
if not self.excel_files:
messagebox.showerror("错误", "待合并路径中没有Excel文件,请先选择正确的文件夹")
return
# 禁用按钮,开始合并
self.progress["value"] = 0
self.progress["maximum"] = len(self.excel_files)
self.log("开始合并Excel文件...")
self.status_var.set("正在合并文件...")
# 在新线程中执行合并操作,避免界面冻结
thread = threading.Thread(target=self.merge_excel_files, args=(source_dir, target_file))
thread.daemon = True
thread.start()
def start_sheet_merge(self):
source_file = self.sheet_file_path.get()
target_file = self.sheet_target_path.get()
if not source_file or not target_file:
messagebox.showerror("错误", "请先选择Excel文件和输出文件路径")
return
if not self.sheet_files:
messagebox.showerror("错误", "Excel文件中没有sheet,请先选择正确的文件")
return
# 检查源文件是否存在
if not os.path.exists(source_file):
messagebox.showerror("错误", "选择的Excel文件不存在")
return
# 禁用按钮,开始合并
self.progress["value"] = 0
self.progress["maximum"] = len(self.sheet_files)
self.log("开始合并Sheet...")
self.status_var.set("正在合并Sheet...")
# 在新线程中执行合并操作,避免界面冻结
thread = threading.Thread(target=self.merge_sheets, args=(source_file, target_file))
thread.daemon = True
thread.start()
def read_excel_file(self, file_path, sheet_name=None):
"""读取Excel文件,根据扩展名选择合适的引擎"""
try:
# 根据文件扩展名选择合适的引擎
if file_path.lower().endswith('.xlsx'):
if sheet_name:
return pd.read_excel(file_path, sheet_name=sheet_name, engine='openpyxl')
else:
return pd.read_excel(file_path, engine='openpyxl')
elif file_path.lower().endswith('.xls'):
if sheet_name:
return pd.read_excel(file_path, sheet_name=sheet_name, engine='xlrd')
else:
return pd.read_excel(file_path, engine='xlrd')
else:
# 对于未知扩展名,尝试两种引擎
try:
if sheet_name:
return pd.read_excel(file_path, sheet_name=sheet_name, engine='openpyxl')
else:
return pd.read_excel(file_path, engine='openpyxl')
except:
if sheet_name:
return pd.read_excel(file_path, sheet_name=sheet_name, engine='xlrd')
else:
return pd.read_excel(file_path, engine='xlrd')
except Exception as e:
# 如果两种引擎都失败,抛出异常
raise Exception(f"无法读取文件: {str(e)}")
def merge_excel_files(self, source_dir, target_file):
try:
# 创建一个空的DataFrame来存储所有数据
all_data = pd.DataFrame()
processed_count = 0
# 逐个读取并合并文件
for i, file in enumerate(self.excel_files):
file_path = os.path.join(source_dir, file)
self.log(f"正在处理: {file} ({i+1}/{len(self.excel_files)})")
try:
# 读取Excel文件
df = self.read_excel_file(file_path)
# 添加文件名作为一列,方便追踪数据来源
df['来源文件'] = file
# 合并数据
all_data = pd.concat([all_data, df], ignore_index=True)
processed_count += 1
# 更新进度
self.root.after(0, lambda: self.progress.step(1))
except Exception as e:
self.log(f"处理文件 {file} 时出错: {str(e)}")
# 保存合并后的数据
self.log("正在保存合并后的文件...")
# 使用openpyxl引擎保存为xlsx格式
all_data.to_excel(target_file, index=False, engine='openpyxl')
self.log(f"合并完成! 成功处理 {processed_count}/{len(self.excel_files)} 个文件,共合并 {len(all_data)} 行数据")
self.log(f"文件已保存到: {target_file}")
# 在主线程中显示完成消息
self.root.after(0, lambda: (
self.status_var.set(f"合并完成! 共处理 {processed_count} 个文件"),
messagebox.showinfo("完成", f"Excel文件合并完成!\n成功处理 {processed_count} 个文件,共合并 {len(all_data)} 行数据")
))
except Exception as e:
error_msg = f"合并过程中发生错误: {str(e)}"
self.log(error_msg)
self.root.after(0, lambda: (
self.status_var.set("合并失败"),
messagebox.showerror("错误", error_msg)
))
finally:
# 重置进度条
self.root.after(0, lambda: self.progress.config(value=0))
def merge_sheets(self, source_file, target_file):
try:
# 创建一个空的DataFrame来存储所有数据
all_data = pd.DataFrame()
processed_count = 0
# 根据文件扩展名选择合适的引擎
if source_file.lower().endswith('.xlsx'):
engine = 'openpyxl'
elif source_file.lower().endswith('.xls'):
engine = 'xlrd'
else:
# 尝试自动检测
try:
pd.read_excel(source_file, engine='openpyxl')
engine = 'openpyxl'
except:
engine = 'xlrd'
# 逐个读取并合并sheet
for i, sheet in enumerate(self.sheet_files):
self.log(f"正在处理: {sheet} ({i+1}/{len(self.sheet_files)})")
try:
# 读取sheet
df = pd.read_excel(source_file, sheet_name=sheet, engine=engine)
# 检查是否为空DataFrame
if df.empty:
self.log(f"警告: Sheet '{sheet}' 为空,跳过")
continue
# 添加sheet名作为一列,方便追踪数据来源
df['来源Sheet'] = sheet
# 合并数据
all_data = pd.concat([all_data, df], ignore_index=True)
processed_count += 1
# 更新进度
self.root.after(0, lambda: self.progress.step(1))
except Exception as e:
self.log(f"处理sheet {sheet} 时出错: {str(e)}")
# 检查是否有数据需要保存
if all_data.empty:
self.log("没有找到有效数据,合并中止")
self.root.after(0, lambda: (
self.status_var.set("没有有效数据"),
messagebox.showwarning("警告", "所有Sheet都为空或无法读取,没有数据需要保存")
))
return
# 保存合并后的数据
self.log("正在保存合并后的文件...")
# 使用openpyxl引擎保存为xlsx格式
all_data.to_excel(target_file, index=False, engine='openpyxl')
self.log(f"合并完成! 成功处理 {processed_count}/{len(self.sheet_files)} 个sheet,共合并 {len(all_data)} 行数据")
self.log(f"文件已保存到: {target_file}")
# 在主线程中显示完成消息
self.root.after(0, lambda: (
self.status_var.set(f"合并完成! 共处理 {processed_count} 个sheet"),
messagebox.showinfo("完成", f"Sheet合并完成!\n成功处理 {processed_count} 个sheet,共合并 {len(all_data)} 行数据")
))
except Exception as e:
error_msg = f"合并过程中发生错误: {str(e)}"
self.log(error_msg)
self.root.after(0, lambda: (
self.status_var.set("合并失败"),
messagebox.showerror("错误", error_msg)
))
finally:
# 重置进度条
self.root.after(0, lambda: self.progress.config(value=0))
if __name__ == "__main__":
root = tk.Tk()
app = ExcelMerger(root)
root.mainloop()