我有许多外部文件的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。
