使い捨てレベルで、クローラを作らなくちゃならなくなったので、とりあえず file_get_contents でもループして放置するか…とも思ったけれど、せっかくなので同時に4接続ほどするようにしてみた。PHP5には curl_multi_exec という便利なものがあるので、これを使う。
ソース
クローラクラス
<?php
class multiCrawler{
var $uriArray = array();
var $output = array();
var $timeout = 0;
var $ua = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12';
//最大同時接続数
var $accessMaxNum = 4;
var $start = 0;
//エラーログを出力するかどうか
var $errorLog = false;
var $errorLogFile = 'crowl_error.txt';
//クロール対象URLをセット
function setUri($uri){
$this->uriArray = (is_array($uri))?$uri:array($uri);
}
//クロールエラーログフラグをセット
function setErrorLogFlg($bool){
$this->errorLog = $bool;
}
//クロールエラーログファイルをセット
function setErrorLogFile($file){
$this->errorLogFile = $file;
}
//タイムアウトをセット
function setTimeout($timeout){
$this->timeout = (is_int($timeout))?$timeout:0;
}
//エラーを出力
function outputError($uri,$error){
$fp = fopen($this->errorLogFile,'a');
fputs($fp,date('Y-m-d H:i:s') . ' ' . $uri . ' : ' . $error."\n");
fclose($fp);
}
//実行
function execute(){
$this->output = array();
$count = count($this->uriArray);
$num = ceil($count / $this->accessMaxNum);
for($i=0;$i<$num;$i++){
sleep(1);
$this->start = $i * $this->accessMaxNum;
$this->end = ($i + 1) * $this->accessMaxNum;
$this->fetchUri();
}
return $this->output;
}
//クロール
function fetchUri(){
for($i=$this->start;$i<$this->end;$i++){
if(!isset($this->uriArray[$i]) || $this->uriArray[$i] == ''){
$this->end = $i;
break;
}
}
for($i=$this->start;$i<$this->end;$i++){
$ch[$i] = curl_init();
curl_setopt($ch[$i],CURLOPT_URL, trim($this->uriArray[$i]));
curl_setopt($ch[$i],CURLOPT_HEADER, 0);
curl_setopt($ch[$i],CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch[$i],CURLOPT_FAILONERROR,1);
curl_setopt($ch[$i],CURLOPT_FOLLOWLOCATION,1);
curl_setopt($ch[$i],CURLOPT_MAXREDIRS,3);
curl_setopt($ch[$i],CURLOPT_SSL_VERIFYPEER,false);
curl_setopt($ch[$i],CURLOPT_SSL_VERIFYHOST,false);
curl_setopt($ch[$i],CURLOPT_USERAGENT,$this->ua);
if($this->timeout){
curl_setopt($ch[$i],CURLOPT_CONNECTTIMEOUT,$this->timeout);
curl_setopt($ch[$i],CURLOPT_TIMEOUT,$this->timeout);
}
}
$mh = curl_multi_init();
for($i=$this->start;$i<$this->end;$i++){
curl_multi_add_handle($mh,$ch[$i]);
}
$active = null;
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
for($i=$this->start;$i<$this->end;$i++){
$this->output[$i] = (curl_error($ch[$i]) == '')?curl_multi_getcontent($ch[$i]):false;
if($this->output[$i] === false && $this->errorLog){
$this->outputError($this->uriArray[$i],curl_error($ch[$i]));
}
curl_multi_remove_handle($mh,$ch[$i]);
curl_close($ch[$i]);
}
curl_multi_close($mh);
}
}
使ってみる
<?php
include('multiCrawler.php');
$uriArray = array(
'http://www.yahoo.co.jp/',
'http://www.google.co.jp/',
);
$mc = new multiCrawler;
$mc->setUri($uriArray);
//$mc->setErrorLogFlg(1); //エラーログ
//$mc->setTimeout(10); //タイムアウト設定
$result = $mc->execute();
print_r($result);
ソースを見てもらえれば、そんなに難しいことはやっていないので、カスタマイズも簡単。同時に4URLへ接続し(設定したURLが5つ以上の場合は、1秒間のスリープ)、データをごそっと配列へ入れて返す仕様。
つまり、設定したURLが膨大、もしくはアクセス先のファイルサイズが大きいと、それだけメモリ食う。使う際には、配列に格納じゃなくて、ファイルに書き出しとかすれば良いんじゃないかな。
今度は、RubyかPythonで、もっと柔軟なクローラでも作ってみますか。