Python 2.5中解码未知Unicode编码的最佳方法

8

我是否理解正确?无论如何,我正在解析大量html,但我并不总是知道它应该使用哪种编码(令人惊讶的是很多都在谎报)。下面的代码展示了我目前的做法,但我相信有更好的方法。非常感谢您的建议。

import logging
import codecs
from utils.error import Error

class UnicodingError(Error):
    pass

# these encodings should be in most likely order to save time
encodings = [ "ascii", "utf_8", "big5", "big5hkscs", "cp037", "cp424", "cp437", "cp500", "cp737", "cp775", "cp850", "cp852", "cp855", 
    "cp856", "cp857", "cp860", "cp861", "cp862", "cp863", "cp864", "cp865", "cp866", "cp869", "cp874", "cp875", "cp932", "cp949", 
    "cp950", "cp1006", "cp1026", "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_13", "iso8859_14", "iso8859_15", "johab", "koi8_r", "koi8_u", 
    "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_sig" ]

def unicode(string):
    '''make unicode'''
    for enc in self.encodings:
        try:
            logging.debug("unicoder is trying " + enc + " encoding")
            utf8 = unicode(string, enc)
            logging.info("unicoder is using " + enc + " encoding")
            return utf8
        except UnicodingError:
            if enc == self.encodings[-1]:
                raise UnicodingError("still don't recognise encoding after trying do guess.")

自动检测对于您的文本一无所知,但如果您至少知道文本所使用的语言,则只有非常少的选项。这就是为什么编码检测基本上意味着根据字母频率猜测语言。 - Jochen Ritzel
嗯,unicode() 返回的是 Unicode 而不是 UTF-8。 - unbeknown
3个回答

10

有两个通用库可以检测未知编码:

chardet 库应该是Firefox 的实现方法

你可以使用以下正则表达式来从字节字符串中检测 utf8:

import re

utf8_detector = re.compile(r"""^(?:
     [\x09\x0A\x0D\x20-\x7E]            # ASCII
   | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
   |  \xE0[\xA0-\xBF][\x80-\xBF]        # excluding overlongs
   | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
   |  \xED[\x80-\x9F][\x80-\xBF]        # excluding surrogates
   |  \xF0[\x90-\xBF][\x80-\xBF]{2}     # planes 1-3
   | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
   |  \xF4[\x80-\x8F][\x80-\xBF]{2}     # plane 16
  )*$""", re.X)

实际上,如果你处理的是英文,我发现以下方法在99.9%的情况下都有效:

  1. 如果它通过了上述正则表达式,则它是ASCII或UTF8编码。
  2. 如果它包含0x80-0x9f之间的任何字节但不包含0xa4,则它是Windows-1252编码。
  3. 如果它包含0xa4,则假设它是Latin-15编码。
  4. 否则假设它是Latin-1编码。

1
我编写了这个并将它放在这里http://pastebin.com/f76609aec - user132262
你粘贴的代码存在问题:^(?:\xA4)*$ 只会匹配字符串完全由 \xA4 组成,没有其他字符。你只需要使用 re.compile(r'\xA4')re.compile(r'[\x80-\xBF]') 两个正则表达式即可。 - ʞɔıu

3
我曾经遇到过同样的问题,并发现在没有关于内容的元数据的情况下,无法确定内容的编码类型。这就是为什么我最终采用了你正在尝试的方法。
我的唯一额外建议是,你应该按照特定性顺序而不是最有可能的顺序对可能的编码列表进行排序。我发现某些字符集是其他字符集的子集,因此如果你将utf_8作为第二选择,你将永远无法找到utf的子集(我认为某个韩语字符集使用与utf相同的数字空间)。

1
确实。asciiutf-8 的子集,也可以正确解码为 utf-8,因此您可以省略 ascii。 8位编码(如 latin-1)在所有情况下都会解码为某些内容,因此请将其中一个放在最后。 - Thomas

2
由于您使用的是Python,您可以尝试使用UnicodeDammit。它是Beautiful Soup 的一部分,您可能也会发现它很有用。
就像名称所示,UnicodeDammit 将尽力从世界上可能找到的垃圾中获取适当的 Unicode。

早期尝试过,但是失败了很多次。 - user132262
真的吗!有什么问题吗?也许让它工作起来比自己搭建要容易些。 - Adam Goode
在这个世界上,你可能会遇到各种无聊的东西。 - Sabuncu

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