読者です 読者をやめる 読者になる 読者になる

大解剖!スクレイピング比較してみた

はじめまして。14卒の能天気エンジニアです。
これから宜しくお願いします。

今回はスクレイピングの比較についてを題材にしました。

最近は業務だけでなく、私生活でもスクレイピングを使っています。スクレイピングによって今まで見づらかった情報が見やすくなったり、さらにそのデータを使って別のものを作成したりと幸せなことが多いです。
スクレイピング何?どういうこと出来るの?どうやってやるの?などなど、スクレイピングのいろはを順番に説明していきます。

スクレイピングとは

簡単にいうと今見ているこのサイト、そしていつも見ているサイトから情報を抽出することです。
HTTP経由でhtmlを取得し、htmlを解釈、DOMに変換します。任意のタグを指定し、DOMを検索、情報を取得します。あくまでもhtmlを変換するので、FLASHなどのコンテンツなどからはスクレイピングすることは出来ません。

どんなことが出来るの?

「なんでも出来る」は言い過ぎかもしれませんが自由度は高いです。
とあるサイトのページには20URLリストで並んでおり、それが5000ページあるとします。ほしい情報は各ページの20URLそれぞれの遷移先の特定の情報です。仮に、ブラウザで手動アクセスしコピペでデータ取得しようとすると、莫大な時間がかかります。
そんな時にスクレイピングの出番です。
プログラムにスクレイピングと、取り出したデータをcsvに書き込む処理を加えれば、待っているだけで、勝手にデータ取得を行ってくれます。

注意点

