如何在 python 3.6 中打开混合编码的 unicode 文件?

How to open mixed encoded unicode files in python 3.6?

我从未记录的资源中收到文件,其中可能包含如下所示的数据:

16058637149881541301278JA1コノマンガガスゴイヘンシュウブ4
#recordsWritten:1293462

以上只是一个示例,我正在处理的文件包含各种不同的语言(因此也包含编码)。然后我用 Python 3.6(我从 Python 2 升级到 Python 3 的继承代码库)使用以下代码打开我的文件:

import os

f = open(file_path, "r")

f.seek(0, os.SEEK_END)
f.seek(f.tell() -40, os.SEEK_SET)
records_str = f.read()
print(records_str)

使用此代码,我收到:UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 0: invalid start byte

如果我将其更改为包含编码:

f = open(file_path, "r", encoding='utf-8'),我收到同样的错误。

将编码更改为 utf-16 会导致打印:

랂菣Ꚃ菣Ɩȴ⌊敲潣摲坳楲瑴湥ㄺ㤲㐳㈶ਂ

这似乎是错误的。

将其切换为以二进制模式打开文件:f = open(file_path, "rb") 结果输出:

b'\x82\xb7\xe3\x83\xa5\xe3\x82\xa6\xe3\x83\x96\x014\x02\n#recordsWritten:1293462\x02\n'

现在稍微好一点,但是,当我最终开始处理文件时,我不想将 \x82\xb7\xe3\x83\xa5\ 添加到我的数据库中,我宁愿添加 ガガスゴイヘンシ .那么,有没有办法处理 Unicode 编码的文件呢?我还查看了 Mozilla chardet 项目以尝试确定编码,但按照代码示例,它认为文件是 utf-8 编码的。

在不知道文件中的实际字节数的情况下,我们所能做的就是推测。

如果文件自始至终都没有使用单一编码,就真的没有办法以编程方式处理它。您必须将它分成几个部分,并使用适合该序列的任何编码分别转换每个部分。这几乎肯定需要手动工作,即使只是为了在具有不同编码的部分之间建立界限。

展望未来,您可能希望将所有内容都转换为单一编码;我对此的建议是 UTF-8。它应该能够容纳任何您可以 Python 首先识别为有效字符串的内容。

作为一个粗略的例子,如果您知道您提供的示例对拉丁部分使用纯 7 位 ASCII,对日文字符使用 EUC-JP,也许可以尝试

with open(filename, 'rb') as filebytes:
    raw_bytes = filebytes.read()
string = raw_bytes[0:26].decode('ascii') + \
    raw_bytes[26:54].decode('euc-jp') + \
    raw_bytes[54:].decode('ascii')

我根据您提供的字符串通过实验确定了字符范围;如果我猜错了你对日语文本使用的编码(特别是),它们可能对你的实际数据不正确。

观察我们如何从使用 rb 打开的文件句柄中读取 bytes,并且 Python 在读取它们时不会尝试应用任何字符编码。但是当然,如​​果我们想把它变成一个字符串,我们必须 decode 它们分别使用正确的编码。

如果您 seek 进入 UTF-8 序列的中间,则错误消息并不一定意味着数据实际上不是 UTF-8,只是您无法精确搜索位置并获得有用的解码。 “无效的起始字节”意味着这不能是有效 UTF-8 字符串的开头

如果你只需要检索文件的最后一行,也许只是读取整个文件并摘下最后一行,或者 use try/ except until you find a position you can safely seek to. 或者只是读取部分或全部文件作为 bytes 然后只解码最后一行。

import os

with open(file_path, "rb") as f:  # notice "b" in "rb"
    f.seek(0, os.SEEK_END)
    f.seek(f.tell() -40, os.SEEK_SET)
    records_bytes = f.read()
records_str = records_bytes.split(b'\n')[-2].decode('ascii')
print(records_str)

我们使用[-2] 假设文件末尾包含最后一个换行符(即它是一个格式正确的文本文件),因此 [-1] 只是一个空字符串,并且这将检索最后一行。

(将此作为单独的答案发布,以免污染我的其他答案,我希望这对未来的访问者也更有用。)