Skip to main content
 首页 » 编程设计

python之使用python清理文本文件中的字符串编码问题

2025年05月04日18jiqing9006

我有许多外部文件的XML文档和文件名,这些文件具有各种形式的文本损坏或Mojibake,在导入期间会导致数据质量问题。我已经在StackOverflow上阅读了许多有关更正字符串的不同文章,但是它们并没有真正概述如何系统地清除文本,而python的decode,encode似乎无济于事。我如何使用Python 2.7恢复XML文件和文件名,其中包含的字符范围为Latin-1(ISO-8859-1),但通常具有混合编码?

请您参考如下方法:

你必须做个假设

如果您无法对所遇到的字母的种类做出假设,则可能会遇到麻烦。因此,在我们的文档中我们可以合理地假设使用挪威字母A-Å是件好事。没有魔术工具可以自动更正遇到的每个文档。

因此,在此域中,我们知道文件可能包含å,其UTF-8 2字节表示形式为0xc3 0xa5或Unicode,Latin-1和Windows-1252会将其表示为0xe5。通常,此character lookup非常好,如果您正在研究角色,则可能会成为一个很好的书签。

例


挪威å
损坏的版本Ã¥


您可以在此方便的debugging chart中找到一长串此类问题。

基本的Python编码,解码

如果您确切知道出了什么问题,这是最简单的方法将字符串变回原形。

our_broken_string = 'Ã¥' 
broken_unicode = our_broken_string.decode('UTF-8') 
print broken_unicode # u'\xc3\xa5' yikes -> two different unicode characters 
down_converted_string = broken_unicode.encode('LATIN-1') 
print down_converted_string # '\xc3\xa5' those are the right bytes 
correct_unicode = down_converted_string.decode('UTF-8') 
print correct_unicode # u'\xe5' correct unicode value 


文件资料

在处理文档时,可以做出一些相对较好的假设。单词,空格和线条。即使文档是XML,您仍然可以将其视为单词,而不必太担心标签,或者如果单词是单词,则只需要找到最小的单元即可。我们还可以假设,如果文件存在文本编码问题,则它可能还会出现行尾问题,具体取决于有多少不同的操作系统处理了该文件。我会中断行尾 rstrip,并使用print将数组重新组合到 StringIO文件句柄。

保留空白时,可能很想通过漂亮的打印功能运行XML文档,但是您不应该这样做,我们只想更正小文本单元的编码,而不更改其他任何内容。一个很好的起点是看是否可以逐行,逐字地浏览文档,而不是在任意字节块中浏览文档,而忽略您正在处理XML的事实。

在这里,我利用以下事实:如果文本超出了UTF-8的范围,则将收到UnicodeDecodeErrors,然后尝试使用LATIN-1。这在本文档中起作用。

import unicodedata 
 
encoding_priority = ['UTF-8', 'LATIN-1'] 
def clean_chunk(file_chunk): 
    error_count = 0 
    corrected_count = 0 
    new_chunk = '' 
    encoding = '' 
    for encoding in encoding_priority: 
        try: 
            new_chunk = file_chunk.decode(encoding, errors='strict') 
            corrected_count += 1 
            break 
        except UnicodeDecodeError, error: 
            print('Input encoding %s failed -> %s' % (encoding, error)) 
            error_count += 1 
    if encoding != '' and error_count > 0 and corrected_count > 0: 
        print('Decoded. %s(%s) from hex(%s)' % (encoding, new_chunk, file_chunk.encode('HEX'))) 
 
    normalized = unicodedata.normalize('NFKC', new_chunk) 
 
    return normalized, error_count, corrected_count 
 
 
def clean_document(document): 
    cleaned_text = StringIO() 
    error_count = 0 
    corrected_count = 0 
 
    for line in document: 
        normalized_words = [] 
        words = line.rstrip().split(' ') 
        for word in words: 
            normalized_word, error_count, corrected_count = clean_chunk(word) 
            error_count += error_count 
            corrected_count += corrected_count 
            normalized_words.append(normalized_word) 
        normalized_line = ' '.join(normalized_words) 
        encoded_line = normalized_line.encode(output_encoding) 
        print(encoded_line, file=cleaned_text) 
 
    cleaned_document = cleaned_text.getvalue() 
    cleaned_text.close() 
 
    return cleaned_document, error_count, corrected_count 


