
这种情况你一定已经经历过了: 当你尝试打开一个文本文件的时候, 屏幕上会出现大量的乱码, 那些文字看起来就像完全读不懂的天书一样。这并不表示你的文件本身已经损坏或者报废了, 真正的原因在于编码方式没有对齐。本文将对编码这一概念进行一次透彻且详细的讲解, 帮助你在阅读和学习后彻底消除因为出现乱码而带来的焦虑情绪。一、那么会出现乱码这种状况, 其根本原因是什么? 我们要先搞清楚三个概念。第一个概念呢, 就是编码到底是什么东西。计算机这东西, 它只认识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\x876个字节# GBK编码中文Windows默认gbk_bytes text.encode(gbk)print(gbk_bytes) # b\xd6\xd0\xce\xc44个字节# 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 你好Pythonprint(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, errorsignore) # 忽略无法编码的字符text.encode(ascii, errorsreplace) # 用?代替text.encode(ascii, errorsbackslashreplace) # 用\uXXXX代替# UnicodeDecodeError解码时用了错误的编码data b\xe4\xb8\xad\xe6\x96\x87 # UTF-8编码的中文# data.decode(gbk) # UnicodeDecodeError: GBK解码UTF-8会报错# 解决方案data.decode(gbk, errorsreplace) # 用代替无法解码的字节data.decode(gbk, errorsignore) # 忽略无法解码的字节三、文件读写编码实战3.1 读取文件时指定编码# 正确做法始终指定 encodingwith open(data.txt, r, encodingutf-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, encodingutf-8) as f:f.write(你好世界\n)f.write(Hello, World!)# 写入GBK文件需要给中文Windows用户时with open(output_gbk.txt, w, encodinggbk) 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 chardetdef convert_file_encoding(filepath, target_encodingutf-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 Falseprint(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 encbreakexcept UnicodeDecodeError:continueelse:print(f 所有编码都失败跳过)return False# 4. 以目标编码重新写入with open(filepath, w, encodingtarget_encoding) as f:f.write(text)print(f 已转换为 {target_encoding})return True# 批量转换当前目录所有txt文件def batch_convert(directory, targetutf-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, encodingutf-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\xbfdef remove_bom(filepath):去掉UTF-8 BOM头with open(filepath, r, encodingutf-8-sig) as f:content f.read()with open(filepath, w, encodingutf-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-8print(response.text)6.2, 从网页编码到字符串。import requestsimport chardetresponse requests.get(https://www.baidu.com)# 查看响应头中的编码信息print(response.headers.get(Content-Type))# text/html; charsetutf-8# requests的apparent_encoding基于chardetprint(response.apparent_encoding) # 自动检测的编码# 如果自动检测不对手动设置response.encoding utf-8text response.text七、这里是对编码最佳实践的总结, 具体包括了七点黄金规则的相关情况。1️⃣ 永远在 open() 中显式指定 encoding不要依赖系统默认2️⃣ 团队协作时统一用 UTF-8不带BOM3️⃣ 写文件用 w, encodingutf-8读文件用 r, encodingutf-84️⃣ 不确定编码时用 chardet.detect() 检测5️⃣ 编码错误用 errorsreplace 垫底别让程序崩溃6️⃣ 网络请求统一用 requests它会帮你处理编码7️⃣ 记住encode字符串→字节decode字节→字符串7.2 快速参考: 常见编码适用范围7.3, 这是一个可以用一句话来进行总结的内容。# 世界上只有两种编码问题# 1. 不知道文件是什么编码 → 用 chardet 检测# 2. 知道编码但系统不匹配 → 统一用 UTF-8写在最后编码问题看似复杂, 但只要掌握一个原则就够: 统一用UTF-8。所有文件读写都显式写utf-8, 95%的问题都解决了。在今天里面所学习到的一些内容里, 其中核心有三句话是:在进行文件的读取和写入操作的时候, 请务必始终加上编码参数等于 utf8这个设置, 千万不要偷懒行事。一旦出现了乱码的情况, 就要去用相关的检测方法来确定原始文件的编码格式, 大家要牢牢记在这里面, 是从字符串转换成字节的转换方向, 以及是字节转换成字符串的转换方向的区别。请关注我, 在下面的文章中你会了解到如何正确挑选四个种类的数据结构, 这四个结构分别是列表、元组、集合还有字典, 并且可以知道在什么样的场景之下使用其中的某一种。