这种情况你一定已经经历过了: 当你尝试打开一个文本文件的时候, 屏幕上会出现大量的乱码, 那些文字看起来就像完全读不懂的天书一样。
这并不表示你的文件本身已经损坏或者报废了, 真正的原因在于编码方式没有对齐。本文将对编码这一概念进行一次透彻且详细的讲解, 帮助你在阅读和学习后彻底消除因为出现乱码而带来的焦虑情绪。
一、那么会出现乱码这种状况, 其根本原因是什么? 我们要先搞清楚三个概念。第一个概念呢, 就是编码到底是什么东西。
计算机这东西, 它只认识0和1这两种数字, 它是根本不认识那些汉字的。编码这个东西, 其实就是文字和二进制数据之间翻译的一种规则。
"你" → 编码规则 → 0100111001010001 → 解码规则 → "你"↑
用UTF-8编码
↓
用GBK解码
→ 乱码!
造成乱码的根本原因, 是写入文件的时候采用了一种编码类型。而读取文件的时候, 却又使用了另一种不同的编码类型。
1.关于第二到第三部分的核心概念, 以及第三个小节所讲述的常见的编码方式的相关情况。
# 用Python看看不同编码的效果text = "中文"
# UTF-8编码(最常用,通用性最强)
utf8_bytes = text.encode('utf-8')
print(utf8_bytes) # b'\xe4\xb8\xad\xe6\x96\x87'(6个字节)
# GBK编码(中文Windows默认)
gbk_bytes = text.encode('gbk')
print(gbk_bytes) # b'\xd6\xd0\xce\xc4'(4个字节)
# UTF-16编码(Windows内部用)
utf16_bytes = text.encode('utf-16')
print(utf16_bytes) # b'\xff\xfe-N\x87e'(加了BOM头)
二、中的字符串与字节串2.1 str vs bytes
这个点就是编码里面最重要的那个意思, 也是大家必须弄明白的东西。
# str(字符串)—— 人看的s = "你好,Python"
print(type(s)) #
# bytes(字节串)—— 电脑看的
b = s.encode('utf-8')
print(type(b)) #
print(b) # b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8cPython'
# str ↔ bytes 转换
text = b.decode('utf-8')
print(text) # 你好,Python
记忆口诀是这么个意思, 所谓的编码呢, 就是把字符串转换成字节码这个过程, 转换之后拿去存到文件里面或者是进行网络传输操作而解码就正好反过来, 把那些字节码重新变回字符串, 这样做主要是为了让人能够直接看懂内容。
2.2 常见错误及解决
# UnicodeEncodeError:把字符串编码时,编码方式不支持某些字符text = "中文"
# text.encode('ascii') # UnicodeEncodeError: ascii不支持中文
# 解决方案:指定错误处理方式
text.encode('ascii', errors='ignore') # 忽略无法编码的字符
text.encode('ascii', errors='replace') # 用?
代替
text.encode('ascii', errors='backslashreplace') # 用\uXXXX代替
# UnicodeDecodeError:解码时用了错误的编码
data = b'\xe4\xb8\xad\xe6\x96\x87' # UTF-8编码的中文
# data.decode('gbk') # UnicodeDecodeError: GBK解码UTF-8会报错
# 解决方案
data.decode('gbk', errors='replace') # 用�代替无法解码的字节
data.decode('gbk', errors='ignore') # 忽略无法解码的字节
三、文件读写编码实战3.1 读取文件时指定编码
# 正确做法:始终指定 encodingwith open('data.txt', 'r', encoding='utf-8') as f:
content = f.read()
# 错误做法:不指定编码
# with open('data.txt', 'r') as f: # 默认用系统编码(中文Windows是gbk)
# content = f.read()
3.当在进行文件写入操作的时候, 需要明确指定采用的编码格式。
# 写入UTF-8文件with open('output.txt', 'w', encoding='utf-8') as f:
f.write("你好,世界!\n")
f.write("Hello, World!
")
# 写入GBK文件(需要给中文Windows用户时)
with open('output_gbk.txt', 'w', encoding='gbk') as f:
f.write("你好,世界!")
3.针对文件编码的问题, 系统进行一个自动的查验工作。
不知道文件是什么编码怎么办?用 库检测:
# 先安装:pip install chardetimport chardet
# 读取文件的原始字节
with open('unknown.txt', 'rb') as f: # rb = 二进制模式读取
raw_data = f.read()
# 检测编码
result = chardet.detect(raw_data)
print(result) # {'encoding': 'utf-8', 'confidence': 0.99}
# 根据检测结果解码
text = raw_data.decode(result['encoding'])
print(text)
# 封装成通用函数
def read_file_safe(path):
"""自动检测编码并读取文件"""
with open(path, 'rb') as f:
raw = f.read()
result = chardet.detect(raw)
return raw.decode(result['encoding'])
四、进行实战操作: 批量转换文件编码。
我们假如一下, 现在有100个文本文件,这些文件的编码格式是GBK, 如果我们需要把这些文件批量地转换成UTF-8编码的话。
import osimport chardet
def convert_file_encoding(filepath, target_encoding='utf-8'):
"""
将文件从任意编码转换为目标编码
"""
# 1. 以二进制模式读取原始数据
with open(filepath, 'rb') as f:
raw_data = f.read()
# 2. 检测原始编码
detected = chardet.detect(raw_data)
source_encoding = detected['encoding']
confidence = detected['confidence']
# 如果检测置信度太低,可能不准
if confidence < 0.7:
print(f"️ {filepath} 编码检测置信度过低 ({confidence}),跳过")
return False
print(f" 检测到编码:{source_encoding} (置信度: {confidence:.0%})")
# 3. 解码为字符串
try:
text = raw_data.decode(source_encoding)
except UnicodeDecodeError:
print(f" 无法用{source_encoding}解码,尝试...")
# 备用方案
for enc in ['gbk', 'utf-8', 'gb2312', 'big5']:
try:
text = raw_data.decode(enc)
print(f" 用{enc}解码成功")
source_encoding = enc
break
except UnicodeDecodeError:
continue
else:
print(f" 所有编码都失败,跳过")
return False
# 4. 以目标编码重新写入
with open(filepath, 'w', encoding=target_encoding) as f:
f.write(text)
print(f" 已转换为 {target_encoding}")
return True
# 批量转换当前目录所有txt文件
def batch_convert(directory, target='utf-8'):
for filename in os.listdir(directory):
if filename.endswith('.txt'):
filepath = os.path.join(directory, filename)
print(f"处理:{filename}")
convert_file_encoding(filepath, target)
batch_convert('.')
五、BOM究竟是指的什么东西, 为何在那些文档的最开头竟然会出现一些令人感觉非常古怪的字符呢? 关于这5.1部分中所探讨的具体的内容, 实际上问的就是什么是BOM。
所谓BOM这个东西, 其实就是那个Byte Order Mark, 它, 是出现在UTF-16或者是UTF-32这种文件格式的最开头的地方的, 它占据的空间大概也就是2到4个字节的样子, 这个标识的作用是什么? 就是要告诉那个正在处理这个文件的程序, 这文件里面的数据是大端序的, 还是小端序的。
不过, 请注意一点, 那就是那种采用UTF-8编码的文件里, 虽然完全没必要存在BOM这个部分, 但是有可能还是会带有它。
# 有BOM和没有BOM的区别# UTF-8 without BOM: 你好 → b'\xe4\xbd\xa0\xe5\xa5\xbd'
# UTF-8 with BOM: 你好 → b'\xef\xbb\xbf\xe4\xbd\xa0\xe5\xa5\xbd'
# ↑
# 有3个多余的字节
# Python读取BOM文件
with open('bom_file.txt', 'r', encoding='utf-8-sig') as f:
# utf-8-sig 会自动跳过BOM头
content = f.read()
# 去掉BOM后保存
if content.startswith('\ufeff'): # BOM对应的Unicode字符
content = content[1:]
5.在实战阶段, 需要执行操作来检测并且进行清理工作。
def has_bom(filepath):"""检测文件是否有BOM头"""
with open(filepath, 'rb') as f:
head = f.read(3)
return head == b'\xef\xbb\xbf'
def remove_bom(filepath):
"""去掉UTF-8 BOM头"""
with open(filepath, 'r', encoding='utf-8-sig') as f:
content = f.read()
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
# 使用
if has_bom('readme.txt'):
remove_bom('readme.txt')
print("已去除BOM头")
六、在网络请求的整个过程里面, 有一个非常关键的环节叫做编码处理, 而6.1这部分内容主要说的是利用库来自动完成这些工作。
import requests# requests会自动检测并解码
response = requests.get('https://www.example.com')
# response.text 已经自动解码好了
# 如果自动检测不对,可以手动指定
response.encoding = 'utf-8'
print(response.text)
6.2, 从网页编码到字符串。
import requestsimport chardet
response = requests.get('https://www.baidu.com')
# 查看响应头中的编码信息
print(response.headers.get('Content-Type'))
# text/html; charset=utf-8
# requests的apparent_encoding基于chardet
print(response.apparent_encoding) # 自动检测的编码
# 如果自动检测不对,手动设置
response.encoding = 'utf-8'
text = response.text
七、这里是对编码最佳实践的总结, 具体包括了七点黄金规则的相关情况。
1️⃣ 永远在 open() 中显式指定 encoding,不要依赖系统默认2️⃣ 团队协作时统一用 UTF-8(不带BOM)
3️⃣ 写文件用 'w', encoding='utf-8'
读文件用 'r', encoding='utf-8'
4️⃣ 不确定编码时用 chardet.detect() 检测
5️⃣ 编码错误用 errors='replace' 垫底,别让程序崩溃
6️⃣ 网络请求统一用 requests,它会帮你处理编码
7️⃣ 记住:encode(字符串→字节)decode(字节→字符串)
7.2 快速参考: 常见编码适用范围7.3, 这是一个可以用一句话来进行总结的内容。
# 世界上只有两种编码问题:# 1. 不知道文件是什么编码 → 用 chardet 检测
# 2. 知道编码但系统不匹配 → 统一用 UTF-8
写在最后
编码问题看似复杂, 但只要掌握一个原则就够: 统一用UTF-8。所有文件读写都显式写='utf-8', 95%的问题都解决了。
在今天里面所学习到的一些内容里, 其中核心有三句话是:
在进行文件的读取和写入操作的时候, 请务必始终加上编码参数等于 utf8这个设置, 千万不要偷懒行事。一旦出现了乱码的情况, 就要去用相关的检测方法来确定原始文件的编码格式, 大家要牢牢记在这里面, 是从字符串转换成字节的转换方向, 以及是字节转换成字符串的转换方向的区别。
请关注我, 在下面的文章中你会了解到如何正确挑选四个种类的数据结构, 这四个结构分别是列表、元组、集合还有字典, 并且可以知道在什么样的场景之下使用其中的某一种。