Line 1: Error: Invalid Blog('by Esehara' )

または私は如何にして心配するのを止めてバグを愛するようになったか

>> Zanmemo

あと何かあれは 「esehara あっと じーめーる」 か @esehara まで

Webサービスのプログラミングに必要なことのだいたいは、スクレイピングに学んだ

この記事を読み始める前に

Rubyでやるんだったら、ちょうどそういう本が出ているから、その本買えばいいのではないでしょうか。

Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例

Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例

はじめに

プログラミングを勉強し始めて、だいたい基礎的な文法を覚えたあとに、次に何をしようかな、と悩む人も結構多いみたいで、明確に「これを作りたい」という場合は、それを作ればいいとは思うんですけど、場合によっては、別段作りたいものが無く、漠然としたプログラミングをしたい、という熱意によって勉強しているという人もいるのではないかと思います。

で、もちろん「作りたいものがないのに、プログラミング勉強してどうするの」という意見もあるかとは思いますが、往々にしてそういう熱意もある。そこで、例えばプログラミング万能練習法という記事だったり、ファイルアップローダを作ろう - 鳩舎といった、各位において作るアプリケーションの提案があったりします。

自分の場合、たいてい作り始めるのは、スクレイピングアプリケーションだったりします。それは、自分が最初に作り始めたWebサービスがそうだった、というのもあるのですが、それを薦める理由はなぜなのか、についてざっと解説します。

背景知識 - 必要な情報を必要な分だけ回収するための、スクレイピング

例えば、こういうブログの記事を読んだりするさいに、私たちは普段ブラウザを使って読んでいると思います。しかし、Webサービスを手動で巡回するのは、非常に効率が悪い。広告はまだわかるにしろ、余計な一言とか、あるいは必要ではない情報とか。あるいは逆に自分がキャッチアップしておきたい情報というのもあるけれども、ブラウザで確認しておくと、それらを取り逃す機会というのが非常に大きくなる。

元々、WebにおけるHTMLで構成されたテキストというのは、構造化されています。構造化、というのは、つまり情報を適切な形に形式化してるということです。適切な形式化を行うことによって、理念的には、その情報が何を示しているのか、実務的には、CSSであったり、HTMLのテンプレートを組む時に、非常に楽になったりします。

さて、情報が適切に構造化されているとするならば、今度はその構造に従って、なんらかのHTMLの情報を処理すればいいことにも繋がります。つまり、「この部分は記事が入っているから、この部分だけかき集めて、それを集めれば情報収集が楽になる」と考えられます。

技術的に雑なところは色々ありますが、基本的にはこういう感じであるというように認識しています。

注意事項 - 過度なアクセスは攻撃と見做される

最初のことと矛盾するかもしれませんが、じゃあ実際にプログラムを作ろうとしたときに気をつけなければいけないというか、スクレイピングは初心者向きではない部分もあります。ここはたぶんそう簡単には引っかからないとは思いますが、十分に気をつける部分です。その部分が何かといえば、過度なアクセスは、攻撃として見做されてしまうということです。

じゃあ「過度なアクセスじゃなければいいのね」と思う人もいるかもしれませんが、「過度なアクセス」という基準は、そのサーバーのキャパシティによって変わってきます。言い換えると、要するにサーバー管理人にとって「不当なアクセスだ」と思われれば、それは不当な攻撃なのです。

例えば、その一例としてLibrahack : 容疑者から見た岡崎図書館事件というのがあります。この事件について、どう考えるかについては、各人思うことが色々あるかと思われますが、とにかく、下手なスクレイピングをやろうとすると、こういう風に過度な攻撃になるので注意が必要ということは考えたほうがいいかもしれません。

始めよう、スクレイピング

というわけで、前置きが長くなってしまいましたが、なぜスクレイピングを薦めるのかといえば、端的に言ってしまえば、「プログラミングが仕事じゃなくなっても、割と使えるもの」だから、というのもあるからです。なので、ちょっとRubyの勉強がてら、どういうフローで作っていったか、みたいなのを確認していきます。

第一段階: ページを引っ張ってこよう

例えば、はてなブックマークから、「2User以下のエントリだけを表示する」というプログラムを書くとします。このとき、まず最初に必要になるのは、あるサイトにアクセスしてきて、HTMLを引っ張ってくる部分です。そこで、愚直にアクセスし、URLを確認し、そのソースを表示するように作成してみます。

require 'open-uri'

URL = 'http://b.hatena.ne.jp/entrylist?sort=hot&threshold=2'
html = open(URL) { |f| f.read }
p html