スクレイピングを行う際はいくつか注意点があります。

  • サーバー負荷
  • プログラムでサーバアクセスを制御するため、人間によるクリックとは違い、1秒間に何回もリクエストを送ることが可能です。過度にアクセスすると、公開されているサイトのサーバーなどにも負荷を与えることになります。場合によっては、その意図がなくてもDoS攻撃とみなされてしまったり、サイトのサーバーを落としてしまったりすると、損害賠償を請求される可能性もあります。
    必ず1リクエスト毎にsleepをかけて、サーバに負荷をかけないようにしましょう。

  • 著作権
  • サイトごとの情報は各サイトが所有しています。仮にサイトからデータを抽出し、加工したものを公開する際は取り扱いに十分に注意してください。(大抵の場合、無断転載はNGだと思います)

  • 利用規約
  • またサイトによっては利用規約に自動でクロールすることを禁止すると記載している場合もあります。予め利用規約を確認して、モラルのある利用をしましょう。

    スクレイピングライブラリの紹介

    今回はいくつかのスクレイピングライブラリをかいてみました。例題として、本サイトのエンジニア紹介ページから各メンバーの画像イメージのurlを取得してきます。

    simple_html_dom(PHP)

    <?php
        require_once('simple_html_dom.php');
        $html = file_get_html('http://technica.speee.jp/members');
    
        // メンバー一覧のdiv要素の中から、img要素を取得
        foreach($html->find('div#member li a img') as $img) {
            // img要素のsrc属性を取得
            echo $img->src . "\n";
        }
        $html->clear();
        unset($html);

    simple_html_domはphpスクレイピングをする際によく使われるライブラリで、ドキュメントも豊富です。
    要素の指定にはCSSのIDセレクタやCLASSセレクタを用いて指定します。下記の例のように、要素指定の後に引数として数値を渡すことで、何番目の要素かを明示して取得することができます。

    echo $html->find('div#member li a img', 0)->src . "\n";

    なお、利用の際にはメモリ管理に注意する必要があります。file_get_html もしくは str_get_htmlメソッドで生成したオブジェクトは、以下のように解放してやります。

    $html->clear();

    Goutte(PHP)

    <?php
        require 'goutte.phar';
        use Goutte\Client;
        $client = new Client();
        $crawler = $client->request('GET', 'http://technica.speee.jp/members');
    
        // メンバー一覧のdiv要素の中から、img要素を取得
        $crawler->filter('div#member li a img')->each(function ($node) {
            // img要素のsrc属性を取得
            echo $node->attr('src') . "\n";
        });

    Goutte(グートと読むそうです)もsimple_html_domと同じくphpのライブラリです。解析の主要な機能はSymfony2やZendFrameworkといったPHPフレームワークコンポーネントをつなぎ合わせることで実現しています。

    cssセレクタxpathどちらでも要素を指定することが可能です。

    //CSSセレクタ
    $crawler->filter('ul#hogehoge > li > a')->attr('href');
    //XPATH
    $crawler->filterXPath('//*[@id="hogehoge"]/li/a')->attr('href');

    Goutteの特徴的な機能を2つ紹介します。
    1つ目は解析したDOMを使って次のリクエストを送信する機能です。DOMからaタグを抽出し、clickメソッドをコールすると、src属性に書いてあるURLにGETリクエストを投げることができます。また、ページにformがあれば、連想配列でパラメータを渡しながらsubmitメソッドをコールすると、formタグに指定されたaction属性とmethod属性をつかってリクエストを投げることができます。人間の動作をコードに落としこむだけなので、非常に直感的に読みやすいコードを書くことができます。
    2つ目はCookie機能です。サーバから送信されたSet-Cookieヘッダを自動で解釈し、保存します。その後のリクエストでは、保存されたデータを自動でCookieヘッダに付与してくれます。

    これらの機能はZend_Http_ClientとSymfonyのDomCrawlerが行ってくれています。

    Nokogiri(Ruby)

    require 'open-uri'
    require 'nokogiri'
    
    charset = nil
    html = open('http://technica.speee.jp/members') do |f|
      charset = f.charset # 文字種別を取得
      f.read # htmlを読み込んで変数htmlに渡す
    end
    
    # htmlをパース(解析)してオブジェクトを作成
    doc = Nokogiri::HTML.parse(html, nil, charset)
    
    doc.xpath('//div[@id="member"]//li/a/img').each do |node|
      p node.attribute('src').value
    end

    次は言語がかわりRubyです。今回はRubyスクレイピングで比較的使用されるNokogiriを使用します。
    ※余談ですが、Scrapeは削りとるなどの意味があるためNokogiriでScrapeといかした名前ですね

    Nokogiriは、先ほど紹介したsimple_html_domやGoutteとは違い、HTTP通信部分は含まれておらず、html解析のみの機能を提供しています。ですので、html自体を通信して取得する部分は別のライブラリで実装してやります。

    さて、コードを見てみると、htmlを取得する部分はRubyっぽさが出ていますね。後半のNokogiriを使ったスクレイピングの部分はPHPの場合と非常に似ています。
    上記の例では要素の指定にXPathを使用してみましたが、以下のように、cssセレクタで要素指定することもできます。

    doc.css('div#member li a img').each do |node|
        p node.attribute('src').value
    end

    Web::Query(Perl)

    #!perl
    use Web::Query;
    
    wq('http://technica.speee.jp/members')->find('div#member li a img')->each(sub {
        print $_->attr('src');
    });

    こちらはperlのweb::queryを使用しています。
    PHPのsimple_html_domと同じくcssのid, クラスセレクタ、子孫セレクタを用いて要素を指定することが出来ます。
    比較的コードもシンプルに書くことができます。

    最近使用しているライブラリ

    最近個人では今回2番目に紹介したGoutteを使うことが多いです。
    理由としては単純にPHPを1番長く書いているので、使い勝手が良いためです。

    当初は一番はじめに紹介したphpのsimple_html_domを使っていましたが現在はすべてGoutteにプログラムをリプレイスしています。
    リプレイスした理由としては3つあります。

  • 文字エンコーディング
  • simple_html_domもGoutteも内部に文字コードを判定する機構を備えています。ただし、文字コード判定のロジックは単純ではなく、HTTPヘッダのContent-Typeや、htmlのmetaタグをどのように判定に使うかで、判定結果が異なってしまいます。
    しばらく使ってみましたが、simple_html_domよりもGoutteの方が文字コード判定の成功率が高いです。特にmetaタグの記述が間違っているサイトに遭遇した場合、simple_html_domの方は文字コード判定に失敗する確率が高いようです。

  • ログイン処理など
  • ログイン処理を行い、遷移先から特定のリンクをたどっての処理の場合は、POSTにてログイン処理を行いCookieを取得し、そのcookieを用いてデータ取得対象のページを再度リクエストする必要があります。
    simple_html_domではこれらの機能は存在しないため、curlなどを使ってこれらの機能を自力で実装する必要がありますが、Goutteではフォーム送信やCookie処理の機能が内包されているため、簡単に処理を記述することができます。
    また、ログイン処理の他にも、例えば、ページャーがあるページにて次のページの要素がなくなるまで次のページリンクをクリックしながらデータを抽出するといったことも、「DOMから次のページの有無を判定するロジックを組む」より、「次へボタンがあったらクリックする」という簡単な記述で実現することができます。

  • ドキュメント
  • ここはスクレイピングに限らず重要だと思います。未知のエラーが出た場合など調べた時に全く情報がないのと情報があるとでは解決スピードが違います。
    simple_html_domもドキュメントは日本語なども多数存在します。Goutte自体はフレームワークコンポーネントをつなぎあわせたものなので、Goutteを作成する際のエラーやお作法などはGoutte自体では出なくても、フレームワーク側のドキュメントを見ることで比較的用意に解決することが出来ます。

    最後に

    今回いくつかの言語でスクレイピングをしましたが、どれが一番いいというのは一概には言えません。
    判断材料として自分の書きやすい言語で試してみるのもいいかもしれません。
    また何度も言いますが適度にsleep処理はかけてあげてください。
    では節度あるスクレイピングライフをお送りください。

    補足

    コードなんて書きたくない、でもスクレイピングして情報を抽出してみたいという方へ。
    kimono 上記のサイトでは一切プログラムを使用することなく、指定のwebサイトの情報をクリックして指定していくことで情報を抽出するAPIを作成することが出来るサービスになります。
    出力形式はjsoncsvとあり、無料版もあるので体験してみたいという方はぜひお試しください。