it-swarm.dev

ruby 1.9: seqüência de bytes gravada em UTF-8

Estou escrevendo um rastreador em Ruby (1.9) que consome muito HTML de muitos sites aleatórios.
Ao tentar extrair links, decidi usar apenas .scan(/href="(.*?)"/i) ao invés de nokogiri/hpricot (speedup maior). O problema é que agora recebo muitos erros "invalid byte sequence in UTF-8".
De acordo com o que entendi, a biblioteca net/http não tem nenhuma opção específica de codificação e o material que vem é basicamente não marcado corretamente.
Qual seria a melhor maneira de trabalhar com os dados recebidos? Eu tentei .encode com o conjunto de opções de substituição e inválido, mas nenhum sucesso até agora ...

106
Marc Seeger

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 ) como 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 entrada 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
170
ecerulm

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. 

77
Amir Raminfar

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

23
Marc Seeger

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
8
Ranjithkumar Ravi

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 HTML com uma expressão regular de forma confiável: tags abertas de correspondência RegEx, exceto tags auto-contidas XHTML

4
Eduardo

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
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

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
2
Nakilon

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 foi um K. em negrito Para corrigir isso, usei 'iso-8859-1' viz. CSV.parse(f, :encoding => "iso-8859-1"), que transformou meus Ks cirílicos freaky esquisitos em um /\xCA/ muito mais gerenciável, que eu poderia então remover com string.gsub!(/\xCA/, '')

1
boulder_ruby

Antes de usar scan, certifique-se de que o cabeçalho Content-Type da página solicitada seja text/html, pois pode haver links para coisas como imagens que não são codificadas em UTF-8. A página também pode ser não-html se você pegar uma href em algo como um elemento <link>. Como verificar isso varia em qual biblioteca HTTP você está usando. Então, certifique-se de que o resultado seja apenas ascii com String#ascii_only? (não UTF-8, pois o HTML deve estar apenas usando ascii, caso contrário, entidades podem ser usadas). Se ambos os testes passarem, é seguro usar scan.

0
Adrian