Ruby 1.9: неверная последовательность байтов в UTF-8

Я пишу искателя в Ruby (1.9), который потребляет много HTML из множества случайных сайтов.
При попытке извлечь ссылки я решил использовать .scan(/href="(.*?)"/i) вместо nokogiri/hpricot (основное ускорение). Проблема в том, что теперь я получаю много ошибок "invalid byte sequence in UTF-8".
Из того, что я понял, библиотека net/http не имеет каких-либо специфических параметров кодирования, а материал, который входит, в основном не помечен должным образом.
Каким будет лучший способ работать с этими входящими данными? Я попробовал .encode с установленными параметрами замены и недопустимых параметров, но пока ничего не добился успеха...

Ответ 1

В Ruby 1.9.3 можно использовать String.encode для "игнорирования" недопустимых последовательностей UTF-8. Вот фрагмент, который будет работать как в 1.8 (iconv) и 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

или если у вас действительно сложный ввод, вы можете сделать двойное преобразование из 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

Ответ 2

Принятый ответ или другой ответ работают на меня. Я нашел этот пост, который предложил

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

Это исправило проблему для меня.

Ответ 3

Мое текущее решение:

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

Это, по крайней мере, избавится от исключений, которые были моей основной проблемой.

Ответ 4

Попробуйте следующее:

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

Ответ 5

Я рекомендую вам использовать парсер HTML. Просто найдите самый быстрый.

Анализ HTML не так прост, как может показаться.

Браузеры анализируют недопустимые последовательности UTF-8, в HTML-документах UTF-8, просто помещая символ "". Поэтому, как только некорректная последовательность UTF-8 в HTML будет проанализирована, результирующий текст является допустимой строкой.

Даже внутри значений атрибутов вы должны декодировать объекты HTML, такие как amp

Вот большой вопрос, который подводит итог, почему вы не можете достоверно разобрать HTML с регулярным выражением: Открывать теги RegEx, за исключением автономных тегов XHTML

Ответ 6

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

Ответ 7

Я столкнулся с строкой, в которой были смешения английского, русского и некоторых других алфавитов, что вызвало исключение. Мне нужен только русский и английский, и сейчас это работает для меня:

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

Ответ 8

В то время как решение Nakilon работает, по крайней мере, до того, как пройти мимо ошибки, в моем случае у меня был этот странный f-ed-персонаж, полученный из Microsoft Excel, преобразованный в CSV, который регистрировался в рубине как (получить) кириллицу K, который в рубине был выделен жирным шрифтом K. Чтобы исправить это, я использовал iso-8859-1. CSV.parse(f, :encoding => "iso-8859-1"), что превратило мою причудливую деактирующую кириллицу K в гораздо более управляемую /\xCA/, которую я мог бы удалить с помощью string.gsub!(/\xCA/, '')

Ответ 9

Это работает:

def sanitize_utf8(string)
  return nil if string.nil?
  return string if string.valid_encoding?
  string.chars.select { |c| c.valid_encoding? }.join
end

Ответ 10

Прежде чем использовать scan, убедитесь, что заголовок требуемой страницы Content-Type text/html, так как могут быть ссылки на такие вещи, как изображения, которые не кодируются в UTF-8. Страница также может быть не-html, если вы выбрали href в чем-то вроде элемента <link>. Как это проверить, зависит от того, какую библиотеку HTTP вы используете. Затем убедитесь, что результатом является только ascii с String#ascii_only? (а не UTF-8, потому что HTML должен использоваться только ascii, объекты могут использоваться иначе). Если оба этих теста пройдут, безопасно использовать scan.

Ответ 11

Если вы не "заботитесь" о данных, вы можете просто сделать что-то вроде:

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

Я просто использовал valid_encoding?, чтобы передать его. Mine - поле поиска, и поэтому я снова и снова обнаруживал ту же странность, поэтому я использовал что-то вроде: просто чтобы система не сломалась. Поскольку я не контролирую работу пользователя, чтобы авторизовать перед отправкой этой информации (например, автоответчик, чтобы сказать "манекен!" ), Я могу просто взять его, снять и вернуть пустые результаты.