Hướng dẫn python decode errors replace - thay thế lỗi giải mã python

I'm trying to replace some text ('id' -> 'doc_id') in hundreds of JSON files. The files include text scraped from the internet, text encoding should be utf-8 but maybe not all the time. I tried to read the files in binary, then decode into utf-8, then replace the text and then write to file. The encoding part has errors='replace' but I still get errors! And the funny part is that if I run the program multiple times, it gets stuck on different files.

Why am I getting errors even with 'replace'?

Here is the code:

import os

folder = 'C:\\some\\path'

for file in os.listdir(folder):
    if file.endswith('.json'):
        print('Processing: ', file)
        f = open(file, 'rb')
        binary_text = f.read()
        f.close()
        decoded_text = binary_text.decode(encoding='UTF-8', errors='replace')
        replaced_text = decoded_text.replace('"id":', '"doc_id":')
        f = open(file, 'w')
        f.write(replaced_text)
        f.close()

print('Done!')

This is one of the example errors I get:

Traceback (most recent call last):
  File "C:\some\path\id_to_docid.py", line 13, in 
    binary_text = f.read()
  File "C:\Program Files\Python36\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 7423: character maps to 

Ví dụ sau minh họa 3 codecs bạn hay gặp nhất trong Python:

>>> s = "sky down no enemy"
>>> print type(s), len(s)
 17
>>> s = "thiên hạ vô địch"
>>> s
'thi\xc3\xaan h\xe1\xba\xa1 v\xc3\xb4 \xc4\x91\xe1\xbb\x8bch'
>>> print type(s), len(s)
 23
>>> s = unicode("thiên hạ vô địch", 'utf8')
>>> s
u'thi\xean h\u1ea1 v\xf4 \u0111\u1ecbch'
>>> print type(s), len(s)
 16

Nghiên cứu ví dụ trên. len(s) trong hai ví dụ đầu thực sự là số byte cần để lưu trữ s. len(s) trong ví dụ 3 là độ dài ký tự, dung lượng nhớ thực sự để lưu trữ s là 16 * 2 = 32 bytes.

Tại bất cứ một thời điểm nào trong chương trình Python bạn cũng phải xác định được bạn đang thao tác với chuỗi kiểu gì: ascii, unicode, hay utf8... Dùng lệnh sau để biết một chuỗi là unicode hay không:

>>> s
u'thi\xean h\u1ea1 v\xf4 \u0111\u1ecbch'
>>> type(s) == type(u"")
True
>>> ss = "sky down no enemy"
>>> type(ss) == type(u"")
False
>>> s = "thiên hạ vô địch"
>>> type(ss) == type(u"")
False

Cộng một chuỗi không phải unicode với một chuỗi unicode thì chuỗi không phải unicode sẽ được tự động convert sang unicode trước khi cộng:

>>> ss = "sky down no enemy"
>>> ss + u""
u'sky down no enemy'
>>> ss = "thiên hạ vô địch"
>>> ss + u""
Traceback (most recent call last):
  File "", line 1, in 
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 3: ordinal not in range(128)
>>> ss = unicode("thiên hạ vô địch", 'utf8')
>>> ss + u""
u'thi\xean h\u1ea1 v\xf4 \u0111\u1ecbch'

Trong tình huống thứ 2 bạn cố gắng cộng một chuỗi utf8 vào chuỗi unicode. Python tự động chuyển chuỗi utf8 thành chuỗi unicode bằng cách decode với codec='ascii', vì vậy gặp lỗi. Tình huống này tương đương với:

ss + u"" = ss.decode('ascii') + u""
>>> ss = "thiên hạ vô địch"
>>> ss.decode('ascii') + u""
Traceback (most recent call last):
  File "", line 1, in 
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 3: ordinal not in range(128)

Hai cách để cộng đúng là:

hoặc

unicode(ss, 'utf8') + u""

Các thao tác với chuỗi khác như join, split, find... cũng tương tự. Nghĩa là nếu có một tham số unicode thì các tham số còn lại được tự động chuyển sang unicode trước khi thực hiện thao tác. Ví dụ:

