目录
  • 前言
  • 先聊聊字符编码
  • 分析中文乱码、编码问题的原因
  • 常见案例
    • 案例一
    • 案例二
    • 案例三
  • 总结

    前言

    最近偶尔会帮忙写一些爬虫代码,有一些需要使用 Python 编写,因此又拾起了很久没有写的 Python,让我无语的是总是遇到各种中文乱码的问题,所以趁着周末,总结一下遇到的中文乱码问题和对应的解决方案,以及为什么会出现中文乱码的问题。

    你可能遇到下列的各种问题:

    • 1、SyntaxError: Non-ASCII character '\xe4' in file XXX
    • 2、UnicodeDecodeError: 'ascii' codec can't decode byte 0xe8 in position 0: ordinal not in range(128)
    • 3、......

    先聊聊字符编码

    • 当我们处理文本时,字符编码是一个关键的概念。字符编码是一种将字符映射到数字表示的方式。在计算机中,文本通常以数字的形式存储和处理,而字符编码就是定义这种映射关系的规则。
    • 在选择字符编码时,需要考虑存储空间、兼容性和处理效率等因素。UTF-8 目前是使用最广泛的字符编码,因为它在兼容性和节省空间方面都有良好的表现。下面对常用的编码做一个简单的介绍:

    ASCII(American Standard Code for Information Interchange)

    ASCII 是一种最早的字符编码,它使用 7 位二进制数字(0 到 127)表示常用的字符,包括字母、数字、标点符号和一些控制字符。由于只使用 7 位,ASCII 编码总共可以表示 128 个字符。

    Unicode

    为了解决 ASCII 编码的局限性,Unicode 应运而生。Unicode 是一个更为庞大的字符集,它包括世界上几乎所有的字符,符号和标点。每个字符都被分配一个唯一的数字,这个数字可能是 16 位(UCS-2)或者 32 位(UCS-4)。然而,Unicode 的缺点在于它需要更多的存储空间。

    UTF-8(Unicode Transformation Format - 8-bit)

    为了解决 Unicode 存储空间的问题,出现了 UTF-8 编码。UTF-8 使用不定长度的编码方案,能够根据字符的不同使用 1 到 4 个字节表示一个字符。它保留了与 ASCII 兼容的部分,因此可以在现有的 ASCII 系统中无缝使用,并且在表示非常用字符时能够更加节省空间。

    UTF-16

    UTF-16 是 Unicode 的一种实现方式,使用 16 位编码方案,每个字符使用 2 个字节表示。UTF-16 的一个特点是在表示一些非常用字符时可能会使用额外的一个或两个字节。

    UTF-32

    UTF-32 是 Unicode 的一种实现方式,使用 32 位编码方案,每个字符使用 4 个字节表示。UTF-32 的特点是每个字符都使用相同的固定长度,方便在字符串中进行随机访问。

    分析中文乱码、编码问题的原因

    从上面我们其实已经大概可以知道我们中文乱码、编码问题的罪魁祸首:编码方式不匹配。下面我将对一些常见的案例进行分析以及提供一些常见的解决方案。

    常见案例

    Python 版本 2.7

    案例一

    看一下下面两个例子:.py 文件的编码格式为UTF-8,Python2默认使用ASCII解码:

    # 例一
    s = "hello"
    print s
    
    // 正常执行
    hello
    
    # 例二
    s = "你好"
    print s
    
    // 执行失败
    SyntaxError: Non-ASCII character '\xe4' in file XXX
    

    • 有些同学可能会比较疑惑,同样是编码格式不匹配,为什么例一可以正常执行,而例二执行失败。
    • 这是因为:UTF-8 是一种向后兼容的编码方式。如果文件中只包含 ASCII 字符,那么该文件符合 UTF-8 编码规范。这种特性使得使用 UTF-8 编码时,与使用 ASCII 编码的现有系统可以很好地协同工作。
    • 例一由于文件中只包含 ASCII 字符,即使使用 UTF-8 编码,编码结果和 ASCII 编码一致,当然也可以使用 ASCII 正常解码。

    解决方案

    指定文件的编码格式,让执行器使用指定的编码方式解析文件。

    # coding=utf-8
    
    s = "你好"
    print s
    
    # 正常执行
    #你好
    

    参考

    PEP 263 – Defining Python Source Code Encodings

    案例二

    我们看一下下面的代码:

    # coding=utf-8
    import xlwt
    
    comment_list = [["标题", "序号"], [1, 2]]
    
    code = "1234"
    
    # Create a new workbook and add a sheet
    workbook = xlwt.Workbook()
    sheet = workbook.add_sheet('Sheet1')
    
    for row_index, row_data in enumerate(comment_list):
        for col_index, cell_data in enumerate(row_data):
            sheet.write(row_index, col_index, cell_data)
    
    workbook.save('./%s.xls' % code)
    
    #程序执行报错
    UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128)
    'ascii' 无法解码字节 0xe6 (UTF-8中的中文字符)
    #如果写入的数据中没有中文数据是可以正常执行的

    原因

    #我们看下这个对象的构造器:workbook = xlwt.Workbook() 可以看到默认使用的是 ascii 编码,而我们传入的数据是 UTF-8 (包含中文字符),因此无法解析
    
        #################################################################
        ## Constructor
        #################################################################
        def __init__(self, encoding='ascii', style_compression=0):
            self.encoding = encoding
            self.__owner = 'None'
            self.__country_code = None # 0x07 is Russia :-)
            self.__wnd_protect = 0
            self.__obj_protect = 0
    

    解决方案

    从上面我们已经知道是编码不一致的原因,因此我们只需要将Workbook编码调整为相同即可,比如调整为utf-8:

    # coding=utf-8
    import xlwt
    
    comment_list = [["标题", "序号"], [1, 2]]
    
    code = "1234"
    
    # Create a new workbook and add a sheet
    workbook = xlwt.Workbook(encoding="utf-8")
    sheet = workbook.add_sheet('Sheet1')
    
    for row_index, row_data in enumerate(comment_list):
        for col_index, cell_data in enumerate(row_data):
            sheet.write(row_index, col_index, cell_data)
    
    workbook.save('./%s.xls' % code)
    

    这里如果你不想用 utf-8 编码,你想用 gbk 编码怎么实现呢?其实中心思想是相同的,只要保证编码格式一致即可:

    # coding=utf-8
    import sys
    import xlwt
    
    comment_list = [["标题", "续保"], [1, 2]]
    
    print "default encoding:", sys.getdefaultencoding()
    
    # Create a new workbook and add a sheet
    workbook = xlwt.Workbook(encoding="gbk")
    sheet = workbook.add_sheet('Sheet1')
    
    for row_index, row_data in enumerate(comment_list):
        for col_index, cell_data in enumerate(row_data):
            sheet.write(row_index, col_index, str(cell_data).decode("utf-8"))
    
    code = "1234"
    workbook.save('./%s.xls' % code)
    
    #程序执行正常
    default encoding: ascii
    

    Python2 字符串的两种表现形式

    可能有朋友对上述案例中使用 gbk 方式中这段代码 str(cell_data).decode("utf-8") 有点疑惑,这其实涉及到 Python2 字符串的两种表现形式。

    字节序列 和 Unicode 对象

    在 Python 2 中,字符串类型被设计为字节序列而不是 Unicode 对象。这是 Python 2 与 Python 3 之间最重要的差异之一。在 Python 2 中,有两种主要的字符串类型:

    #1、str: 表示字节序列,是原始的字节串,而不涉及字符编码。
    #2、unicode: 表示 Unicode 字符串,用于处理字符编码和文本
    
    #示例:
    # 在 Python 2 中,默认创建的是字节串而不是 Unicode 字符串
    byte_str = "Hello, World!"
    print type(byte_str)  # 输出 <type 'str'>
    
    # 创建 Unicode 字符串
    unicode_str = u"你好"
    print type(unicode_str)  # 输出 <type 'unicode'>
    

    字节序列 和 Unicode 对象相互转换

    # 从 str 转换成 unicode
    print byte_str.decode('utf-8')
    
    # 从 unicode 转换成 str
    print unicode_str.encode('utf-8')
    

    有朋友也许很奇怪,为什么从 strunicode 使用 decode,而 unicode 转换成 str 使用 encode,其实这是因为 Python 认为 16 位的 unicode 才是字符的唯一内码,而大家常用的字符集如 gb2312gb18030/gbkutf-8,以及 ascii 都是字符的二进制(字节)编码形式。因此从 unicode 到其它二进制编码格式都使用 encode

    字符串运算

    在进行同时包含 str 与 unicode 的运算时,Python 一律都把 str 转换成 unicode 再运算,当然运算结果也是 unicode。

    str(cell_data).decode("utf-8") 写法的原因

    # 转换为 str 类型
    str(cell_data)
    
    # 这里为什么么需要先 decode("utf-8") 转为 unicode
    # 实际上 Python 运行时并不知道 str 的编码,因此需要开发者指定正确的编码方式进行解码
    # 如果开发者不指定编码方式进行手动解码则会使用 sys.getdefaultencoding() 配置的值 ascii 进行解码
    str(cell_data).decode("utf-8")
    
    # 由于我们在程序开头指定了编码方式为 utf-8 即 str 的编码格式,如果这样写:
    sheet.write(row_index, col_index, cell_data)
    #程序执行异常:
    #UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128)
    
    #因为 ascii 编码无法解析带有中文的 utf-8 编码
    

    案例三

    案例三我们来聊聊,有关控制台打印乱码的问题。

    上面的示例中,控制台的编码方式为 gbk,当按照 utf-8 字符集打印时,控制台无法解析,出现乱码。

    实际上我们打印控制台时,实际上打印的是字符串的字符集编码,但一般情况会根据系统的字符集设置来将字符编码输出到控制台。如果出现打印乱码,说明字符集匹配失败或控制台不支持该编码格式,我们可以根据实际情况进行确认。

    总结

    本文从 Python2.7 中出现的一些乱码案例出发,从字符编码、Python2.7字符原理分析了中文乱码、编码问题导致的原因并给出相应的解决方案,实际上在编码乱码分析这一块在其它语言上也是共通的,可以作为一定的参考依据。

    在 Python 3 中,字符串默认是 Unicode 类型,这样可以更容易地处理文本数据。由于 Python 2 已于 2020 年停止维护,建议在可能的情况下迁移到 Python 3,以便利用更现代和更直观的字符串处理方式。

    一些建议:

    1、Python3 以前使用字符串,建议都带上前缀u,在 Python 3 中,字符串默认是 Unicode。Unicode 支持编码自动转换。

    比如同样是上文的案例,你不再需要解码

    # coding=utf-8
    import sys
    import xlwt
    
    comment_list = [[u"标题", u"续保"], [1, 2]]
    
    print "default encoding:", sys.getdefaultencoding()
    
    # Create a new workbook and add a sheet
    workbook = xlwt.Workbook(encoding="gbk")
    sheet = workbook.add_sheet('Sheet1')
    
    for row_index, row_data in enumerate(comment_list):
        for col_index, cell_data in enumerate(row_data):
            sheet.write(row_index, col_index, cell_data)
    
    code = "1234"
    workbook.save('./%s.xls' % code)

    2、不要用str()函数,使用unicode()代替。

    3、不要用过时的 string 模块(string 模块已经停止了更新,只保留了 ASCII 码的支持,不再推荐使用,Python 保留该模块仅仅是为了向后兼容)

    4、非必要时,不需要对 unicode 字符进行编码,因为大多数情况会自动编码解码。

    到此这篇关于一文教你解决所有Python中文乱码问题的文章就介绍到这了,更多相关Python中文乱码解决内容请搜索本网站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本网站!

    您可能感兴趣的文章:

    • Python使用matplotlib时显示中文乱码解决方法(或更改字体)
    • Python批量解压文件中出现中文乱码的原因及解决方法
    • Python OS系统解决路径中空格原因导致文件打不开的问题
    • 在python中路径含有空格的问题及解决
    • python 解决Windows平台上路径有空格的问题
    • python调用cmd命令时遇到的路径空格问题和中文乱码的解决