如何确定文本的编码方式

312
16个回答

280

编辑:chardet 似乎已经不再维护,但大多数答案仍然适用。可以查看https://pypi.org/project/charset-normalizer/ 获取替代方案。

始终正确地检测编码是不可能的

(来自 chardet 的常见问题解答)

然而,有些编码针对特定语言进行了优化,而语言并非随机的。一些字符序列总是出现, 而其他序列则毫无意义。精通英语的人打开报纸看到“txzqJv 2!dasd0a QqdKjvz”就会立即认为它不是英语(尽管它完全由英文字母组成)。通过研究大量的“典型”文本,计算机算法可以模拟这种流畅性,并对文本的语言进行教育猜测。

有一个名为chardet的库使用该方法来尝试检测编码。chardet 是 Mozilla 自动检测代码的移植版。

你也可以使用UnicodeDammit。它将尝试以下方法:

  • 在文档本身中发现的编码:例如,在 XML 声明或(对于 HTML 文档)http-equiv META 标签中。如果 BeautifulSoup 在文档中找到这种类型的编码,则会重新从头解析文档并尝试新编码。唯一的例外是,如果您明确指定了编码,并且该编码实际上有效,则它将忽略在文档中找到的任何编码。
  • 通过查看文件的前几个字节来探测编码。如果在此阶段检测到编码,则它将是 UTF-* 编码、EBCDIC 或 ASCII 中的一种。
  • 如果您已经安装了 chardet 库,则它将探测出一种编码方式。
  • UTF-8 编码。
  • Windows-1252 编码。

  • 26
    “编码标准”并不存在。文本编码是计算机发展过程中就存在的,随着时间和需求的增长而不断演化,没有经过规划。Unicode 是试图解决这个问题的一种方案。 - nosklo
    1
    总的来说,这不是一个坏问题。我想知道的是,如何找出打开的文本文件使用的编码方式? - holdenweb
    2
    @dumbledad,我的意思是说,在所有时间正确检测它是不可能的。你所能做的只是猜测,但有时会失败,由于编码并非真正可检测。为了进行猜测,您可以使用我在答案中提到的工具之一。 - nosklo
    1
    chardet有一些非常好的命令行界面,我不确定你的用例是什么,对我来说,我只是想在运行时猜测文件字符集,而不是在脚本或其他东西中使用它。要使用CLI选项(在pip install chardet之后),请键入以下命令:$ chardet filename,然后您可以使用猜测的编码将文本文件编码为其他选项,使用像iconv这样的工具。 - adonese
    2
    @LasseKärkkäinen 这个答案的重点是展示正确检测编码是不可能的;你提供的函数可以猜对你的情况,但对许多情况来说是错误的。 - nosklo
    显示剩余10条评论

    97

    另一种查找编码的选项是使用libmagic(这是file命令背后的代码)。有大量可用的Python绑定。

    生存在文件源代码树中的Python绑定可通过python-magic(或python3-magic)debian软件包获得。它可以通过执行以下操作来确定文件的编码:

    import magic
    
    blob = open('unknown-file', 'rb').read()
    m = magic.open(magic.MAGIC_MIME_ENCODING)
    m.load()
    encoding = m.buffer(blob)  # "utf-8" "us-ascii" etc
    

    在pypi上也有一个名字相同但不兼容的python-magic pip软件包,它也使用libmagic。它可以通过以下方式获取编码:

    import magic
    
    blob = open('unknown-file', 'rb').read()
    m = magic.Magic(mime_encoding=True)
    encoding = m.from_buffer(blob)
    

    7
    libmagic确实是一个可替代chardet的选择。关于名为python-magic的不同包的信息很有用!我相信这种歧义会困扰很多人。 - MestreLion
    2
    file 并不是特别擅长识别文本文件中的人类语言。但它非常擅长识别各种容器格式,尽管有时您需要知道它的含义(例如,“Microsoft Office 文档”可能指 Outlook 消息等)。 - tripleee
    寻找管理文件编码问题的方法时,我发现了这篇文章。不幸的是,使用示例代码时,我无法通过open()函数:UnicodeDecodeError: 'utf-8' codec can't decode byte 0xfc in position 169799: invalid start byte。根据vim的:set fileencoding命令,该文件的编码为latin1 - xtian
    1
    如果我使用可选参数errors='ignore',那么示例代码的输出将是不太有用的binary - xtian
    2
    @xtian 你需要以二进制模式打开,即 open("filename.txt", "rb")。 - Tronic
    显示剩余4条评论

    45

    一些编码策略,请按照自己的口味取消注释:

    #!/bin/bash
    #
    tmpfile=$1
    echo '-- info about file file ........'
    file -i $tmpfile
    enca -g $tmpfile
    echo 'recoding ........'
    #iconv -f iso-8859-2 -t utf-8 back_test.xml > $tmpfile
    #enca -x utf-8 $tmpfile
    #enca -g $tmpfile
    recode CP1250..UTF-8 $tmpfile
    

    您可能希望通过循环打开和读取文件来检查编码...但您可能需要先检查文件大小:

    # PYTHON
    encodings = ['utf-8', 'windows-1250', 'windows-1252'] # add more
    for e in encodings:
        try:
            fh = codecs.open('file.txt', 'r', encoding=e)
            fh.readlines()
            fh.seek(0)
        except UnicodeDecodeError:
            print('got unicode error with %s , trying different encoding' % e)
        else:
            print('opening the file with encoding:  %s ' % e)
            break
    

    1
    你也可以使用 io,例如 io.open(filepath, 'r', encoding='utf-8'),这更加方便,因为 codecs 在读写时不会自动转换 \n。更多信息请参见此处 - Searene

    40

    下面是一个读取文件并直接采用 chardet 编码预测结果的例子,当文件比较大时,只读取 n_lines 行。

    chardet 还会给出它的编码预测的概率(即confidence),以及从 chardet.predict() 返回的预测结果,如果您喜欢,可以将其与预测结果一起使用。

    import chardet
    from pathlib import Path
    
    def predict_encoding(file_path: Path, n_lines: int=20) -> str:
        '''Predict a file's encoding using chardet'''
    
        # Open the file as binary data
        with Path(file_path).open('rb') as f:
            # Join binary lines for specified number of lines
            rawdata = b''.join([f.readline() for _ in range(n_lines)])
    
        return chardet.detect(rawdata)['encoding']
    

    在得到一次点赞后再看这个问题,现在发现如果第一行有大量数据,这种解决方案可能会变慢。在某些情况下,最好以不同的方式读取数据。 - ryanjdillon
    5
    我已经这样修改了这个函数:def predict_encoding(file_path, n=20): ... 省略 ... 然后 rawdata = b''.join([f.read() for _ in range(n)])在 Python 3.6 中尝试了这个函数,对于 "ascii"、"cp1252"、"utf-8" 和 "unicode" 编码都能完美运行。所以这绝对值得点赞。 - n158
    2
    这非常适合处理各种格式的小型数据集。我在我的根目录上递归测试了它,效果非常好。谢谢伙计。 - Umar.H
    我对以字节级别阅读数据并不十分熟悉。@n158,如果在字符中间停止读取字节并混淆“chardet”的话,有可能吗? - kuzzooroo
    在所有的好建议中,我发现这个最有用。我还包括@n158的建议,只读取20个字节而不是20行。 - panofish

    19

    这可能会有帮助

    from bs4 import UnicodeDammit
    with open('automate_data/billboard.csv', 'rb') as file:
       content = file.read()
    
    suggestion = UnicodeDammit(content)
    suggestion.original_encoding
    #'iso-8859-1'
    

    2
    谢谢,我现在可以确定正确的编码方式了! - sakell
    1
    UnicodeDammit 应该被加入标准库,我个人认为。 - skinnedpanda

    9
    如果您对自动工具不满意,可以尝试使用所有编解码器并手动查看哪个编解码器是正确的。
    all_codecs = ['ascii', 'big5', 'big5hkscs', 'cp037', 'cp273', 'cp424', 'cp437', 
    'cp500', 'cp720', 'cp737', 'cp775', 'cp850', 'cp852', 'cp855', 'cp856', 'cp857', 
    'cp858', 'cp860', 'cp861', 'cp862', 'cp863', 'cp864', 'cp865', 'cp866', 'cp869', 
    'cp874', 'cp875', 'cp932', 'cp949', 'cp950', 'cp1006', 'cp1026', 'cp1125', 
    'cp1140', 'cp1250', 'cp1251', 'cp1252', 'cp1253', 'cp1254', 'cp1255', 'cp1256', 
    'cp1257', 'cp1258', 'euc_jp', 'euc_jis_2004', 'euc_jisx0213', 'euc_kr', 
    'gb2312', 'gbk', 'gb18030', 'hz', 'iso2022_jp', 'iso2022_jp_1', 'iso2022_jp_2', 
    'iso2022_jp_2004', 'iso2022_jp_3', 'iso2022_jp_ext', 'iso2022_kr', 'latin_1', 
    'iso8859_2', 'iso8859_3', 'iso8859_4', 'iso8859_5', 'iso8859_6', 'iso8859_7', 
    'iso8859_8', 'iso8859_9', 'iso8859_10', 'iso8859_11', 'iso8859_13', 
    'iso8859_14', 'iso8859_15', 'iso8859_16', 'johab', 'koi8_r', 'koi8_t', 'koi8_u', 
    'kz1048', 'mac_cyrillic', 'mac_greek', 'mac_iceland', 'mac_latin2', 'mac_roman', 
    'mac_turkish', 'ptcp154', 'shift_jis', 'shift_jis_2004', 'shift_jisx0213', 
    'utf_32', 'utf_32_be', 'utf_32_le', 'utf_16', 'utf_16_be', 'utf_16_le', 'utf_7', 
    'utf_8', 'utf_8_sig']
    
    def find_codec(text):
        for i in all_codecs:
            for j in all_codecs:
                try:
                    print(i, "to", j, text.encode(i).decode(j))
                except:
                    pass
    
    find_codec("The example string which includes ö, ü, or ÄŸ, ö")
    

    这个脚本至少会生成9409行输出。如果输出无法适应终端屏幕,请尝试将输出写入文本文件中。


    3
    作为对这个好答案的补充,我编写了一个Python脚本,它可以在用户猜测到原始正确字符串的小样本的情况下打印出可能的编码不匹配的漂亮表格。然后,它建议哪种编码/解码对可以消除不匹配。 - dominecf
    感谢 @dominecf 的贡献。 - rebahozkoc
    这太棒了!对我非常有帮助。非常感谢你! - Sergio Belevskij
    谢谢。通过不断的尝试和错误,我最终成功地将一个具有排序规则SQL_Latin1_General_CP1_CI_AS的SQL Server表格使用utf_8_sig导出为CSV数据。这是一个有用的编码列表(通过Python/SQLAlchemy/PYODBC)...现在可以正确显示Noël等字符了。 - JGFMK

    6
    # Function: OpenRead(file)
    
    # A text file can be encoded using:
    #   (1) The default operating system code page, Or
    #   (2) utf8 with a BOM header
    #
    #  If a text file is encoded with utf8, and does not have a BOM header,
    #  the user can manually add a BOM header to the text file
    #  using a text editor such as notepad++, and rerun the python script,
    #  otherwise the file is read as a codepage file with the 
    #  invalid codepage characters removed
    
    import sys
    if int(sys.version[0]) != 3:
        print('Aborted: Python 3.x required')
        sys.exit(1)
    
    def bomType(file):
        """
        returns file encoding string for open() function
    
        EXAMPLE:
            bom = bomtype(file)
            open(file, encoding=bom, errors='ignore')
        """
    
        f = open(file, 'rb')
        b = f.read(4)
        f.close()
    
        if (b[0:3] == b'\xef\xbb\xbf'):
            return "utf8"
    
        # Python automatically detects endianess if utf-16 bom is present
        # write endianess generally determined by endianess of CPU
        if ((b[0:2] == b'\xfe\xff') or (b[0:2] == b'\xff\xfe')):
            return "utf16"
    
        if ((b[0:5] == b'\xfe\xff\x00\x00') 
                  or (b[0:5] == b'\x00\x00\xff\xfe')):
            return "utf32"
    
        # If BOM is not provided, then assume its the codepage
        #     used by your operating system
        return "cp1252"
        # For the United States its: cp1252
    
    
    def OpenRead(file):
        bom = bomType(file)
        return open(file, 'r', encoding=bom, errors='ignore')
    
    
    #######################
    # Testing it
    #######################
    fout = open("myfile1.txt", "w", encoding="cp1252")
    fout.write("* hi there (cp1252)")
    fout.close()
    
    fout = open("myfile2.txt", "w", encoding="utf8")
    fout.write("\u2022 hi there (utf8)")
    fout.close()
    
    # this case is still treated like codepage cp1252
    #   (User responsible for making sure that all utf8 files
    #   have a BOM header)
    fout = open("badboy.txt", "wb")
    fout.write(b"hi there.  barf(\x81\x8D\x90\x9D)")
    fout.close()
    
    # Read Example file with Bom Detection
    fin = OpenRead("myfile1.txt")
    L = fin.readline()
    print(L)
    fin.close()
    
    # Read Example file with Bom Detection
    fin = OpenRead("myfile2.txt")
    L =fin.readline() 
    print(L) #requires QtConsole to view, Cmd.exe is cp1252
    fin.close()
    
    # Read CP1252 with a few undefined chars without barfing
    fin = OpenRead("badboy.txt")
    L =fin.readline() 
    print(L)
    fin.close()
    
    # Check that bad characters are still in badboy codepage file
    fin = open("badboy.txt", "rb")
    fin.read(20)
    fin.close()
    

    4

    原则上,在一般情况下,确定文本文件的编码是不可能的。因此,没有标准的Python库可以为您完成这项工作。

    如果您对文本文件有更具体的了解(例如它是XML),那么可能会有库函数可用。


    3

    根据您所使用的平台,我选择使用 Linux shell 的file 命令。由于我在运行脚本时只在我们的 Linux 机器上运行,因此这对我来说很有效。

    显然,这并不是一个理想的解决方案或答案,但可以修改以适应您的需求。在我的情况下,我只需要确定文件是否为 UTF-8。

    import subprocess
    file_cmd = ['file', 'test.txt']
    p = subprocess.Popen(file_cmd, stdout=subprocess.PIPE)
    cmd_output = p.stdout.readlines()
    # x will begin with the file type output as is observed using 'file' command
    x = cmd_output[0].split(": ")[1]
    return x.startswith('UTF-8')
    

    不需要分叉一个新进程。Python代码已经在一个进程内运行,并且可以调用适当的系统函数,而无需加载新进程的开销。 - vdboor

    1

    如果您知道文件的某些内容,可以尝试使用多种编码进行解码,并查看哪个缺失。一般来说,没有办法,因为文本文件就是文本文件,它们很愚蠢;)


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接