>>> ss = "sky down no enemy"
>>> ss.split(u" ")
[u'sky', u'down', u'no', u'enemy']
>>> l = ["sky", "down", "no", "enemy"]
>>> unichr(32).join(l)
u'sky down no enemy'

Tương tự thao tác sau sẽ gây lỗi:

>>> s = "thiên hạ vô địch"
>>> s.split(u" ")
Traceback (most recent call last):
  File "", line 1, in 
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 3: ordinal not in range(128)

Để chuyển một chuỗi tiếng Việt utf sang dạng viết hoa, bạn luôn luôn phải chuyển nó về unicode trước. Nghiên cứu ví dụ sau:

>>> s = "thiên hạ vô địch"
>>> s.upper()
'THI\xc3\xaaN H\xe1\xba\xa1 V\xc3\xb4 \xc4\x91\xe1\xbb\x8bCH'
>>> s
'thi\xc3\xaan h\xe1\xba\xa1 v\xc3\xb4 \xc4\x91\xe1\xbb\x8bch'
<>

So sánh với s ta thấy s.upper() không upper case được các ký tự ê, ạ, ô, đ, ị...

Để upper một chuỗi utf bạn phải chuyển nó về unicode trước, upper xong thì chuyển ngược lại. Hàm Upper sau nhận đầu vào là chuỗi utf, upper nó và trả về utf khác đã được upper, hàm này thích hợp cho mục đích upper tiếng Việt:

Traceback (most recent call last):
  File "C:\some\path\id_to_docid.py", line 13, in 
    binary_text = f.read()
  File "C:\Program Files\Python36\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 7423: character maps to 
0

Kết quả:

Traceback (most recent call last):
  File "C:\some\path\id_to_docid.py", line 13, in 
    binary_text = f.read()
  File "C:\Program Files\Python36\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 7423: character maps to 
1

Đầu vào thích hợp của Upper là ascii, utf8, unicode, các dạng khác có thể sai: latin1, tcvn... Tương tự như vậy cho hàm lower, và capwords. Xem xét ví sau với capwords:

Traceback (most recent call last):
  File "C:\some\path\id_to_docid.py", line 13, in 
    binary_text = f.read()
  File "C:\Program Files\Python36\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 7423: character maps to 
2

Kết quả:

Traceback (most recent call last):
  File "C:\some\path\id_to_docid.py", line 13, in 
    binary_text = f.read()
  File "C:\Program Files\Python36\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 7423: character maps to 
3

Đầu vào thích hợp của Upper là ascii, utf8, unicode, các dạng khác có thể sai: latin1, tcvn... Tương tự như vậy cho hàm lower, và capwords. Xem xét ví sau với capwords:

Traceback (most recent call last):
  File "C:\some\path\id_to_docid.py", line 13, in 
    binary_text = f.read()
  File "C:\Program Files\Python36\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 7423: character maps to 
4

Các tên file hoặc tên thư mục là tiếng Việt có dấu cũng đòi hỏi cách xử lý đặc biệt. Giả sử bạn có thư mục abc với một file duy nhất tên là: tiếng việt.txt. Thư mục abc đặt trong thư mục cá nhân của bạn. Nghiên cứu đoạn chương trình sau:

  • Chú ý:"C:\Documents and Settings\YourAccountName\abc"
  • Trên Windows path = "C:\Documents and Settings\YourAccountName\abc"

Trên Linux path = "/home/YourAccountName/abc"

Vì chỉ có duy nhất một file tiếng việt.txt trong thư mục abc nên chúng ta mong đợi kết quả in ra là: [True]. Tuy nhiên chỉ Linux cho câu trả lời này. Windows thì không. Không tin bạn thử xem. Chương trình trên cho dù đã được viết với việc tận dụng thư viện sẵn có os nhằm nâng cao tính khả chuyển nhưng vẫn không khả chuyển, khi tên file là tiếng Việt có dấu.