第二段階: DOMを解析してみよう

HTMLはDocument Object Model、つまりDOMというのでデータが構造化されています。

今回の場合だと、2User以下のエントリだけを表示するということを考えています。しかし、単純にUser数だけを数えても、それは意味ないので、ここで「必要な情報は何か」を考えます。自分の場合は、以下のようになりました。

  • ページタイトル
  • ページのURL
  • User数
require 'nokogiri'
doc = Nokogiri::HTML.parse(html, nil)

doc.css(".entry-unit").each do |entry|
  p entry.css('h3').text
  p entry.css('h3 a.entry-link')[0][:href]
  p entry.css('blockquote').text
  p entry['data-bookmark-count'].to_i 
end

今現在、Domを解析するようなライブラリというのが、Rubyにおいて何が標準であるのかは知らないのですが、とりあえず良く使われているnokogiriでギコギコと切り出してみます。すると、大抵こういう感じのものになります。

さらに、このようにDomを読み、必要な情報をどうやったら取り出せるか、ということを通じて、いわば「ちゃんとしたHTML構造とは何か」であったり「こういう作りにすると、後でいろいろと困るな」みたいな、そういうHTMLを読む癖というのが身についたりします。

第三段階: 必要なデータ構造にまとめられるようにする

これだけでも単純にいいのですが、先ほどあげた三つに関しても、データ構造みたいなのを定義したほうが、のちのち扱いやすくなりそうです。このままだと、バラバラのままです。なので、Classでまとめてみます。とにかく必要なデータ構造にまとめられればいいです。

class HatenaEntry

  def initialize(entry)
      @title = entry.css('h3').text
      @url = entry.css('h3 a.entry-link')[0][:href]
      @user = entry['data-bookmark-count'].to_i
  end

  def put_console
    p @title
    p @url
    p @user
  end
end

entries = doc.css(".entry-unit").map do |entry|
  HatenaEntry.new entry
end

entries.each { |entry| entry.put_console } 

こうすることによって、ここでやるよりもだいぶやりやすくなりました。しかし、このままだと利点が分かりにくいかもしれません。例えば、こういうメソッドを生やしてみるのはどうでしょうか。

class HatenaEntry
  ...(中略)...
  
  def show?
    @user < 3
  end

  ...(中略)...
end

なぜshow?というメソッド名にしたかというと、単純に表示条件を確認するためだから、というのがあります。また、今後、例えばブラックリストを組み込んだりするときにも、show?で統一されていれば便利であるということです。

しかし、こんなことを考えるのは、早すぎる最適化かもしれません。YAGNI - Wikipediaという言葉があるように、「今後のことを考えるけれども、しかし、これは今必要なことなのか?」ということを考えるのも重要ではあります。

ちなみに、実際にこのメソッドを生やしたらどうなるかについて見てみましょう。

entries.each do |entry| 
  if entry.show? then
    entry.put_console
  end
end 

と、こういう感じで「表示したいエントリ」と「そうではないエントリ」みたいなのを区別して出せるようになりました。

宿題と課題

だいたい、基本的にはこんな感じです。あとは適当にHTMLに吐き出して、それを保存して、ブラウザから見れば、汚くとも、自分の必要な情報を集めるのに便利ツールが出来るというわけです。別に、外部に公開しなければ、必要最低限でいい気がします。

しかし、とはいえ、これだけではものたりないでしょう。というわけで、今後のロードマップみたいなのを提示して、お茶を濁します。

第四段階: 動的なアプリケーションにしてみる

以上のは、Rubyのコマンドを叩いて云々するということでしたけれども、むしろいっそのこと、あるURLを叩いたらスクリプトが走って取りにいくようなシステムにするといいでしょう。たいてい、Rubyというと、Railsを想像するかもしれませんが、フルスタックなやつは覚えることが多いので、最初はsinatraみたいな軽量な奴のほうがいいかもしれません。

実は俺自身も、四年前くらいに一度Railsを使って挫折しています……。

第四段階: テンプレートエンジンを使ってみる

例えば、プログラムからHTMLを組み立てるのに、テンプレートエンジンというのがあります。この程度だったら自分で表示したほうが早いかもしれませんが、テンプレートエンジンを使うと、HTMLを作る部分と、それに必要なデータを分離できるので便利です。

第五段階: データベースにぶちこんでみる

