由于错误的编码导致数据乱码,最佳纠正方法是什么?

4
我有一组数据,其中包含了许多因为导入/导出时的编码错误而产生乱码的文本字段。大部分错误是由于将UTF-8转换为ISO-8859-1引起的。奇怪的是,这些错误并不是一致的:单词“München”在某些地方显示为“München”,在其他地方则显示为“MÃœnchen”。
在SQL Server中是否有技巧可以修正这种烂摊子?我能想到的第一件事就是利用COLLATE子句,以便ü被解释为ü,但我并不确定如何操作。如果无法在数据库层面上进行操作,您是否知道任何工具可以批量纠正此类问题?(不是手动查找/替换工具,而是可以猜测乱码文本并对其进行纠正的工具)

我认为不一致性是由于对utf-8进行了双重转换(latin-1 => utf-8 => utf-8)造成的。要撤消此操作,请反向执行它。 - Martin Wickman
是的,但问题是,我对这些该死的导入/导出的顺序一无所知。直到现在,数据一直由一对...咳咳,姑且说是新手管理员来管理。 - ercan
4个回答

4

我曾经处于完全相同的位置。生产MySQL服务器被设置为latin1,旧数据也是latin1,新数据是utf8但存储在latin1列中,然后添加了utf8列...每行可以包含任意数量的编码。

最大的问题是没有单一的解决方案可以纠正所有问题,因为许多传统编码使用相同的字节表示不同的字符。这意味着您将不得不采用启发式方法。在我的Utf8Voodoo类中,有一个巨大的数组,其中包含从127到255的字节,即传统的单字节编码非ASCII字符。

// ISO-8859-15 has the Euro sign, but ISO-8859-1 has also been used on the
// site. Sigh. Windows-1252 has the Euro sign at 0x80 (and other printable
// characters in 0x80-0x9F), but mb_detect_encoding never returns that
// encoding when ISO-8859-* is in the detect list, so we cannot use it.
// CP850 has accented letters and currency symbols in 0x80-0x9F. It occurs
// just a few times, but enough to make it pretty much impossible to
// automagically detect exactly which non-ISO encoding was used. Hence the
// need for "likely bytes" in addition to the "magic bytes" below.

/**
 * This array contains the magic bytes that determine possible encodings.
 * It works by elimination: the most specific byte patterns (the array's
 * keys) are listed first. When a match is found, the possible encodings
 * are that entry's value.
 */
public static $legacyEncodingsMagicBytes = array(
    '/[\x81\x8D\x8F\x90\x9D]/' => array('CP850'),
    '/[\x80\x82-\x8C\x8E\x91-\x9C\x9E\x9F]/' => array('Windows-1252', 'CP850'),
    '/./' => array('ISO-8859-15', 'ISO-8859-1', 'Windows-1252', 'CP850'),
);

/**
 * This array contains the bytes that make it more likely for a string to
 * be a certain encoding. The keys are the pattern, the values are arrays
 * with (encoding => likeliness score modifier).
 */
