使用python清理文本文件中的字符串编码问题
Using python to clean up string encoding problems in text files
我有很多 XML 外部文件的文档和文件名在导入过程中存在各种形式的文本损坏或 Mojibake 导致数据质量问题。我在 Whosebug 上阅读了许多关于更正字符串的不同帖子,但它们未能真正概述如何以系统的方式清理文本,python 的 decode
、encode
不似乎没有帮助。如何使用 Python 2.7 恢复包含 Latin-1 (ISO-8859-1) 范围内的字符但通常具有混合编码的 XML 文件和文件名?
你必须做出假设
如果您不能对您将遇到的字母类型做出假设,您可能就有麻烦了。因此,在我们的文档中我们可以合理地假设挪威字母 A-Å
是件好事。没有神奇的工具可以自动更正您遇到的每个文档。
所以在这个域中,我们知道一个文件可能包含 å
和 UTF-8 2 字节表示 0xc3 0xa5
或 Unicode, Latin-1 and Windows-1252 will represent it as 0xe5
. Generally, this 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,您仍然可以将其视为单词,而不必太担心标签,或者如果单词真的是单词,您只需要找到您能找到的最小单位即可。我们还可以假设,如果文件有文本编码问题,它也可能有行尾问题,这取决于有多少不同的 OSes 损坏了该文件。我会在行尾中断 rstrip
,然后使用 print 将数组重新组合为 StringIO
文件句柄。
当保留空格时,它可能很想通过漂亮的打印功能 运行 一个 XML 文档,但你不应该这样做,我们只是想更正小文本单元的编码而不改变其他任何东西。一个好的起点是查看您是否可以逐行、逐字而不是任意字节块地阅读文档,并忽略您正在处理 XML.[=40= 的事实]
我在这里利用了这样一个事实:如果文本超出 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, like perhaps a bad filename. You can use 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。
您需要注意 OS 如何编码文件名。您可以通过以下方式检索该信息:
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。
我有很多 XML 外部文件的文档和文件名在导入过程中存在各种形式的文本损坏或 Mojibake 导致数据质量问题。我在 Whosebug 上阅读了许多关于更正字符串的不同帖子,但它们未能真正概述如何以系统的方式清理文本,python 的 decode
、encode
不似乎没有帮助。如何使用 Python 2.7 恢复包含 Latin-1 (ISO-8859-1) 范围内的字符但通常具有混合编码的 XML 文件和文件名?
你必须做出假设
如果您不能对您将遇到的字母类型做出假设,您可能就有麻烦了。因此,在我们的文档中我们可以合理地假设挪威字母 A-Å
是件好事。没有神奇的工具可以自动更正您遇到的每个文档。
所以在这个域中,我们知道一个文件可能包含 å
和 UTF-8 2 字节表示 0xc3 0xa5
或 Unicode, Latin-1 and Windows-1252 will represent it as 0xe5
. Generally, this 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,您仍然可以将其视为单词,而不必太担心标签,或者如果单词真的是单词,您只需要找到您能找到的最小单位即可。我们还可以假设,如果文件有文本编码问题,它也可能有行尾问题,这取决于有多少不同的 OSes 损坏了该文件。我会在行尾中断 rstrip
,然后使用 print 将数组重新组合为 StringIO
文件句柄。
当保留空格时,它可能很想通过漂亮的打印功能 运行 一个 XML 文档,但你不应该这样做,我们只是想更正小文本单元的编码而不改变其他任何东西。一个好的起点是查看您是否可以逐行、逐字而不是任意字节块地阅读文档,并忽略您正在处理 XML.[=40= 的事实]
我在这里利用了这样一个事实:如果文本超出 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, like perhaps a bad filename. You can use 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。
您需要注意 OS 如何编码文件名。您可以通过以下方式检索该信息:
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。