FTFY处理Mojibake

如果您的问题是真实的 Mojibake,例如文件名错误。您可以使用 FTFY尝试尝试纠正问题。同样,我会逐字逐句地取得最佳效果。

import os 
import sys 
import ftfy 
import unicodedata 
 
 
if __name__ == '__main__': 
    path = sys.argv[1] 
    file_system_encoding = sys.getfilesystemencoding() 
    unicode_path = path.decode(file_system_encoding) 
 
    for root, dirs, files in os.walk(unicode_path): 
        for f in files: 
            comparable_original_filename = unicodedata.normalize('NFC', f) 
            comparable_new_filename = ftfy.fix_text(f, normalization='NFC') 
 
            if comparable_original_filename != comparable_new_filename: 
                original_path = os.path.join(root, f) 
                new_path = os.path.join(root, comparable_new_filename) 
                print "Renaming:" + original_path + " to:" + new_path 
                os.rename(original_path, new_path) 


这遍历了目录,纠正了 å被改编成 A\xcc\x83\xc2\xa5的更难看的错误。这是什么?大写字母 A + COMBINING LETTER TILDE 0xcc 0x83是使用 unicode equivalence表示 Ã的几种方法之一。对于FTFY而言,这确实是一项工作,因为它实际上将执行启发式操作并解决此类问题。

Unicode规范化,用于比较和文件系统

另一种方法是使用Unicode规范化来获取正确的字节。

import unicodedata 
 
a_combining_tilde = 'A\xcc\x83' 
# Assume: Expecting UTF-8  
unicode_version = a_combining_tilde.decode('UTF-8') # u'A\u0303' and this cannot be converted to LATIN-1 and get à 
normalized = unicodedata.normalize('NFC', unicode_version) # u'\c3' 
broken_but_better = normalized.encode('UTF-8') # '\xc3\x83` correct UTF-8 bytes for Ã. 


因此,总而言之,如果将其视为UTF-8编码的字符串 A\xcc\x83\xc2\xa5,对其进行规范化,然后下变频为LATIN-1字符串,然后再转换回UTF-8,则将获得正确的unicode。

您需要注意操作系统如何编码文件名。您可以使用以下方法检索该信息:

file_system_encoding = sys.getfilesystemencoding() 


因此,假设 file_system_encoding是 UTF-8,很好吗?然后,您比较两个看似相同的unicode字符串,但它们不相等! FTFY,默认情况下标准化为 NFC,HFS标准化为旧版本的 NFD。因此,仅知道编码是相同的还不够好,您必须以相同的方式进行标准化以使比较有效。


Windows NTFS在不进行规范的情况下存储unicode。
Linux存储unicode而不进行标准化。
Mac HFS使用专有的HFD规范化存储UTF-8。


Node.js提供了有关 dealing with different filesystems的良好指南。总而言之,请标准化以进行比较,请勿随意重新标准化文件名。

最后的笔记

谎言,该死的谎言和XML声明

在XML文档中,您将得到类似这样的信息,该信息应通知XML解析器有关文本编码的信息。

<?xml version="1.0" encoding="ISO-8859-1"?> 


如果您看到此情况,则应将其视为谎言,直到被证明是真实的。在将该文档交给XML解析器之前,您需要验证和处理编码问题,并且需要更正声明。

谎言,该死的谎言和BOM标记

字节序标记听起来是个好主意,但是就像它们的XML声明表亲一样,它们完全不可靠地指示文件的编码情况。 Within UTF-8, BOMs are NOT recommended,对于字节顺序没有任何意义。它们的唯一值是指示某些内容已以UTF-8编码。但是,考虑到文本编码的问题,默认值为UTF-8,并且应该是UTF-8。