ruby 1.9: chuỗi byte không hợp lệ trong UTF-8


109

Tôi đang viết một trình thu thập thông tin trong Ruby (1.9) sử dụng rất nhiều HTML từ nhiều trang web ngẫu nhiên.
Khi cố gắng giải nén các liên kết, tôi quyết định chỉ sử dụng .scan(/href="(.*?)"/i)thay vì nokogiri / hpricot (tăng tốc lớn). Vấn đề là bây giờ tôi nhận rất nhiều invalid byte sequence in UTF-8lỗi "".
Theo những gì tôi hiểu, net/httpthư viện không có bất kỳ tùy chọn mã hóa cụ thể nào và những thứ đi kèm về cơ bản không được gắn thẻ đúng cách.
Cách tốt nhất để thực sự làm việc với dữ liệu đến đó là gì? Tôi đã thử .encodevới các tùy chọn thay thế và không hợp lệ được đặt, nhưng không thành công cho đến nay ...


một cái gì đó mà có thể phá vỡ các nhân vật, nhưng giữ chuỗi giá trị trong các thư viện khác: valid_string = untrusted_string.unpack ( 'C *') đóng gói ( 'U *').
Marc Seeger

Gặp sự cố chính xác, hãy thử các giải pháp tương tự khác. Không tình yêu. Đã thử của Marc, nhưng nó dường như cắt xén mọi thứ. Bạn có chắc chắn 'U*'hoàn tác 'C*'không?
Jordan Feldstein

Không, nó không :) Tôi chỉ sử dụng nó trong một webcrawler, nơi tôi quan tâm đến việc các thư viện của bên thứ 3 không gặp sự cố nhiều hơn tôi làm về một câu ở đây và ở đó.
Marc Seeger,

Câu trả lời:


172

Trong Ruby 1.9.3, có thể sử dụng String.encode để "bỏ qua" các chuỗi UTF-8 không hợp lệ. Đây là một đoạn mã sẽ hoạt động ở cả 1.8 ( iconv ) và 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

hoặc nếu bạn có đầu vào thực sự rắc rối, bạn có thể thực hiện chuyển đổi kép từ UTF-8 sang UTF-16 và quay lại 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
Với một số đầu vào vấn đề tôi cũng sử dụng một chuyển đổi kép từ UTF-8 sang UTF-16 và sau đó trở lại UTF-8 file_contents.encode!('UTF-16', 'UTF-8', :invalid => :replace, :replace => '') file_contents.encode!('UTF-8', 'UTF-16')
RubenLaguna

7
Ngoài ra còn có tùy chọn của force_encoding. Nếu bạn đọc ISO8859-1 dưới dạng UTF-8 (và do đó chuỗi đó chứa UTF-8 không hợp lệ) thì bạn có thể "diễn giải lại" nó thành ISO8859-1 với the_string.force_encoding ("ISO8859-1") và chỉ hoạt động với chuỗi đó trong mã hóa thực của nó.
RubenLaguna

3
Thủ thuật mã hóa kép đó vừa cứu Bacon của tôi! Tôi tự hỏi tại sao nó được yêu cầu mặc dù?
johnf

1
Tôi nên đặt những dòng đó ở đâu?
Lefsler,

5
Tôi nghĩ rằng chuyển đổi kép hoạt động vì nó buộc chuyển đổi mã hóa (và với nó là kiểm tra các ký tự không hợp lệ). Nếu chuỗi nguồn đã được mã hóa bằng UTF-8, thì chỉ cần gọi .encode('UTF-8')là no-op và không có kiểm tra nào được chạy. Tài liệu về Ruby Core để mã hóa . Tuy nhiên, việc chuyển đổi nó thành UTF-16 trước hết buộc phải chạy tất cả các quá trình kiểm tra chuỗi byte không hợp lệ và thực hiện thay thế nếu cần.
Jo Hund

79

Câu trả lời được chấp nhận cũng như câu trả lời khác phù hợp với tôi. Tôi tìm thấy bài đăng này được đề xuất

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

Điều này đã khắc phục sự cố cho tôi.


1
Điều này đã khắc phục sự cố cho tôi và tôi thích sử dụng các phương pháp không bị phản đối (hiện tại tôi có Ruby 2.0).
La-comadreja

1
Đây là cái duy nhất hoạt động! Tôi đã thử tất cả các giải pháp trên, không có giải pháp nào trong số chúng hoạt động Chuỗi được sử dụng trong thử nghiệm "fdsfdsf dfsf sfds fs sdf <div> hello <p> fooo ??? {! @ # $% ^ & * () _ +} < / p> </div> \ xEF \ xBF \ xBD \ xef \ xbf \ x9c <div> \ xc2 \ x90 </div> \ xc2 \ x90 "
Chihung Yu

1
Đối số thứ hai 'nhị phân' dùng để làm gì?
Henley Chiu

24

Giải pháp hiện tại của tôi là chạy:

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

Điều này ít nhất sẽ loại bỏ các ngoại lệ vốn là vấn đề chính của tôi


3
Tôi đang sử dụng phương pháp này kết hợp với valid_encoding?nó dường như để phát hiện khi có điều gì đó không ổn. val.unpack('C*').pack('U*') if !val.valid_encoding?.
Aaron Gibralter

Điều này đã làm việc cho tôi. Chuyển đổi thành công \xB0biểu tượng lưng của tôi thành độ. Ngay cả những valid_encoding?trở lại đúng nhưng tôi vẫn kiểm tra xem nó không và loại bỏ các nhân vật xúc phạm bằng câu trả lời Amir của trên: string.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: ''). Tôi cũng đã thử force_encodinglộ trình nhưng không thành công.
hamstar

