Lỗi Gặp Phải
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x?? in position ??: invalid start byte
Ví dụ đầy đủ khi đọc một file:
Traceback (most recent call last):
File "read.py", line 3, in <module>
content = f.read()
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb2 in position 14: invalid start byte
Nguyên Nhân
Python mặc định sử dụng UTF-8. Nếu file được lưu với encoding khác, bộ giải mã sẽ vấp phải byte đầu tiên nó không nhận ra — byte 0xb2 trong ví dụ trên hợp lệ với Latin-1 nhưng không hợp lệ trong UTF-8. Chín trên mười trường hợp, thủ phạm là một trong những nguyên nhân sau:
- File CSV/văn bản được lưu với encoding Windows-1252 (CP1252) hoặc Latin-1 (ISO-8859-1) — cực kỳ phổ biến khi xuất file từ Excel
- File tạo trên Windows mặc định dùng encoding ANSI
- File nhị phân vô tình được mở ở chế độ text
- HTTP response khi server gửi charset khác với những gì khai báo trong header
- Database dump từ MySQL với charset
latin1
Cách Sửa 1: Phát Hiện Encoding Thực Tế Trước
Đừng đoán mò — dùng chardet để phát hiện. Nó không phải lúc nào cũng chính xác 100%, nhưng đúng đủ để tiết kiệm nhiều công thử và sai:
pip install chardet
import chardet
with open('data.csv', 'rb') as f:
raw = f.read()
result = chardet.detect(raw)
print(result) # {'encoding': 'Windows-1252', 'confidence': 0.73, 'language': ''}
Sau đó truyền encoding đó trực tiếp vào open():
with open('data.csv', encoding='windows-1252') as f:
content = f.read()
Cách Sửa 2: Thử Thủ Công Các Encoding Phổ Biến
Không có chardet? Năm encoding sau đây bao phủ khoảng 95% file thực tế. Thử theo thứ tự:
encodings = ['utf-8', 'windows-1252', 'latin-1', 'utf-16', 'cp1250']
for enc in encodings:
try:
with open('data.txt', encoding=enc) as f:
content = f.read()
print(f'Works with: {enc}')
break
except UnicodeDecodeError:
continue
Cách Sửa 3: Dùng Tham Số errors Như Phương Án Dự Phòng
Đôi khi bạn không thể thay đổi encoding — file đến từ hệ thống cũ, nhà cung cấp, hoặc file upload mà bạn không kiểm soát được. Trong những trường hợp đó, hãy cho Python biết phải làm gì với các byte lỗi thay vì để chương trình crash:
# Tùy chọn A: Bỏ qua các byte lỗi hoàn toàn
with open('data.txt', encoding='utf-8', errors='ignore') as f:
content = f.read()
# Tùy chọn B: Thay thế byte lỗi bằng � (ký tự thay thế Unicode)
with open('data.txt', encoding='utf-8', errors='replace') as f:
content = f.read()
# Tùy chọn C: Dùng backslashreplace để debug — hiển thị chính xác byte nào bị lỗi
with open('data.txt', encoding='utf-8', errors='backslashreplace') as f:
content = f.read()
Cảnh báo: errors='ignore' âm thầm bỏ mất ký tự. Chỉ dùng khi mất dữ liệu là chấp nhận được — tên, địa chỉ, và các trường văn bản tự do hiếm khi còn nguyên vẹn với tùy chọn này.
Cách Sửa 4: Đọc Dạng Binary, Giải Mã Thủ Công
Dùng cách này khi chardet đoán sai, hoặc khi bạn cần kiểm tra các byte thô trước khi quyết định xử lý thế nào:
with open('data.txt', 'rb') as f:
raw_bytes = f.read()
# Giải mã với encoding đã phát hiện hoặc đã biết
content = raw_bytes.decode('windows-1252')
# Hoặc re-encode sang UTF-8 để xử lý tiếp
content_utf8 = raw_bytes.decode('windows-1252').encode('utf-8').decode('utf-8')
Cách Sửa 5: HTTP Response
API và trang web cào dữ liệu khá lén lút — header Content-Type có thể ghi UTF-8 trong khi body thực tế lại là Latin-1. Thư viện requests cung cấp vài cách thoát hiểm:
import requests
response = requests.get('https://example.com/data')
# Tùy chọn A: Để requests tự phát hiện encoding từ body (dùng chardet nội bộ)
response.encoding = response.apparent_encoding
text = response.text
# Tùy chọn B: Ép buộc encoding đã biết
response.encoding = 'windows-1252'
text = response.text
# Tùy chọn C: Làm việc với byte thô và tự giải mã
text = response.content.decode('latin-1')
Cách Sửa 6: pandas read_csv
Đây là nơi số 1 mọi người gặp lỗi này. File Excel xuất ra CSV trên Windows hầu như luôn dùng Windows-1252, không phải UTF-8 — dù file trông bình thường trong Excel:
import pandas as pd
# Chỉ định encoding trực tiếp
df = pd.read_csv('export.csv', encoding='windows-1252')
# Không biết encoding? Thay thế byte lỗi và kiểm tra sau
df = pd.read_csv('export.csv', encoding_errors='replace')
# latin-1 chấp nhận tất cả 256 giá trị byte — không bao giờ raise UnicodeDecodeError
df = pd.read_csv('export.csv', encoding='latin-1')
Cách Sửa 7: Migration từ Python 2 lên Python 3
Code Python 2 cũ coi string là byte thô. Chuyển sang Python 3, bạn phải khai báo rõ ràng. Dùng io.open() cho code cần chạy trên cả hai phiên bản:
import io
with io.open('legacy.txt', encoding='utf-8') as f:
content = f.read()
Kiểm Tra Sau Khi Sửa
Trước khi đưa lên production, hãy chạy kiểm tra nhanh. Output lộn xộn do sai encoding có thể rất tinh vi — không phải lúc nào cũng crash:
with open('data.txt', encoding='windows-1252') as f:
content = f.read()
# repr() hiển thị chuỗi \x?? nếu vẫn còn vấn đề
print(repr(content[:200]))
print(len(content)) # Phải khớp với kích thước file mong đợi
# Kiểm tra nội dung đã biết có mặt
assert 'expected_string' in content, 'Encoding mismatch — got garbage'
Phòng Ngừa
Xây dựng những thói quen này và bạn sẽ hiếm khi gặp lỗi này nữa:
- Luôn chỉ định encoding khi mở file. Đừng bao giờ dựa vào mặc định của hệ thống. Trên Windows là CP1252, trên macOS là UTF-8, trên một số server Linux là ASCII — code của bạn sẽ hỏng ngay khi chuyển môi trường.
Sai
with open('file.txt') as f: ...
Đúng
with open('file.txt', encoding='utf-8') as f: ...
- **Lưu file dưới dạng UTF-8 ngay từ đầu.** Trong VS Code, encoding hiển thị ở thanh trạng thái dưới cùng bên phải. Click vào để thay đổi. Một phút bây giờ tiết kiệm được một giờ debug sau này.
- **Kiểm tra tại ranh giới hệ thống.** File đến qua upload, SFTP, hoặc API bên thứ ba nên được kiểm tra encoding ngay khi nhận — không phải giữa chừng một batch job lúc 2 giờ sáng.
- **Đặt `PYTHONIOENCODING` cho output qua pipe.** Script ghi ra stdout vẫn có thể gặp vấn đề encoding khi pipe sang process khác:
```
PYTHONIOENCODING=utf-8 python myscript.py
Bảng Tóm Tắt Encoding Nhanh
utf-8— chuẩn phổ quát; dùng cái này ở bất cứ đâu bạn kiểm soát được dữ liệuwindows-1252/cp1252— Windows Tây Âu; mặc định cho file CSV xuất từ Excellatin-1/iso-8859-1— ánh xạ tất cả 256 giá trị byte; không bao giờ raiseUnicodeDecodeError, hữu ích như phương án cuối cùngutf-16— định dạng lưu "Unicode" của Windows Notepad; có BOM header ở đầu fileshift_jis/cp932— văn bản tiếng Nhật từ hệ thống Windows
Khi terminal của bạn hiển thị output sai do encoding, kiểm tra byte thô trong trình duyệt có thể giúp ích. Công cụ Base64 Encoder/Decoder tại toolcraft.app cho phép bạn dán byte thô vào và xem chính xác nội dung bên trong — rất tiện cho những trường hợp mà repr() đơn thuần không đủ.

