用 AI 實(shí)現(xiàn)一個(gè) GBK/GB2312 轉(zhuǎn) UTF-8 工具:輕松解決文本編碼轉(zhuǎn)換難題(附完整源碼)

用 AI 實(shí)現(xiàn)一個(gè) GBK/GB2312 轉(zhuǎn) UTF-8 工具:輕松解決文本編碼轉(zhuǎn)換難題

在處理歷史文件或與不同系統(tǒng)交互時(shí),我們經(jīng)常會(huì)遇到 GBK 或 GB2312 編碼的文本文件。雖然現(xiàn)在 UTF-8 是主流,但手動(dòng)轉(zhuǎn)換這些舊編碼文件既繁瑣又容易出錯(cuò)。為了解決這個(gè)問(wèn)題,我開(kāi)發(fā)了一個(gè)簡(jiǎn)單的圖形界面工具,可以批量將指定文件夾下的 GBK/GB2312 文件轉(zhuǎn)換為 UTF-8 編碼。

工具概覽

這個(gè)工具使用 Python 和 Tkinter 構(gòu)建,提供了一個(gè)直觀的用戶(hù)界面。它具備以下主要功能:

  • 圖形界面: 簡(jiǎn)潔易用,無(wú)需命令行操作。
  • 文件夾選擇: 方便地選擇包含需要轉(zhuǎn)換文件的文件夾。
  • 智能編碼檢測(cè): 利用 chardet 庫(kù)自動(dòng)識(shí)別文件編碼,并針對(duì) GBK/GB2312 做了優(yōu)化處理。
  • 批量轉(zhuǎn)換: 遞歸遍歷文件夾,自動(dòng)處理所有識(shí)別出的 GBK/GB2312 文本文件。
  • 跳過(guò)機(jī)制: 自動(dòng)跳過(guò)已經(jīng)是 UTF-8 或無(wú)法識(shí)別編碼的文件。
  • 實(shí)時(shí)日志: 在界面上顯示詳細(xì)的轉(zhuǎn)換過(guò)程和結(jié)果。
  • 多線程: 轉(zhuǎn)換過(guò)程在后臺(tái)線程進(jìn)行,避免界面卡頓。

界面截圖

file

如何使用

您可以選擇以下兩種方式之一來(lái)使用該工具:

1. 直接運(yùn)行 (需要 Python 環(huán)境)

  • 確保您的電腦已安裝 Python 3。
  • 下載或克隆項(xiàng)目代碼。
  • 在項(xiàng)目目錄下打開(kāi)終端或命令提示符,安裝依賴(lài):
    pip install -r requirements.txt
    
  • 運(yùn)行主程序:
    python main.py
    
  • 在彈出的窗口中,點(diǎn)擊“瀏覽”選擇目標(biāo)文件夾,然后點(diǎn)擊“開(kāi)始轉(zhuǎn)換”。

2. 使用預(yù)編譯的可執(zhí)行文件

  • 訪問(wèn)項(xiàng)目的 GitHub Releases 頁(yè)面。
  • 下載適用于您操作系統(tǒng)的最新版本(例如 Windows 下載 .exe 文件)。
  • 直接雙擊運(yùn)行下載的文件即可。

完整源代碼

以下是工具的主要 Python 源代碼 (main.py):

import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext
import os
import chardet
import threading
import webbrowser # <-- 添加導(dǎo)入

# 支持檢測(cè)和轉(zhuǎn)換的編碼列表
SUPPORTED_ENCODINGS = ['gb2312', 'gbk']
# 目標(biāo)編碼
TARGET_ENCODING = 'utf-8'
# 常見(jiàn)的文本和代碼文件擴(kuò)展名 (小寫(xiě))
TEXT_FILE_EXTENSIONS = {
    '.txt', '.log', '.csv', '.json', '.xml', '.html', '.htm', '.css', '.js', 
    '.py', '.java', '.c', '.cpp', '.h', '.hpp', '.cs', '.php', '.rb', '.go', 
    '.rs', '.swift', '.kt', '.kts', '.sql', '.md', '.rst', '.yaml', '.yml', 
    '.ini', '.cfg', '.toml', '.sh', '.bat', '.ps1'
}

