プログラマでありたい

おっさんになっても、プログラマでありつづけたい

Ruby製のクローラー Anemoneの文字化け対策

 何度も取り上げていますが、Ruby製のクローラーであるAnemoneについてです。もう2年ほどメンテナンスされていないものの、Rubyの中のクローラー・フレームワークとしては未だに一番の完成度です。しかし、残念ながら幾つかの問題点があります。その中で日本語を扱う我々にとっては一番大きな問題は、文字化けです。

Anemoneの文字化けの原因



 Anemoneの文字化けの原因は、ずばりUTF-8以外の考慮が何もされていないためです。Anemoneが利用するHTMLパーサーであるNokogiriは、もともと内部的な文字コードをUTF-8として扱います。UTF-8以外の文字コードを扱う場合は、文字コードを指定して渡す必要があります。それにもかかわらず、AnemoneがHTMLをパースする時は、次のような実装になっています。

#
# Nokogiri document for the HTML body
#
def doc
  return @doc if @doc
  @doc = Nokogiri::HTML(@body) if @body && html? rescue nil
end

 この実装ではUTF-8やASCII以外の場合は、文字化けが発生します。

小手先のAnemoneの文字化け対策



 対策の1つとしては、AnemoneがパースしたNokogiriドキュメントを使わない方法があります。
取得先のページの文字コードをみて、String#encodeを使って明示的に変換します。

require 'anemone'

urls = []
urls.push("http://www.amazon.co.jp/gp/bestsellers/books/466282/")

Anemone.crawl(urls, :depth_limit => 0) do |anemone|
  anemone.on_every_page do |page|
    doc = Nokogiri::HTML.parse(page.body.encode("utf-8","shift_jis"))
    puts doc.xpath("//title").text
  end
end

 実行すると、この通り文字化けせずに結果を取得できます。

$ ruby anemone-convert.rb 
Amazon.co.jp ベストセラー: ビジネス・経済 の中で最も人気のある商品です

抜本的なAnemoneの文字化け対策



 一方で、折角Anemone内部でNokogiriドキュメントを作っているのに、それを切り捨てて新たなNokogoriドキュメントを作るのは無駄以外の何者でもありません。また、複数のサイトをクローリングする際は、決め打ちの文字コードの対処はできません。抜本的な対策としては、Anemone内部の問題を解決するのが良いでしょう。

 AnemoneがNokogiriドキュメントを生成しているのは、Anemone::Pageクラスのdocメソッドで実体はlib/anemone/paage.rbにあります。問題は、変換の際の文字コードに何を指定すれば良いかという点です。Anemoneが扱うのは、HTMLです。なので、HTML中からcharsetを抜き出し、それをセットすることにしましょう。幸い、Anemoneにはcontent_typeというメソッドがあり、HTMLヘッダーからcontent-typeを抜き出してくれています。ここからcharsetを抜き出せば大丈夫です。

#
# Nokogiri document for the HTML body
#
def doc
  return @doc if @doc
  if @body && html?
if charset == 'utf-8' || charset.nil?
  body = @body
else
  body = @body.encode("UTF-8", charset, :invalid => :replace, :undef => :replace) rescue nil
end
@doc = Nokogiri::HTML(body) if body
  end
end

#
# The content-type returned by the HTTP request for this page
#
def content_type
  headers['content-type'].first
end

