2010-06-06 6 views
100

J'écris un moteur de balayage 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 (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 n'a pas d'options spécifiques d'encodage et les éléments qui entrent dans la base ne sont pas correctement étiquetés.
Quelle serait la meilleure façon de travailler avec ces données entrantes? J'ai essayé .encode le remplacer et les options invalides ensemble, mais sans succès jusqu'à présent ...ruby ​​1.9: séquence d'octets invalide dans UTF-8

+0

quelque chose qui pourrait briser les caractères, mais conserve la chaîne valable pour d'autres bibliothèques: valid_string = untrusted_string.unpack ('C *') paquet ('U *. ') –

+0

Ayant le problème exact, essayé les mêmes solutions. Pas d'amour. J'ai essayé Marc, mais il semble tout brouiller. Êtes-vous sûr que '' U '' 'défait' 'C *' '? –

+0

Non, ce n'est pas le cas :) Je l'ai juste utilisé dans un webcrawler où je m'intéresse à ce que les bibliothèques tierces ne se plantent pas plus que moi à propos d'une phrase ici et là. –

Répondre

0

Avant d'utiliser scan, assurez-vous que l'en-tête de page demandée est Content-Typetext/html, car il peut y avoir des liens vers des choses comme des images qui sont pas encodé en UTF-8. La page pourrait également être non-html si vous avez ramassé un href dans quelque chose comme un élément <link>. Comment vérifier cela varie sur quelle bibliothèque HTTP vous utilisez. Ensuite, assurez-vous que le résultat est seulement ascii avec String#ascii_only? (pas UTF-8 parce que le HTML est seulement supposé utiliser ascii, les entités peuvent être utilisées autrement). Si ces deux tests réussissent, vous pouvez utiliser scan en toute sécurité.

+0

merci, mais ce n'est pas mon problème :) J'extrais seulement la partie hôte de l'URL de toute façon et ne frappe que la première page. Mon problème est que mon entrée n'est apparemment pas UTF-8 et que le foo de l'encodage 1.9 ne fonctionne pas. –

+0

@Marc Seeger: Que voulez-vous dire par "mon entrée"? Stdin, l'URL ou le corps de la page? – Adrian

+0

HTML peut être codé en UTF-8: http://en.wikipedia.org/wiki/Character_encodings_in_HTML – Eduardo

4

Je vous recommande d'utiliser un analyseur HTML. Trouve juste le plus rapide.

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

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

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

est une question qui résume pourquoi vous ne pouvez pas analyser de manière fiable HTML avec une expression régulière: RegEx match open tags except XHTML self-contained tags

+2

J'aimerais garder l'expression rationnelle car elle est environ 10 fois plus rapide et je ne veux vraiment pas analyser le HTML correctement, mais je veux juste extraire les liens. Je devrais être en mesure de remplacer les parties invalides dans Ruby en faisant juste: ok_string = bad_string.encode ("UTF-8", {: invalide =>: remplacer,: undef =>: remplacer}) mais cela doesn ne semble pas fonctionner :( –

2

J'ai rencontré chaîne, qui avait des mélanges d'anglais, de russe et d'autres alphabets, ce qui a causé une exception. J'ai besoin que le russe et 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 
23

Ma solution actuelle consiste à exécuter:

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

Cela au moins se débarrasser des exceptions qui était mon principal problème

+3

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

+0

Celui-ci a fonctionné pour moi et a réussi à convertir mes' \ xB0' en symboles degrés Même le 'valid_encoding?' revient vrai mais je vérifie toujours s'il ne le fait pas et dépouille les caractères offensants en utilisant La réponse d'Amir ci-dessus: 'string.encode! ('UTF-8', 'binary', invalide:: replace, undef:: replace, replace: '')'. J'avais aussi essayé la route 'force_encoding' mais ça a échoué. – hamstar

+0

C'est génial, merci –

166

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 fonctionne aussi bien dans 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 entrée vraiment gênants, vous pouvez faire une double conversion UTF-8 en UTF-16 et retour à 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 
+0

J'ai comparé avec ma solution et trouvé, que le mien perd quelques lettres, au moins 'ё':' "Alena V. \" '. Alors que votre solution le conserve: '" Ale \ u0308na V \ ". Agréable. – Nakilon

+3

Avec une entrée problématique, j'utilise aussi une double conversion de UTF-8 à UTF-16 puis de nouveau à UTF-8 'file_contents.encode! ('UTF-16', 'UTF-8',: invalid =>: replace ,: replace => '') '' file_contents.encode! ('UTF-8', 'UTF-16') ' – ecerulm

+7

Il y a aussi l'option' force_encoding'. Si vous avez lu un ISO8859-1 comme un UTF-8 (et donc cette chaîne contient un UTF-8 invalide) alors vous pouvez le "réinterpréter" comme ISO8859-1 avec the_string.force_encoding ("ISO8859-1") et juste travailler avec cette chaîne dans son encodage réel. – ecerulm

1

Alors que la solution de Nakilon fonctionne, au moins jusqu'à ce qu'ils aient passé l'erreur, dans mon cas, j'ai eu cette étrange caractère f-ed up provenant de Microsoft Excel converti au format CSV qui enregistrait en ruby comme un (obtenir ceci) K cyrillique qui dans ruby ​​était un K. en gras Pour réparer cela j'ai utilisé 'iso-8859-1' à savoir.CSV.parse(f, :encoding => "iso-8859-1"), qui a tourné mon Freaky Deaky cyrillique K de dans beaucoup plus facile à gérer /\xCA/, que je pourrais alors enlever avec string.gsub!(/\xCA/, '')

+0

Encore une fois, je veux juste noter que pendant que Nakilon (et d'autres) fixait les caractères cyrilliques provenant de (haha) Cyrillia, cette sortie est sortie standard pour un csv qui a été converti à partir de xls! –

1

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 
69

La réponse acceptée ni l'autre travail de réponse pour moi. J'ai trouvé this post qui a suggéré

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

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

+1

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

Celui-ci est le seul qui fonctionne! J'ai essayé toute la solution ci-dessus, aucun d'entre eux travaillent chaîne qui a utilisé dans le test "fdsfdsf dfsf SFD fs sdf

hello

fooo??? {[email protected]#$%^&*()_+}

\ xEF \ XBF \ XBD \ xef \ XBF \ x9c
\xc2\x90
\ xc2 \ x90" –

-1

Si vous ne le faites pas « soins » sur les données que 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 obtenir passé il. Le mien est un champ de recherche, et donc je trouvais la même bizarrerie encore et encore alors j'ai utilisé quelque chose comme: juste pour que le système ne se casse pas. Puisque je ne contrôle pas l'expérience de l'utilisateur pour autovalidate avant d'envoyer cette information (comme rétroaction automatique pour dire "dummy up!") Je peux juste le prendre, dépouiller et renvoyer des résultats vierges.

6

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

Meilleure réponse pour mon Cas! Merci – Aldo

3
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 
Questions connexes