ruby 1.9:UTF-8中的无效字节序列


109

我正在用Ruby(1.9)编写一个爬虫,该爬虫使用了来自许多随机站点的大量HTML。
尝试提取链接时,我决定只使用.scan(/href="(.*?)"/i)nokogiri / hpricot(主要是提高速度)。问题是我现在收到很多“ invalid byte sequence in UTF-8”错误。
据我了解,该net/http库没有任何特定于编码的选项,并且进来的东西基本上没有正确标记。
实际使用该传入数据的最佳方法是什么?我尝试.encode设置了replace和invalid选项,但到目前为止没有成功...


可能会破坏字符,但使字符串对其他库有效的东西:有效字符串=不信任字符串.unpack('C *')。pack('U *')
Marc Seeger

遇到确切问题后,请尝试其他相同的解决方案。没爱。尝试过Marc,但似乎一切都乱了。您确定要'U*'撤消'C*'吗?
乔丹·费尔德斯坦,

不,它不是:)我只是在网络爬虫中使用了它,在这里我关心的是第三方库崩溃的次数不会比我在这里和那里的句子崩溃多。
Marc Seeger

Answers:


172

在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

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

7
也有的选项force_encoding。如果您将ISO8859-1读取为UTF-8(因此该字符串包含无效的UTF-8),则可以使用the_string.force_encoding(“ ISO8859-1”)将其“重新解释”为ISO8859-1,然后开始工作该字符串以其实际编码显示。
RubenLaguna 2012年

3
那个双重编码技巧就救了我的培根!我不知道为什么为什么要这样做?
约翰·约翰逊(Johnf)2012年

1
我应该把这些线放在哪里?
Lefsler

5
我认为双重转换有效,因为它强制进行编码转换(并与之一起检查无效字符)。如果源字符串已经用UTF-8编码,则仅调用.encode('UTF-8')是空操作,并且不运行检查。Ruby Core文档进行编码。但是,将其转换为UTF-16首先会强制执行所有检查无效字节序列的检查,并根据需要进行替换。
洪德

79

接受的答案或其他答案都对我有用。我发现这篇文章暗示

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

这为我解决了问题。


1
这为我解决了这个问题,我喜欢使用不推荐使用的方法(我现在有Ruby 2.0)。
La-comadreja 2014年

1
这是唯一可行的一种!我已经尝试了上述所有解决方案,但都没有一个用于测试“ fdsfdsf dfsf sfds fs sdf <div> hello <p> fooo ???”的字符串。{!@#$%^&*()_ +} < / p> </ div> \ xEF \ xBF \ xBD \ xef \ xbf \ x9c <div> \ xc2 \ x90 </ div> \ xc2 \
x90

1
第二个参数“二进制”是什么意思?
Henley Chiu

24

我当前的解决方案是运行:

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

这至少会摆脱我主要的问题


3
我正在结合使用此方法valid_encoding?,似乎可以检测出何时出现问题。val.unpack('C*').pack('U*') if !val.valid_encoding?
亚伦·吉布拉特

这个为我工作。成功地将我转换\xB0回度数符号。即使valid_encoding?返回的结果正确,但我仍然检查是否不正确,并使用上述Amir的答案删除有问题的字符string.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')。我也尝试过这force_encoding条路线,但是失败了。
hamstar

这很棒。谢谢。
d_ethier 2015年

8

试试这个:

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

我的情况的最佳答案!谢谢
Aldo

4

我建议您使用HTML解析器。只要找到最快的一个。

解析HTML并不像看起来那样容易。

浏览器会在UTF-8 HTML文档中解析无效的UTF-8序列,只需将“ ...”符号放入即可。因此,一旦解析了HTML中的无效UTF-8序列,生成的文本即为有效字符串。

即使在内部属性值中,您也必须解码amp等HTML实体

这是一个很好的问题,它总结了为什么不能可靠地使用正则表达式解析HTML: RegEx匹配除XHTML自包含标签以外的其他开放标签


2
我希望保留正则表达式,因为它快了10倍左右,我真的不想正确解析html,而只想提取链接。我应该能够通过以下方式替换ruby中的无效部分:ok_string = bad_string.encode(“ UTF-8”,{:invalid =>:replace,:undef =>:replace}),但这似乎并没有工作:(
Marc Seeger 2010年

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的解决方案有效,至少可以克服错误,但就我而言,我将源自Microsoft Excel的这个怪异的f-ed字符转换为CSV,并将其注册为红宝石西里尔字母K,红宝石是一个加粗的K。为了解决这个问题,我使用了“ iso-8859-1”即。CSV.parse(f, :encoding => "iso-8859-1"),这使我怪异的,古朴的西里尔字母K变成了更易于管理的代码/\xCA/,然后可以通过删除string.gsub!(/\xCA/, '')


再次,我只想指出,尽管Nakilon(及其他)的修复程序是针对源自(haha)西里尔语的西里尔字母的,但是此输出是从xls转换而来的csv的标准输出!
boulder_ruby 2012年

0

在使用之前scan,请确保所请求页面的Content-Type标题为text/html,因为可以链接到诸如未以UTF-8编码的图像之类的东西。如果您使用href诸如<link>元素之类的元素,则该页面也可能不是HTML 。根据使用的HTTP库的不同,检查方法也不同。然后,确保结果仅使用ascii String#ascii_only?(而不是UTF-8,因为HTML仅应使用ascii,否则可以使用实体)。如果这两个测试均通过,则可以安全使用scan


谢谢,但这不是我的问题:)无论如何,我仅提取URL的主机部分,并且仅访问首页。我的问题是我的输入内容显然不是UTF-8,而1.9编码的foo
Marc Seeger 2010年

@Marc Seeger:“我的输入”是什么意思?是Stdin,URL还是页面正文?
阿德里安


我的输入=页面正文@Eduardo:我知道。我的问题是来自net / http的数据似乎有时会编码不正确
Marc Seeger 2010年

网页实际上具有不好的真实编码的情况并不少见。响应标头可能会说这是一种编码,但实际上却在提供另一种编码。
sunkencity

-1

如果您不关心数据,则可以执行以下操作:

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

我只是valid_encoding?过去而已。我的是一个搜索领域,所以我一遍又一遍地发现了同样的怪异,所以我使用了类似的东西:只是为了不让系统崩溃。由于我无法控制用户体验在发送此信息之前进行自动验证(例如自动反馈说“ dummy up!”),因此我可以直接将其接收,剥离并返回空白结果。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.