#
# The charset returned by the content-type request for this page
#
def charset
  matcher = content_type.match(/charset=[\"]?([a-zA-Z\_\-\d]*)[\"]?/)
  matcher[1].downcase if matcher
end

 実装している途中で気がついたのですが、anemoneのPull Requestの中にほぼ同じ実装がありました。totothinkという方によるForkです。
totothink/anemone · GitHub
 
 私の方では、少し実装を変えています。content_typeのマッチの部分が「_」が含まれていなかった為にShift_JISに対応出来かなったので追加しています。また、もともとのHTMLのbodyが格納されている@body変数も、変換することなく元のままで残しております。その方が、後々使いやすいと考えたからです。

追記 2014/04/07
 content_typeだけで大丈夫なのというご指摘ありました。その通りだと思います。が、必要に応じて、変更していけば良いと思います。(MetaタグとかLangとか優先順位をつけて判定)ちなみに@bodyに入っている文字列をString#encodingで判定しようとしたのですが、必ずUS_ASCIIと判定されるので使えません。

Fork版のGitHubからのインストール



 GitHubにForkして、修正版を置いています。興味あれば使ってください。

git clone https://github.com/takuros/anemone.git
cd anemone
gem build anemone.gemspec
gem install anemone-0.7.2.gem

まとめ



 Pythonには、ScrapyというCrawlerのフレームワークがあります。今でも開発は活発です。RubyのCrawlerフレームワークとして網羅的な機能を持つのはAnemoneしかありません。Anemoneは残念ながら、もう開発されていない模様です。Pull Requestも幾つもあるけど、取り込まれる様子はありません。Forkで何か新しいの作っても良いのかもしれませんね。


See Also:
あらためてRuby製のクローラー、"anemone"を調べてみた
オープンソースのRubyのWebクローラー"Anemone"を使ってみる
JavaScriptにも対応出来るruby製のクローラー、Masqueを試してみる
複数並行可能なRubyのクローラー、「cosmicrawler」を試してみた
takuros/anemone · GitHub


参照:
chriskite/anemone · GitHub
anemone RDoc


Rubyによるクローラー開発技法

Rubyによるクローラー開発技法

Spidering hacks―ウェブ情報ラクラク取得テクニック101選

Spidering hacks―ウェブ情報ラクラク取得テクニック101選

Google Adsenseのレスポンシブル広告ユニット(ベータ版)を、スマホで見たらひどかった件

 はてなブログに移行しまたので、スマホの方にGoogle Adsenseの設定をしてみました。幾つか方法を調べていたのですが、JavaScriptを使い自分でサイズに応じて広告の大きさを変える方法と、Googleが提供するレスポンシブル広告ユニット(ベータ版)を使う方法があるそうです。時代はレスポンシブルだということで、後者の方を選んでみました。(本音は、JavaScript書くのが面倒くさかったから。)

 そうしたら、ひどいのです。Googleの広告の配信に最適化されました。記事が見えんです。
f:id:dkfj:20140406214905p:plain 

Google Adsenseのレスポンシブル広告ユニット(ベータ版)の調整



 さすがにひどすぎたので、対策をちょっと調べてみました。Google Adsenseから広告コードを取得の際に、デフォルトの「スマートサイズ(推奨)」ではなく、「アドバンス(コード変更が必要)」を選ぶと調整ができるようです。
f:id:dkfj:20140406215336p:plain

 取得したコードのうち、下記の部分を整形します。
デフォルトでは320×50になっているようです。(どう見ても、300×250だったけど)

<style>
.my_adslot { width: 320px; height: 50px; }
@media(min-width: 500px) { .my_adslot { width: 468px; height: 60px; } }
@media(min-width: 800px) { .my_adslot { width: 728px; height: 90px; } }
</style>

 私は、次のように変えています。広告サイズのところを、Googleが出していないサイズにすると、違反になるそうなのでご注意を。

<style>
.my_adslot { width: 234px; height: 60px; }
@media(min-width: 500px) { .my_adslot { width: 468px; height: 60px; } }
@media(min-width: 800px) { .my_adslot { width: 728px; height: 90px; } }
</style>

 
 結果、このようになりました。まぁウザくならない程度なので、良しとします。カテゴリと広告が近いので、もうちょっと調整する予定です。
f:id:dkfj:20140406214931p:plain

まとめというか雑感



 昔、子供向けアプリの広告について文句を書いたことがありますが、読者や広告主の為にならない広告手法って淘汰されると思うのですね。最近のスマホの広告、誤クリックを増やす方向にいっている気がしてならないです。今回の件をみてると、Googleのようなプラットフォーム側の問題もあるのではと感じました。どうなんでしょうね?
※こんなこと書いていると広告外せやと言われますが、効果測定という意味で中々面白い指標なんですね。


追記:
スマホに気を取られて、PC版がサイズオーバーしてました。これは、完全に私のミス。
f:id:dkfj:20140406220847p:plain


See Also:
絵本作家という無理ゲーと、幼児向けスマフォ・タブレットアプリについて - プログラマになりたい