结合Ai生成了工作中需要用到的EXCEL合并软件

查看 51|回复 9
作者:忆惘惜   
各位道友好!!!
       我又来分享日常需求使用的软件啦,软件用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()

文件, 路径

一场荒唐半生梦   

楼主可以弄个演示 和效果图之类的
cxqdly   

看头像好像是冷月大佬
guohuanxian   

这个工具还是不错的
jesseding   

谢谢开发,使用看看
忆惘惜
OP
  


cxqdly 发表于 2025-9-22 11:38
看头像好像是冷月大佬

我不是您说的冷月, 这个头像我从零几年就开始用了
cxqdly   


忆惘惜 发表于 2025-9-22 13:12
我不是您说的冷月, 这个头像我从零几年就开始用了

认错人了哈  试了下工具挺好用的,多谢分享
我在找拆分功能,下午用DS试试
呆萌柠檬   

好奇开发过程
不哥   

怎么转换成EXE格式的
woaipojie23456   

一个excel 了里面的某几个sheet   合并   这是一种    还有就是  多个excel  里面的所有sheet  都合并??
您需要登录后才可以回帖 登录 | 立即注册

返回顶部