将ASCII-8BIT格式的非ASCII字符转换为UTF-8

53

我正在从远程站点获取文本,并尝试将其加载到默认使用utf-8编码的Ruby 1.9/Rails 3应用程序中。

这里是一些有问题的文本示例:

Cancer Res; 71(3); 1-11. ©2011 AACR.\n

版权代码扩展后如下所示:

Cancer Res; 71(3); 1-11. \xC2\xA92011 AACR.\n

Ruby告诉我该字符串的编码方式为ASCII-8BIT,将其输入我的Rails应用程序会得到以下结果:

incompatible character encodings: ASCII-8BIT and UTF-8

使用以下正则表达式可以去除版权代码:

str.gsub(/[\x00-\x7F]/n,'?')

制作这个

Cancer Res; 71(3); 1-11. ??2011 AACR.\n

我该如何将版权符号(以及其他一些符号,如希腊字母)转换为UTF-8中的相同符号?这肯定是可能的...

我看到有人提到使用force_encoding,但这并不起作用:

str.force_encoding('utf-8').encode

我知道有许多其他人遇到了类似的问题,但我还没有看到有效的解决方案。


1
你是如何从远程网站中提取文本的呢?是使用爬虫来抓取网页吗?请展示一些示例代码,包括你使用的HTTP客户端以及是否使用Nokogiri、Hpricot或ReXML来解析页面。这个问题可能是由于你获取页面的方式和/或解析页面的方式导致的。一旦我们知道你是以数据安全的方式获取内容,我们就可以帮助你在不同编码之间进行数据转换。 - the Tin Man
非常简单的代码 - 使用open-uri和nokogiri - 例如:doc = Nokogiri::XML(open(url)),然后使用doc.css(...)来提取相关的文本块 - craic.com
1
请展示示例代码。你要检索的文件是HTML还是XML?Nokogiri在解析时确实关心差异。此外,请提供一些URL,因为互联网上的每个站点都不同。 - the Tin Man
3
我看到有人提到使用force_encoding,但这并不起作用。"不起作用"是什么意思?它会引发错误吗?Ruby会崩溃吗?你的电脑会着火吗?它会将字符串的内容替换为《扬基杜德尔歌》的歌词吗?请提供详细信息! :) - Phrogz
4个回答

78

这对我有效:

#encoding: ASCII-8BIT
str = "\xC2\xA92011 AACR"
p str, str.encoding
#=> "\xC2\xA92011 AACR"
#=> #<Encoding:ASCII-8BIT>

str.force_encoding('UTF-8')
p str, str.encoding
#=> "©2011 AACR"
#=> #<Encoding:UTF-8>

7
这可能导致“UTF-8无效字节序列”错误。我建议您使用encode('UTF-8')代替。 - jpemberthy
很奇怪,对我来说 "\xC2\xA92011 AACR" 片段返回的是 UTF-8 编码 "©2011 AACR" #<Encoding:UTF-8> - Mike R
@MikeR 你的文件开头有编码魔法注释吗? - Phrogz
@Phrogz 不是,我只是打开了一个 irb 会话(我在 ubuntu 上使用 ruby-2.2.1),然后复制粘贴了这两行代码。 - Mike R
@MikeR 那就是你的答案。 - Phrogz
显示剩余2条评论

34
有两种可能性:
  1. 输入数据已经是UTF-8编码,但Ruby不知道它是UTF-8编码。这似乎是您的情况,因为“\xC2\xA9”是版权符号的有效UTF-8编码。在这种情况下,您只需要使用force_encoding告诉Ruby数据已经是UTF-8编码。

    例如,“\xC2\xA9”.force_encoding('ASCII-8BIT')将重新创建您输入数据中相关的位。而“\xC2\xA9”.force_encoding('ASCII-8BIT').force_encoding('UTF-8')将演示您可以告诉Ruby它实际上是UTF-8编码,并获得所需的结果。

  2. 输入数据采用其他编码方式,您需要让Ruby将其转换为UTF-8编码。在这种情况下,您需要告诉Ruby当前的编码方式(ASCII-8BIT是Ruby对二进制数据的称呼,不是真正的编码方式),然后告诉Ruby进行转码操作。

    例如,假设您的输入数据采用ISO-8859-1编码,那么版权符号就是“\xA9”。这将生成这样一段数据:“\xA9”.force_encoding('ISO-8859-1')。而这将演示您可以让Ruby将其转换为UTF-8编码:“\xA9”.force_encoding('ISO-8859-1').encode('UTF-8')


我在使用Rails + SQL Server时遇到了这种错误。通过在database.yml中设置“encoding: ISO-8859-1”并使用“lating string”.encode(“UTF-8”)来解决问题。 - Lucas Renan
完美。第二个解决了我的问题,我也可以通过Ruby/DBI从Sql Server中提取数据。@Lucas Renan:感谢您提醒我关于rails/database.yml的事情。我可能会在网站上添加rails。 - jetimms

6
我曾为一个用open-uri、iconv和Hpricot解析带有希腊Windows编码的页面的脚本做过这件事:
doc = open(DATA_URL)
doc.rewind
data = Hpricot(Iconv.conv('utf-8', "WINDOWS-1253", doc.readlines.join("\n")))

我相信那是 Ruby 1.8.7,不确定 ruby 1.9 的情况如何。


谢谢!以上方法都不能正确处理"\x96",仍会出错。使用Iconv.conv('utf-8', "WINDOWS-1253", str)可以完美解决问题。 - spilliton
你可能还需要将Iconv的transliterate值设置为true。http://ruby-doc.org/stdlib-1.9.2/libdoc/iconv/rdoc/Iconv.html#method-i-transliterate-3D - wmarbut

2

我一直在遇到字符编码的问题,其他答案虽然有帮助但并不适用于所有情况。这是我想出来的解决方案,它可以在可能时强制使用编码,并在不可能时使用问号进行转码。以下是解决方案:

  def encode str
    encoded = str.force_encoding('UTF-8')
    unless encoded.valid_encoding?
      encoded = str.encode("utf-8", invalid: :replace, undef: :replace, replace: '?')
    end
    encoded
  end

force_encoding在大多数情况下都有效,但我遇到过一些字符串无法处理。这些字符串可能会被替换为无效字符:

 str = "don't panic: \xD3"
 str.valid_encoding?
 false
 str = str.encode("utf-8", invalid: :replace, undef: :replace, replace: '?')
 "don't panic: ?"
 str.valid_encoding?
 true

更新:我在生产中遇到了一些问题。建议您设置单元测试,使用已知的问题文本来确保此代码按照您需要的方式运行。一旦我想出第二个版本,我会更新这个答案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接