Ruby 1.9: Ungültige Bytesequenz in UTF-8

109

Ich schreibe einen Crawler in Ruby (1.9), der viel HTML von vielen zufälligen Sites verbraucht.
Beim Versuch, Links zu extrahieren, habe ich mich entschieden, .scan(/href="(.*?)"/i)anstelle von nokogiri / hpricot (größere Beschleunigung) zu verwenden. Das Problem ist, dass ich jetzt viele " invalid byte sequence in UTF-8" Fehler erhalte .
Soweit ich verstanden habe, verfügt die net/httpBibliothek über keine codierungsspezifischen Optionen, und das eingehende Material ist im Grunde nicht richtig gekennzeichnet.
Was wäre der beste Weg, um tatsächlich mit diesen eingehenden Daten zu arbeiten? Ich habe versucht, .encodedie Optionen "Ersetzen" und "Ungültig" festzulegen, aber bisher kein Erfolg ...

Marc Seeger
quelle
Etwas, das Zeichen brechen könnte, aber die Zeichenfolge für andere Bibliotheken gültig hält: valid_string = untrusted_string.unpack ('C *'). pack ('U *')
Marc Seeger
Nachdem ich das genaue Problem hatte, versuchte ich die gleichen anderen Lösungen. Keine Liebe. Versuchte Marc, aber es scheint alles zu verstümmeln. Bist du sicher 'U*'rückgängig zu machen 'C*'?
Jordan Feldstein
Nein, das tut es nicht :) Ich habe das gerade in einem Webcrawler verwendet, in dem es mir wichtig ist, dass Bibliotheken von Drittanbietern nicht mehr abstürzen als hier und da ein Satz.
Marc Seeger

Antworten:

172

In Ruby 1.9.3 ist es möglich, String.encode zu verwenden, um die ungültigen UTF-8-Sequenzen zu "ignorieren". Hier ist ein Ausschnitt, der sowohl in 1.8 ( iconv ) als auch in 1.9 ( String # encode ) funktioniert :

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

oder wenn Sie wirklich problematische Eingaben haben, können Sie eine doppelte Konvertierung von UTF-8 nach UTF-16 und zurück nach UTF-8 durchführen:

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
RubenLaguna
quelle
3
Mit einigen problematischen Eingaben verwende ich auch eine doppelte Konvertierung von UTF-8 zu UTF-16 und dann zurück zu UTF-8 file_contents.encode!('UTF-16', 'UTF-8', :invalid => :replace, :replace => '') file_contents.encode!('UTF-8', 'UTF-16')
RubenLaguna
7
Es besteht auch die Möglichkeit von force_encoding. Wenn Sie eine ISO8859-1 als UTF-8 gelesen haben (und diese Zeichenfolge daher ungültige UTF-8 enthält), können Sie sie mit the_string.force_encoding ("ISO8859-1") als ISO8859-1 "neu interpretieren" und einfach arbeiten mit dieser Zeichenfolge in ihrer realen Codierung.
RubenLaguna
3
Dieser Doppelcodierungstrick hat gerade meinen Speck gerettet! Ich frage mich, warum es erforderlich ist?
Johnf
1
Wo soll ich diese Zeilen setzen?
Lefsler
5
Ich denke, die doppelte Konvertierung funktioniert, weil sie eine Codierungskonvertierung erzwingt (und damit die Überprüfung auf ungültige Zeichen). Wenn die Quellzeichenfolge bereits in UTF-8 codiert .encode('UTF-8')ist , ist das Aufrufen nur ein No-Op, und es werden keine Überprüfungen ausgeführt. Ruby Core-Dokumentation zum Codieren . Wenn Sie es jedoch zuerst in UTF-16 konvertieren, werden alle Überprüfungen auf ungültige Byte-Sequenzen ausgeführt, und die Ersetzungen werden nach Bedarf durchgeführt.
Jo Hund
79

Die akzeptierte Antwort oder die andere Antwort funktionieren für mich. Ich habe diesen Beitrag gefunden, der vorgeschlagen hat

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

Dies hat das Problem für mich behoben.

Amir Raminfar
quelle
1
Dies hat das Problem für mich behoben und ich verwende gerne nicht veraltete Methoden (ich habe jetzt Ruby 2.0).
La-comadreja
1
Dieser ist der einzige, der funktioniert! Ich habe alle oben genannten Lösungen ausprobiert. Keiner von ihnen funktioniert mit einem String, der beim Testen von "fdsfdsf dfsf sfds fs sdf <div> Hallo <p> fooo ??? {! @ # $% ^ & * () _ +} <Verwendet wurde / p> </ div> \ xEF \ xBF \ xBD \ xef \ xbf \ x9c <div> \ xc2 \ x90 </ div> \ xc2 \ x90 "
Chihung Yu
1
Wofür ist das zweite Argument 'binär'?
Henley Chiu
24

Meine aktuelle Lösung lautet:

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

Dies wird zumindest die Ausnahmen beseitigen, die mein Hauptproblem waren

Marc Seeger
quelle
3
Ich verwende diese Methode in Kombination, valid_encoding?die zu erkennen scheint, wenn etwas nicht stimmt. val.unpack('C*').pack('U*') if !val.valid_encoding?.
Aaron Gibralter
Dieser hat für mich gearbeitet. Konvertiert meinen \xB0Rücken erfolgreich in Gradsymbole. Sogar das valid_encoding?kommt wieder wahr, aber ich überprüfe immer noch, ob es nicht wahr ist und entferne die beleidigenden Charaktere mit Amirs Antwort oben : string.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: ''). Ich hatte auch die force_encodingRoute ausprobiert , aber das schlug fehl.
Hamstar
Das ist toll. Vielen Dank.
d_ethier
8

