1. Qiita
  2. 投稿
  3. クローラー
  • 5
    いいね
  • 0
    コメント

こんにちは。これを読んでいる皆さん日々スクレイピングに励んでいると思います。一緒に頑張りましょう。

さて、世の中には異常なWebアプリケーション、異常な設定のHTTPサーバ、異常な構造のHTMLドキュメントなどのとにかく異常な現実が氾濫しています。また、異常ではないもののパッと見で要素の切り出しが難しいものなどもあります。この記事では、そういったものにどのように向き合っていくのか、ということを書いていきます。

サンプルについて

サンプルURLのサーバ側の実装については、GitHub上にソースコードを公開しています。
また、クライアント側のサンプルは Ruby 2.3.3 と 標準ライブラリの net/http または mechanize を利用しています。

リダイレクトループ

サンプルURL: http://www.spacepro.be/crawl/redirect_loop
無限に自身のURLへリダイレクトする何かです。袋小路です。

mechanize
# 以後のサンプルコード内で Mechanize インスタンスは agent という名前で取り扱う
agent = Mechanize.new

# mechanize では特に問題ない(例外が帰る)
agent.get('http://www.spacepro.be/crawl/redirect_loop')
# => Mechanize::RedirectLimitReachedError: Redirect limit of 20 reached

# ちなみにリダイレクト回数の制限は agent.redirection_limit で確認 / 再設定できる
puts agent.redirection_limit
# => 20
agent.redirection_limit = 42
agent.get('http://www.spacepro.be/crawl/redirect_loop')
# => Mechanize::RedirectLimitReachedError: Redirect limit of 42 reached

net/http公式リファレンスマニュアル内に実装例があります。

リダイレクトループ(URLが変化する)

サンプルURL: http://www.spacepro.be/crawl/redirect_loop/1

リダイレクト先URLが都度変化します。
アクセスしたURLとリダイレクト先のURLが同一か否かのみで判定しているような場合に死にます。上記実装例に沿っている限りでは問題ありません。

スクレイピングしにくいテーブル

サンプルURL: http://www.spacepro.be/crawl/nested_table
これは某有名ウェブサイトのテーブルの構造をほぼそのままコピーしています。
さて、このテーブルの問題点としては、各tableにid/class指定がないため、パッと見どのように手軽に要素を取得するかに迷います。

そんな時はこんな具合のメソッドを作ってあげると可読性が高くなって便利です。 net/http はレスポンスボディを nokogiri に食わせれば同じなので省略します。

mechanize
# 取引情報ボックスから情報を取得して返却する
# trade_info_value(page, '出品者情報', '氏名') => '田中 太郎'
# trade_info_value(page, '出品者情報', '存在しない要素') => nil
def trade_info_value(page, name, key)
  page.css('div#example table').each do |table|
    if table.css('th').first.text == name
      table.css('tr').each do |row|
        return row.css('td').first.text if row.css('th').first.text == key
      end
    end
  end

  nil
end

page = agent.get('http://www.spacepro.be/crawl/nested_table')

trade_info_value(page, '発送者情報', '住所')
# => "602-0881 京都府京都市上京区京都御苑3"

終わらないレスポンス

サンプルURL: http://www.spacepro.be/crawl/stream

ごくまれにこのようなWebページが存在します
これは、 HTTP/1.1 の Transfer-Encoding: chunked で延々とレスポンスを返し続けるので、クライアント側からコネクションを閉じない限り通信が終わりません。
一般的には、Twitter の Streaming API などで用いられているアレです。

今のところ、一般的なクローリングに用いるライブラリでこれをなんとかする手軽で有効な方策が思いつかないので、これはこれを読んでいるみなさんの宿題とします。(クローリングを中断したいだけなら、timeout ライブラリなどを活用することも考えられますが、レスポンスボディは打ち切った時点での内容を取得したい、という要件だとします。)

ごめんなさい

もっと色々ネタを用意するつもりでしたが、あまり時間をかけられませんでした。もしネタをふと思い出したらこの記事にちょいちょい追記していきますのでよろしくおねがいいたします。