class EncodingConverterApp:
    def __init__(self, master):
        self.master = master
        master.title("GBK/GB2312 轉(zhuǎn) UTF-8 工具")
        master.geometry("600x480") # <-- 調(diào)整窗口大小以容納鏈接

        # 文件夾選擇
        self.folder_path_var = tk.StringVar()
        tk.Label(master, text="選擇文件夾:").grid(row=0, column=0, padx=5, pady=5, sticky='w')
        self.folder_entry = tk.Entry(master, textvariable=self.folder_path_var, width=50)
        self.folder_entry.grid(row=0, column=1, padx=5, pady=5, sticky='ew')
        self.browse_button = tk.Button(master, text="瀏覽", command=self.browse_folder)
        self.browse_button.grid(row=0, column=2, padx=5, pady=5)

        # 開(kāi)始轉(zhuǎn)換按鈕
        self.convert_button = tk.Button(master, text="開(kāi)始轉(zhuǎn)換", command=self.start_conversion_thread)
        self.convert_button.grid(row=1, column=0, columnspan=3, pady=10)

        # 日志輸出區(qū)域
        tk.Label(master, text="轉(zhuǎn)換日志:").grid(row=2, column=0, padx=5, pady=5, sticky='w')
        self.log_text = scrolledtext.ScrolledText(master, wrap=tk.WORD, height=15)
        self.log_text.grid(row=3, column=0, columnspan=3, padx=5, pady=5, sticky='nsew')
        self.log_text.config(state=tk.DISABLED) # 初始設(shè)為不可編輯

        # GitHub 鏈接
        self.github_url = "https://github.com/dependon/gbk2utf8"
        self.github_label = tk.Label(master, text="項(xiàng)目地址: " + self.github_url, fg="blue", cursor="hand2")
        self.github_label.grid(row=4, column=0, columnspan=3, padx=5, pady=(5, 10), sticky='w') # 放置在日志下方
        self.github_label.bind("<Button-1>", self.open_link)

        # 配置行列權(quán)重,使控件隨窗口縮放
        master.grid_rowconfigure(3, weight=1) # 日志區(qū)域占滿剩余空間
        master.grid_rowconfigure(4, weight=0) # 鏈接行不擴(kuò)展
        master.grid_columnconfigure(1, weight=1)

    def log(self, message):
        """向日志區(qū)域添加消息"""
        self.log_text.config(state=tk.NORMAL)
        self.log_text.insert(tk.END, message + "\n")
        self.log_text.see(tk.END) # 滾動(dòng)到底部
        self.log_text.config(state=tk.DISABLED)
        self.master.update_idletasks() # 強(qiáng)制更新界面

    def browse_folder(self):
        """打開(kāi)文件夾選擇對(duì)話框"""
        folder_selected = filedialog.askdirectory()
        if folder_selected:
            self.folder_path_var.set(folder_selected)
            self.log(f"已選擇文件夾: {folder_selected}")

    def is_text_file(self, filename):
        """根據(jù)擴(kuò)展名判斷是否可能是文本或代碼文件"""
        _, ext = os.path.splitext(filename)
        return ext.lower() in TEXT_FILE_EXTENSIONS

    def detect_encoding(self, file_path):
        """檢測(cè)文件編碼"""
        try:
            with open(file_path, 'rb') as f:
                raw_data = f.read(4096) # 讀取一部分?jǐn)?shù)據(jù)進(jìn)行檢測(cè)
            result = chardet.detect(raw_data)
            encoding = result['encoding']
            confidence = result['confidence']
            # chardet有時(shí)會(huì)將GBK/GB2312檢測(cè)為其他編碼,增加一些兼容性判斷
            if encoding and encoding.lower() in ['gb2312', 'gbk', 'gb18030']:
                 return encoding.lower(), confidence
            # 對(duì)于置信度不高的常見(jiàn)誤判,也嘗試按GBK處理
            if encoding and confidence < 0.9 and encoding.lower() in ['ascii', 'windows-1252']:
                 # 嘗試用GBK解碼,如果成功則認(rèn)為是GBK
                 try:
                     raw_data.decode('gbk')
                     return 'gbk', 0.5 # 置信度設(shè)低一些
                 except UnicodeDecodeError:
                     pass
            return encoding, confidence
        except Exception as e:
            self.log(f"檢測(cè)編碼錯(cuò)誤 ({os.path.basename(file_path)}): {e}")
            return None, 0

    def convert_file_encoding(self, file_path, original_encoding):
        """將文件從原始編碼轉(zhuǎn)換為UTF-8"""
        try:
            with open(file_path, 'r', encoding=original_encoding, errors='replace') as f_read:
                content = f_read.read()
            
            # 檢查是否真的需要轉(zhuǎn)換 (避免不必要的寫(xiě)操作和潛在BOM問(wèn)題)
            needs_conversion = False
            try:
                # 嘗試用UTF-8無(wú)BOM讀取,如果失敗或內(nèi)容不同,則需要轉(zhuǎn)換
                with open(file_path, 'r', encoding='utf-8') as f_utf8_check:
                     utf8_content = f_utf8_check.read()
                if content != utf8_content:
                    needs_conversion = True
            except UnicodeDecodeError:
                 needs_conversion = True
            except Exception:
                 # 其他讀取錯(cuò)誤,也認(rèn)為需要轉(zhuǎn)換以修復(fù)
                 needs_conversion = True

            if needs_conversion:
                with open(file_path, 'w', encoding=TARGET_ENCODING) as f_write:
                    f_write.write(content)
                self.log(f"成功: {os.path.basename(file_path)} ({original_encoding} -> {TARGET_ENCODING})")
                return True
            else:
                self.log(f"跳過(guò): {os.path.basename(file_path)} (已經(jīng)是 {TARGET_ENCODING} 或無(wú)需轉(zhuǎn)換)")
                return False

        except Exception as e:
            self.log(f"轉(zhuǎn)換失敗: {os.path.basename(file_path)} - {e}")
            return False

    def process_folder(self, folder_path):
        """處理指定文件夾下的所有文件"""
        converted_count = 0
        skipped_count = 0
        error_count = 0
        processed_files = 0

        self.log(f"\n開(kāi)始掃描文件夾: {folder_path}")
        for root, _, files in os.walk(folder_path):
            for filename in files:
                if not self.is_text_file(filename):
                    # self.log(f"忽略非文本文件: {filename}")
                    continue

                file_path = os.path.join(root, filename)
                processed_files += 1
                self.log(f"正在處理: {file_path}")
                
                encoding, confidence = self.detect_encoding(file_path)

                if encoding and encoding.lower() in SUPPORTED_ENCODINGS:
                    self.log(f"檢測(cè)到 {encoding.upper()} (置信度: {confidence:.2f}): {filename}")
                    if self.convert_file_encoding(file_path, encoding):
                        converted_count += 1
                    else:
                        error_count += 1
                elif encoding:
                    # self.log(f"跳過(guò) (非GBK/GB2312編碼: {encoding}): {filename}")
                    skipped_count += 1
                else:
                    # self.log(f"跳過(guò) (無(wú)法檢測(cè)編碼): {filename}")
                    skipped_count += 1
                    error_count += 1 # 無(wú)法檢測(cè)也算一種錯(cuò)誤
        
        self.log(f"\n處理完成。共掃描 {processed_files} 個(gè)文本/代碼文件。")
        self.log(f"成功轉(zhuǎn)換: {converted_count}")
        self.log(f"跳過(guò)文件: {skipped_count}")
        self.log(f"轉(zhuǎn)換/檢測(cè)失敗: {error_count}")
        messagebox.showinfo("完成", f"轉(zhuǎn)換完成!\n成功: {converted_count}\n跳過(guò): {skipped_count}\n失敗: {error_count}")
        # 轉(zhuǎn)換完成后重新啟用按鈕
        self.convert_button.config(state=tk.NORMAL)
        self.browse_button.config(state=tk.NORMAL)

    def start_conversion_thread(self):
        """在單獨(dú)的線程中開(kāi)始轉(zhuǎn)換過(guò)程,避免GUI卡死"""
        folder_path = self.folder_path_var.get()
        if not folder_path or not os.path.isdir(folder_path):
            messagebox.showerror("錯(cuò)誤", "請(qǐng)先選擇一個(gè)有效的文件夾!")
            return

        # 禁用按鈕,防止重復(fù)點(diǎn)擊
        self.convert_button.config(state=tk.DISABLED)
        self.browse_button.config(state=tk.DISABLED)
        self.log_text.config(state=tk.NORMAL)
        self.log_text.delete('1.0', tk.END) # 清空日志
        self.log_text.config(state=tk.DISABLED)

        # 創(chuàng)建并啟動(dòng)線程
        conversion_thread = threading.Thread(target=self.process_folder, args=(folder_path,), daemon=True)
        conversion_thread.start()

    def open_link(self, event):
        """打開(kāi)GitHub鏈接"""
        webbrowser.open_new(self.github_url)

if __name__ == "__main__":
    root = tk.Tk()
    app = EncodingConverterApp(root)
    root.mainloop()

項(xiàng)目地址

如果您對(duì)這個(gè)工具感興趣,或者想查看完整的項(xiàng)目代碼和構(gòu)建說(shuō)明,請(qǐng)?jiān)L問(wèn)項(xiàng)目的 GitHub 倉(cāng)庫(kù):

https://github.com/dependon/gbk2utf8

希望這個(gè)小工具能對(duì)您有所幫助!

本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容