Python實現(xiàn)CSV文件編碼轉(zhuǎn)換工具(轉(zhuǎn)成UTF-8格式)
背景
一個支持拖拽操作的 CSV 文件批量編碼轉(zhuǎn)換工具,可以將各種編碼的 CSV 文件統(tǒng)一轉(zhuǎn)換為 UTF-8 編碼,解決中文亂碼問題。
功能特點
多種文件添加方式
- 拖拽支持:直接將 CSV 文件或包含 CSV 的文件夾拖拽到程序窗口
- 單文件選擇:支持選擇單個或多個 CSV 文件
- 文件夾批量:選擇文件夾,自動收集其中的所有 CSV 文件
智能文件管理
- 自動去重:重復(fù)添加的文件會被自動過濾
- 列表管理:支持移除選中文件、清空列表等操作
- 實時反饋:狀態(tài)欄顯示當(dāng)前文件數(shù)量和操作結(jié)果
強大的編碼兼容性
- 多編碼支持:自動嘗試 MBCS、ANSI、UTF-8、UTF-8-SIG 等編碼
- 智能識別:按優(yōu)先級依次嘗試,最大程度避免亂碼
- 統(tǒng)一輸出:所有文件統(tǒng)一轉(zhuǎn)換為 UTF-8 編碼
現(xiàn)代化界面
- 美觀布局:使用 ttk 控件,界面清晰美觀
- 進(jìn)度可視化:實時進(jìn)度條顯示轉(zhuǎn)換進(jìn)度
- 狀態(tài)提示:底部狀態(tài)欄顯示當(dāng)前操作狀態(tài)
系統(tǒng)要求
Python 版本:3.7 或更高版本
操作系統(tǒng):Windows、macOS、Linux(推薦 Windows)
可選依賴:tkinterdnd2(用于拖拽功能)
安裝與運行
基礎(chǔ)運行
python main.py
啟用拖拽功能(推薦)
pip install tkinterdnd2 python main.py
提示:如果未安裝 tkinterdnd2,程序會自動降級運行,并在界面提示安裝方法。
使用指南
步驟 1:添加文件
有三種方式添加需要轉(zhuǎn)換的 CSV 文件:
1.拖拽方式(推薦)
- 直接將 CSV 文件拖拽到文件列表區(qū)域
- 也可以拖拽包含 CSV 文件的文件夾
2.按鈕選擇
- 點擊「添加文件…」選擇單個或多個 CSV 文件
- 點擊「添加文件夾…」選擇包含 CSV 的文件夾
3.列表管理
- 選中文件后點擊「移除選中」刪除不需要的文件
- 點擊「清空列表」清除所有文件
步驟 2:設(shè)置輸出目錄
- 在「輸出設(shè)置」區(qū)域點擊「瀏覽…」按鈕
- 選擇一個用于保存轉(zhuǎn)換后文件的目錄
- 建議選擇空目錄,避免同名文件被覆蓋
步驟 3:開始轉(zhuǎn)換
- 點擊「開始轉(zhuǎn)換」按鈕
- 觀察進(jìn)度條和狀態(tài)欄的實時反饋
- 轉(zhuǎn)換完成后會彈出結(jié)果提示框
技術(shù)實現(xiàn)
編碼檢測策略
程序采用多重編碼嘗試機(jī)制:
encodings = ['mbcs', 'ansi', 'utf-8', 'utf-8-sig']
按優(yōu)先級依次嘗試讀取,一旦成功即停止嘗試,確保最大兼容性。
文件處理流程
- 文件收集:掃描指定路徑,過濾 .csv 文件
- 編碼檢測:按策略嘗試不同編碼讀取
- 內(nèi)容轉(zhuǎn)換:逐行讀取并寫入 UTF-8 文件
- 進(jìn)度反饋:實時更新界面狀態(tài)
拖拽功能實現(xiàn)
使用 tkinterdnd2 庫實現(xiàn)跨平臺拖拽支持:
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):
# 解析拖拽數(shù)據(jù),兼容包含空格的路徑
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 文件轉(zhuǎn)換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):
# ========== 輸入?yún)^(qū)域(列表 + 拖拽) ==========
input_frame = ttk.LabelFrame(self.root, text="待轉(zhuǎn)換文件(將 CSV 文件或文件夾拖拽到此區(qū)域)")
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,拖拽功能不可用??蓤?zhí)行: 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)
# ========== 輸出設(shè)置 ==========
out_frame = ttk.LabelFrame(self.root, text="輸出設(shè)置")
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)
# ========== 轉(zhuǎn)換 & 進(jìn)度 ==========
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="開始轉(zhuǎn)換", command=self.convert_files).pack(side=tk.RIGHT)
# 狀態(tài)欄
self.status_var = tk.StringVar(value="準(zhǔn)備就緒")
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} 個文件,當(dāng)前總數(shù):{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"已移除,當(dāng)前總數(shù):{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="請選擇保存轉(zhuǎn)換后的文件夾")
if folder:
self.output_folder_var.set(folder)
# ---------- 轉(zhuǎn)換邏輯 ----------
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("提示", "請先添加需要轉(zhuǎn)換的 CSV 文件或文件夾!")
return
output_folder = self.output_folder_var.get().strip()
if not output_folder:
messagebox.showerror("錯誤", "請選擇保存轉(zhuǎn)換后的文件夾!")
return
if not os.path.exists(output_folder):
try:
os.makedirs(output_folder, exist_ok=True)
except Exception as e:
messagebox.showerror("錯誤", f"無法創(chuàng)建輸出文件夾:{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"轉(zhuǎn)換成功:{os.path.basename(src)}")
except Exception as e:
failed += 1
self.status_var.set(f"轉(zhuǎn)換失敗:{os.path.basename(src)} -> {e}")
finally:
self.progress['value'] = i
self.root.update_idletasks()
msg = f"轉(zhuǎn)換完成!成功:{success},失?。簕failed}。輸出目錄:{output_folder}"
if failed:
messagebox.showwarning("完成(部分失?。?, msg)
else:
messagebox.showinfo("完成", msg)
def main():
# 優(yōu)先使用帶 DnD 的 Tk
if DND_AVAILABLE:
root = TkinterDnD.Tk()
else:
root = tk.Tk()
# Windows 上 ttk 默認(rèn)樣式更美觀
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: 轉(zhuǎn)換后 Excel 打開仍有亂碼
A: 本工具輸出標(biāo)準(zhǔn) UTF-8 格式。部分舊版 Excel 可能需要 UTF-8-BOM 格式,可以考慮修改輸出編碼為 utf-8-sig。
Q: 輸出文件會覆蓋原文件嗎
A: 不會。程序會在指定的輸出目錄創(chuàng)建新文件,不會修改原始文件。但如果輸出目錄中存在同名文件,會被覆蓋。
Q: 支持哪些分隔符
A: 目前默認(rèn)支持逗號分隔符。如需支持其他分隔符(如分號、制表符),可以擴(kuò)展使用 csv.Sniffer 進(jìn)行自動檢測。
使用建議
最佳實踐
- 批量處理:將同類型的 CSV 文件放在同一文件夾,整體拖拽添加
- 輸出管理:始終選擇空的輸出目錄,避免文件覆蓋
- 編碼兼容:如需與特定軟件兼容,可調(diào)整輸出編碼格式
性能優(yōu)化
- 大文件處理時,程序會顯示實時進(jìn)度
- 支持中斷操作(關(guān)閉程序窗口)
- 內(nèi)存占用優(yōu)化,逐行處理避免大文件內(nèi)存溢出
擴(kuò)展功能規(guī)劃
- 輸出文件名自動添加后綴,避免覆蓋
- 自動分隔符檢測與配置
- 轉(zhuǎn)換報告導(dǎo)出(成功/失敗統(tǒng)計)
- 遞歸掃描子目錄選項
- 編碼檢測結(jié)果預(yù)覽
- 批量重命名功能
開發(fā)者: 專注于提供簡單可靠的數(shù)據(jù)處理工具
到此這篇關(guān)于Python實現(xiàn)CSV文件編碼轉(zhuǎn)換工具(轉(zhuǎn)成UTF-8格式)的文章就介紹到這了,更多相關(guān)Python轉(zhuǎn)換csv文件編碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用PyInstaller將Python程序文件轉(zhuǎn)換為可執(zhí)行程序文件
與py2exe一樣,PyInstaller程序也可以將Python的.py程序文件轉(zhuǎn)換為.exe,并且還有Linux的版本,下面我們就來詳細(xì)看一下如何使用PyInstaller將Python程序文件轉(zhuǎn)換為可執(zhí)行程序文件2016-07-07
把csv文件轉(zhuǎn)化為數(shù)組及數(shù)組的切片方法
今天小編就為大家分享一篇把csv文件轉(zhuǎn)化為數(shù)組及數(shù)組的切片方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-07-07
Python調(diào)用C/C++函數(shù)庫的多種方法與實踐指南
Python作為一門高級編程語言,以其簡潔的語法和豐富的庫生態(tài)贏得了開發(fā)者的青睞,然而,在計算密集型任務(wù)中,Python的性能往往無法滿足要求,Python調(diào)用C/C++函數(shù)庫成為提升應(yīng)用性能的關(guān)鍵技術(shù)路徑,本文將深入探討Python調(diào)用C/C++函數(shù)庫的多種方法,需要的朋友可以參考下2025-08-08

