【问题标题】:Guessing at string encodings for stream of bytes in log file猜测日志文件中字节流的字符串编码
【发布时间】:2012-02-13 18:09:39
【问题描述】:

tl;dr 摘要:给定一个表示未知编码的字符串的字节流,我应该尝试使用什么编码以及以什么顺序来解释这些字节以获得找到'的最佳机会正确的编码?

问题示例

我有一个文件arrows.txt,我碰巧知道它是使用UTF-8 保存的,其单字符内容为。如果我假装我不知道这个文件的编码是什么,Windows 上的以下 Ruby 代码会失败:

s = IO.read('foo.txt')
p s.encoding,        #=> #<Encoding:IBM437>
  s.valid_encoding?, #=> true
  s.chars.to_a       #=> ["\xE2", "\x87", "\x88"]

它“失败”是因为它告诉我文件实际上包含 Γçê 的内容,并且一切正常(编码有效)。

真实世界场景

我有 Nginx 日志文件和 Akamai 日志文件,它们记录的查询没有任何特定编码,我需要在数据库中处理数据并将其存储为 UTF-8。大多数情况下,将每一行解释为 UTF-8 会产生一个具有有效编码的字符串,但有时不会。

我想请 Ruby 为每一行尝试各种编码,以找到一个有效且可能(但当然不能保证)正确的编码。

尝试失败

我最初写了以下代码:

def guess_encoding( str, result='utf-8', *encodings )
  # Try every encoding if none were passed in
  encodings = Encoding.list if encodings.empty?

  # Keep forcing a new encoding until we find one that is valid
  unless encodings.find{ |e| str.force_encoding(e) && str.valid_encoding? }
    raise "None of the supplied encodings was valid"
  end

  # Convert from the valid encoding to the desired, replacing 'bad' characters
  str.encode(result, invalid: :replace, undef: :replace)
end

问题在于Encoding.list 中的第一个编码是ASCII-8BIT,它对所有字节流都有效。因此,如果我使用上面的代码并调用s2 = guess_encoding(s),则结果是字符串���,表示上面的三字节双箭头字符。

最后是问题

我应该以什么顺序测试编码以提供最大的机会让第一个 valid_encoding? 成为正确的?哪些常见的编码对使用的字节最挑剔,我应该先尝试它们,哪些常见的编码是完全允许的,所以我应该最后尝试它们?

在猜测正确性时我应该使用其他启发式方法吗? (如果特定编码导致的字符少于另一个,是否更可能是正确的?)

【问题讨论】:

  • 你可以试试rchardet19 gem。它“采用未知字符编码的字节序列,并尝试确定编码。”
  • @louism 您应该将此作为答案。 :)
  • 谢谢,我不确定宝石推荐是否算作答案,但既然其他人都在这样做,我想我也会这样做:-)。
  • 要记住的一点是,bytes 返回构成字符的字节,但 chars 返回多字节字符本身。因此,s.bytes.to_a 返回三个字节,s.chars.to_a 将只返回单个字符。
  • @theTinMan 一个很好的说明;输出bytes 真的很吵。我更新了问题以表明在该特定编码下 chars.to_a.length 也是三个坏字符(问题的症结所在)。

标签: ruby encoding


【解决方案1】:

您可以试试rchardet19 gem。它“采用未知字符编码的字节序列,并尝试确定编码。”它还为您返回的编码提供置信度分数。过去它曾多次为我工作过,看起来它可以完成您想要完成的工作。

示例用法:

require 'rchardet19'
cd = CharDet.detect("some data")
# => #<struct #<Class:0x102216198> encoding="ascii", confidence=1.0>

【讨论】:

  • 虽然不是我所问问题的直接答案,但这正是我所需要的:一个可靠的编码猜测来源,可能比我想出的任何启发式方法都要好。
  • @Phrogz,不要自以为是。 :-) 我用了一段时间的 Linux 等效版本,发现很多案例都失败了。
【解决方案2】:

如果您的代码可以在 unix/linux 机器上运行,那么 filemagic gem 可能适合您。

gem install ruby-filemagic

作为确定整个文件编码的工具最有用,然后可以将其用于文件中的所有行。以下内容应该可以帮助您开始使用它:

$ irb 
irb(main):001:0> require 'filemagic' 
=> true
irb(main):002:0> fm = FileMagic.new
=> #<FileMagic:0x7fd4afb0>
irb(main):003:0> fm.file('afile.zip') 
=> "Zip archive data, at least v2.0 to extract"
irb(main):004:0>

【讨论】:

  • Filemagic 适用于文件的第一块,但我遇到了日志和大 HTML 文件的问题,其中可能隐藏着各种肮脏的东西。有时我们必须逐行阅读并分别处理。
  • 这不仅必须在 Windows 上运行,而且@theTinMan 的评论是完全正确的:看起来这些日志文件在每行基础上都有混合编码。
  • @Phrogz,作为练习,将这种疯狂添加到解析 RSS 和 Atom 提要中。它们应该是 XML 一点也不重要。我炸了 Hpricot 很多次,最后得到了一个完全无法读取的组合,这就是我现在偏爱 Nokogiri 的原因。我绝对能感受到你的痛苦。
【解决方案3】:

当我处理编写蜘蛛时,我总是从 ISO-8859-1 开始,然后是 Win-1252。两者之间的差异很小,因此大多数情况下都应该适合。我首先选择这两个的原因是,我认为您最有可能遇到它们。

如果某些内容不符合这些要求,那么我只需使用 iconv 将其转换为 UTF-8,或去掉变音符号,使其在视觉上与我们期望看到的相似,然后继续。

有时没有什么是成功的;我的代码提取了 iconv 的所有编码,然后剥离了所有 ASCII 值,并试图找到剩余字符命中次数最多的编码。 XML 和 HTML 有时格式不正确,没有任何帮助,那时我会恢复去除重音符号。

【讨论】:

    猜你喜欢
    • 2013-04-01
    • 2011-10-10
    • 2011-03-17
    • 2012-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-07
    • 1970-01-01
    相关资源
    最近更新 更多