沿著位元組到文字的路徑查
中文欄位變成問號或奇怪字串時,先不要反覆另存不同編碼。先保留原檔,列出來源輸出、傳輸、接收解碼、資料庫保存與畫面顯示五個位置,找出文字第一次改變的那一層。只有定位到轉換位置,才知道能否從原始資料恢復。
文字編碼是字元與位元組之間的規則。UTF-8、Big5或特定代碼頁把同一段文字存成的位元組可能不同;接收端必須用來源實際編碼解讀。檔名是中文、檔案副檔名是CSV,或者使用者電腦設為繁體中文,都不能證明內容一定採哪一種編碼。
本文適用於檔案、通訊服務與資料庫欄位的轉碼排查,Python只作離線觀察工具。PLC與HMI字串可能有固定長度、字元集及位元組排列限制,要依指定型號文件核對,不能假設寫入Unicode文字就能在所有設備顯示。
先分辨顯示字型缺字與資料真的改了。方框可能是字型缺少字形,但也可能已有錯誤字元;複製文字到能查看碼點的工具,或比對保存的字元序列,比只換字型更可靠。看起來正常的字也可能是相似但不同的碼點。
用已知文字檢查每一個交接點
準備固定測試文字「壓力A01」,同時保存來源宣告編碼、原始位元組與正確字元。先在來源匯出後檢查,再在接收端解碼後檢查,接著從資料庫讀回,最後比對畫面。每一站都用同一份測試,不要改成不同備註再猜差異。
以「中」為小例子,UTF-8位元組是十六進位E4 B8 AD。用正確UTF-8解碼得到中;把這些位元組當另一種編碼讀,可能出錯,也可能得到完全不同但合法的字元。能成功解碼不等於選對編碼,還要比對來源規格與已知內容。
英文數字在多種編碼中的位元組相同,所以只測A01不能證明中文路徑正確。測試資料應含實際需要的繁體字、標點與可能出現的特殊符號,並保留預期文字。不要只用一個各編碼恰好都能處理的字就宣布完整相容。
如果來源檔位元組正確,接收解碼後就錯,先修接收編碼設定;若解碼正確、存入後讀回變問號,查資料庫欄位型別、連線編碼與寫入轉型;若資料庫讀回正確但畫面錯,查顯示層解碼或字型。逐站比對可縮小問題範圍。
完成後應得到一張轉換紀錄:每站輸入是位元組還是文字、採用哪個編碼、輸出是否與預期一致,以及第一個失敗位置。不要只有「改UTF-8後好了」,否則下一次另一個來源進來仍可能亂碼。
區分可逆誤讀與已經丟失的字元
Python嚴格解碼遇到不合法位元組會報錯,replace會以替代字元表示,ignore則忽略無法處理的部分。排查時優先保留錯誤與原位元組,不用ignore把檔案勉強讀完;被忽略的字可能是設備識別或批號的一部分。
若原本的多種中文字都已被轉成相同問號,再改編碼通常無法知道問號各自代表什麼。應回到尚未損失的原檔、來源緩衝或備份重新處理。替代字元只是損失跡象,不是可以自動推回原文的密碼。
某些誤讀保留了可逆的位元組關係,理論上可能用反向轉換恢復,但必須已知錯誤步驟且逐筆驗證。不要對整個資料庫套用網路上的轉碼公式;原本正確的文字、混合來源與已含替代字元的資料可能因此被二次破壞。
UTF-8的BOM可協助部分程式辨識檔案,Python的utf-8-sig可在解碼時處理開頭簽記。BOM不是所有格式都要求,也不能拿來證明後面每個位元組正確;若接收端未處理,第一個欄位名稱可能多出不可見字元而無法匹配。
Big5與Windows特定繁體中文代碼頁並非可以任意視為完全相同,擴充字元支援可能不同。契約要寫確切編碼名稱與錯誤策略,對不支援的字元明確拒絕或依約轉寫,而不是默默用問號取代後當作成功。
避免截斷與多次轉換重新製造亂碼
字元數與位元組數不同。UTF-8的「中文」有兩個字元、六個位元組;若欄位只能放五個位元組,直接取前五個會切斷第二個字。依介面容量判斷可接受的完整字元長度,不能用畫面看見兩個字就認為一定放得下。
網路封包也可能在一個字元中間分段。不要每收到一小段位元組就獨立解碼成文字再拼接,應使用能保存未完成序列的串流解碼方式,或先按完整訊息邊界收齊。分包邊界與文字字元邊界不是同一件事。
失敗時先查是否存在重複轉碼:文字已正確解碼,卻又被當成原始位元組處理;或同一檔案先經試算表另存,再由服務用舊編碼讀。每一段只負責明確的編碼或解碼,記錄實際輸入型態,減少靠預設猜測。
驗收同時包含可支援字元、不可支援字元、空文字、長度邊界與跨封包字元。對錯誤輸入應看到明確失敗與原資料保留;對正確輸入應逐字讀回一致。只確認程式沒有拋出例外,不能證明資料未被替代或截短。
定位過程使用副本並保存每次轉換設定,確認修正方法能完整讀回後才重新匯入。若同一檔案混有不同來源編碼,應從產生端拆清責任,不能反覆猜整份檔案的單一編碼。
練習與常見問題
練習:把「中文」以UTF-8編碼,確認六個位元組,完整解碼應回到原文。再只取前五個位元組並嚴格解碼,預期失敗,因最後字元不完整。這是離線截斷示例,不要在正式資料上故意截短來測。
問:自動偵測編碼一定可靠嗎?答:只能作線索,短文字與相容字元可能有多種合理解讀。
問:全部改成UTF-8就能修復舊問號嗎?答:已丟失的原字元仍須從原始資料恢復。
問:加BOM能解決所有CSV亂碼嗎?答:要看接收程式契約,BOM只處理辨識的一部分。
問:資料看起來相同就不用逐字比較嗎?答:相似字形可能是不同碼點,識別欄尤其需要精確比對。
參考:Python Unicode HOWTO:字元、位元組與解碼錯誤策略。
參考:Python codecs:編碼、串流解碼與utf-8-sig。