web-dev-qa-db-ja.com

ruby 1.9:UTF-8のバイトシーケンスが無効です

私はRuby(1.9)でクローラーを書いています。これは、多くのランダムなサイトから多くのHTMLを消費します。
リンクを抽出しようとしたときに、nokogiri/hpricotの代わりに.scan(/href="(.*?)"/i)を使用することにしました(大幅な高速化)。問題は、多くの「invalid byte sequence in UTF-8」エラーを受け取るようになったことです。
私が理解したことから、net/httpライブラリにはエンコーディング固有のオプションがなく、入ってくるものは基本的に適切にタグ付けされていません。
着信データを実際に処理する最良の方法は何でしょうか?置換オプションと無効なオプションを設定して.encodeを試しましたが、今のところ成功していません...

108
Marc Seeger

Ruby 1.9.3では、String.encodeを使用して無効なUTF-8シーケンスを「無視」することができます。 1.8( iconv )と1.9( String#encode )の両方で機能するスニペットを次に示します。

require 'iconv' unless String.method_defined?(:encode)
if String.method_defined?(:encode)
  file_contents.encode!('UTF-8', 'UTF-8', :invalid => :replace)
else
  ic = Iconv.new('UTF-8', 'UTF-8//IGNORE')
  file_contents = ic.iconv(file_contents)
end

または、本当に面倒な入力がある場合は、UTF-8からUTF-16への二重変換を行い、UTF-8に戻すことができます。

require 'iconv' unless String.method_defined?(:encode)
if String.method_defined?(:encode)
  file_contents.encode!('UTF-16', 'UTF-8', :invalid => :replace, :replace => '')
  file_contents.encode!('UTF-8', 'UTF-16')
else
  ic = Iconv.new('UTF-8', 'UTF-8//IGNORE')
  file_contents = ic.iconv(file_contents)
end
171
ecerulm

受け入れられた答えも他の答えも私のために働く。 この投稿 が見つかりました

string.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')

これで問題は解決しました。

79
Amir Raminfar

私の現在のソリューションは、実行することです:

my_string.unpack("C*").pack("U*")

これは少なくとも私の主な問題であった例外を取り除くでしょう

23
Marc Seeger

これを試して:

def to_utf8(str)
  str = str.force_encoding('UTF-8')
  return str if str.valid_encoding?
  str.encode("UTF-8", 'binary', invalid: :replace, undef: :replace, replace: '')
end
8

HTMLパーサーを使用することをお勧めします。最速のものを見つけてください。

HTMLの解析は、見かけほど簡単ではありません。

ブラウザは、UTF-8 HTMLドキュメント内の無効なUTF-8シーケンスを解析し、単に「�」記号を付けます。したがって、HTMLの無効なUTF-8シーケンスが解析されると、結果のテキストは有効な文字列になります。

属性値の中であっても、ampのようなHTMLエンティティをデコードする必要があります

ここに、正規表現でHTMLを確実に解析できない理由を要約した素晴らしい質問があります。 RegExは、XHTML自己完結型タグ

4
Eduardo

これはうまくいくようです:

def sanitize_utf8(string)
  return nil if string.nil?
  return string if string.valid_encoding?
  string.chars.select { |c| c.valid_encoding? }.join
end
3
Spajus
attachment = file.read

begin
   # Try it as UTF-8 directly
   cleaned = attachment.dup.force_encoding('UTF-8')
   unless cleaned.valid_encoding?
     # Some of it might be old Windows code page
     cleaned = attachment.encode( 'UTF-8', 'Windows-1252' )
   end
   attachment = cleaned
 rescue EncodingError
   # Force it to UTF-8, throwing out invalid bits
   attachment = attachment.force_encoding("ISO-8859-1").encode("utf-8", replace: nil)
 end
3
rusllonrails

英語、ロシア語、およびその他のアルファベットが混在している文字列に遭遇したため、例外が発生しました。私はロシア語と英語だけが必要で、これは現在私のために働いています:

ec1 = Encoding::Converter.new "UTF-8","Windows-1251",:invalid=>:replace,:undef=>:replace,:replace=>""
ec2 = Encoding::Converter.new "Windows-1251","UTF-8",:invalid=>:replace,:undef=>:replace,:replace=>""
t = ec2.convert ec1.convert t
2
Nakilon

Nakilonのソリューションが機能している間、少なくともエラーを回避する限り、私の場合、Microsoft Excelから発信されたこの奇妙なf-edキャラクターをRubyに(getこれ)Rubyで太字のKであったキリル文字K。これを修正するために、「iso-8859-1」を使用しました。 CSV.parse(f, :encoding => "iso-8859-1")。これにより、私の気紛れなキリル文字のKがはるかに管理しやすい/\xCA/に変わり、string.gsub!(/\xCA/, '')で削除できます

1
boulder_ruby

scanを使用する前に、要求されたページのContent-Typeヘッダーがtext/htmlであることを確認してください。UTF-8でエンコードされていない画像などへのリンクがある可能性があります。 <link>要素のようなものでhrefを選択した場合、ページはHTML以外になる可能性もあります。これを確認する方法は、使用しているHTTPライブラリによって異なります。次に、結果がString#ascii_only?を持つasciiのみであることを確認します(HTMLはasciiのみを使用するため、UTF-8ではありません。エンティティは他の方法で使用できます)。これらのテストの両方に合格した場合、scanを使用しても安全です。

0
Adrian