さまざまな形式のテキスト破損または Mojibake がインポート中にデータ品質の問題を引き起こす外部ファイルの XML ドキュメントとファイル名が多数あります。文字列の修正に関する StackOverflow のさまざまな投稿を読んだことがありますが、体系的な方法でテキストをクリーンアップする方法を実際に概説してdecode
いencode
ません。Python 2.7 を使用して XML ファイルとファイル名を復元するにはどうすればよいですか?
1 に答える
仮定を立てる必要があります
遭遇する文字の種類について推測できない場合は、おそらく問題が発生しています。したがって、私たちのドキュメントでノルウェー語のアルファベットを合理的に想定できるのは良いことA-Å
です。遭遇するすべてのドキュメントを自動修正する魔法のツールはありません。
å
したがって、このドメイン内では、ファイルに UTF-8 の 2 バイト表現0xc3 0xa5
またはUnicodeが含まれている可能性があることがわかっています。Latin -1およびWindows-1252は、それ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 であっても、それを単語と見なすことができ、タグについてあまり心配する必要はありません。または、単語が本当に単語である場合は、見つけることができる最小単位が必要なだけです。また、ファイルにテキスト エンコーディングの問題がある場合、そのファイルを破損した OS の数に応じて、おそらく行末の問題もあると推測できます。行末で中断し、ファイルハンドルrstrip
への出力を使用して配列を再結合します。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である場合、おそらくファイル名が悪いなどです。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 の同等性Ã
を使用して表すいくつかの方法の 1 つです。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 が返されます。
OS がファイル名をエンコードする方法に注意する必要があります。その情報は次の方法で取得できます。
file_system_encoding = sys.getfilesystemencoding()
では、素晴らしいfile_system_encoding
ですUTF-8
ね。次に、2 つの一見同一の 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 内では、BOM は推奨されておらず、バイト オーダーに関しては意味がありません。それらの唯一の値は、何かが UTF-8 でエンコードされていることを示すことです。ただし、テキスト エンコーディングの問題を考えると、デフォルトは UTF-8 であり、そうあるべきです。