Python學習日誌-解決UnicodeDecodeError問題

Kayden
8 min readDec 26, 2020

--

前言

內文為自行查看書籍、查閱網路資訊,或者利用程式自行嘗試後,進行整理幫助自我理解的學習日誌,並無商業行為,內容大多轉載於書籍以及他人的分享,來源皆附於文章下方的參考資料。此外內容表達的為自我的理解與解釋,並不保證一定正確,請僅以參考視之,若有錯誤歡迎告知。

在讀取檔案時常常出現UnicodeDecodeError錯誤,報錯內容為「'utf-8' codec can’t decode byte 0xc8 in position 0: invalid contin」,令人十分好奇到底是為甚麼會出現錯誤? 背後的運作邏輯是什麼? 報錯內容中有 decode,在讀取資料或寫入資料時常看到 encode, decode, encoding, decoding 它們分別是什麼? 之間的差異又是什麼?此次的紀錄希望可以解決這些問題 :D

字元編碼之概述

在計算機記憶體中,統一使用 Unicode 編碼,若當需要儲存到硬碟或者需要傳輸的時候就轉換為 UTF-8 編碼。像是在記事本中紀錄文字,電腦從檔案讀取的 UTF-8 字元會被轉換為 Unicode 字元存到記憶體裡,編輯完成後,儲存時在把 Unicode 字元轉換為 UTF-8 字元存到檔案。

ASCII編碼: 可編譯大小寫英文字母、數字、一些符號 (無法處理中文)
GB2312、GBK、GB18030編碼: 至少需要兩個位元組才有辦法處理中文,而且為了不跟 ASCII 編碼起衝突,因此中國制定了這些編碼
Unicode編碼: 因應融合各國語言的編碼需求,將所有語言統一到一套編碼裡,以避免亂碼產生。Unicode 只是一個字形和內碼上的標準,並沒有定義實際在電腦上存取的方法。
UTF-8編碼: UTF-8 把一個 Unicode 字元根據不同的自數大小編成 1–6 個位元組,英文字母為1個位元組,中文字為3個位元組。

ASCII vs Unicode:
ASCII 是1個位元組(byte),Unicode通常是兩個位元組(bytes)

encode() 與 decode()

Python的字串型別是str,在 Python 內部(亦即在記憶體中)以 Unicode 表示,一個字元(str)對應若干個位元組(bytes)。因此在做編碼轉換時,例如ASCII 轉換為 UTF-8,通常需要以 unicode 做為中間編碼,意指先將其他編碼的字串解碼 (decode) 成 unicode,在從 unicode 編碼(encode) 成另一種編碼。因此在轉碼時一定要先搞清楚,字串str是以什麼進行編碼的,才能對其正確的解碼 (decode)。
encode: 將unicode編碼轉換成其他編碼的字串
decode: 將其他編碼的字串轉換成unicode編碼

str 與 bytes

Python的字串型別是str,在 Python 內部(亦即在記憶體中)以 Unicode 表示,亦即在python裡 unicode編碼=str

unicode vs bytes

如果要在網路上傳輸,或者儲存到磁碟上,就需要str變為以位元組為單位的bytes,一個字元對應若干個位元組。
注意'ABC'b’ABC’之間的差異,雖然顯示內容長的一致,但 ’ABC’ 為 str 型別,而 b’ABC’ 為 bytes 型別並且每個字都只占用一個位元組。

# Python對bytes型別的資料用帶b字首的單引號或雙引號表示
x_bytes = b'ABC'
x_str = 'ABC'

前面敘述看一看,還是有種霧裡看花的感覺吧~~bytes, str, unicode, encode, decode 全部在一起打架了,讓我們試著執行程式進一步感受之間的差異吧。

【例子說明】

# 將'ABC123'(str型別/unicode編碼)以'ASCII'進行編譯,編譯成bytes(位元組)
'ABC123'.encode('ascii')
# 將'英文ABC123'(str型別/unicode編碼)以'UTF-8'進行編譯,編譯成bytes(位元組)
'英文ABC123'.encode('utf-8')
#將'英文ABC123'(str型別/unicode編碼)以'ASCII'進行編譯,編譯成bytes(位元組)
'英文ABC123'.encode('ascii')

執行結果可以看到,bytes 型別中把單個位元組能顯示的字元顯示,不能顯示的用\x##顯示。純英文的 str 可以用 ASCII 編碼為 bytes,內容是一樣的,含有中文的 str 可以用 UTF-8 編碼為 bytes 。含有中文的 str 無法用 ASCII 編碼,因為中文編碼的範圍超過了 ASCII 編碼的範圍,Python會報錯。

如果我們從網路或磁碟上讀取了位元組流,那麼讀到的資料就是 bytes 。要把bytes 變為 str (或轉為 unicode),就需要用 decode() 方法:

如果 bytes 中包含無法解碼的位元組,decode() 方法會報錯,如果 bytes 中只有一小部分無效的位元組,可以傳入 errors='ignore' 忽略錯誤的位元組:
可以看第199陳述時,顯示錯誤,表示在該解碼的位元組有錯誤,因此無法正確解碼。而第200陳述時,將錯誤部分忽略。因此可看到正確解碼的部分為「英ABC123」。

在操作字串時,經常遇到 str 與 bytes 的相互轉換,因此為了避免亂碼出現的問題,應堅持使用 UTF-8 編碼對 str 與 bytes 之間進行轉換。

解決UnicodeDecodeError問題

出現 UnicodeDecodeError 問題,表示你對於字串是什麼編碼判斷錯誤,因此解碼失敗。如果記事本是以 ASCII 進行編碼的,你讀取記事本後卻對其以 UTF-8 進行解碼,那程式當然會告訴你錯誤訊號。故在轉碼時一定要先搞清楚,字串 str 是以什麼進行編碼的,才能對其正確的解碼!!

【例子1】

一個以ASCII編碼的記事本,再用 python 讀取,並以 utf-8 進行解碼

UnicodeDecodeError: 以utf-8 進行解碼

如預期的,結果可以看到出現了UnicodeDecodeError,但如果使用 ASCII 進行解碼呢?

UnicodeDecodeError: 以ASCII 進行解碼

結果一樣出現了UnicodeDecodeError,原因為記事本中有中文字,因此在一開始以 ASCII 編碼時,就出現亂碼了,現在就算再以 ASCII 進行解碼,它也無法處裡亂碼。

【例子2】

以 utf-8 編碼的記事本,再用 python 讀取,並以 utf-8 進行解碼,其結果將成功進行解碼。

【例子3】

如果我們不知道該文本是使用什麼編碼進行編碼的,可以使用模組 chardet 裡的chardet.detect() 進行查詢。在瞭解字串 str 是以什麼進行編碼後,再對其正確的解碼。

參考資料:
python 讀取資料出現UnicodeDecodeError:: 'utf-8' codec can’t decode byte 0xc8 in position 0: invalid contin
Python讀取各種格式的txt文檔(ANSI、Unicode、Unicode big endian、UTF-8等)
新手需要知道decode 和 encode 區別【轉載】

--

--

No responses yet