【问题标题】:Python - dealing with mixed-encoding filesPython - 处理混合编码文件
【发布时间】:2012-04-18 01:55:48
【问题描述】:

我有一个主要是 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)

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

【问题讨论】:

  • 怀疑它会解决你的问题,但str.translate() 比一堆替换更适合你想要做的事情。例如:cp1252_to_unicode = string.maketrans({...}) 然后l.translate(cp1252_to_unicode)
  • 很难相信只有那些 Windows 标点字符原来是 cp1252...你知道混淆是怎么发生的吗?您确定您的 UTF8 编码字符解码为 * 有意义的 * unicode 吗?文字是用什么语言写的?
  • 不幸的是,我没有太多关于文件如何损坏的信息。这些文件是用英文编写的,最初可能没有编码为 Unicode,而只是编码为 Ascii(99% 的文本是纯 Ascii)。我猜想在 Windows 上工作的人使用为他们这样做的编辑器或使用 alt- 快捷方式插入了字符(破折号等)。我手动查找了 Unicode 字符,所以如果它们被用作替换并且文件读取为 Unicode,这些应该可以工作。

标签: python unicode encoding utf-8 windows-1252


【解决方案1】:

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

但是,Python 编解码器允许您使用 codecs.register_error 函数注册 callback to handle encoding/decoding 错误 - 它获取 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 和超出范围(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çã 

【讨论】:

  • 很好的答案,但包括一个例子会更好。
  • @Duncan:我正在研究这个例子 - 由于上面提到的 ctach,我花了一些时间。
  • 我想知道您是否是第一个真正尝试编写此代码的人?这听起来像一个错误。抱歉,我不能对示例代码再次投赞成票。
  • 我认为这不是一个错误 - 它从链中的第一个字符重新引发错误,它无法识别为 utf-8。
  • 嗨 Keith - 抱歉,我没有测试所有情况 - 元组中的项目 [2] 不是我最初认为的错误 sart。但我发现“unicode error”对象有一个“start”属性,这是我期望的数字——现在试试。虽然它确实有改进的余地
【解决方案2】:

感谢 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,以便在我自己的表格中显示或查找。

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

【讨论】:

    【解决方案3】:

    @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çã"
    

    【讨论】:

      【解决方案4】:

      这通常称为Mojibake

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

      例子:

      >>> from ftfy import fix_text
      >>> fix_text("Ð¨ÐµÐ¿Ð¾Ñ (напоминалки)")
      'Шепот (напоминалки)'
      

      【讨论】:

        【解决方案5】:

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

        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。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-12-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多