Versuche dies:

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
Ranjithkumar Ravi
quelle
Beste Antwort für meinen Fall! Vielen Dank
Aldo
4

Ich empfehle Ihnen, einen HTML-Parser zu verwenden. Finden Sie einfach den schnellsten.

Das Parsen von HTML ist nicht so einfach, wie es scheint.

Browser analysieren ungültige UTF-8-Sequenzen in UTF-8-HTML-Dokumenten und setzen einfach das Symbol " ". Sobald die ungültige UTF-8-Sequenz im HTML analysiert wurde, ist der resultierende Text eine gültige Zeichenfolge.

Selbst innerhalb von Attributwerten müssen Sie HTML-Entitäten wie amp dekodieren

Hier ist eine großartige Frage, die zusammenfasst, warum Sie HTML nicht zuverlässig mit einem regulären Ausdruck analysieren können: RegEx stimmt mit offenen Tags überein, mit Ausnahme von in sich geschlossenen XHTML-Tags

Eduardo
quelle
2
Ich würde gerne den regulären Ausdruck behalten, da er ungefähr zehnmal schneller ist und ich den HTML-Code wirklich nicht richtig analysieren möchte, sondern nur Links extrahieren möchte. Ich sollte in der Lage sein, die ungültigen Teile in Ruby zu ersetzen, indem ich einfach Folgendes mache: ok_string = bad_string.encode ("UTF-8", {: invalid =>: replace ,: undef =>: replace}), aber das scheint nicht zu sein Arbeit :(
Marc Seeger
3

Das scheint zu funktionieren:

def sanitize_utf8(string)
  return nil if string.nil?
  return string if string.valid_encoding?
  string.chars.select { |c| c.valid_encoding? }.join
end
Spajus
quelle
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
Rusllonrails
quelle
2

Ich bin auf eine Saite gestoßen, die eine Mischung aus Englisch, Russisch und einigen anderen Alphabeten enthielt, was zu Ausnahmen führte. Ich brauche nur Russisch und Englisch, und das funktioniert derzeit für mich:

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
Nakilon
quelle
1

Während Nakilons Lösung funktioniert, zumindest um den Fehler zu überwinden, ließ ich in meinem Fall dieses seltsame F-Ed-Up-Zeichen aus Microsoft Excel in CSV konvertieren, das sich in Ruby als (get this) cyrillic K registrierte Ruby war ein fettgedrucktes K. Um dies zu beheben, habe ich 'iso-8859-1' verwendet, nämlich. CSV.parse(f, :encoding => "iso-8859-1"), was meine verrückten deaky kyrillischen K's zu einem viel handlicheren machte /\xCA/, mit dem ich sie dann entfernen konntestring.gsub!(/\xCA/, '')

boulder_ruby
quelle
Ich möchte noch einmal darauf hinweisen, dass Nakilons (und andere) Korrektur für kyrillische Zeichen war, die aus (haha) Cyrillia stammen, diese Ausgabe jedoch die Standardausgabe für eine CSV ist, die aus XLS konvertiert wurde!
boulder_ruby
0

Stellen Sie vor der Verwendung scansicher, dass der Content-TypeHeader der angeforderten Seite lautet text/html, da Links zu Bildern vorhanden sein können, die nicht in UTF-8 codiert sind. Die Seite kann auch nicht HTML sein, wenn Sie eine hrefin so etwas wie ein <link>Element aufgenommen haben. Wie Sie dies überprüfen, hängt davon ab, welche HTTP-Bibliothek Sie verwenden. String#ascii_only?Stellen Sie dann sicher, dass das Ergebnis nur ASCII mit ist (nicht UTF-8, da HTML nur ASCII verwenden soll, Entitäten können anderweitig verwendet werden). Wenn beide Tests bestanden sind, ist die Verwendung sicher scan.

Adrian
quelle
danke, aber das ist nicht mein Problem :) Ich extrahiere sowieso nur den Host-Teil der URL und drücke nur auf die Titelseite. Mein Problem ist, dass meine Eingabe anscheinend nicht UTF-8 ist und die 1.9-Codierung foo drunter und drüber geht
Marc Seeger
@Marc Seeger: Was meinst du mit "meine Eingabe"? Stdin, die URL oder der Seitenkörper?
Adrian
HTML kann in UTF-8 codiert werden: en.wikipedia.org/wiki/Character_encodings_in_HTML
Eduardo
meine Eingabe = der Seitenkörper @Eduardo: Ich weiß. Mein Problem ist, dass die Daten von net / http von Zeit zu Zeit eine schlechte Codierung zu haben scheinen
Marc Seeger
Es ist nicht ungewöhnlich, dass Webseiten tatsächlich eine schlechte Codierung aufweisen. Der Antwortheader könnte sagen, dass es sich um eine Codierung handelt, die dann aber tatsächlich eine andere Codierung bereitstellt.
versunkene Stadt
-1

Wenn Sie sich nicht für die Daten "interessieren", können Sie einfach Folgendes tun:

search_params = params[:search].valid_encoding? ? params[:search].gsub(/\W+/, '') : "nothing"

Ich habe es nur valid_encoding?bestanden. Meins ist ein Suchfeld, und so fand ich immer wieder die gleiche Verrücktheit, also benutzte ich etwas wie: nur um das System nicht kaputt zu machen. Da ich die Benutzererfahrung nicht so kontrolliere, dass sie vor dem Senden dieser Informationen automatisch validiert wird (z. B. automatische Rückmeldung, um "Dummy-up!" Zu sagen), kann ich sie einfach aufnehmen, entfernen und leere Ergebnisse zurückgeben.

pjammer
quelle