編碼 及編碼問題異常處理:UnicodeDecodeError: 'gbk' codec can't...


字符編碼

  參考鏈接:https://www.liaoxuefeng.com/wiki/1016959663602400/1017075323632896

  字節 

  一個字節(byte)=8個比特(bit)

  字符

  

  ASCII 編碼

  由於計算機是美國人發明的,所以最早只有127個字符被編碼到計算機中,也就是大小寫英文字符、數字和一些符號,這個編碼表被稱為ASCII編碼

 

  GB2312

  字符串雖然也是一種數據類型,但是他有一個比較特殊的編碼問題。

  因為計算機只能處理數字,如果要處理文本,就必須把文本轉為數字,對應中文,就是為每個漢字確定一個編號,這個編號就代表這個漢字。

  最早的計算機在設計時采用8個比特(bit)作為一個字節(byte),所以一個字節能表示的最大整數就是255,如果要表示更大的整數,就必須用更多的字節。比如兩個字節可以表示的最大整數是65535,4個字節可以表示的最大整數是4294967295

  而光是常用漢字就有3500個,所以要處理中文使用一個字節是不夠的,至少需要兩個字節,而且還不能和ASCII沖突,所以中國就制定了GB2312編碼,用來把中文編進去。

  

  Unicode

  你可以想得到的是,全世界有上百種語言,日本把日文編到Shift_JIS里,韓國把韓文編到Euc-kr里,各國有各國的標准,就會不可避免地出現沖突,結果就是,在多語言混合的文本中,顯示出來會有亂碼。

  因此,Unicode應運而生。Unicode把所有語言都統一到一套編碼里,這樣就不會再有亂碼問題了。

  Unicode標准也在不斷的發展,但最常用的是用兩個字節表示一個字符(如果是非常偏僻的字符,就需要用到四個字節),現代操作系統和大多數編程語言都直接支持Unicode

  支持Unicode編碼就意味着支持多語言

  現在,捋一捋ASCII編碼和Unicode編碼的區別:

  ASCII編碼是1個字節,而Unicode編碼通常是2個字節。

  字母A用ASCII編碼是十進制的65,二進制的01000001

  字符0用ASCII編碼是十進制的48,二進制的00110000,注意字符'0'和整數0是不同的;

  漢字已經超出了ASCII編碼的范圍,用Unicode編碼是十進制的20013,二進制的01001110 00101101

  你可以猜測,如果把ASCII編碼的A用Unicode編碼,只需要在前面補0就可以,因此,A的Unicode編碼是00000000 01000001

 

  Utf-8

  采用Unicode編碼會帶來一個新的問題,由於原本一個英文字符只需要一個字節,但使用Unicode編碼需要兩個字節,在傳輸和儲存上就變得十分不划算。

  所以本着節約的精神,就又把Unicode編碼轉變為可變長度的Utf-8編碼

  UTF-8編碼把一個Unicode字符根據不同的數字大小編碼成1-6個字節,常用的英文字母被編碼成1個字節,漢字通常是3個字節,只有很生僻的字符才會被編碼成4-6個字節。如果你要傳輸的文本包含大量英文字符,用UTF-8編碼就能節省空間:

 

   UTF-8編碼有一個額外的好處,就是ASCII編碼實際上可以被看成是UTF-8編碼的一部分,所以,大量只支持ASCII編碼的歷史遺留軟件可以在UTF-8編碼下繼續工作。

  作者說清楚了ASCII、Unicode、Utf-8他們之間的關系,然后就告訴我們說可以現在可以總結計算機系統通用的字符編碼方式:

  在計算機內存中,統一使用Unicode編碼,當需要保存到硬盤或者進行傳輸的時候,就轉話為Utf-8編碼

  然后作者又舉了一些常用的例子

 

python字符串

  Unicode編碼就是意味着支持多語言,Python3的字符串采用的Unicode編碼

  然后作者介紹了一些函數

 

  ord( )函數

  可以獲取單個字符的的整數表示,注意一是單個,二是字符

 

  char( )函數

  把編碼轉換為相應的字符

