我有很多外部文件的XML文档和文件名,这些文件具有各种形式的文本损坏或Mojibake导致导入期间的数据质量问题。我已经在StackOverflow上阅读了许多关于纠正字符串的不同帖子,但是他们没有真正概述如何以系统的方式清理文本而python的decode
,encode
似乎没有帮助。如何使用Python 2.7恢复XML文件和文件名,包含Latin-1(ISO-8859-1)范围内的字符,但通常有混合编码?
答案 0 :(得分:6)
如果你不能对你将要遇到的那些字母做出假设,你可能会遇到麻烦。因此,在我们的文档中,我们可以合理地假设挪威字母A-Å
。没有神奇的工具可以自动更正您遇到的每个文档。
因此,在此域中,我们知道文件可能包含å
,其中包含UTF-8 2字节表示0xc3 0xa5
或Unicode,Latin-1和{{3}将它表示为0xe5
。一般来说,这个Windows-1252非常好,如果你发现自己正在研究一个角色,可能会成为一个好的书签。
å
Ã¥
您可以在这个方便的character lookup找到一长串这类问题。
如果你确切知道出了什么问题,这是将字符串修复成形状的最简单方法。
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范围然后尝试LATIN-1,您将获得UnicodeDecodeErrors的事实。这在本文件中有效。
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
如果您的问题是真实的debugging chart,可能是错误的文件名。您可以使用Mojibake尝试启发式更正您的问题。同样,我会逐字逐句地获得最佳结果。
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是使用FTFY表示Ã
的几种方式之一。这对于FTFY来说确实是一项工作,因为它实际上会执行启发式操作并解决这些问题。
另一种方法是使用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
。因此,仅仅知道编码是相同的还不够好,你必须以相同的方式规范化,以使比较有效。
Node.js有一个很好的unicode equivalence指南。总之,规范化比较,不要随意重新规范文件名。
在XML文档中,您将获得类似这样的内容,以便向XML解析器通知文本编码。
<?xml version="1.0" encoding="ISO-8859-1"?>
如果你看到这一点,那就应该被视为谎言,直到证明是真的。在将此文档交给XML解析器之前,您需要验证并处理编码问题,并且需要更正声明。
字节顺序标记听起来像一个好主意,但像他们的XML声明表兄弟是完全不可靠的文件编码情况指标。 dealing with different filesystems并且对于字节顺序没有任何意义。它们唯一的价值是表明某些东西是用UTF-8编码的。但是,考虑到文本编码的问题,默认情况是并且应该是期望UTF-8。