19

我有一个主要是 UTF-8 的文件,但也有一些 Windows-1252 字符可以进入。

我创建了一个表来将 Windows-1252 (cp1252) 字符映射到它们的 Unicode 对应字符,并想用它来修复错误编码的字符,例如

cp1252_to_unicode = {
    "\x85": u'\u2026', # …
    "\x91": u'\u2018', # ‘
    "\x92": u'\u2019', # ’
    "\x93": u'\u201c', # “
    "\x94": u'\u201d', # ”
    "\x97": u'\u2014'  # —
}

for l in open('file.txt'):
    for c, u in cp1252_to_unicode.items():
        l = l.replace(c, u)

但是尝试以这种方式进行替换会导致引发 UnicodeDecodeError,例如:

"\x85".replace("\x85", u'\u2026')
UnicodeDecodeError: 'ascii' codec can't decode byte 0x85 in position 0: ordinal not in range(128)

关于如何处理这个问题的任何想法?

4

5 回答 5

29

如果您尝试将此字符串解码为 utf-8,如您所知,您将收到“UnicodeDecode”错误,因为这些虚假的 cp1252 字符是无效的 utf-8 -

但是,Python 编解码器允许您使用 codecs.register_error 函数注册回调以处理编码/解码错误 - 它获取 UnicodeDecodeerror aa 参数 - 您可以编写这样的处理程序来尝试将数据解码为“cp1252”,并且继续以 utf-8 解码字符串的其余部分。

在我的 utf-8 终端中,我可以构建一个混合的错误字符串,如下所示:

>>> a = u"maçã ".encode("utf-8") + u"maçã ".encode("cp1252")
>>> print a
maçã ma�� 
>>> a.decode("utf-8")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/encodings/utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode bytes in position 9-11: invalid data

我在这里写了上述回调函数,发现了一个问题:即使你将解码字符串的位置加 1,这样它就会从下一个字符开始,如果下一个字符也不是 utf-8 并且 out对于范围(128),错误在第一个超出范围(128)字符时引发 - 这意味着,如果发现连续的非 ascii、非 utf-8 字符,则解码“后退”。

这一轮的工作是在error_handler中有一个状态变量,它检测到这个“走回”并从最后一次调用它恢复解码 - 在这个简短的例子中,我将它实现为一个全局变量 - (它必须手动在每次调用解码器之前重置为“-1”):

import codecs

last_position = -1

def mixed_decoder(unicode_error):
    global last_position
    string = unicode_error[1]
    position = unicode_error.start
    if position <= last_position:
        position = last_position + 1
    last_position = position
    new_char = string[position].decode("cp1252")
    #new_char = u"_"
    return new_char, position + 1

codecs.register_error("mixed", mixed_decoder)

在控制台上:

>>> a = u"maçã ".encode("utf-8") + u"maçã ".encode("cp1252")
>>> last_position = -1
>>> print a.decode("utf-8", "mixed")
maçã maçã 
于 2012-04-04T11:16:44.210 回答
7

多亏了 jsbueno 以及其他谷歌搜索和其他冲击,我以这种方式解决了它。

#The following works very well but it does not allow for any attempts to FIX the data.
xmlText = unicode(xmlText, errors='replace').replace(u"\uFFFD", "?")

此版本允许修复无效字符的机会有限。未知字符被替换为安全值。

import codecs    
replacement = {
   '85' : '...',           # u'\u2026' ... character.
   '96' : '-',             # u'\u2013' en-dash
   '97' : '-',             # u'\u2014' em-dash
   '91' : "'",             # u'\u2018' left single quote
   '92' : "'",             # u'\u2019' right single quote
   '93' : '"',             # u'\u201C' left double quote
   '94' : '"',             # u'\u201D' right double quote
   '95' : "*"              # u'\u2022' bullet
}

#This is is more complex but allows for the data to be fixed.
def mixed_decoder(unicodeError):
    errStr = unicodeError[1]
    errLen = unicodeError.end - unicodeError.start
    nextPosition = unicodeError.start + errLen
    errHex = errStr[unicodeError.start:unicodeError.end].encode('hex')
    if errHex in replacement:
        return u'%s' % replacement[errHex], nextPosition
    return u'%s' % errHex, nextPosition   # Comment this line out to get a question mark
    return u'?', nextPosition

codecs.register_error("mixed", mixed_decoder)

xmlText = xmlText.decode("utf-8", "mixed")

基本上我试图把它变成utf8。对于任何失败的字符,我只需将其转换为 HEX,这样我就可以在自己的表格中显示或查找它。

这并不漂亮,但它确实让我能够理解混乱的数据

于 2014-10-21T17:38:41.607 回答
1

@jsbueno 的好解决方案,但不需要全局变量last_position,请参阅:

def mixed_decoder(error: UnicodeError) -> (str, int):
     bs: bytes = error.object[error.start: error.end]
     return bs.decode("cp1252"), error.start + 1

import codecs
codecs.register_error("mixed", mixed_decoder)

a = "maçã".encode("utf-8") + "maçã".encode("cp1252")
# a = b"ma\xc3\xa7\xc3\xa3ma\xe7\xe3"

s = a.decode("utf-8", "mixed")
# s = "maçãmaçã"
于 2020-03-05T17:36:11.953 回答
1

这通常称为Mojibake

有一个不错的 Python 库可以为您解决这些问题,称为ftfy

例子:

>>> from ftfy import fix_text
>>> fix_text("Ð¨ÐµÐ¿Ð¾Ñ (напоминалки)")
'Шепот (напоминалки)'
于 2020-09-05T01:24:56.820 回答
1

今天刚刚进入这个,所以这是我的问题和我自己的解决方案:

original_string = 'Notifica\xe7\xe3o de Emiss\xe3o de Nota Fiscal Eletr\xf4nica.'

def mixed_decoding(s):
    output = ''
    ii = 0
    for c in s:
        if ii <= len(s)-1:
            if s[ii] == '\\' and s[ii+1] == 'x':
                b = s[ii:ii+4].encode('ascii').decode('unicode-escape')
                output = output+b
                ii += 3
            else:
                output = output+s[ii]
        ii += 1
    print(output)
    return output

decoded_string = mixed_decoding(original_string)

现在它打印:
>>> Notificação de Emissão de Nota Fiscal Eletrônica。

于 2021-05-18T14:28:23.217 回答