Python实现CSV文件编码转换工具(转成UTF-8格式)

 更新时间:2025年08月26日 10:05:52   作者:小庄-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文件编码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • pip命令突然无法使用问题以及解决

    pip命令突然无法使用问题以及解决

    当你在使用pip安装Python库时遇到问题,可以尝试以下两种解决方案,第一种是直接在Scripts文件夹内使用CMD命令进行安装,第二种是将Scripts的路径设置为系统环境变量,这样就可以直接在dos中使用pip install进行安装了,以上解决方案仅供参考,如有更好的方法欢迎交流分享
    2024-10-10
  • 探究Python中isalnum()方法的使用

    探究Python中isalnum()方法的使用

    这篇文章主要介绍了探究Python中isalnum()方法的使用,是Python入门学习中的基础知识,需要的朋友可以参考下
    2015-05-05
  • python 如何在 Matplotlib 中绘制垂直线

    python 如何在 Matplotlib 中绘制垂直线

    这篇文章主要介绍了python 如何在 Matplotlib 中绘制垂直线,帮助大家更好的理解和学习使用python,感兴趣的朋友可以了解下
    2021-04-04
  • Python selenium 八种定位元素的方式

    Python selenium 八种定位元素的方式

    这篇文章主要介绍了Python selenium八种定位元素的方式,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-08-08
  • 使用PyInstaller将Python程序文件转换为可执行程序文件

    使用PyInstaller将Python程序文件转换为可执行程序文件

    与py2exe一样,PyInstaller程序也可以将Python的.py程序文件转换为.exe,并且还有Linux的版本,下面我们就来详细看一下如何使用PyInstaller将Python程序文件转换为可执行程序文件
    2016-07-07
  • python爬取晋江文学城小说评论(情绪分析)

    python爬取晋江文学城小说评论(情绪分析)

    这篇文章主要介绍了使用python爬取晋江文学城小说评论(情绪分析),全文代码详细,逻辑清晰,很适合学习爬虫爬取的朋友,需要的朋友可以参考下
    2021-04-04
  • scrapy实践之翻页爬取的实现

    scrapy实践之翻页爬取的实现

    这篇文章主要介绍了scrapy实践之翻页爬取的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • 把csv文件转化为数组及数组的切片方法

    把csv文件转化为数组及数组的切片方法

    今天小编就为大家分享一篇把csv文件转化为数组及数组的切片方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-07-07
  • python基础详解之if循环语句

    python基础详解之if循环语句

    这篇文章主要介绍了python基础详解之if循环语句,文中有非常详细的代码示例,对正在学习python的小伙伴们有很好的帮助需要的朋友可以参考下
    2021-04-04
  • Python调用C/C++函数库的多种方法与实践指南

    Python调用C/C++函数库的多种方法与实践指南

    Python作为一门高级编程语言,以其简洁的语法和丰富的库生态赢得了开发者的青睐,然而,在计算密集型任务中,Python的性能往往无法满足要求,Python调用C/C++函数库成为提升应用性能的关键技术路径,本文将深入探讨Python调用C/C++函数库的多种方法,需要的朋友可以参考下
    2025-08-08

最新评论