使用python解决文本文件中的字符串编码问题
我有很多XML文档和外部文件的文件名,这些文件具有各种形式的文本损坏或Mojibake,在导入过程中会导致数据质量问题。我读过许多关于StackOverflow的关于纠正字符串的帖子,但它们没有真正概述如何以系统的方式清理文本,python的使用python解决文本文件中的字符串编码问题,python,python-2.7,unicode,mojibake,Python,Python 2.7,Unicode,Mojibake,我有很多XML文档和外部文件的文件名,这些文件具有各种形式的文本损坏或Mojibake,在导入过程中会导致数据质量问题。我读过许多关于StackOverflow的关于纠正字符串的帖子,但它们没有真正概述如何以系统的方式清理文本,python的解码,编码似乎也没有什么帮助。如何使用Python 2.7恢复包含拉丁语-1(ISO-8859-1)范围内字符但通常具有混合编码的XML文件和文件名?您必须做出假设 如果你不能对你将遇到的信件种类做出假设,你可能会遇到麻烦。因此,在我们的文档中,我们可以合理
解码
,编码似乎也没有什么帮助。如何使用Python 2.7恢复包含拉丁语-1(ISO-8859-1)范围内字符但通常具有混合编码的XML文件和文件名?您必须做出假设
如果你不能对你将遇到的信件种类做出假设,你可能会遇到麻烦。因此,在我们的文档中,我们可以合理地假设挪威字母A-Å
。没有神奇的工具可以自动更正您遇到的每个文档
因此,在这个域中,我们知道一个文件可能包含UTF-8 2字节表示的0xc3 0xa5
或,并将其表示为0xe5
。一般来说,这是非常好的,如果你发现自己正在研究一个角色,它可能会成为一个好的书签
例子
- 挪威语
- 损坏的版本
Ã¥
你可以在这个便利的网站上找到一长串这类问题
基本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-to-aStringIO
文件句柄重新组合数组
在保留空白时,通过一个漂亮的打印函数运行XML文档可能很有诱惑力,但您不应该这样做,我们只想在不改变任何其他内容的情况下更正小文本单元的编码。一个很好的起点是,看看您是否可以逐行逐字而不是在任意字节块中浏览文档,并忽略您正在处理XML的事实
在这里,我利用了这样一个事实,即如果文本超出UTF-8的范围,您将得到UnicodeDecodeErrors,然后尝试使用拉丁语-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
如果你的问题是真的,比如一个坏的文件名。您可以使用来尝试启发式地纠正您的问题。同样,我会采取逐字逐句的方法来获得最佳效果
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
+组合字母TILDE
0xcc 0x83是表示Ã
的几种方法之一。这实际上是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
,对其进行规范化,然后向下转换为拉丁语-1字符串,然后再返回UTF-8,您将获得正确的unicode
您需要注意操作系统如何编码文件名。您可以通过以下方式检索该信息:
file_system_encoding = sys.getfilesystemencoding()
那么让我们假设文件系统编码是UTF-8
,很好吧?然后比较两个看似相同的unicode字符串,它们并不相等!默认情况下,FTFY标准化为NFC
,HFS标准化为较旧版本的NFD
。因此,仅仅知道编码是相同的是不够的,您必须以相同的方式进行规范化,以使比较有效
- Windows NTFS存储unicode而不进行规范化
- Linux存储unicode而不进行规范化
- Mac HFS使用专有HFD标准化存储UTF-8
Node.js有一个很好的指南。总之,为了比较而规范化,不要随意地重新规范化文件名
最后说明
谎言、该死的谎言和XML声明
在XML文档中,您将看到类似这样的内容,它应该通知XML解析器有关文本编码的信息
<?xml version="1.0" encoding="ISO-8859-1"?>
如果你看到这一点,在证明它是真的之前,它应该被视为谎言。在将此文档交给XML解析器之前,需要验证和处理编码问题,并且需要更正声明
谎言,该死的谎言和BOM标记
字节顺序标记听起来是个不错的主意,但就像它们的XML声明一样,它们是文件编码情况的完全不可靠的指示器。并且对于字节顺序没有任何意义。它们唯一的价值是指示某些东西是用UTF-8编码的。然而,考虑到文本编码的问题,默认值是,也应该是UTF-8。这个问题旨在结合从其他领域特定的问题中可以收集到的谜题的各个部分,并添加我的个人经验,为如何清理文本编码问题提供更规范的答案。在段落中“谎言、该死的谎言和XML声明”,您的意思是编码被明确声明为ISO-8859-1,但这似乎不是真的吗?(例如,它是损坏的UTF-8。)因此,它是否包含对ISO-8859-1实际上无效的字符,即0x7F-0x9F范围内的字符?是的。标题可能是机器生成的,内容可能是UTF-8,可能是拉丁语-1,也可能是b