public static $legacyEncodingsLikelyBytes = array(
    // Byte | ISO-1  | ISO-15 | W-1252 | CP850
    // 0x80 | -      | -      | €      | Ç
    '/\x80/' => array(
        'Windows-1252' => +10,
    ),
    // Byte | ISO-1  | ISO-15 | W-1252 | CP850
    // 0x93 | -      | -      | “      | ô
    // 0x94 | -      | -      | ”      | ö
    // 0x95 | -      | -      | •      | ò
    // 0x96 | -      | -      | –      | û
    // 0x97 | -      | -      | —      | ù
    // 0x99 | -      | -      | ™      | Ö
    '/[\x93-\x97\x99]/' => array(
        'Windows-1252' => +1,
    ),
    // Byte | ISO-1  | ISO-15 | W-1252 | CP850
    // 0x86 | -      | -      | †      | å
    // 0x87 | -      | -      | ‡      | ç
    // 0x89 | -      | -      | ‰      | ë
    // 0x8A | -      | -      | Š      | è
    // 0x8C | -      | -      | Œ      | î
    // 0x8E | -      | -      | Ž      | Ä
    // 0x9A | -      | -      | š      | Ü
    // 0x9C | -      | -      | œ      | £
    // 0x9E | -      | -      | ž      | ×
    '/[\x86\x87\x89\x8A\x8C\x8E\x9A\x9C\x9E]/' => array(
        'Windows-1252' => -1,
    ),
    // Byte | ISO-1  | ISO-15 | W-1252 | CP850
    // 0xA4 | ¤      | €      | ¤      | ñ
    '/\xA4/' => array(
        'ISO-8859-15' => +10,
    ),
    // Byte | ISO-1  | ISO-15 | W-1252 | CP850
    // 0xA6 | ¦      | Š      | ¦      | ª
    // 0xBD | ½      | œ      | ½      | ¢
    '/[\xA6\xBD]/' => array(
        'ISO-8859-15' => -1,
    ),
    // Byte | ISO-1  | ISO-15 | W-1252 | CP850
    // 0x82 | -      | -      | ‚      | é
    // 0xA7 | §      | §      | §      | º
    // 0xFD | ý      | ý      | ý      | ²
    '/[\x82\xA7\xCF\xFD]/' => array(
        'CP850' => +1
    ),
    // Byte | ISO-1  | ISO-15 | W-1252 | CP850
    // 0x91 | -      | -      | ‘      | æ
    // 0x92 | -      | -      | ’      | Æ
    // 0xB0 | °      | °      | °      | ░
    // 0xB1 | ±      | ±      | ±      | ▒
    // 0xB2 | ²      | ²      | ²      | ▓
    // 0xB3 | ³      | ³      | ³      | │
    // 0xB9 | ¹      | ¹      | ¹      | ╣
    // 0xBA | º      | º      | º      | ║
    // 0xBB | »      | »      | »      | ╗
    // 0xBC | ¼      | Œ      | ¼      | ╝
    // 0xC1 | Á      | Á      | Á      | ┴
    // 0xC2 | Â      | Â      | Â      | ┬
    // 0xC3 | Ã      | Ã      | Ã      | ├
    // 0xC4 | Ä      | Ä      | Ä      | ─
    // 0xC5 | Å      | Å      | Å      | ┼
    // 0xC8 | È      | È      | È      | ╚
    // 0xC9 | É      | É      | É      | ╔
    // 0xCA | Ê      | Ê      | Ê      | ╩
    // 0xCB | Ë      | Ë      | Ë      | ╦
    // 0xCC | Ì      | Ì      | Ì      | ╠
    // 0xCD | Í      | Í      | Í      | ═
    // 0xCE | Î      | Î      | Î      | ╬
    // 0xD9 | Ù      | Ù      | Ù      | ┘
    // 0xDA | Ú      | Ú      | Ú      | ┌
    // 0xDB | Û      | Û      | Û      | █
    // 0xDC | Ü      | Ü      | Ü      | ▄
    // 0xDF | ß      | ß      | ß      | ▀
    // 0xE7 | ç      | ç      | ç      | þ
    // 0xE8 | è      | è      | è      | Þ
    '/[\x91\x92\xB0-\xB3\xB9-\xBC\xC1-\xC5\xC8-\xCE\xD9-\xDC\xDF\xE7\xE8]/' => array(
        'CP850' => -1
    ),
/* etc. */

然后,您需要在字符串中循环遍历字节(而不是字符),并保留分数。如果您需要更多信息,请告诉我。


这是用PHP写的,对我的问题提供了一个很好的起点示例。谢谢!你的网站或其他地方有完整的类可用吗?如果您不想公开源代码,我会理解,但它可能对他人非常有帮助。 - ercan
我很想开源它,但是这是在公司时间写的。我需要确认一下。对了,它是用PHP编写的。 - janmoesen

1

下载 iconv - 你可以获取适用于 Win32 和 Unix/Linux 的二进制文件。这是一个命令行工具,它将接受源文件,并在指定输入编码和输出编码后,将必要的转换操作输出到 STDOUT。

我经常使用它将 UTF-16(作为 SQL Server 2005 导出文件的输出格式)转换为 ASCII。

您可以从这里下载: http://gnuwin32.sourceforge.net/packages/libiconv.htm


这是一个不错的工具,但是有一个挑战:文件中既有“München”又有“München”。当我从UTF-8转换为ISO-8859-1时,第二个会被纠正,但第一个会损坏。我需要更智能的解决方案。 - ercan
也许你需要编写一个最佳猜测脚本。显然,你正在一个文件中使用多个编码。也许你可以编写一个Perl脚本,按照以下规则在内部解码每一行:如果(m/ [\x80-\xFF] [a-zA-Z0-9] /),{解码(ISO)} else {解码(utf)} - PP.
有时同一行中会出现"München"和"München"两种写法!但你说得对,在这种情况下几乎不可能做出明智的猜测。我将编写一个脚本,尝试将以'Ã'开头的每个双字节字符串转换为ISO Latin 1编码。 - ercan

0

考虑到数据的复杂性(单行/条目上的多个编码),我认为您需要导出/转储数据,然后对其进行处理。

我认为最好的方法是一系列手动替换。也许某种拼写纠错代码可以找到所有错误-然后您可以添加显式的纠正代码。然后迭代直到拼写检查停止发现错误?

(显然,将任何正确的单词添加到拼写检查器的字典中)。


0

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