Traceback (most recent call last):
  File "C:\some\path\id_to_docid.py", line 13, in 
    binary_text = f.read()
  File "C:\Program Files\Python36\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 7423: character maps to 
5

Giải quyết vấn đề này rất đơn giản bạn chỉ cần thay "abc" thành u"abc" để tất cả các đường dẫn tên file bị ép chuyển sang unicode là OK. Bạn làm như sau:

Traceback (most recent call last):
  File "C:\some\path\id_to_docid.py", line 13, in 
    binary_text = f.read()
  File "C:\Program Files\Python36\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 7423: character maps to 
6

hoặc

Các vấn đề tương tự cũng áp dụng cho hàm glob hoặc walk, Các hàm này cần các đường dẫn là chuỗi unicode để có thể lấy chính xác các file có tên tiếng Việt.

Traceback (most recent call last):
  File "C:\some\path\id_to_docid.py", line 13, in 
    binary_text = f.read()
  File "C:\Program Files\Python36\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 7423: character maps to 
7

Kết quả:

Traceback (most recent call last):
  File "C:\some\path\id_to_docid.py", line 13, in 
    binary_text = f.read()
  File "C:\Program Files\Python36\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 7423: character maps to 
8

Đầu vào thích hợp của Upper là ascii, utf8, unicode, các dạng khác có thể sai: latin1, tcvn... Tương tự như vậy cho hàm lower, và capwords. Xem xét ví sau với capwords:

Các tên file hoặc tên thư mục là tiếng Việt có dấu cũng đòi hỏi cách xử lý đặc biệt. Giả sử bạn có thư mục abc với một file duy nhất tên là: tiếng việt.txt. Thư mục abc đặt trong thư mục cá nhân của bạn. Nghiên cứu đoạn chương trình sau:

Traceback (most recent call last):
  File "C:\some\path\id_to_docid.py", line 13, in 
    binary_text = f.read()
  File "C:\Program Files\Python36\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 7423: character maps to 
9

Chú ý:

>>> s = "sky down no enemy"
>>> print type(s), len(s)
 17
>>> s = "thiên hạ vô địch"
>>> s
'thi\xc3\xaan h\xe1\xba\xa1 v\xc3\xb4 \xc4\x91\xe1\xbb\x8bch'
>>> print type(s), len(s)
 23
>>> s = unicode("thiên hạ vô địch", 'utf8')
>>> s
u'thi\xean h\u1ea1 v\xf4 \u0111\u1ecbch'
>>> print type(s), len(s)
 16
0

Trên Windows path = "C:\Documents and Settings\YourAccountName\abc"

Trên Linux path = "/home/YourAccountName/abc"

>>> s = "sky down no enemy"
>>> print type(s), len(s)
 17
>>> s = "thiên hạ vô địch"
>>> s
'thi\xc3\xaan h\xe1\xba\xa1 v\xc3\xb4 \xc4\x91\xe1\xbb\x8bch'
>>> print type(s), len(s)
 23
>>> s = unicode("thiên hạ vô địch", 'utf8')
>>> s
u'thi\xean h\u1ea1 v\xf4 \u0111\u1ecbch'
>>> print type(s), len(s)
 16
1

Vì chỉ có duy nhất một file tiếng việt.txt trong thư mục abc nên chúng ta mong đợi kết quả in ra là: [True]. Tuy nhiên chỉ Linux cho câu trả lời này. Windows thì không. Không tin bạn thử xem. Chương trình trên cho dù đã được viết với việc tận dụng thư viện sẵn có os nhằm nâng cao tính khả chuyển nhưng vẫn không khả chuyển, khi tên file là tiếng Việt có dấu.

Đọc ghi file dữ liệu tiếng Việt. File chứa dữ liệu dạng văn bản tiếng Việt thường được ghi dưới dạng unicode hoặc utf8. Đoạn chương trình sau đọc nội dung utf8:

>>> s = "sky down no enemy"
>>> print type(s), len(s)
 17
