117 votes

Ruby 1.9: séquence d'octets invalide en UTF-8

Je suis en train d'écrire un crawler en Ruby (1.9) qui consomme beaucoup de HTML à partir de nombreux sites aléatoires.
Lorsque j'essaie d'extraire des liens, j'ai décidé d'utiliser simplement .scan(/href="(.*?)"/i) au lieu de nokogiri/hpricot (une importante accélération). Le problème est que je reçois maintenant beaucoup d'erreurs "séquence d'octets non valide en UTF-8".
D'après ce que j'ai compris, la bibliothèque net/http n'a pas d'options spécifiques d'encodage et les données reçues ne sont essentiellement pas correctement étiquetées.
Quelle serait la meilleure façon de travailler réellement avec ces données entrantes ? J'ai essayé .encode avec les options de remplacement et d'invalidité définies, mais jusqu'à présent sans succès...

0 votes

Quelque chose qui pourrait casser les caractères, mais conserve la chaîne valide pour d'autres bibliothèques : valid_string = untrusted_string.unpack('C*').pack('U*')

0 votes

Ayant exactement le même problème, essayé les mêmes autres solutions. Pas d'amour. J'ai essayé celle de Marc, mais cela semble tout mélanger. Êtes-vous sûr que 'U*' annule 'C*'?

0 votes

Non, ça ne fait pas :) Je l'ai juste utilisé dans un webcrawler où je me soucie plus du fait que des bibliothèques tierces ne plantent pas que d'une phrase par-ci par-là.

176voto

ecerulm Points 1834

En Ruby 1.9.3, il est possible d'utiliser String.encode pour "ignorer" les séquences UTF-8 invalides. 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)

ou si vous avez une entrée vraiment problématique, vous pouvez effectuer une double conversion de l'UTF-8 à l'UTF-16 et de retour à l'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)

0 votes

J'ai comparé avec ma solution et j'ai trouvé que la mienne perd quelques lettres, au moins : "Alena V.\". Alors que votre solution le garde : "Ale\u0308na V.\". Bien.

3 votes

Avec certaines entrées problématiques, j'utilise également une double conversion de UTF-8 à UTF-16 puis de retour à UTF-8 file_contents.encode!('UTF-16', 'UTF-8', :invalid => :replace, :replace => '') file_contents.encode!('UTF-8', 'UTF-16')

7 votes

Il existe également l'option de force_encoding. Si vous avez lu un fichier ISO8859-1 comme un UTF-8 (et donc que la chaîne contient du UTF-8 invalide), vous pouvez le "réinterpréter" en tant qu'ISO8859-1 avec the_string.force_encoding("ISO8859-1") et simplement travailler avec cette chaîne dans son encodage réel.

83voto

Amir Raminfar Points 17939

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

chaîne.encode!('UTF-8', 'binaire', invalid: :replace, undef: :replace, replace: '')

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

1 votes

Cela a résolu le problème pour moi et j'aime utiliser des méthodes non obsolètes (j'ai maintenant Ruby 2.0).

1 votes

Cette solution est la seule qui fonctionne! J'ai essayé toutes les solutions ci-dessus, aucune d'entre elles ne fonctionne. Chaîne utilisée pour le test "fdsfdsf dfsf sfds fs sdf

hello

fooo??? {!@#$%^&*()_+}

\xEF\xBF\xBD \xef\xbf\x9c

\xc2\x90

\xc2\x90"

1 votes

Quel est le deuxième argument 'binary' pour?

25voto

Marc Seeger Points 1213

Ma solution actuelle consiste à exécuter :

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

Cela permettra au moins de se débarrasser des exceptions qui étaient mon principal problème

3 votes

Je utilise cette méthode en combinaison avec valid_encoding? qui semble détecter quand quelque chose ne va pas. val.unpack('C*').pack('U*') if !val.valid_encoding?.

0 votes

Cela a fonctionné pour moi. Convertit avec succès mon \xB0 en symboles de degrés. Même la méthode valid_encoding? renvoie true, mais je vérifie quand même au cas où et j’enlève les caractères offensants en utilisant la réponse d'Amir ci-dessus : string.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: ''). J'avais également essayé la méthode force_encoding mais cela a échoué.

0 votes

C'est génial. Merci.

13voto

Rubinsh Points 1634

Je pense que c'est ce que vous cherchez :

fixing-invalid-utf-8-in-ruby-revisited

Veuillez noter - cette réponse est seulement pertinente pour ruby 1.9.2.

En ruby 1.9.3 et plus, Iconv est obsolète.

8voto

Ranjithkumar Ravi Points 851

Essayer ceci:

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

0 votes

Meilleure réponse pour mon cas! Merci

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X