话说,上次发现台湾用big5-uao很多,于是和某台湾人士讨论了一下,而下面要说的问题就是在讨论过程中遇到的。
嗯,该问题虽然还是跟Encoding有关,不过这次倒不能说是Ruby的错。
以下是代码:
require 'mechanize' agent = Mechanize.new # 请自备梯子 agent.set_proxy('127.0.0.1', '8118') # 该页面有用big5-uao编码的字符串 url = 'http://www.ptt.cc/man/Japanese-B95/index.html' page = agent.get(url) t = page.root.css('#finds > p')[0] puts t.text.encoding p t.text p t.text.encode('gbk') puts t.to_s.encoding p t.to_s p t.to_s.encode('gbk') # 以下为程序输出: #UTF-8 #"\u60A8\u73FE\u5728\u7684\u4F4D\u7F6E\u662F Japanese-B95 - " #"您現在的位置是 Japanese-B95 - " #Big5 #"<p>\x{B17A}\x{B27B}\x{A662}\x{AABA}\x{A6EC}\x{B86D}\x{AC4F} Japanese-B95 - </p>" #"<p>您現在的位置是 Japanese-B95 - </p>" #[#<Nokogiri::XML::SyntaxError: input conversion failed due to input error, bytes 0x93 0xAB 0xC7 0x66>, #<Nokogiri::XML::SyntaxError: input conversion failed due to input error, bytes 0x93 0xAB 0xC7 0x66>, #<Nokogiri::XML::SyntaxError: htmlCheckEncoding: encoder error>, #<Nokogiri::XML::SyntaxError: input conversion failed due to input error, bytes 0x93 0xAB 0xC7 0x66>, #<Nokogiri::XML::SyntaxError: encoder error>]
以我对Mechanize和Nokogiri的理解,该脚本的输出很正常。因为Nokogiri在其内部使用libxml解析HTML,而libxml使用libiconv处理编码问题。libiconv用big5去解码big5-uao字符串并尝试转换为utf-8,遇到不认识的部分时中断处理并丢弃剩余的部分,libxml则补完HTML的关闭标签并返回给Nokogiri。
(注:用Nokogiri去抓有编码问题的HTML页面时并不会主动报错,因此必须去检查page.root.errors,这很重要。)
然而这么一个符合我认知的脚本在某人的Mac上却跑出了不一样的结果。以下是稍微修改了一点的代码:
puts t.text.encoding p t.text.lines.first.force_encoding('utf-8') p t.text.lines.first.force_encoding('utf-8').encode('gbk', :undef=>:replace) puts t.to_s.encoding p t.to_s.lines.first p t.to_s.lines.first.force_encoding('big5-uao').encode('gbk') # 以下为程序输出: #ASCII-8BIT #"\u60A8\u73FE\u5728\u7684\u4F4D\u7F6E\u662F Japanese-B95 - \uE66B\uF735\uE6C3\uF735\u4E2D\n" #"您現在的位置是 Japanese-B95 - ????中\n" #Big5 #"<p>\x{B17A}\x{B27B}\x{A662}\x{AABA}\x{A6EC}\x{B86D}\x{AC4F} Japanese-B95 - \x93\x{ABC7}f\x94D\x{C766}\x{A4A4}\n" #"<p>您現在的位置是 Japanese-B95 - 読み込み中\n" #[]
t.text.encoding不知为何变成了ascii-8bit,内容倒仍然是utf-8,可是却包含无效字符。而且libxml解析该页面没有发现错误(page.root.errors为空),t.to_s还居然能拿到正确的内容。
伊妹来伊妹去,最后我们俩在gtalk上兜了半天圈子,终于找到了问题所在。原来,他的Nokogiri用的并不是libiconv,而是ICU(IBM的那个,OSX里边默认安装),就这样而已。
(插花,t.text.encoding变成ascii-8bit的问题应该是Nokogiri的bug,这个还没去追的说。)
big5-uao只是一个big5的扩展,也就是在big5的private use area里安排了很多本不存在于big5里的内容。libiconv用严格符合big5的码表去解码big5-uao,于是就出错了。
而ICU的处理策略跟libiconv不同。在ICU的big5码表中,将big5的PUA与Unicode的PUA一一对应(第二个脚本输出的“\uE66B\uF735\uE6C3\uF735”这四个字符就在PUA里)。这样做的好处是即使字符串内包含非法字符,big5到utf-8再到big5也能还原到原始字符串而不会丢失信息。坏处么,ICU和别的编码转换工具混用会变成一件非常危险的事情。
虽然非常偏门,不过对处理big5-uao有需要的同学,可以试试这个:http://www.boxcn.net/shared/iu0uo12l70
压缩包里有我弄的一个为libiconv增加big5-uao的补丁(该补丁非常初步,不过正确性应该不成问题)以及编译好的dll,还有libxml以及相关dll。如果是动态链接的库,换上libiconv就行,只是Windows里gem安装的Nokogiri是静态编译,要处理big5-uao还需要继续改。
打开Nokogiri里的lib/nokogiri/ffi/libxml.rb,开头的部分改成这样:
# :stopdoc: module Nokogiri module LibXML extend FFI::Library if RbConfig::CONFIG['host_os'] =~ /(mswin|mingw)/i raise(RuntimeError, "Nokogiri requires JRuby 1.4.0 or later on Windows") if RUBY_PLATFORM =~ /java/ && JRUBY_VERSION < "1.4.0"
再把压缩包里的所有dll文件放到lib/ext/nokogiri下,gem装上ffi,就可以这么用了:
ENV['NOKOGIRI_FFI'] = '1' require 'mechanize' agent = Mechanize.new agent.set_proxy('127.0.0.1', '8118') url = 'http://www.ptt.cc/man/Japanese-B95/index.html' page = agent.get(url) page.encoding = 'big5-uao' t = page.root.css('#finds > p')[0] puts t.text.encoding # => ASCII-8BIT,这里还是不对,仍然需要force一下 puts t.to_s.encoding # => Big5-UAO
貌似我应该去找找text的问题,然后给Nokogiri提交两个补丁…
git代码库里的Nokogiri已经将ffi的部分移除,以后如果要处理big5-uao,看来只能自己编译Nokogiri了。
没有评论 :
发表评论