トップ «前の日記(2011-01-13) 最新 次の日記(2011-01-28)» 編集

Route 477



2011-01-19

[ruby] open-uriでUTF-8でないページを取得するときの注意

Ruby標準添付のopen-uriライブラリを使うと、HTMLを簡単に取得することができる。

 irb> require 'open-uri'
 irb> url = "http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-dev/43008"
 irb> html = open(url).read

このとき、文字コードがUTF-8でないことが分かっているなら、第二引数でエンコーディング名を指定しておいた方が良いだろう。(あ、ここからはRuby 1.9の話です。)

 irb> html = open(url, "r:euc-jp").read

読んだあとは、String#encodeでUTF-8に変換することができる。

 irb> html = open(url, "r:euc-jp").read.encode("utf-8")

文字化け対策

ところで、このコードは例外を投げる可能性がある。例えば http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-dev/43039 のように文字化け(EUC-JPでないバイト列)を含んだものをUTF-8にしようとすると、

Encoding::UndefinedConversionError: "\xAE\xE6" from EUC-JP to UTF-8

という例外が発生する。普段はちゃんと動いてたプログラムが、入力によって突然死するのは避けたいところだ。

で、例外なので、begin-rescueでキャッチしてやることも可能だが、もっと簡単な方法がある。String#encodeにオプションを渡すと、不正なバイト列の扱いを指定することができる。例えば以下のようにすると、不正なバイト列や変換が未定義のバイト列は適当な記号で置き換えられる。

irb> html = open(url, "r:euc-jp").read.encode("utf-8", :invalid => :replace, :undef => :replace)

というわけで、文字化けには注意しようという話でした。

余談1 : 安らかに眠れ、Hashロケット

ちなみにRuby 1.9では、=>なんていう指負担の大きい文字列を入力しなくてすむ文法が用意された。

irb> html = open(url, "r:euc-jp").read.encode("utf-8", invalid: :replace, undef: :replace)

余談2 : Railsではreadの時点で例外が起きる

irb上とRailsアプリ内では、例外の起きるタイミングが違う。(というのが実は本題)

Rails(3.0.3)はEncoding.default_internalをUTF-8に設定するため、readしたときにEUC-JPからUTF-8への変換が行われる。それは便利だ、と思うかも知れないが、上の例でいうと encodeしたときではなく、readした段階で例外が発生するので、

open(url, "r:euc-jp").read

がUndefinedConversionErrorを吐く可能性があるわけだ。

解決法としては、一端binary(ASCII-8BIT)として読み込んでやるという方法がある。

open(url, "r:binary").read.encode("utf-8", "euc-jp", invalid: :replace, undef: :replace)

encode(enc, invalid: :replace, undef: :replace)は、safe_encodeみたいな別名があってもいい気がしてきましたね。