Điều đó thật tuyệt. Cảm ơn.
d_ethier

8

Thử cái này:

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

Câu trả lời tốt nhất cho trường hợp của tôi! Cảm ơn
Aldo

4

Tôi khuyên bạn nên sử dụng trình phân tích cú pháp HTML. Chỉ cần tìm một trong những nhanh nhất.

Phân tích cú pháp HTML không dễ dàng như bạn tưởng.

Trình duyệt phân tích cú pháp chuỗi UTF-8 không hợp lệ, trong tài liệu HTML UTF-8, chỉ cần đặt ký hiệu " ". Vì vậy, một khi chuỗi UTF-8 không hợp lệ trong HTML được phân tích cú pháp thì văn bản kết quả là một chuỗi hợp lệ.

Ngay cả bên trong các giá trị thuộc tính, bạn phải giải mã các thực thể HTML như amp

Đây là một câu hỏi tuyệt vời tóm tắt lý do tại sao bạn không thể phân tích cú pháp HTML với một biểu thức chính quy một cách đáng tin cậy: RegEx khớp các thẻ mở ngoại trừ các thẻ chứa XHTML


2
Tôi muốn giữ lại regexp vì nó nhanh hơn khoảng 10 lần và tôi thực sự không muốn phân tích cú pháp html một cách chính xác mà chỉ muốn trích xuất các liên kết. Tôi có thể thay thế các bộ phận không hợp lệ trong ruby ​​chỉ bằng cách thực hiện: ok_string = bad_string.encode ("UTF-8", {: invalid =>: Replace,: undef =>: Replace}) nhưng điều đó dường như không làm việc :(
Marc Seeger

3

Điều này dường như hoạt động:

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

Tôi đã gặp phải chuỗi ký tự có sự pha trộn giữa tiếng Anh, tiếng Nga và một số bảng chữ cái khác, gây ra ngoại lệ. Tôi chỉ cần tiếng Nga và tiếng Anh và điều này hiện phù hợp với tôi:

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

Trong khi giải pháp của Nakilon hoạt động, ít nhất là cho đến khi vượt qua được lỗi, trong trường hợp của tôi, tôi đã có ký tự f-ed up kỳ lạ này có nguồn gốc từ Microsoft Excel được chuyển đổi thành CSV đang đăng ký bằng ruby ​​dưới dạng ký tự cyrillic K (lấy cái này) trong ruby là chữ K. Để khắc phục điều này, tôi đã sử dụng 'iso-8859-1' viz. CSV.parse(f, :encoding => "iso-8859-1"), điều này đã biến K's cyrillic yếu ớt kỳ quái của tôi thành một thứ dễ quản lý hơn nhiều /\xCA/, sau đó tôi có thể xóa nó bằngstring.gsub!(/\xCA/, '')


Một lần nữa, tôi chỉ muốn lưu ý rằng mặc dù bản sửa lỗi của Nakilon (và những người khác) dành cho các ký tự Cyrillic có nguồn gốc từ (haha) Cyrillia, đầu ra này là đầu ra tiêu chuẩn cho một csv được chuyển đổi từ xls!
boulder_ruby

0

Trước khi bạn sử dụng scan, hãy đảm bảo rằng Content-Typetiêu đề của trang được yêu cầu là text/htmlvì có thể có các liên kết đến những thứ như hình ảnh không được mã hóa bằng UTF-8. Trang cũng có thể không phải là html nếu bạn chọn một hrefthứ gì đó giống như một <link>phần tử. Cách kiểm tra điều này thay đổi tùy theo thư viện HTTP bạn đang sử dụng. Sau đó, đảm bảo kết quả chỉ là ascii với String#ascii_only?(không phải UTF-8 vì HTML chỉ được cho là sử dụng ascii, các thực thể có thể được sử dụng theo cách khác). Nếu cả hai bài kiểm tra đó đều vượt qua thì có thể yên tâm sử dụng scan.


cảm ơn, nhưng đó không phải là vấn đề của tôi :) Dù sao tôi cũng chỉ trích xuất phần máy chủ của URL và chỉ truy cập trang đầu. Vấn đề của tôi là đầu vào của tôi dường như không phải là UTF-8 và foo 1,9 mã hóa đi haywire
Marc Seeger

@Marc Seeger: Ý bạn là "đầu vào của tôi" là gì? Stdin, URL hay nội dung trang?
Adrian

HTML có thể được mã hóa theo UTF-8: en.wikipedia.org/wiki/Character_encodings_in_HTML
Eduardo

my input = the page body @Eduardo: Tôi biết. Vấn đề của tôi là các dữ liệu đến từ net / http dường như có một mã hóa xấu bất cứ lúc nào
Marc Seeger

Không có gì lạ khi các trang web thực sự có mã hóa xấu. Tiêu đề phản hồi có thể nói rằng đó là một mã hóa nhưng sau đó thực sự phân phát một mã hóa khác.
chìm đắm vào

-1

Nếu bạn không "quan tâm" đến dữ liệu, bạn có thể làm điều gì đó như:

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

Tôi chỉ sử dụng valid_encoding?để vượt qua nó. Của tôi là một lĩnh vực tìm kiếm, và vì vậy tôi đã tìm thấy sự kỳ lạ giống nhau lặp đi lặp lại vì vậy tôi đã sử dụng một cái gì đó như: chỉ để hệ thống không bị hỏng. Vì tôi không kiểm soát trải nghiệm người dùng để tự động xác thực trước khi gửi thông tin này (chẳng hạn như phản hồi tự động để nói "dummy up!") Nên tôi chỉ có thể lấy nó ra, tách nó ra và trả về kết quả trống.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.