UTF-16编码/解码存在困难

5

我正在解析一个包含一些UTF-16编码字符串的文档。

我有一个字节串,其中包含以下内容:

my_var = b'\xc3\xbe\xc3\xbf\x004\x004\x000\x003\x006\x006\x000\x006\x00-\x001\x000\x000\x003\x008\x000\x006\x002\x002\x008\x005'

当转换为utf-8时,我得到以下输出:

print(my_var.decode('utf-8'))
#> þÿ44036606-10038062285

前两个字符þÿ表示这是UTF-16BE的BOM,如维基百科所示

但是,我不明白的是,如果我尝试使用UTF16 BOM,就像这样:

if value.startswith(codecs.BOM_UTF16_BE)

这将返回false。实际上,打印codecs.BOM_UTF16_BE不会显示相同的结果:

print(codecs.BOM_UTF16_BE)
#> b'\xfe\xff'

为什么会这样呢?我怀疑高端存在一些编码问题,但不确定如何解决。

在Stackoverflow上已经有几篇关于如何解码UTF-16的文章(例如这篇),它们都说同一件事:使用utf-16进行解码,Python会处理BOM。

... 但对我来说并没有用。

print(my_var.decode('utf-16')
#> 뻃뿃㐀㐀 ㌀㘀㘀 㘀ⴀ㄀  ㌀㠀 㘀㈀㈀㠀㔀

但是使用UTF-16BE编码方式:
print(my_var.decode('utf-16be')
#> 쎾쎿44036606-10038062285

(BOM未被移除)
(使用UTF-16LE)
print(my_var.decode('utf-16le')
#> 뻃뿃㐀㐀 ㌀㘀㘀 㘀ⴀ㄀  ㌀㠀 㘀㈀㈀㠀㔀

出于我无法解释的原因,仅使用.decode('UTF-16')不能正常工作。为什么?

更新

原始源字符串不是我提到的那个,而是这一个:

source = '\376\377\0004\0004\0000\0003\0006\0006\0000\0006\000-\0001\0000\0000\0003\0008\0000\0006\0002\0002\0008\0005'

我使用以下方式进行转换:
def decode_8bit(cls, match):
    value = match.group().replace(b'\\', b'')
    return chr(int(value, base=8)).encode('utf-8')

my_var = re.sub(b'\\\\[0-9]{1,3}', decode_8bit, source)

也许我在这里做错了什么?


1
UTF-16 BOM 是 0xFE 0xFF。您的输入有些不同。可能与 https://dev59.com/9WbWa4cB1Zd3GeqPU0Od 相关。 - Tomalak
1
您提供的二进制序列不是有效的UTF-16。检查print(...)的结果并不是一种有效的检查编码方式,因为print可能无法打印某些字符,所以您不应该信任它。 - Giacomo Alzetta
@Tomalak,我更新了我的问题(在结尾处)。我忘记提到原始来源,也许这会改变一切? - Cyril N.
2个回答

1

如果您使用CP1252编码,则正确的是þÿ表示UTF-16BE的BOM。

区别如下:

您的第一个字节为0xC3,在二进制中为11000011。

  • UTF-8:

前两位被设置并指示您的UTF-8字符长度为2个字节。 对于UTF-8,获取0xC3 0xBE作为您的第一个字符,即þ。

  • CP1252

CP1252始终为1个字节,并返回Ã作为0xC3。

但是,如果您在链接的BOM列表中查找0xC3,则找不到任何匹配的编码。 看起来一开始就没有BOM。

使用默认编码可能是最好的选择,对于Windows来说是UTF-16LE

添加原始来源后进行编辑

您的UTF-8编码破坏了BOM,因为它不是有效的UTF-8。尝试避免解码并传递一个字节列表。

OP的解决方案:

bytes(int(value, base=8))

谢谢。我在我的问题中添加了更多细节,解释了我如何获得my_var的值,也许我一开始就做错了什么? - Cyril N.
@CyrilN。 \376\377是你在base8中的BOM。你的UTF-8编码可能会破坏它,因为它不是有效的UTF-8。尽量避免解码并传递字节列表,或者如果没有其他方法,请使用单字节编码。这方面的Python专家可能会更有帮助。 - Hyarus
2
哦,用bytes(int(value, base=8))替换chr(int(value, base=8)).encode('utf-8')就解决了问题! - Cyril N.
@CyrilN。你能写一篇答案,解释一下发生了什么吗?我认为这可能会有益,因为至少还有一个人在这里遇到了“c3 82 c2 bf”字节序列,那个帖子上的讨论还没有得出结论。 - Tomalak
@CyrilN。我已将您的解决方案添加到我的答案中。如果您想提供自己的答案,请告诉我。在这种情况下,我会尽快删除它。如果您知道为什么会发生这种情况,我鼓励您这样做。我试图查看chr()和encode()函数,但无法解释为什么0xFF 0xFE会转换为0xC3 0x82 0xC2 0xBF。 - Hyarus
我已经回答了它,但是@Hyarus,你帮助我朝着正确的方向前进。你可以保留被接受的答案。 - Cyril N.

0

根据@Tomalak和@Hyarus的要求,这是我的问题原因:

在解码8位值时,我将它们作为UTF-8编码返回:

def decode_8bit(cls, match):
    value = match.group().replace(b'\\', b'')
    return chr(int(value, base=8)).encode('utf-8')

my_var = re.sub(b'\\\\[0-9]{1,3}', decode_8bit, source)

这会影响返回的数据,因为它没有使用UTF-8进行编码(呵呵)。所以正确的代码应该是:

def decode_8bit(cls, match):
    value = match.group().replace(b'\\', b'')
    return bytes(int(value, base=8))

my_var = re.sub(b'\\\\[0-9]{1,3}', decode_8bit, source)

希望这能帮助到其他人... 祝你编码好运! :/

你知道确切的原因吗?chr()函数是否没有返回255?还是encode()函数强制使用UTF-8并切换了一些位? - Hyarus
很遗憾,我不知道。我在多个不同的源上运行我的代码,一切都正常,所以很好。但我不知道是什么导致了这个问题。 - Cyril N.

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