Ruby 1.9: ลำดับไบต์ที่ไม่ถูกต้องใน UTF-8


109

ฉันกำลังเขียนโปรแกรมรวบรวมข้อมูลใน Ruby (1.9) ซึ่งใช้ HTML จำนวนมากจากไซต์สุ่มจำนวนมาก
เมื่อพยายามแยกลิงก์ฉันตัดสินใจใช้.scan(/href="(.*?)"/i)แทน nokogiri / hpricot (การเร่งความเร็วที่สำคัญ) ปัญหาคือตอนนี้ฉันได้รับinvalid byte sequence in UTF-8ข้อผิดพลาด "" จำนวนมาก
จากสิ่งที่ฉันเข้าใจnet/httpไลบรารีไม่มีตัวเลือกเฉพาะในการเข้ารหัสและสิ่งที่เข้ามานั้นไม่มีแท็กอย่างถูกต้อง
วิธีใดเป็นวิธีที่ดีที่สุดในการทำงานกับข้อมูลที่เข้ามาจริง ฉันลอง.encodeใช้ชุดตัวเลือกการแทนที่และไม่ถูกต้อง แต่ยังไม่ประสบความสำเร็จ ...


สิ่งที่อาจทำให้อักขระแตก แต่ทำให้สตริงถูกต้องสำหรับไลบรารีอื่น ๆ : valid_string = untrusted_string.unpack ('C *') pack ('U *')
Marc Seeger

หากพบปัญหาจริงให้ลองวิธีแก้ปัญหาอื่น ๆ เช่นเดียวกัน ไม่รัก. พยายามของ Marc แต่ดูเหมือนว่าจะเข้าใจทุกอย่าง คุณแน่ใจหรือ'U*'ปลด'C*'?
Jordan Feldstein

ไม่มันไม่ :) ฉันเพิ่งใช้สิ่งนั้นใน webcrawler ที่ฉันสนใจเกี่ยวกับไลบรารีของบุคคลที่สามที่ไม่ล่มมากกว่าที่ฉันทำเกี่ยวกับประโยคที่นี่และที่นั่น
Marc Seeger

คำตอบ:


172

