ruby 1.9: sequência de bytes inválida em UTF-8

Estou escrevendo um rastreador em Ruby (1.9) que consome muito HTML de muitos sites randoms.
Ao tentar extrair links, decidi usar apenas .scan(/href="(.*?)"/i) vez de nokogiri / hpricot (maior aceleração). O problema é que agora recebo muitos erros de ” invalid byte sequence in UTF-8 “.
Pelo que eu entendi, a biblioteca net/http não tem nenhuma opção específica de codificação e o material que entra basicamente não é apropriadamente marcado.
Qual seria a melhor maneira de realmente trabalhar com esses dados recebidos? Eu tentei. .encode com o replace e opções inválidas definido, mas nenhum sucesso até agora …

No Ruby 1.9.3 é possível usar String.encode para “ignorar” as seqüências UTF-8 inválidas. Aqui está um trecho que funcionará tanto em 1.8 ( iconv ) quanto em 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 

ou se você tiver uma input muito problemática, você pode fazer uma conversão dupla de UTF-8 para UTF-16 e de volta para 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 

A resposta aceita nem a outra resposta funcionam para mim. Eu encontrei este post que sugeriu

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

Isso resolveu o problema para mim.

Minha solução atual é executar:

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

Isso vai pelo menos se livrar das exceções que foi o meu principal problema

Tente isto:

 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 

Eu recomendo que você use um analisador de HTML. Basta encontrar o mais rápido.

Analisar HTML não é tão fácil quanto parece.

Os navegadores analisam sequências UTF-8 inválidas, em documentos HTML UTF-8, apenas colocando o símbolo ” “. Então, uma vez que a seqüência UTF-8 inválida no HTML é analisada, o texto resultante é uma string válida.

Mesmo dentro de valores de atributos, você precisa decodificar entidades HTML como amp

Aqui está uma ótima pergunta que resume por que você não pode analisar com segurança HTML com uma expressão regular: tags abertas de correspondência RegEx, exceto tags independentes XHTML

 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 

Eu encontrei string, que tinha misturas de inglês, russo e alguns outros alfabetos, o que causou exceção. Eu preciso apenas de russo e inglês, e isso atualmente funciona para mim:

 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 

Enquanto a solução de Nakilon funciona, pelo menos no que diz respeito a superar o erro, no meu caso, eu tive esse estranho personagem criado a partir do Microsoft Excel convertido para CSV que estava registrando em ruby ​​como um (get this) cyrillic K que em ruby era um K em negrito. Para corrigir isso, usei ‘iso-8859-1’ viz. CSV.parse(f, :encoding => "iso-8859-1") , que transformou meus Ks esquisitos e cirílicos em um /\xCA/ muito mais gerenciável, que eu poderia remover com string.gsub!(/\xCA/, '')

Isso parece funcionar:

 def sanitize_utf8(string) return nil if string.nil? return string if string.valid_encoding? string.chars.select { |c| c.valid_encoding? }.join end 

Antes de usar a scan , verifique se o header Content-Type da página solicitada é text/html , pois pode haver links para itens como imagens não codificadas em UTF-8. A página também pode ser não-html se você pegar um href em algo como um elemento . Como verificar isso varia em qual biblioteca HTTP você está usando. Então, verifique se o resultado é apenas ascii com String#ascii_only? (não UTF-8 porque o HTML só deveria estar usando ascii, entidades podem ser usadas de outra forma). Se ambos os testes passarem, é seguro usar a scan .

Se você não se importa com os dados, basta fazer algo como:

search_params = params[:search].valid_encoding? ? params[:search].gsub(/\W+/, '') : "nothing"

Acabei de usar o valid_encoding? passar por isso. O meu é um campo de pesquisa, e assim eu estava encontrando a mesma estranheza várias vezes, então usei algo como: apenas para que o sistema não quebrasse. Desde que eu não controle a experiência do usuário para auto-validação antes de enviar esta informação (como feedback automático para dizer “manequim up!”) Eu posso apenas levá-lo, retirá-lo e retornar resultados em branco.