ruby 1.9: séquence d'octets invalide en UTF-8
j'écris un crawler dans Ruby (1.9) qui consomme beaucoup de HTML à partir de beaucoup de sites aléatoires.
En essayant d'extraire des liens, j'ai décidé d'utiliser .scan(/href="(.*?)"/i)
au lieu de nokogiri/hpricot (speedup majeur). Le problème est que je reçois beaucoup de " invalid byte sequence in UTF-8
erreurs".
D'après ce que j'ai compris, la bibliothèque net/http
n'a pas d'options spécifiques d'encodage et la substance qui entre n'est pas correctement étiquetée.
Quelle serait la meilleure façon de travailler avec les données entrantes? J'ai essayé .encode
avec les options remplacer et invalider définies, mais aucun succès jusqu'à présent...
11 réponses
dans Ruby 1.9.3 il est possible d'utiliser la chaîne.Encoder pour "ignorer" les séquences UTF-8 invalides. Voici un extrait qui fonctionnera à la fois en 1.8 ( iconv ) et 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 entrées vraiment gênantes, vous pouvez faire une double conversion de UTF-8 en 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
la réponse acceptée ou l'autre réponse ne fonctionne pas pour moi. J'ai trouvé ce post qui suggérait
string.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
cela a réglé le problème pour moi.
ma solution actuelle est d'exécuter:
my_string.unpack("C*").pack("U*")
Cela permettra au moins de se débarrasser des exceptions qui était mon principal problème
essayez 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
je vous recommande D'utiliser un analyseur HTML. Il suffit de trouver le plus rapide.
L'analyse HTML n'est pas aussi facile qu'il n'y paraît.
les navigateurs analysent les séquences UTF-8 invalides, dans les documents HTML UTF-8, en mettant simplement le symbole"". Ainsi, 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 les entités HTML comme amp
ici est une grande question qui résume pourquoi vous ne pouvez pas analyser de façon fiable HTML avec une expression régulière: RegEx match ouvert à l'exception des balises XHTML autonome tags
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
j'ai rencontré la corde, qui a eu des mélanges de l'anglais, le russe et quelques autres alphabets, qui a causé l'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
alors que la solution de Nakilon fonctionne, au moins aussi loin que passer l'erreur, dans mon cas, j'ai eu ce caractère bizarre f-ed up provenant de Microsoft Excel converti en CSV qui s'enregistrait dans ruby comme un (get this) cyrillic K qui dans ruby était un K en caractères gras.pour corriger cela, j'ai utilisé 'iso-8859-1' viz. CSV.parse(f, :encoding => "iso-8859-1")
, qui a transformé mes k's deaky deaky en un /\xCA/
beaucoup plus gérable, que je pourrais alors enlever avec string.gsub!(/\xCA/, '')
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
avant d'utiliser scan
, assurez-vous que l'en-tête Content-Type
de la page demandée est text/html
, car il peut y avoir des liens vers des choses comme des images qui ne sont pas encodées dans UTF-8. La page pourrait aussi être non-html si vous avez trouvé un href
dans quelque chose comme un élément <link>
. La façon de vérifier cela varie selon la bibliothèque HTTP que vous utilisez. Ensuite, assurez-vous que le résultat est seulement ascii avec String#ascii_only?
(pas UTF-8 parce que HTML est seulement censé utiliser ascii, entities peut être utilisé autrement). Si ces deux tests sont positifs, il est sans danger d'utiliser scan
.
si vous ne vous "souciez" pas des données, vous pouvez juste faire quelque chose comme:
search_params = params[:search].valid_encoding? ? params[:search].gsub(/\W+/, '') : "nothing"
je viens d'utiliser valid_encoding?
pour passer. Le mien est un champ de recherche, et donc je trouvais la même bizarrerie encore et encore donc j'ai utilisé quelque chose comme: juste pour avoir le système ne casse pas. Puisque je ne contrôle pas l'expérience utilisateur d'autovalidate avant d'envoyer cette information (comme auto feedback pour dire " dummy up!") Je peux juste la prendre dans, bande de sortir et retour vide résultats.