ruby 1.9: UTF-8'de geçersiz bayt dizisi


109

Ruby (1.9) ile birçok rastgele siteden çok fazla HTML tüketen bir tarayıcı yazıyorum.
Bağlantıları çıkarmaya çalışırken, sadece .scan(/href="(.*?)"/i)nokogiri / hpricot (büyük hızlanma) yerine kullanmaya karar verdim . Sorun şu ki, artık birçok " invalid byte sequence in UTF-8" hata alıyorum .
Anladığım kadarıyla, net/httpkütüphanenin kodlamaya özgü herhangi bir seçeneği yok ve gelen şeyler temelde doğru şekilde etiketlenmemiş.
Bu gelen verilerle gerçekten çalışmanın en iyi yolu nedir? .encodeDeğiştir ve geçersiz seçenekler setiyle denedim , ancak şu ana kadar başarılı olamadım ...


karakterleri bozabilecek, ancak dizeyi diğer kütüphaneler için geçerli tutan bir şey: valid_string = untrusted_string.unpack ('C *'). pack ('U *')
Marc Seeger

Tam sorunu yaşarken, aynı diğer çözümleri denedim. Aşk yok. Marc'ı denedim, ama görünüşe göre her şeyi alt üst ediyor. 'U*'Unutmayacağına emin 'C*'misin?
Ürdün Feldstein

Hayır, öyle değil :) Bunu, burada ve orada bir cümle için yaptığımdan daha fazla çökmeyen 3. parti kitaplıklara önem verdiğim bir web tarayıcısı için kullandım.
Marc Seeger

Yanıtlar:


172

Ruby 1.9.3'te, geçersiz UTF-8 dizilerini "yok saymak" için String.encode kullanmak mümkündür. Hem 1.8 ( iconv ) hem de 1.9 ( Dize # kodlama ) ile çalışacak bir kod parçacığı :

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

veya gerçekten sorunlu bir girdiniz varsa, UTF-8'den UTF-16'ya ve UTF-8'e çift dönüşüm yapabilirsiniz:

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

3
Bazı sorunlu girdilerle, UTF-8'den UTF-16'ya ve ardından UTF-8'e çift dönüşüm de kullanıyorum file_contents.encode!('UTF-16', 'UTF-8', :invalid => :replace, :replace => '') file_contents.encode!('UTF-8', 'UTF-16')
RubenLaguna

7
Seçeneği de var force_encoding. Bir ISO8859-1'i UTF-8 olarak okursanız (ve dolayısıyla bu dize geçersiz UTF-8 içeriyorsa), o zaman bunu ISO8859-1 olarak_string.force_encoding ("ISO8859-1") ile "yeniden yorumlayabilir" ve yalnızca çalışabilirsiniz gerçek kodlamasında bu dizeyle.
RubenLaguna

3
Bu çift kodlama numarası az önce Bacon'umu kurtardı! Acaba neden gerekli?
johnf

1
Bu satırları nereye koymalıyım?
Lefsler

5
Bence çift dönüşüm, bir kodlama dönüşümünü zorladığı için (ve bununla birlikte geçersiz karakterlerin kontrolü) işe yarıyor. Kaynak dizesi zaten UTF-8 olarak kodlandıysa, yalnızca çağırma .encode('UTF-8')işlemsizdir ve kontrol çalıştırılmaz. Kodlama için Ruby Core Belgeleri . Ancak, bunu UTF-16'ya dönüştürmek, önce geçersiz bayt dizileri için tüm kontrolleri çalıştırmaya zorlar ve gerektiğinde değiştirmeler yapılır.
Jo Hund

79

Kabul edilen cevap ne de diğer cevap benim için işe yarıyor. Bulduğum bu yazı önerdi

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

Bu benim için sorunu çözdü.