>>> help(chr)
Help on built-in function chr in module builtins:

chr(i, /)
    Return a Unicode string of one character with ordinal i; 0 <= i <= 0x10ffff.

>>>chr(97)
>>>'a'

  如果知道字符的整數編碼,還可以用十六進制這么寫str

>>> '\u4e2d\u6587'
'中文'

  兩種寫法完全是等價的。

  

  b前綴

  由於Python的字符串類型是str,在內存中以Unicode表示,一個字符對應若干個字節。如果要在網絡上傳輸,或者保存到磁盤上,就需要把str變為以字節為單位的bytes

  Python對bytes類型(注意是一種數據類型)的數據用b前綴的單引號或者雙引號表示

>>> x=b'ABC'
>>> x
b'ABC'
>>> x=b'中文'#對於中文,用別的方法
  File "<stdin>", line 1
SyntaxError: bytes can only contain ASCII literal characters.
>>>

  要注意區分'ABC'b'ABC',前者是str,后者雖然內容顯示得和前者一樣,但bytes的每個字符都只占用一個字節。

 

  encode( )

  以Unicode表示的str通過encode( )方法可以編碼為指定的bytes

>>> 'ABC'.encode('ascii')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> '中文'.encode('ascii')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

  純英文的str可以用ASCII編碼為bytes,內容是一樣的,含有中文的str可以用UTF-8編碼為bytes含有中文的str無法用ASCII編碼,因為中文編碼的范圍超過了ASCII的范圍,Python會報錯

  在bytes中,無法顯示為ASCII字符的字節,用\x##顯示

 

  decode( )

  如果我們從網絡或者磁盤撒謊給你讀取了字節流,那么讀取到的數據就是bytes,要把bytes變為str就需要用到decode( )

>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'

  如果bytes中包含無法解碼的字節,decode()方法會報錯:

>>> b'\xe4\xb8\xad\xff'.decode('utf-8')
Traceback (most recent call last):
  ...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 3: invalid start byte

  如果bytes中只有一小部分無效的字節,可以傳入errors='ignore'忽略錯誤的字節:

>>> b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
'中'

  

  len( )

  要計算str包含多少個字符,可以用len()函數:

>>> len('ABC')
3
>>> len('中文')
2

  len()函數計算的是str字符數,如果換成byteslen()函數就計算字節數

>>> len(b'ABC')
3
>>> len(b'\xe4\xb8\xad\xe6\x96\x87')
6
>>> len('中文'.encode('utf-8'))
6

  可見,1個中文字符經過UTF-8編碼后通常會占用3個字節,而1個英文字符只占用1個字節。

  在操作字符串時,我們經常遇到strbytes的互相轉換。為了避免亂碼問題,應當始終堅持使用UTF-8編碼對strbytes進行轉換。

 

  由於Python源代碼也是一個文本文件,所以,當你的源代碼中包含中文的時候,在保存源代碼時,就需要務必指定保存為UTF-8編碼。當Python解釋器讀取源代碼時,為了讓它按UTF-8編碼讀取,我們通常在文件開頭寫上這兩行:

#!/usr/bin/env python3#告訴Linux/OS X系統,這是一個Python可執行程序,Windows系統會忽略這個注釋;
# -*- coding: utf-8 -*-#告訴Python解釋器,按照UTF-8編碼讀取源代碼,否則,你在源代碼中寫的中文輸出可能會有亂碼。

  申明了UTF-8編碼並不意味着你的.py文件就是UTF-8編碼的,必須並且要確保文本編輯器正在使用UTF-8 without BOM編碼:

 

 

作為編碼問題集合:

*)UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc8 in position 2: invalid continuation byte

 print(output.decode('utf-8'))#這是由於windows 運行環境默認的字符編碼是gbk,而unix底層的運行環境是utf-8,如果在windows運行環境下報錯的話,改下解碼字符集就好了
#改為
print(output.decode('gbk'))

  

  這是因為