ใน Ruby 1.9.3 เป็นไปได้ที่จะใช้ String.encode เพื่อ "ละเว้น" ลำดับ UTF-8 ที่ไม่ถูกต้อง นี่คือตัวอย่างข้อมูลที่จะใช้งานได้ทั้งใน 1.8 ( iconv ) และ 1.9 ( เข้ารหัสสตริง # ):

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

หรือหากคุณมีอินพุตที่ลำบากจริงๆคุณสามารถทำการแปลงสองครั้งจาก UTF-8 เป็น UTF-16 และกลับไปเป็น 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

3
ด้วยการป้อนข้อมูลที่มีปัญหาฉันยังใช้การแปลงสองครั้งจาก UTF-8 เป็น UTF-16 แล้วกลับไปเป็น UTF-8 file_contents.encode!('UTF-16', 'UTF-8', :invalid => :replace, :replace => '') file_contents.encode!('UTF-8', 'UTF-16')
RubenLaguna

7
นอกจากนี้ยังมีตัวเลือกของforce_encoding. หากคุณอ่าน ISO8859-1 เป็น UTF-8 (ดังนั้นสตริงนั้นจึงมี UTF-8 ที่ไม่ถูกต้อง) คุณสามารถ "ตีความใหม่" เป็น ISO8859-1 ด้วย the_string.force_encoding ("ISO8859-1") และใช้งานได้ ด้วยสตริงนั้นในการเข้ารหัสจริง
RubenLaguna

3
เคล็ดลับการเข้ารหัสสองครั้งนั้นช่วยบันทึกเบคอนของฉัน! ฉันสงสัยว่าทำไมถึงต้องใช้?
johnf

1
ฉันควรวางเส้นเหล่านั้นไว้ที่ไหน?
Lefsler

5
ฉันคิดว่าการแปลงสองครั้งทำงานได้เนื่องจากบังคับให้มีการแปลงการเข้ารหัส (และด้วยการตรวจสอบอักขระที่ไม่ถูกต้อง) หากสตริงต้นทางถูกเข้ารหัสเป็น UTF-8 อยู่แล้วเพียงแค่การโทร.encode('UTF-8')ก็จะไม่ต้องดำเนินการใด ๆ และจะไม่มีการเรียกใช้การตรวจสอบ เอกสารทับทิมหลักสำหรับการเข้ารหัส อย่างไรก็ตามการแปลงเป็น UTF-16 ก่อนจะบังคับให้รันการตรวจสอบลำดับไบต์ที่ไม่ถูกต้องทั้งหมดและการแทนที่จะทำได้ตามต้องการ
Jo Hund

79

คำตอบที่ยอมรับหรือคำตอบอื่นใช้ได้ผลสำหรับฉัน ฉันพบโพสต์นี้ซึ่งแนะนำ

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

สิ่งนี้ช่วยแก้ปัญหาให้ฉันได้


1
สิ่งนี้แก้ไขปัญหาสำหรับฉันและฉันชอบใช้วิธีที่ไม่เลิกใช้งาน (ตอนนี้ฉันมี Ruby 2.0 แล้ว)
La-comadreja

1
ตัวนี้ตัวเดียวได้ผล! ฉันได้ลองใช้วิธีแก้ปัญหาข้างต้นทั้งหมดแล้วไม่มีสตริงใดที่ใช้ในการทดสอบ "fdsfdsf dfsf sfds fs sdf <div> สวัสดี <p> fooo ??? {! @ # $% ^ & * () _ +} < / p> </div> \ xEF \ xBF \ xBD \ xef \ xbf \ x9c <div> \ xc2 \ x90 </div> \ xc2 \ x90 "
Chihung Yu

1
อาร์กิวเมนต์ที่สอง 'ไบนารี' มีไว้เพื่ออะไร?
Henley Chiu

24

วิธีแก้ปัญหาปัจจุบันของฉันคือเรียกใช้:

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

อย่างน้อยสิ่งนี้ก็จะกำจัดข้อยกเว้นซึ่งเป็นปัญหาหลักของฉัน


3
ฉันใช้วิธีนี้ร่วมกับวิธีvalid_encoding?ที่ดูเหมือนจะตรวจพบเมื่อมีสิ่งผิดปกติ val.unpack('C*').pack('U*') if !val.valid_encoding?.
Aaron Gibralter

อันนี้ใช้ได้ผลสำหรับฉัน แปลง\xB0สัญลักษณ์ด้านหลังเป็นองศาเรียบร้อยแล้ว แม้valid_encoding?เป็นจริงกลับ string.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')แต่ฉันยังคงตรวจสอบว่ามันไม่ได้และดึงออกจากตัวละครที่กระทำผิดโดยใช้คำตอบของอาเมียร์ด้านบน: ฉันเคยลองforce_encodingเส้นทางด้วย แต่ก็ล้มเหลว
hamstar

นี่มันเยี่ยมมาก ขอบคุณ.
d_ethier


4

ขอแนะนำให้คุณใช้โปรแกรมแยกวิเคราะห์ HTML เพียงแค่ค้นหาหนึ่งที่เร็วที่สุด

การแยกวิเคราะห์ HTML ไม่ใช่เรื่องง่ายอย่างที่คิด

เบราว์เซอร์แยกวิเคราะห์ลำดับ UTF-8 ที่ไม่ถูกต้องในเอกสาร HTML แบบ UTF-8 เพียงแค่ใส่สัญลักษณ์ " " ดังนั้นเมื่อลำดับ UTF-8 ที่ไม่ถูกต้องใน HTML ได้รับการแยกวิเคราะห์ข้อความผลลัพธ์จะเป็นสตริงที่ถูกต้อง

แม้แต่ในค่าแอตทริบิวต์คุณต้องถอดรหัสเอนทิตี HTML เช่น amp

นี่คือคำถามที่ดีที่สรุปว่าเหตุใดคุณจึงไม่สามารถแยกวิเคราะห์ HTML ด้วยนิพจน์ทั่วไปได้อย่างน่าเชื่อถือ: RegEx จับคู่แท็กที่เปิดอยู่ยกเว้นแท็กที่มีอยู่ในตัว XHTML


2
ฉันชอบที่จะเก็บ regexp ไว้เพราะมันเร็วกว่าประมาณ 10 เท่าและฉันไม่ต้องการแยกวิเคราะห์ html อย่างถูกต้อง แต่แค่ต้องการแยกลิงก์ ฉันควรจะสามารถแทนที่ส่วนที่ไม่ถูกต้องในทับทิมได้โดยทำ: ok_string = bad_string.encode ("UTF-8", {: invalid =>: replace,: undef =>: replace}) แต่ดูเหมือนจะไม่ ทำงาน :(
Marc Seeger

3

ดูเหมือนว่าจะได้ผล:

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

ฉันเคยพบสตริงซึ่งมีตัวอักษรภาษาอังกฤษรัสเซียและอื่น ๆ ผสมกันซึ่งทำให้เกิดข้อยกเว้น ฉันต้องการเพียงภาษารัสเซียและภาษาอังกฤษเท่านั้นและสิ่งนี้ใช้ได้กับฉัน:

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 ใช้งานได้อย่างน้อยที่สุดเท่าที่จะผ่านข้อผิดพลาดได้ในกรณีของฉันฉันมีอักขระ f-ed แปลก ๆ ที่มาจาก Microsoft Excel แปลงเป็น CSV ที่ลงทะเบียนในทับทิมเป็น (รับสิ่งนี้) ซิริลลิก K ซึ่งใน ทับทิมเป็นตัวหนาในการแก้ไขปัญหานี้ฉันใช้ 'iso-8859-1' ได้แก่ CSV.parse(f, :encoding => "iso-8859-1")ซึ่งทำให้ซีริลลิก K ที่ผิดปกติของฉันกลายเป็นสิ่งที่จัดการได้ง่ายขึ้น/\xCA/ซึ่งฉันสามารถลบออกได้ด้วยstring.gsub!(/\xCA/, '')


อีกครั้งฉันแค่อยากทราบว่าในขณะที่การแก้ไขของ Nakilon (และอื่น ๆ ) ใช้สำหรับอักขระซีริลลิกที่มาจาก (ฮ่าฮ่า) ซีริลเลียเอาต์พุตนี้เป็นเอาต์พุตมาตรฐานสำหรับ csv ซึ่งถูกแปลงจาก xls!
boulder_ruby

0

ก่อนที่คุณจะใช้scanตรวจสอบให้แน่ใจว่าContent-Typeส่วนหัวของหน้าที่ร้องขอคือtext/htmlเนื่องจากอาจมีลิงก์ไปยังสิ่งต่างๆเช่นรูปภาพที่ไม่ได้เข้ารหัสใน UTF-8 หน้านี้อาจไม่ใช่ html หากคุณเลือกhrefสิ่งที่คล้ายกับ<link>องค์ประกอบ วิธีตรวจสอบสิ่งนี้แตกต่างกันไปตามไลบรารี HTTP ที่คุณใช้ จากนั้นตรวจสอบให้แน่ใจว่าผลลัพธ์เป็นเพียง ascii กับString#ascii_only?(ไม่ใช่ UTF-8 เนื่องจาก HTML ควรจะใช้ ascii เท่านั้นเอนทิตีสามารถใช้เป็นอย่างอื่นได้) scanหากทั้งสองของการทดสอบเหล่านั้นผ่านไปมันมีความปลอดภัยในการใช้งาน


ขอบคุณ แต่นั่นไม่ใช่ปัญหาของฉัน :) ฉันแตกเฉพาะส่วนโฮสต์ของ URL ต่อไปและกดเฉพาะหน้าแรกเท่านั้น ปัญหาของฉันคือเห็นได้ชัดว่าอินพุตของฉันไม่ใช่ UTF-8 และการเข้ารหัส 1.9 foo ก็ยุ่งเหยิง
Marc Seeger

@Marc Seeger: "อินพุตของฉัน" หมายความว่าอย่างไร Stdin, URL หรือเนื้อหาของเพจ?
Adrian

HTML สามารถเข้ารหัสใน UTF-8: en.wikipedia.org/wiki/Character_encodings_in_HTML
Eduardo

ข้อมูลของฉัน = เนื้อหาของหน้า @Eduardo: ฉันรู้ ปัญหาของฉันคือข้อมูลที่มาจาก net / http ดูเหมือนจะมีการเข้ารหัสที่ไม่ดีเป็นครั้งคราว
Marc Seeger

ไม่ใช่เรื่องแปลกที่หน้าเว็บจะมีการเข้ารหัสที่ไม่ดีจริง ส่วนหัวของการตอบกลับอาจบอกว่าเป็นการเข้ารหัสอย่างหนึ่ง แต่แท้จริงแล้วให้บริการการเข้ารหัสอื่น
sunkencity

-1

หากคุณไม่ "สนใจ" เกี่ยวกับข้อมูลคุณสามารถทำสิ่งต่อไปนี้

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

ฉันแค่เคยvalid_encoding?ผ่านมันมา ของฉันเป็นช่องค้นหาดังนั้นฉันจึงพบความแปลกประหลาดแบบเดียวกันซ้ำแล้วซ้ำเล่าดังนั้นฉันจึงใช้บางสิ่งเช่นเพื่อให้ระบบไม่พัง เนื่องจากฉันไม่ได้ควบคุมประสบการณ์ของผู้ใช้ในการตรวจสอบอัตโนมัติก่อนที่จะส่งข้อมูลนี้ (เช่นการตอบกลับอัตโนมัติที่จะพูดว่า "หลอก!") ฉันสามารถนำมันเข้ามาแล้วถอดมันออกและแสดงผลลัพธ์ที่ว่างเปล่า

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.