1
Bu benim için sorunu çözdü ve artık kullanılmayan yöntemleri kullanmayı seviyorum (şimdi Ruby 2.0'ım var).
La-comadreja

1
Bu, işe yarayan tek kişi! Yukarıdaki çözümlerin hepsini denedim, hiçbiri "fdsfdsf dfsf sfds fs sdf <div> merhaba <p> fooo ??? {! @ # $% ^ & * () _ +} <Testinde kullanılan String çalışmıyor / p> </div> \ xEF \ xBF \ xBD \ xef \ xbf \ x9c <div> \ xc2 \ x90 </div> \ xc2 \ x90 "
Chihung Yu

1
İkinci argüman 'ikili' ne için?
Henley Chiu

24

Mevcut çözümüm çalıştırmaktır:

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

Bu en azından benim asıl sorunum olan istisnalardan kurtulacaktır.


3
Bir valid_encoding?şeylerin yanlış olduğunu tespit eden bu yöntemi birlikte kullanıyorum . val.unpack('C*').pack('U*') if !val.valid_encoding?.
Aaron Gibralter

Bu benim için çalıştı. Sırtımı başarıyla \xB0derece sembollerine dönüştürür . Hatta valid_encoding?geri gerçek oluyor ama yine de bunları yapmazsa kontrol etmek ve yukarıda Amir'in cevabı kullanarak hakaret karakterleri çıkarmaz: string.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: ''). force_encodingRotayı da denedim ama başarısız oldu.
hamstar

Bu harika. Teşekkürler.
d_ethier

8

Bunu dene:

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

Davam için en iyi cevap! Teşekkürler
Aldo

4

Bir HTML ayrıştırıcı kullanmanızı tavsiye ederim. Sadece en hızlı olanı bulun.

HTML'yi ayrıştırmak göründüğü kadar kolay değildir.

Tarayıcılar UTF-8 HTML belgelerinde geçersiz UTF-8 dizilerini ayrıştırır, sadece " " sembolünü koyar. Dolayısıyla, HTML'deki geçersiz UTF-8 dizisi ayrıştırıldığında, ortaya çıkan metin geçerli bir dizedir.

Özellik değerlerinin içinde bile, amp gibi HTML varlıklarının kodunu çözmeniz gerekir.

HTML'yi bir normal ifadeyle neden güvenilir bir şekilde ayrıştıramadığınızı özetleyen harika bir soru: RegEx, XHTML bağımsız etiketler dışında açık etiketleri eşleştirir


2
Yaklaşık 10 kat daha hızlı olduğu için regexp'i tutmayı çok isterim ve html'yi gerçekten doğru bir şekilde ayrıştırmak istemiyorum ama sadece bağlantıları çıkarmak istiyorum. Ruby'deki geçersiz parçaları sadece şunu yaparak değiştirebilmeliyim: ok_string = bad_string.encode ("UTF-8", {: geçersiz =>: replace,: undef =>: replace}), ancak bu görünmüyor iş :(
Marc Seeger

3

Bu işe yarıyor gibi görünüyor:

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

2

İngilizce, Rusça ve diğer bazı alfabelerin karışımına sahip olan ve istisnaya neden olan dizeyle karşılaştım. Yalnızca Rusça ve İngilizce'ye ihtiyacım var ve bu şu anda benim için çalışıyor:

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

1

Nakilon'un çözümü işe yarasa da, en azından hatayı aşarken, benim durumumda, Microsoft Excel'den kaynaklanan ve yakutta (bunu elde et) kiril K olarak kaydedilen CSV'ye dönüştürülen bu garip f-ed up karakterim vardı. yakut kalın bir K idi. Bunu düzeltmek için 'iso-8859-1' kullandım. CSV.parse(f, :encoding => "iso-8859-1"), bu benim acayip deaky Kiril K'larımı çok daha yönetilebilir hale getirdi ve /\xCA/sonra bunu kaldırabilirdim.string.gsub!(/\xCA/, '')


Tekrar belirtmek isterim ki Nakilon'un (ve diğerlerinin) düzeltmesi (haha) Cyrillia'dan gelen Kiril karakterleri için iken, bu çıktının xls'den dönüştürülmüş bir csv için standart çıktı olduğunu belirtmek isterim!
boulder_ruby

0

Kullanmadan önce scan, istenen sayfanın Content-Typebaşlığının olduğundan emin olun text/html, çünkü resimler gibi UTF-8 ile kodlanmamış olan şeylere bağlantılar olabilir. hrefBir <link>öğe gibi bir şeyde bir aldıysanız, sayfa html olmayabilir . Bunun nasıl kontrol edileceği, kullandığınız HTTP kitaplığına göre değişir. Ardından, sonucun yalnızca ascii ile olduğundan emin olun String#ascii_only?(UTF-8 değil, çünkü HTML yalnızca ascii kullanıyor olmalıdır, aksi takdirde varlıklar kullanılabilir). Bu testlerin her ikisi de başarılı olursa kullanmak güvenlidir scan.


teşekkürler, ama bu benim sorunum değil :) Yine de URL’nin yalnızca ana bilgisayar kısmını çıkarıyorum ve yalnızca ön sayfaya basıyorum. Benim sorunum, görünüşe göre girdimin UTF-8 olmaması ve 1.9 kodlama foo'nun karışması
Marc Seeger

@Marc Seeger: "Benim girdim" ile neyi kastediyorsunuz? Stdin, URL veya sayfa gövdesi?
Adrian


benim girdim = sayfa gövdesi @Eduardo: Biliyorum. Benim sorunum, net / http'den gelen verilerin zaman zaman kötü kodlanması gibi görünmesi
Marc Seeger

Web sayfalarının gerçekte kötü kodlamaya sahip olması alışılmadık bir durum değildir. Yanıt başlığı bunun bir kodlama olduğunu söyleyebilir, ancak daha sonra aslında başka bir kodlamayı sunar.
46'da batma

-1

Verileri "umursamıyorsanız" aşağıdaki gibi bir şey yapabilirsiniz:

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

Ben sadece valid_encoding?geçerdim. Benimki bir arama alanı ve bu yüzden aynı tuhaflığı defalarca buluyordum, bu yüzden şöyle bir şey kullandım: sadece sistemin bozulmaması için. Bu bilgiyi göndermeden önce otomatik olarak onaylamak için kullanıcı deneyimini kontrol etmediğim için ("sahte!" Demek için otomatik geri bildirim gibi), sadece içeri alabilir, çıkarabilir ve boş sonuçlar verebilirim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.