Python实现CSV文件编码转换工具(转成UTF-8格式)
作者:小庄-Python办公
这篇文章主要为大家详细介绍了如何使用Python实现CSV文件编码转换工具,即转成UTF-8格式,文中的示例代码讲解详细感兴趣的小伙伴可以了解一下
背景
一个支持拖拽操作的 CSV 文件批量编码转换工具,可以将各种编码的 CSV 文件统一转换为 UTF-8 编码,解决中文乱码问题。
功能特点
多种文件添加方式
- 拖拽支持:直接将 CSV 文件或包含 CSV 的文件夹拖拽到程序窗口
- 单文件选择:支持选择单个或多个 CSV 文件
- 文件夹批量:选择文件夹,自动收集其中的所有 CSV 文件
智能文件管理
- 自动去重:重复添加的文件会被自动过滤
- 列表管理:支持移除选中文件、清空列表等操作
- 实时反馈:状态栏显示当前文件数量和操作结果
强大的编码兼容性
- 多编码支持:自动尝试 MBCS、ANSI、UTF-8、UTF-8-SIG 等编码
- 智能识别:按优先级依次尝试,最大程度避免乱码
- 统一输出:所有文件统一转换为 UTF-8 编码
现代化界面
- 美观布局:使用 ttk 控件,界面清晰美观
- 进度可视化:实时进度条显示转换进度
- 状态提示:底部状态栏显示当前操作状态
系统要求
Python 版本:3.7 或更高版本
操作系统:Windows、macOS、Linux(推荐 Windows)
可选依赖:tkinterdnd2(用于拖拽功能)
安装与运行
基础运行
python main.py
启用拖拽功能(推荐)
pip install tkinterdnd2 python main.py
提示:如果未安装 tkinterdnd2,程序会自动降级运行,并在界面提示安装方法。
使用指南
步骤 1:添加文件
有三种方式添加需要转换的 CSV 文件:
1.拖拽方式(推荐)
- 直接将 CSV 文件拖拽到文件列表区域
- 也可以拖拽包含 CSV 文件的文件夹
2.按钮选择
- 点击「添加文件…」选择单个或多个 CSV 文件
- 点击「添加文件夹…」选择包含 CSV 的文件夹
3.列表管理
- 选中文件后点击「移除选中」删除不需要的文件
- 点击「清空列表」清除所有文件
步骤 2:设置输出目录
- 在「输出设置」区域点击「浏览…」按钮
- 选择一个用于保存转换后文件的目录
- 建议选择空目录,避免同名文件被覆盖
步骤 3:开始转换
- 点击「开始转换」按钮
- 观察进度条和状态栏的实时反馈
- 转换完成后会弹出结果提示框
技术实现
编码检测策略
程序采用多重编码尝试机制:
encodings = ['mbcs', 'ansi', 'utf-8', 'utf-8-sig']
按优先级依次尝试读取,一旦成功即停止尝试,确保最大兼容性。
文件处理流程
- 文件收集:扫描指定路径,过滤 .csv 文件
- 编码检测:按策略尝试不同编码读取
- 内容转换:逐行读取并写入 UTF-8 文件
- 进度反馈:实时更新界面状态
拖拽功能实现
使用 tkinterdnd2 库实现跨平台拖拽支持:
self.listbox.drop_target_register(DND_FILES) self.listbox.dnd_bind('<<Drop>>', self._on_drop)
完整代码
import csv import os import sys import tkinter as tk from tkinter import filedialog, messagebox from tkinter import ttk # 尝试启用拖拽支持(需要安装: pip install tkinterdnd2) try: from tkinterdnd2 import DND_FILES, TkinterDnD # type: ignore DND_AVAILABLE = True except Exception: DND_AVAILABLE = False TkinterDnD = None # 占位,未启用时不使用 def is_csv_file(path: str) -> bool: return os.path.isfile(path) and path.lower().endswith('.csv') def collect_csv_from_dir(folder: str): files = [] if os.path.isdir(folder): for f in os.listdir(folder): full = os.path.join(folder, f) if is_csv_file(full): files.append(full) return files def parse_dnd_event_data(root: tk.Tk, data: str): # 解析拖拽数据,兼容包含空格的路径 try: parts = root.tk.splitlist(data) except Exception: parts = data.split() return [p.strip('{').strip('}') for p in parts] class App: def __init__(self, root: tk.Tk): self.root = root self.root.title("CSV 文件转换UTF-8格式 工具(支持拖拽/选择文件/选择文件夹)") self.root.geometry('760x560') self.root.minsize(680, 480) self.files = [] # 去重使用 self.files_set = set() self.output_folder_var = tk.StringVar() self._build_ui() def _build_ui(self): # ========== 输入区域(列表 + 拖拽) ========== input_frame = ttk.LabelFrame(self.root, text="待转换文件(将 CSV 文件或文件夹拖拽到此区域)") input_frame.pack(fill=tk.BOTH, expand=True, padx=12, pady=(12, 6)) list_frame = ttk.Frame(input_frame) list_frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=8) self.listbox = tk.Listbox(list_frame, selectmode=tk.EXTENDED) self.listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.listbox.yview) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.listbox.config(yscrollcommand=scrollbar.set) hint_text = "提示:可点击下方按钮添加文件/文件夹,也可直接拖拽 CSV 文件或文件夹到列表中。" self.hint_label = ttk.Label(input_frame, text=hint_text, foreground="#666") self.hint_label.pack(anchor='w', padx=10, pady=(0, 8)) # 拖拽支持 if DND_AVAILABLE and isinstance(self.root, TkinterDnD.Tk): try: self.listbox.drop_target_register(DND_FILES) self.listbox.dnd_bind('<<Drop>>', self._on_drop) except Exception: pass else: if not DND_AVAILABLE: warn = "(未安装 tkinterdnd2,拖拽功能不可用。可执行: pip install tkinterdnd2)" self.hint_label.configure(text=f"{hint_text}\n{warn}") # ========== 操作按钮 ========== btns = ttk.Frame(self.root) btns.pack(fill=tk.X, padx=12, pady=(0, 6)) ttk.Button(btns, text="添加文件…", command=self.add_files_dialog).pack(side=tk.LEFT, padx=(0, 6)) ttk.Button(btns, text="添加文件夹…", command=self.add_folder_dialog).pack(side=tk.LEFT, padx=(0, 6)) ttk.Button(btns, text="移除选中", command=self.remove_selected).pack(side=tk.LEFT, padx=(0, 6)) ttk.Button(btns, text="清空列表", command=self.clear_list).pack(side=tk.LEFT) # ========== 输出设置 ========== out_frame = ttk.LabelFrame(self.root, text="输出设置") out_frame.pack(fill=tk.X, padx=12, pady=6) row = ttk.Frame(out_frame) row.pack(fill=tk.X, padx=10, pady=8) ttk.Label(row, text="输出文件夹:").pack(side=tk.LEFT) self.out_entry = ttk.Entry(row, textvariable=self.output_folder_var) self.out_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=6) ttk.Button(row, text="浏览…", command=self.select_output_folder).pack(side=tk.LEFT) # ========== 转换 & 进度 ========== bottom = ttk.Frame(self.root) bottom.pack(fill=tk.X, padx=12, pady=10) self.progress = ttk.Progressbar(bottom, mode='determinate') self.progress.pack(fill=tk.X, expand=True, side=tk.LEFT, padx=(0, 10)) ttk.Button(bottom, text="开始转换", command=self.convert_files).pack(side=tk.RIGHT) # 状态栏 self.status_var = tk.StringVar(value="准备就绪") status = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor='w') status.pack(fill=tk.X, side=tk.BOTTOM) # ---------- 文件源管理 ---------- def _add_paths(self, paths): added = 0 for p in paths: if os.path.isdir(p): for f in collect_csv_from_dir(p): if f not in self.files_set: self.files.append(f) self.files_set.add(f) self.listbox.insert(tk.END, f) added += 1 else: if is_csv_file(p) and p not in self.files_set: self.files.append(p) self.files_set.add(p) self.listbox.insert(tk.END, p) added += 1 self.status_var.set(f"已添加 {added} 个文件,当前总数:{len(self.files)}") def _on_drop(self, event): paths = parse_dnd_event_data(self.root, event.data) self._add_paths(paths) def add_files_dialog(self): paths = filedialog.askopenfilenames(title="选择 CSV 文件", filetypes=[("CSV 文件", "*.csv"), ("所有文件", "*.*")]) if paths: self._add_paths(paths) def add_folder_dialog(self): folder = filedialog.askdirectory(title="选择包含 CSV 的文件夹") if folder: self._add_paths([folder]) def remove_selected(self): indices = list(self.listbox.curselection()) if not indices: return # 从后向前删除,避免索引错乱 for idx in reversed(indices): path = self.listbox.get(idx) self.listbox.delete(idx) if path in self.files_set: self.files_set.remove(path) if path in self.files: self.files.remove(path) self.status_var.set(f"已移除,当前总数:{len(self.files)}") def clear_list(self): self.listbox.delete(0, tk.END) self.files.clear() self.files_set.clear() self.status_var.set("列表已清空") def select_output_folder(self): folder = filedialog.askdirectory(title="请选择保存转换后的文件夹") if folder: self.output_folder_var.set(folder) # ---------- 转换逻辑 ---------- def _read_rows_with_encodings(self, file_path): # 按顺序尝试不同编码读取 encodings = ['mbcs', 'ansi', 'utf-8', 'utf-8-sig'] last_err = None for enc in encodings: try: with open(file_path, newline='', encoding=enc) as csvfile: reader = csv.reader(csvfile, delimiter=',', quotechar='"') for row in reader: yield row return except Exception as e: last_err = e continue # 如果都失败,再抛出最后的异常 raise last_err if last_err else UnicodeDecodeError("codec", b"", 0, 1, "无法解码") def convert_files(self): if not self.files: messagebox.showwarning("提示", "请先添加需要转换的 CSV 文件或文件夹!") return output_folder = self.output_folder_var.get().strip() if not output_folder: messagebox.showerror("错误", "请选择保存转换后的文件夹!") return if not os.path.exists(output_folder): try: os.makedirs(output_folder, exist_ok=True) except Exception as e: messagebox.showerror("错误", f"无法创建输出文件夹:{e}") return total = len(self.files) self.progress.config(maximum=total, value=0) success, failed = 0, 0 for i, src in enumerate(self.files, start=1): dst = os.path.join(output_folder, os.path.basename(src)) try: with open(dst, 'w', newline='', encoding='utf-8') as f_w: writer = csv.writer(f_w) for row in self._read_rows_with_encodings(src): writer.writerow(row) success += 1 self.status_var.set(f"转换成功:{os.path.basename(src)}") except Exception as e: failed += 1 self.status_var.set(f"转换失败:{os.path.basename(src)} -> {e}") finally: self.progress['value'] = i self.root.update_idletasks() msg = f"转换完成!成功:{success},失败:{failed}。输出目录:{output_folder}" if failed: messagebox.showwarning("完成(部分失败)", msg) else: messagebox.showinfo("完成", msg) def main(): # 优先使用带 DnD 的 Tk if DND_AVAILABLE: root = TkinterDnD.Tk() else: root = tk.Tk() # Windows 上 ttk 默认样式更美观 try: ttk.Style().theme_use('clam') except Exception: pass app = App(root) root.mainloop() if __name__ == '__main__': main()
效果图
常见问题
Q: 为什么拖拽功能不可用
A: 需要安装 tkinterdnd2 库。运行 pip install tkinterdnd2 后重启程序即可。
Q: 转换后 Excel 打开仍有乱码
A: 本工具输出标准 UTF-8 格式。部分旧版 Excel 可能需要 UTF-8-BOM 格式,可以考虑修改输出编码为 utf-8-sig。
Q: 输出文件会覆盖原文件吗
A: 不会。程序会在指定的输出目录创建新文件,不会修改原始文件。但如果输出目录中存在同名文件,会被覆盖。
Q: 支持哪些分隔符
A: 目前默认支持逗号分隔符。如需支持其他分隔符(如分号、制表符),可以扩展使用 csv.Sniffer 进行自动检测。
使用建议
最佳实践
- 批量处理:将同类型的 CSV 文件放在同一文件夹,整体拖拽添加
- 输出管理:始终选择空的输出目录,避免文件覆盖
- 编码兼容:如需与特定软件兼容,可调整输出编码格式
性能优化
- 大文件处理时,程序会显示实时进度
- 支持中断操作(关闭程序窗口)
- 内存占用优化,逐行处理避免大文件内存溢出
扩展功能规划
- 输出文件名自动添加后缀,避免覆盖
- 自动分隔符检测与配置
- 转换报告导出(成功/失败统计)
- 递归扫描子目录选项
- 编码检测结果预览
- 批量重命名功能
开发者: 专注于提供简单可靠的数据处理工具
到此这篇关于Python实现CSV文件编码转换工具(转成UTF-8格式)的文章就介绍到这了,更多相关Python转换csv文件编码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!