Web系のこととかー。

使い捨てレベルで、クローラを作らなくちゃならなくなったので、とりあえず 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で、もっと柔軟なクローラでも作ってみますか。

§258 · 12月 10, 2010 · プログラミング_PHP · · [Print]

Leave a Reply