PHP で Google 第二回 サイト収集ツール(クローラ)と本文情報抽出を PHP で実装

前回に引き続き検索エンジンについて勉強中です。今回は少し PHP だと気軽に出来るかもしれない、出来ないかもしれないサイトの情報収集ツールつまるところのクローラを構築してみました。


Google や Yahoo などの検索エンジンは、いくら検索アルゴリズムを良くして、ランキングの精度をあげても、収集したサイトの情報あってこそで、元の情報が少なかったり、精度が悪いと、良い情報を提示することは出来ません。そんなわけでサイト情報を頑張って収集するクローラが必要です。


流れとしては
1. 起点のサイトを決めて、そこからリンクを再帰的に巡り情報を収集する
2. 収集したサイトから本文を抽出する
となります。


まず 2. のフェーズは、検索を行った際に、広告情報などでヒットされては困る(精度が悪くなる)ので、必要になってきます。


サイボウズラボの nakatani さんが実装されたものを PHP に移植してみました。ちょっとまだ出来ていないところはありますが、精度は比較的いいかんじです。
http://hakaselab.sakura.ne.jp/make/extractcontent/index.html


処理内容については id:tarao さんの資料がわかりやすいかと思います。


1. についてもリンクを再帰的に巡る形でつくってみました。ステータスや robots.txt を読まないなど、クローラとしては致命的にまずい部分はありますが、一応、対象サイトより外へはいかないように変更しているので、実験的には使えます。

<?php
ini_set('default_socket_timeout', 3);
print_r(get_linkarray('http://www.example.com'));
exit;

function get_linkarray($link) {
  $context = stream_context_create(array('http'=>array('method'=>'GET', 'header'=>'User-Agent: simplecrawler.library.php 0.0.1')));
  $resultR = array();
  $resultS = simplecrawler($context, $link, $link, parse_url($link));
  foreach($resultS as $k => $v) {
      $resultR[] = $v;
  }
  return $resultR;
}

function simplecrawler($context, $link, $burl, $base, $linkArrayDat = array()) {
  $linkArrayPre = crawler_link(crawler_page($link, $burl, $base, $context), $link, parse_url($link));
  foreach($linkArrayPre as $k => $v) {
    if(!isset($linkArrayDat[$v])) {
      $linkArrayDat[$v] = $v;
      $linkArrayDat     = array_merge($linkArrayDat, simplecrawler($context, $v, $burl, $base, $linkArrayDat));
    }
  }
  return $linkArrayDat;
}

function crawler_page($link, $burl, $base, $context) {
  if(strpos($link, $burl) === 0) {
    $page = @file_get_contents($link, false, $context);
    return $page === FALSE ? null : $page;
  } else {
    return null;
  }
}

function crawler_link($page, $burl, $base) {
  $linkArray = array();

  if($page === null) {
    return $linkArray;
  }

  preg_match_all('/[\s\n\t]+href\s?=\s?"(.*?)"/i', $page, $href);
  for($i = 0; $i < count($href[1]); $i++) {
    $link = $href[1][$i];
    if(preg_match('/^http(s)*\:\/\//', $link)) {
      $result = $link;
    } elseif(preg_match('/^\/.+$/', $link)) {
      $result = $base['scheme'] . '://' . $base['host'] . $link;
    } else {
      // echo $base['path'] . "\n";
      $b = split('/', dirname($base['path']));
      $t = split('/', $link);
      foreach($t as $v) {
        $l = $v === '.' ? true : ($v === '..' ? array_pop($b) : array_push($b, $v));
      }
      $result = $base['scheme'] . '://' . $base['host'] . join('/', $b);
    }
    $linkArray[$result] = $result;
  }
  return $linkArray;
}


本文抽出については、上記以外にも、もちろんいろいろな方法があり、私はそのへんの技術をあんまり追えてないのであれですが、かなりおもしろそうな分野です。


クローラについても、上記のような基本的な処理自体は比較的簡単に書くことが出来ますが、おそらく難しいのは、これを分散処理させて集めたり、速度をあげたり、当初想定していなかったようなパターンのサイトがあった場合の対処方法などだと思います。


Google の論文 The Anatomy of a Large-Scale Hypertextual Web Search Engine の 4.3 Crawling the Web でも処理に DNS による名前解決で時間がかかっていることや、クローラがオンラインゲームをはじめてしまって、処理がおわらなくなってしまったことなどが書いてありました。


個人的には GoogleBot がまだあまり知られていなかった頃に、普通の訪問者だと思った人が "Wow, you looked at a lot of pages from my web site. How did you like it?" とメールを寄越した。というのが、なんとも牧歌的でおもしかったです。