さて、これらのデータを集めたときに、保存先としてテキストだったりHTMLだったりすると、管理が正直面倒くさいので、何らかのデータベースにスクレイプしたデータを入れておいたほうがいいかもしれません。そのときに、例えば

  • データとして重複した場合どうするのか
  • データをデータベースに保存する場合はどうするのか
  • データをデータベースから取り出す場合はどうするのか
  • それを如何にして表示するのか

といったことを考えていく必要があります。また、場合によっては極端に読み込みとか遅くなったりするので、それをどうチューニングすると早くなるか、みたいなことも考える必要も出てきます(要するにindex貼れ、という話になるのだけど)。

第六段階: サービスの「ページ」を考慮しよう

さて、HTMLを読み、パースし、データベースにぶち込んだのはいいけれども、いちいちURLをそれぞれに叩くのは大変なので、サービスが如何にしてページを管理しているのか、みたいなのを考えて、それぞれに対して自動的にページをめくってアクセスするようにしたらいいかもしれません。

第六段階: エラーをうまく管理しよう

さて、ここまで来て、ここでエラーかよ、という話になります。当然、通信するアプリケーションなので、相手のサーバー状態であったり、あるいはこっちの状態によって、うまくデータが取れない場合があるので、これをどうやって管理するのかということを考えてみると良いかもしれない。「なんでこの段階でエラー管理なのか」っていうと、上くらいだったら、別段エラー処理に対してどうこう、というのを考えなくてもいいからです。

しかし、やはり、エラー自体は管理していたほうがよい。例えば、方針としては次のようなことが考えられる。

  1. 通信エラーが起きたら、リトライする仕組みを作る
  2. タスクを管理するのを別途作り、それぞれに対してエラーが起きたかどうかを保存しておき、エラーが起きたら、その旨を保存しておいて、また別のときにまとめて処理する

単純に上の方針でもいいけれど、いわゆる実装をミスったときに何度も連打する愉快スクリプトが出来たりするので(できるときはできるから仕方ないんだけど)、どうせだったらキューのシステムを作ってみるといいかもしれません。

第七段階: 並列化してみよう

さて、実際のところ、ページネーションで各ページを取得してみるとわかるのだけれども、一つずつやると、結構遅い。というのは、ページごとに受け取りに行くからで、そのときにハンドシェイクして云々みたいなのが挟まったりする。そうなると、段々とイライラし始めて、「遅い、遅すぎる!」という風になる。贅沢なんだけど。

ある処理が終わるまで、次の処理が出来ないことをブロッキングと理解している。この場合であるならば、ページの読み込みが終わるまで処理がブロックされているわけだが、各ページの情報解析自体は、それぞれ独立したものになっている(筈)だ。つまりお互いに依存しているものではない。とするならば、ある処理が終わるまで待たなくてもいい。

というわけで、あるページが処理している間に、他のページも処理できるようにしてみよう。単純に並列に出来るライブラリを使ってもいいし、スレッド化してもいい。とにかく、いろんな方法がある。試してみるといいと思う。

さらなる課題

さらなる課題としては、例えばrobot.txtを読んだり、あるいはこういうスクレイピングが相手側にとってどのような影響を与えるのか、理想的なBotの作り方はなんなのか、というのも考えるといいかもしれません。

例えば、User-agentに、これがRubyでかかれたbotであり、連絡先みたいなのが載っているだけでも、もしスクレイピングに問題があるなら連絡が出来るというのもあったりします。

まとめ

以上が、自分がスクレイピングするスクリプトを作るさいに考えていることだ。もちろん、この設計は穴だらけだし、恐らく技術的にツッコミどころがたくさんあると思う。また、正直書いてみて初心者にとって大変な部分もいくつかあると思う。ただ、これをこなし終わって見ると、たぶんWebサービスを作る上において、どういうことを考えるといいのか、みたいなことがわかる気がするし、自分の場合はそうだった。

プログラミングにおいて、手を動かすということは重要なことだし、こういう簡単なことでも、考慮すべきことはたくさんある。だから、もし「プログラミングやりたいけど、作りたいものがないなー」といったときは、スクレイピングを、個人的にはお薦めします。

Python版は?

このスライドがわかりやすいと思う

参考文献

Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例

Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例

マスタリングTCP/IP 入門編 第5版

マスタリングTCP/IP 入門編 第5版

  • 作者: 竹下隆史,村山公保,荒井透,苅田幸雄
  • 出版社/メーカー: オーム社
  • 発売日: 2012/02/25
  • メディア: 単行本(ソフトカバー)
  • 購入: 4人 クリック: 34回
  • この商品を含むブログ (24件) を見る