web-dev-qa-db-fra.com

ruby 1.9: séquence d'octets non valide dans UTF-8

J'écris un robot dans Ruby (1.9) qui consomme beaucoup de HTML provenant de nombreux sites aléatoires.
En essayant d'extraire des liens, j'ai décidé d'utiliser simplement .scan(/href="(.*?)"/i) au lieu de nokogiri/hpricot (accélération majeure). Le problème est que je reçois maintenant beaucoup d'erreurs "invalid byte sequence in UTF-8".
D'après ce que j'ai compris, la bibliothèque net/http ne contient aucune option d'encodage spécifique et les éléments fournis ne sont en principe pas correctement étiquetés.
Quel serait le meilleur moyen de travailler avec ces données entrantes? J'ai essayé .encode avec l'ensemble des options replace et invalid, mais aucun succès jusqu'à présent ...

106
Marc Seeger

Dans Ruby 1.9.3, il est possible d’utiliser String.encode pour "ignorer" les séquences UTF-8 non valides. Voici un extrait qui fonctionnera à la fois en 1.8 ( iconv ) et en 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 si vous avez des problèmes de saisie, vous pouvez effectuer une double conversion de UTF-8 à UTF-16 et revenir à 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

La réponse acceptée, ni l'autre réponse ne fonctionnent pour moi. J'ai trouvé ce post qui a suggéré 

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

Cela a résolu le problème pour moi. 

77
Amir Raminfar

Ma solution actuelle consiste à exécuter: 

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

Cela éliminera au moins les exceptions qui étaient mon principal problème

23
Marc Seeger

Essaye ça:

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

Je vous recommande d'utiliser un analyseur HTML. Il suffit de trouver le plus rapide.

L'analyse HTML n'est pas aussi facile que cela puisse paraître.

Les navigateurs analysent les séquences UTF-8 non valides, dans les documents HTML UTF-8, en mettant simplement le symbole "". Donc, une fois que la séquence UTF-8 invalide dans le HTML est analysée, le texte résultant est une chaîne valide.

Même à l'intérieur des valeurs d'attribut, vous devez décoder des entités HTML comme amp

Voici une excellente question qui résume la raison pour laquelle vous ne pouvez pas analyser HTML de manière fiable avec une expression régulière: RegEx correspond aux balises ouvertes, à l'exception des balises autonomes XHTML

4
Eduardo

Cela semble fonctionner:

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

J'ai rencontré string, qui mélangeait l'anglais, le russe et d'autres alphabets, ce qui a provoqué une exception. Je n'ai besoin que du russe et de l'anglais, et cela fonctionne actuellement pour moi:

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

Tandis que la solution de Nakilon fonctionne, au moins en ce qui concerne l’erreur, dans mon cas, j’ai converti cet étrange personnage créé à partir de Microsoft Excel en CSV enregistré dans Ruby comme un Ruby était un K. audacieux. Pour résoudre ce problème, j'ai utilisé 'iso-8859-1', à savoir. CSV.parse(f, :encoding => "iso-8859-1"), qui a transformé mon effrayant Keaks cyrillique en un /\xCA/ beaucoup plus maniable, que je pourrais ensuite supprimer avec string.gsub!(/\xCA/, '')

1
boulder_ruby

Avant d'utiliser scan, assurez-vous que l'en-tête Content-Type de la page demandée est text/html, car il peut exister des liens vers des éléments tels que des images non codées en UTF-8. La page peut également être non-HTML si vous avez récupéré une href dans quelque chose comme un élément <link>. Comment vérifier cela varie selon la bibliothèque HTTP que vous utilisez. Ensuite, assurez-vous que le résultat est uniquement ascii avec String#ascii_only? (pas UTF-8 car HTML est supposé utiliser uniquement ascii, les entités peuvent être utilisées autrement). Si ces deux tests réussissent, vous pouvez utiliser scan en toute sécurité.

0
Adrian