>>> s = "thiên hạ vô địch"
>>> s
'thi\xc3\xaan h\xe1\xba\xa1 v\xc3\xb4 \xc4\x91\xe1\xbb\x8bch'
>>> print type(s), len(s)
 23
>>> s = unicode("thiên hạ vô địch", 'utf8')
>>> s
u'thi\xean h\u1ea1 v\xf4 \u0111\u1ecbch'
>>> print type(s), len(s)
 16
2

Dữ liệu tiếng Việt lưu dưới dạng utf8 thường có BOM_UTF8 (= "\xef\xbb\xbf") ở đầu file. Bạn phải loại bỏ cái này trước khi có thể làm cái gì đó. Đoạn chương trình sau làm việc này:

>>> s = "sky down no enemy"
>>> print type(s), len(s)
 17
>>> s = "thiên hạ vô địch"
>>> s
'thi\xc3\xaan h\xe1\xba\xa1 v\xc3\xb4 \xc4\x91\xe1\xbb\x8bch'
>>> print type(s), len(s)
 23
>>> s = unicode("thiên hạ vô địch", 'utf8')
>>> s
u'thi\xean h\u1ea1 v\xf4 \u0111\u1ecbch'
>>> print type(s), len(s)
 16
3

Chú ý rằng đoạn chương trình trên không thích hợp cho việc xử lý các file lớn (hơn 200MB). Với các file lớn bạn cần chia nhỏ thành các file nhỏ hơn.

codecs là thư viện chứa rất nhiều BOM.

>>> s = "sky down no enemy"
>>> print type(s), len(s)
 17
>>> s = "thiên hạ vô địch"
>>> s
'thi\xc3\xaan h\xe1\xba\xa1 v\xc3\xb4 \xc4\x91\xe1\xbb\x8bch'
>>> print type(s), len(s)
 23
>>> s = unicode("thiên hạ vô địch", 'utf8')
>>> s
u'thi\xean h\u1ea1 v\xf4 \u0111\u1ecbch'
>>> print type(s), len(s)
 16
4

Dùng thư viện codecs bạn có thể nhanh chóng đọc nội dung file mà không mất nhiều công biến đổi encoding. Ví dụ:

>>> s = "sky down no enemy"
>>> print type(s), len(s)
 17
>>> s = "thiên hạ vô địch"
>>> s
'thi\xc3\xaan h\xe1\xba\xa1 v\xc3\xb4 \xc4\x91\xe1\xbb\x8bch'
>>> print type(s), len(s)
 23
>>> s = unicode("thiên hạ vô địch", 'utf8')
>>> s
u'thi\xean h\u1ea1 v\xf4 \u0111\u1ecbch'
>>> print type(s), len(s)
 16
5
>>> s = "sky down no enemy"
>>> print type(s), len(s)
 17
>>> s = "thiên hạ vô địch"
>>> s
'thi\xc3\xaan h\xe1\xba\xa1 v\xc3\xb4 \xc4\x91\xe1\xbb\x8bch'
>>> print type(s), len(s)
 23
>>> s = unicode("thiên hạ vô địch", 'utf8')
>>> s
u'thi\xean h\u1ea1 v\xf4 \u0111\u1ecbch'
>>> print type(s), len(s)
 16
6

Ghi dữ liệu tiếng Việt ra file:

>>> s = "sky down no enemy"
>>> print type(s), len(s)
 17
>>> s = "thiên hạ vô địch"
>>> s
'thi\xc3\xaan h\xe1\xba\xa1 v\xc3\xb4 \xc4\x91\xe1\xbb\x8bch'
>>> print type(s), len(s)
 23
>>> s = unicode("thiên hạ vô địch", 'utf8')
>>> s
u'thi\xean h\u1ea1 v\xf4 \u0111\u1ecbch'
>>> print type(s), len(s)
 16
7

Ở đây content là chuỗi utf8, nếu bạn đưa vào content là dạng unicode, nó sẽ được tự động chuyển về dạng utf8 trước khi được ghi ra file.