2)UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbd in position 0: invalid start byte

  從晚上復制的文本以ANSI編碼儲存為新文件使用以下代碼報錯

import numpy as np
DATA_PATH = r'C:\Users\Administrator.SC-201605202132\AppData\Local\Programs\Python\Python37\forTest\test.txt'
SENTENSE_NUM = 200000 #要讀取的句子數目
X = []
y = []
with open(DATA_PATH,'rt',encoding='utf8') as f:#
    for s in f.readlines():#這里報錯
        s = s.strip()
        if not s:
            continue
        tag_index = []
        for i in range(0,len(s)-1):
            c = s[i]
            if c == '|':
                continue
            next_c = s[i+1]
            if next_c == '|':
                tag_index.append(1)
            else:
                tag_index.append(0) 
        s = s.replace('|','')
        X.append(list(s))
        y.append(tag_index)
        if len(X)>SENTENSE_NUM:
            break

X = np.array(X)
y = np.array(y)
print('X[0]:',X[0])
print('y[0]:',y[0])

 

  解決辦法,通過將源文件另存為的工程中選擇編碼方式為“utf-8”來解決

 

1)UnicodeDecodeError: 'gbk' codec can't...

  去年在寫一個批量讀取文件夾下的指定文件類型的所有文件中遇到了一個小錯誤,今天終於把他干掉了,小小的成就感,記錄一下

錯誤:

1 Traceback (most recent call last):
2   File "<pyshell#84>", line 1, in <module>
3     open(path, encoding='gbk', mode='r').read()
4 UnicodeDecodeError: 'gbk' codec can't decode byte 0x88 in position 38: illegal multibyte sequence

解決辦法:

#! python3
# chapter08-test03.py - 打開文件夾中所有的.txt文件
#   結果打印在屏幕上
import os,re
import chardet 

#定義獲取文件編碼的函數
def get_encoding(file):
    with open(file,'rb') as f:
        return chardet.detect(f.read())['encoding']

path=os.path.join('F:\便簽')   
fileNameList=os.listdir(path)   #listdir()返回一個包含路徑path下所有文件名稱的列表
for objFile in fileNameList:
    fileType=os.path.splitext(os.path.join(path,objFile))
    #splitext()會返回路徑名和擴展名的元組
    if fileType[1]=='.txt':
        correctFileName=os.path.join(path,objFile)#拼接出來文件的完整地址
        encoding=get_encoding(correctFileName)
        #這里是關鍵點
        searchObjFile=open(correctFileName,encoding=encoding,errors='ignore')
        print('*'*20+str(correctFileName)+'*'*20)   #重復打印的快捷方式是這樣的
        print(searchObjFile.read())
        searchObjFile.close()

詳細過程:

  首先錯誤是有python3的默認編碼與要打開文件的編碼不同導致的

  我搜到的第一個解決辦法是將默認編碼改成‘gbk’

searchObjFile=open(correctFileName,encoding=‘gbk’,errors='ignore')

  結果無效

  第二次解決辦法的思想是獲取每個文件的編碼類型,用相應的編碼去創建文件對象,這樣動態的調用 open() ,這樣做需要

  1)獲取文件編碼

#定義獲取文件編碼的函數
def get_encoding(file):
    with open(file,'rb') as f:
        return chardet.detect(f.read())['encoding']

   open() 中的‘rb’代表用二進制打開一個文件用於只讀, chardet.detect(data) 用於檢測data的編碼類型,至於后面的 ['encoding'] 我也不知道

  2)然后就是在每次調用 open() 之前獲取文件編碼,然后調用時傳入即:

searchObjFile=open(correctFileName,encoding=encoding,errors='ignore')

  可以通過設置 errors 來控制出現錯誤時的策略

默認的參數就是strict,代表遇到非法字符時拋出異常; 如果設置為ignore,則會忽略非法字符; 如果設置為replace,則會用?取代非法字符; 如果設置為xmlcharrefreplace,則使用XML的字符引用

  這個方法有效 感謝作者!>>>原文鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM