Python Tkinter实现将ZIP中的CSV批量转换为Excel

以下是一个完整的Python Tkinter应用程序,用于批量将ZIP文件中的CSV文件转换为Excel格式:

图片[1]_Python Tkinter实现将ZIP中的CSV批量转换为Excel_知途无界

完整代码实现

import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import zipfile
import os
import pandas as pd
from pathlib import Path
import threading
import tempfile
import shutil

class CSVtoExcelConverter:
    def __init__(self, root):
        self.root = root
        self.root.title("ZIP CSV批量转Excel工具")
        self.root.geometry("800x600")
        self.root.resizable(True, True)
        
        # 变量初始化
        self.zip_files = []
        self.output_dir = ""
        self.is_converting = False
        
        self.setup_ui()
    
    def setup_ui(self):
        """设置用户界面"""
        # 主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 配置网格权重
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        
        # 标题
        title_label = ttk.Label(main_frame, text="ZIP CSV批量转Excel工具", 
                               font=("Arial", 16, "bold"))
        title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
        
        # ZIP文件选择区域
        ttk.Label(main_frame, text="ZIP文件:", font=("Arial", 10, "bold")).grid(
            row=1, column=0, sticky=tk.W, pady=5)
        
        # ZIP文件列表框
        self.zip_listbox = tk.Listbox(main_frame, height=6, selectmode=tk.EXTENDED)
        self.zip_listbox.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), 
                             padx=(0, 10), pady=5)
        
        # 滚动条
        zip_scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.zip_listbox.yview)
        zip_scrollbar.grid(row=2, column=2, sticky=(tk.N, tk.S), pady=5)
        self.zip_listbox.configure(yscrollcommand=zip_scrollbar.set)
        
        # 按钮区域
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=3, column=0, columnspan=3, pady=10)
        
        ttk.Button(button_frame, text="添加ZIP文件", command=self.add_zip_files).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="移除选中", command=self.remove_selected).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="清空列表", command=self.clear_all).pack(side=tk.LEFT, padx=5)
        
        # 输出目录选择
        ttk.Label(main_frame, text="输出目录:", font=("Arial", 10, "bold")).grid(
            row=4, column=0, sticky=tk.W, pady=(20, 5))
        
        self.output_var = tk.StringVar()
        output_entry = ttk.Entry(main_frame, textvariable=self.output_var, width=50)
        output_entry.grid(row=5, column=0, sticky=(tk.W, tk.E), padx=(0, 10), pady=5)
        
        ttk.Button(main_frame, text="浏览...", command=self.select_output_dir).grid(
            row=5, column=1, sticky=tk.W, pady=5)
        
        # 选项设置
        options_frame = ttk.LabelFrame(main_frame, text="转换选项", padding="10")
        options_frame.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=20)
        options_frame.columnconfigure(1, weight=1)
        
        # Excel引擎选择
        ttk.Label(options_frame, text="Excel引擎:").grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
        self.engine_var = tk.StringVar(value="openpyxl")
        engine_combo = ttk.Combobox(options_frame, textvariable=self.engine_var, 
                                    values=["openpyxl", "xlsxwriter"], state="readonly", width=15)
        engine_combo.grid(row=0, column=1, sticky=tk.W, padx=(0, 20))
        
        # 编码选择
        ttk.Label(options_frame, text="CSV编码:").grid(row=0, column=2, sticky=tk.W, padx=(0, 10))
        self.encoding_var = tk.StringVar(value="utf-8")
        encoding_combo = ttk.Combobox(options_frame, textvariable=self.encoding_var,
                                     values=["utf-8", "gbk", "gb2312", "latin-1"], 
                                     state="readonly", width=15)
        encoding_combo.grid(row=0, column=3, sticky=tk.W)
        
        # 包含表头选项
        self.header_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(options_frame, text="包含表头", variable=self.header_var).grid(
            row=1, column=0, sticky=tk.W, pady=(10, 0))
        
        # 分隔符选项
        ttk.Label(options_frame, text="分隔符:").grid(row=1, column=1, sticky=tk.W, padx=(20, 10))
        self.separator_var = tk.StringVar(value=",")
        separator_combo = ttk.Combobox(options_frame, textvariable=self.separator_var,
                                      values=[",", ";", "\t", "|"], state="readonly", width=5)
        separator_combo.grid(row=1, column=2, sticky=tk.W)
        
        # 进度条
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var, maximum=100)
        self.progress_bar.grid(row=7, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
        
        # 状态标签
        self.status_var = tk.StringVar(value="就绪")
        status_label = ttk.Label(main_frame, textvariable=self.status_var, foreground="blue")
        status_label.grid(row=8, column=0, columnspan=3, pady=5)
        
        # 日志区域
        ttk.Label(main_frame, text="转换日志:", font=("Arial", 10, "bold")).grid(
            row=9, column=0, columnspan=3, sticky=tk.W, pady=(20, 5))
        
        self.log_text = scrolledtext.ScrolledText(main_frame, height=8, width=70)
        self.log_text.grid(row=10, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
        
        # 操作按钮
        action_frame = ttk.Frame(main_frame)
        action_frame.grid(row=11, column=0, columnspan=3, pady=20)
        
        self.convert_btn = ttk.Button(action_frame, text="开始转换", command=self.start_conversion)
        self.convert_btn.pack(side=tk.LEFT, padx=10)
        
        ttk.Button(action_frame, text="退出", command=self.root.quit).pack(side=tk.LEFT, padx=10)
        
        # 配置行权重
        main_frame.rowconfigure(2, weight=1)
        main_frame.rowconfigure(10, weight=1)
    
    def add_zip_files(self):
        """添加ZIP文件"""
        files = filedialog.askopenfilenames(
            title="选择ZIP文件",
            filetypes=[("ZIP files", "*.zip"), ("All files", "*.*")]
        )
        
        if files:
            for file in files:
                if file not in self.zip_files:
                    self.zip_files.append(file)
                    self.zip_listbox.insert(tk.END, os.path.basename(file))
            
            self.log(f"添加了 {len(files)} 个ZIP文件")
    
    def remove_selected(self):
        """移除选中的ZIP文件"""
        selected = self.zip_listbox.curselection()
        if selected:
            for index in reversed(selected):
                self.zip_files.pop(index)
                self.zip_listbox.delete(index)
            self.log(f"移除了 {len(selected)} 个文件")
    
    def clear_all(self):
        """清空所有ZIP文件"""
        self.zip_files.clear()
        self.zip_listbox.delete(0, tk.END)
        self.log("已清空文件列表")
    
    def select_output_dir(self):
        """选择输出目录"""
        directory = filedialog.askdirectory(title="选择输出目录")
        if directory:
            self.output_dir = directory
            self.output_var.set(directory)
            self.log(f"输出目录设置为: {directory}")
    
    def log(self, message):
        """添加日志消息"""
        self.log_text.insert(tk.END, f"{message}\n")
        self.log_text.see(tk.END)
        self.root.update_idletasks()
    
    def update_status(self, message):
        """更新状态"""
        self.status_var.set(message)
        self.root.update_idletasks()
    
    def start_conversion(self):
        """开始转换(在单独线程中执行)"""
        if self.is_converting:
            return
            
        if not self.zip_files:
            messagebox.showerror("错误", "请先添加ZIP文件!")
            return
        
        if not self.output_dir:
            messagebox.showerror("错误", "请先选择输出目录!")
            return
        
        # 在新线程中执行转换
        thread = threading.Thread(target=self.convert_files)
        thread.daemon = True
        thread.start()
    
    def convert_files(self):
        """执行文件转换"""
        self.is_converting = True
        self.convert_btn.config(state='disabled')
        self.progress_var.set(0)
        
        try:
            total_files = len(self.zip_files)
            processed_files = 0
            
            for zip_path in self.zip_files:
                if not self.is_converting:  # 检查是否被取消
                    break
                
                zip_name = os.path.basename(zip_path)
                self.log(f"\n处理文件: {zip_name}")
                self.update_status(f"正在处理: {zip_name}")
                
                try:
                    # 创建临时目录
                    with tempfile.TemporaryDirectory() as temp_dir:
                        # 解压ZIP文件
                        extracted_count = self.extract_and_convert(zip_path, temp_dir)
                        
                        if extracted_count > 0:
                            self.log(f"✓ 成功转换 {extracted_count} 个CSV文件")
                        else:
                            self.log(f"⚠ 未在ZIP中找到CSV文件")
                            
                except Exception as e:
                    self.log(f"✗ 处理失败: {str(e)}")
                
                # 更新进度
                processed_files += 1
                progress = (processed_files / total_files) * 100
                self.progress_var.set(progress)
            
            if self.is_converting:
                self.log(f"\n🎉 转换完成!共处理 {total_files} 个ZIP文件")
                self.update_status("转换完成")
                messagebox.showinfo("完成", "所有文件转换完成!")
            else:
                self.log("\n❌ 转换已取消")
                self.update_status("转换已取消")
                
        except Exception as e:
            self.log(f"❌ 转换过程出错: {str(e)}")
            self.update_status("转换失败")
            messagebox.showerror("错误", f"转换过程中发生错误:\n{str(e)}")
        
        finally:
            self.is_converting = False
            self.convert_btn.config(state='normal')
            self.progress_var.set(0)
    
    def extract_and_convert(self, zip_path, temp_dir):
        """解压ZIP并转换其中的CSV文件"""
        converted_count = 0
        
        with zipfile.ZipFile(zip_path, 'r') as zip_ref:
            # 获取所有CSV文件
            csv_files = [f for f in zip_ref.namelist() if f.lower().endswith('.csv')]
            
            if not csv_files:
                return converted_count
            
            # 解压所有CSV文件
            zip_ref.extractall(temp_dir, csv_files)
            
            # 转换每个CSV文件
            for csv_file in csv_files:
                csv_path = os.path.join(temp_dir, csv_file)
                
                # 保持目录结构
                relative_path = os.path.dirname(csv_file)
                if relative_path:
                    output_subdir = os.path.join(self.output_dir, relative_path)
                    os.makedirs(output_subdir, exist_ok=True)
                else:
                    output_subdir = self.output_dir
                
                # 生成Excel文件名
                excel_filename = Path(csv_file).stem + '.xlsx'
                excel_path = os.path.join(output_subdir, excel_filename)
                
                try:
                    # 读取CSV文件
                    df = pd.read_csv(
                        csv_path,
                        encoding=self.encoding_var.get(),
                        sep=self.separator_var.get(),
                        header=0 if self.header_var.get() else None
                    )
                    
                    # 保存为Excel
                    engine = self.engine_var.get()
                    if engine == "openpyxl":
                        df.to_excel(excel_path, index=False, engine='openpyxl')
                    else:  # xlsxwriter
                        df.to_excel(excel_path, index=False, engine='xlsxwriter')
                    
                    converted_count += 1
                    self.log(f"  ✓ {os.path.basename(csv_file)} → {excel_filename}")
                    
                except Exception as e:
                    self.log(f"  ✗ 转换失败 {os.path.basename(csv_file)}: {str(e)}")
        
        return converted_count

def main():
    root = tk.Tk()
    app = CSVtoExcelConverter(root)
    
    # 设置图标(如果有的话)
    try:
        root.iconbitmap('icon.ico')  # 可选:添加应用图标
    except:
        pass
    
    # 居中显示窗口
    root.update_idletasks()
    x = (root.winfo_screenwidth() // 2) - (root.winfo_width() // 2)
    y = (root.winfo_screenheight() // 2) - (root.winfo_height() // 2)
    root.geometry(f"+{x}+{y}")
    
    root.mainloop()

if __name__ == "__main__":
    main()

安装依赖

在运行程序前,需要安装以下Python包:

pip install pandas openpyxl xlsxwriter tkinter

主要功能特点

1. ​直观的图形界面

  • 文件列表管理(添加、移除、清空)
  • 输出目录选择
  • 实时进度显示
  • 详细的操作日志

2. ​灵活的转换选项

  • 支持多种Excel引擎(openpyxl/xlsxwriter)
  • 可配置CSV编码(UTF-8/GBK/GB2312/Latin-1)
  • 自定义分隔符(逗号/分号/制表符/竖线)
  • 可选择是否包含表头

3. ​强大的处理能力

  • 批量处理多个ZIP文件
  • 保持原始目录结构
  • 自动跳过非CSV文件
  • 错误处理和详细日志

4. ​用户体验优化

  • 多线程处理,界面不卡顿
  • 实时状态更新
  • 完成后消息提示
  • 窗口居中显示

使用步骤

  1. 添加ZIP文件​:点击”添加ZIP文件”按钮选择要处理的压缩包
  2. 选择输出目录​:点击”浏览…”选择转换后的Excel文件保存位置
  3. 设置选项​:根据需要调整编码、分隔符等参数
  4. 开始转换​:点击”开始转换”按钮,程序会自动处理所有文件
  5. 查看结果​:在日志区域查看处理进度和结果

注意事项

  • 确保系统中已安装所需依赖包
  • 对于大型ZIP文件,转换可能需要一些时间
  • 建议选择合适的CSV编码以避免乱码问题
  • 程序会自动创建必要的子目录来保持文件组织结构

这个工具特别适合需要处理大量ZIP压缩包中CSV数据的场景,如数据分析、报表整理等工作。

© 版权声明
THE END
喜欢就点个赞,支持一下吧!
点赞79 分享
评论 抢沙发
头像
欢迎您留下评论!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容