ふぁぼったーをパースしてみたよ

ふぁぼったーにはAPIもないっぽいし、パースするライブラリが単体で配布されてなかったので、PHP Simple HTML DOM Parserの練習ついでに作ってみました。
くれぐれもふぁぼったーの鯖に負荷をかけるような使い方はしないよう、お願いします。*1
動作サンプル


利用するためには、PHP Simple HTML DOM Parserのsimple_html_dom.phpが必要です。
同じディレクトリか、パスが通っている場所に置いといてください。
PHP Simple HTML DOM ParserはjQueryと同じ感覚で使えるので、パースする際にはかなり便利です。


ダウンロードはこちらを右クリック→リンク先を保存

<?php

/***************************favotter_lib.php****************************
Author: shak (http://d.hatena.ne.jp/shak/)
Version: 0.1
Licensed under The MIT License

Requires simple_html_dom.php (http://simplehtmldom.sourceforge.net/)
***********************************************************************/

/* EXAMPLE */
/*
	require_once('favotter_lib.php');
	$favotter_lib = favotterLib();
	$option = array(
		'screen_name' => 'screen_name',
		'mode'        => 'new',
		'threshold'   => 1,
		'limit'       => 40,
		'page'        => 1,
		'data_type'   => 0,
		);
	$data = $favotter_lib->get($option);
	if ($data) print_r($option);
	else print_r($favotter_lib->notice);
*/

class favotterLib
{
	var $screen_name = null;	//(string)取得対象となるスクリーンネーム
	var $mode = null;	//(string)モード:home.phpではnew/best、userではnew/best/fav
	var $threshold = 1;	//(int)favしきい値:phpだしstringでも構わない。
	var $limit = 100;	//(int)data取得件数。多めに取得しちゃったら切り捨て。
	var $page = 1;	//(int)開始ページ数:1始まり。thresholdと同様stringでも構わない気がする。
	var $notice;	//(array)最後に実行した関数の実行ログ:最後の要素の行頭が'$'ならFavotter落ちの可能性、'%'ならデータ終端の可能性、'!'ならその他エラー。{end($notice); echo current($notice);}など。
	var $favorited = 0;	//(string)ふぁぼられ数:user()を実行した際に自動で取得。一応取得時点ではstring。
	var $data;	//(array)ふぁぼられpost:連想配列→配列の入れ子。連想配列のkeyはtext,screen_name,updated,twitter_link,favotter_link,favotters(favした人のscreen_nameを配列で格納),favorited(被fav数)。
	var $data_type = 0;	//0なら$dataの形式は上記の通りだけど、1なら配列→連想配列の入れ子になる。
	
	function __construct( $screen_name=null )
	{
		require_once 'simple_html_dom.php';
		$this->notice = array();
		if ($this->data_type)
		{
			$this->data = array();
		}
		else
		{
			$this->data = array(
				'text' => array(),
				'screen_name' => array(),
				'updated' => array(),
				'favotter_link' => array(),
				'twitter_link' => array(),
				'favotters' => array(),
				'favorited' => array(),
			);
		}
		if (isset($screen_name))
		{
			$this->screen_name = $screen_name;
		}
	}
	
	//screen_nameがあればuser()、なければhome()を実行する。
	function get( $option )
	{
		if (empty($this->screen_name) && !isset($option['screen_name'])) return $this->home($option);
		else return $this->user($option);
	}
	
	//ユーザー毎のふぁぼられを取得。
	//screen_nameは何らかの方法でセットされないとエラー。mode=new,threshold=1,page=1がfavotter側のデフォ。
	function user( $option )
	{
		$this->favorited = 0;
		if (isset($option['data_type'])) $this->data_type = $option['data_type'];
		$this->__construct();
		
		//$optionを配列に格納したりargsに設定したり。
		//$optionで設定しなくても、直接$favotterLib->modeとかに代入しても良い。
		$names = array('screen_name', 'limit', 'mode', 'threshold', 'page');
		for ($i=0; $i<count($names); $i++)
		{
			if (isset($option[$names[$i]]))
			{
				$this->notice[] = sprintf('set %s to %s.', $names[$i], $option[$names[$i]]);
				$this->{$names[$i]} = $option[$names[$i]];
			}
			else if (empty($this->{$names[$i]}))
			{
				if ($names[$i] == 'screen_name')
				{
					//ユーザー名が空ならエラー吐いて終了。
					$this->notice[] = '![screen_name] is empty.';
					return false;
				}
				$this->notice[] = sprintf('[%s] is empty.', $names[$i]);
			}
			else
			{
				$this->notice[] = sprintf('use %s=%s asis.', $names[$i], $this->{$names[$i]});
			}
		}
		
		//引数配列生成(あとで結合)
		$args = array();
		for ($i=2; $i<count($names); $i++)
 		{
 			if (!empty($this->{$names[$i]}))
 			{
	 			$args[] = sprintf('%s=%s', $names[$i], $this->{$names[$i]});
	 		}
		}
		
		if (isset($option['limit'])) $this->limit = $option['limit'];
		$current_page = $this->page;
		
		for ($j=0; $j<$this->limit/20; $j++)
		{
			
			//html取得
			$html = new simple_html_dom();
			$url = sprintf('http://favotter.net/user/%s?%s', $this->screen_name, implode('&', $args));	//$argsが空配列ならimplodeはnullを返すから問題ないはず
			$html->load_file($url);
			
			//Favotterダウン時処理
			if (strpos($html->plaintext, 'Sorry, Database Server is down なう...') !== false || empty($html))
			{
				$this->notice[] = '$favotter seems to be down now.';
				return false;
			}
			
			//ふぁぼられ数取得
			//結構大雑把な正規表現
			$desc = $html->find('div.message-left div.description', 0);
			if ( preg_match('/a\>のふぁぼられ\((\d+)?\)\<d/', $desc->outertext, $match) )
			{
				$this->favorited = $match[1];
			}
			
			//ふぁぼられpost群取得
			$bubbles = $html->find('.bubble');
			if (count($bubbles) == 0) break;
			$info = $html->find('.info');
			
			if ($this->data_type) $start = count($this->data);
			else $start = count($this->data['text']);
			
			for ($i=0; $i<count($bubbles); $i++)
			{
				if ($this->data_type)
				{
					$this->data[$i+$start] = array(
						'text' => trim($bubbles[$i]->find('span', 0)->plaintext),
						'screen_name' => $info[$i]->find('strong', 0)->find('a', 0)->title,
						'favotter_link' => 'http://favotter.net'.$info[$i]->find('a', 1)->href,
						'updated' => date('Y-m-d H:i:s', strtotime($info[$i]->find('a', 1)->find('abbr', 0)->title)),
						'twitter_link' => $info[$i]->find('a', 2)->href,
						'favotters' => array(),
					);
					foreach ( $info[$i]->find('span', 1)->find('a') as $frst )
					{
						$this->data[$i+$start]['favotters'][] = $frst->find('img', 0)->title;
					}
					$this->data[$i+$start]['favorited'] = count($this->data[$i+$start]['favotters']);
					if (count($this->data) >= $this->limit) break 2;
				}
				else
				{
					$this->data['text'][$i+$start] = trim($bubbles[$i]->find('span', 0)->plaintext);
					$this->data['screen_name'][$i+$start] = $info[$i]->find('strong', 0)->find('a', 0)->title;
					$this->data['favotter_link'][$i+$start] = 'http://favotter.net'.$info[$i]->find('a', 1)->href;
					$this->data['updated'][$i+$start] = date('Y-m-d H:i:s', strtotime($info[$i]->find('a', 1)->find('abbr', 0)->title));
					$this->data['twitter_link'][$i+$start] = $info[$i]->find('a', 2)->href;
					$this->data['favotters'][$i+$start] = array();
					foreach ( $info[$i]->find('span', 1)->find('a') as $frst )
					{
						$this->data['favotters'][$i+$start][] = $frst->find('img', 0)->title;
					}
					$this->data['favorited'][$i+$start] = count($this->data['favotters'][$i+$start]);
					if (count($this->data['text']) >= $this->limit) break 2;
				}
			}
			$current_page++;
			$args['page'] = 'page='.$current_page;
			$html->clear();
		}
		
		if (empty($this->data))
		{
			$this->notice[] = '%no data found.';
			return false;
		}
		
		return $this->data;
	}
	
	function home( $option )
	{
		//基本的にuser()と同じルーチンだけど細かいところが違う。([screen_name]周りとか)
		if (isset($option['data_type'])) $this->data_type = $option['data_type'];
		$this->__construct();
		
		//$optionを配列に格納したりargsに設定したり。
		//$optionで設定しなくても、直接$favotterLib->modeとかに代入しても良い。
		$names = array('limit', 'mode', 'threshold', 'page');
		for ($i=0; $i<count($names); $i++)
		{
			if (isset($option[$names[$i]]))
			{
				$this->notice[] = sprintf('set %s to %s.', $names[$i], $option[$names[$i]]);
				$this->{$names[$i]} = $option[$names[$i]];
			}
			else if (empty($this->{$names[$i]}))
			{
				$this->notice[] = sprintf('[%s] is empty.', $names[$i]);
			}
			else
			{
				$this->notice[] = sprintf('use %s=%s asis.', $names[$i], $this->{$names[$i]});
			}
		}
		
		//引数配列生成(あとで結合)
		$args = array();
		for ($i=1; $i<count($names); $i++)
 		{
 			if (!empty($this->{$names[$i]}))
 			{
	 			$args[] = sprintf('%s=%s', $names[$i], $this->{$names[$i]});
	 		}
		}
		
		if (isset($option['limit'])) $this->limit = $option['limit'];
		$current_page = $this->page;
		
		for ($j=0; $j<$this->limit/50; $j++)
		{
			
			//html取得
			$html = new simple_html_dom();
			$url = sprintf('http://favotter.net/home.php?%s', implode('&', $args));	//$argsが空配列ならimplodeはnullを返すから問題ないはず
			$html->load_file($url);
			
			//Favotterダウン時処理
			if (strpos($html->plaintext, 'Sorry, Database Server is down なう...') !== false || empty($html))
			{
				$this->notice[] = '$favotter seems to be down now.';
				return false;
			}
			
			//ふぁぼられ数取得
			$desc = $html->find('div.message-left div.description', 0);
			if ( preg_match('/a\>のふぁぼられ\((\d+)?\)\<d/', $desc->outertext, $match) )
			{
				$this->favorited = $match[1];
			}
			
			//ふぁぼられpost群取得
			$bubbles = $html->find('.bubble');
			if (count($bubbles) == 0) break;
			$info = $html->find('.info');
			
			if ($this->data_type) $start = count($this->data);
			else $start = count($this->data['text']);
			
			for ($i=0; $i<count($bubbles); $i++)
			{
				if ($this->data_type)
				{
					$this->data[$i+$start] = array(
						'text' => trim($bubbles[$i]->find('span', 0)->plaintext),
						'screen_name' => $info[$i]->find('strong', 0)->find('a', 0)->title,
						'favotter_link' => 'http://favotter.net'.$info[$i]->find('a', 1)->href,
						'updated' => date('Y-m-d H:i:s', strtotime($info[$i]->find('a', 1)->find('abbr', 0)->title)),
						'twitter_link' => $info[$i]->find('a', 2)->href,
						'favotters' => array(),
					);
					foreach ( $info[$i]->find('span', 1)->find('a') as $frst )
					{
						$this->data[$i+$start]['favotters'][] = $frst->find('img', 0)->title;
					}
					$this->data[$i+$start]['favorited'] = count($this->data[$i+$start]['favotters']);
					if (count($this->data) >= $this->limit) break 2;
				}
				else
				{
					$this->data['text'][$i+$start] = trim($bubbles[$i]->find('span', 0)->plaintext);
					$this->data['screen_name'][$i+$start] = $info[$i]->find('strong', 0)->find('a', 0)->title;
					$this->data['favotter_link'][$i+$start] = 'http://favotter.net'.$info[$i]->find('a', 1)->href;
					$this->data['updated'][$i+$start] = date('Y-m-d H:i:s', strtotime($info[$i]->find('a', 1)->find('abbr', 0)->title));
					$this->data['twitter_link'][$i+$start] = $info[$i]->find('a', 2)->href;
					$this->data['favotters'][$i+$start] = array();
					foreach ( $info[$i]->find('span', 1)->find('a') as $frst )
					{
						$this->data['favotters'][$i+$start][] = $frst->find('img', 0)->title;
					}
					$this->data['favorited'][$i+$start] = count($this->data['favotters'][$i+$start]);
					if (count($this->data['text']) >= $this->limit) break 2;
				}
			}
			$current_page++;
			$args['page'] = 'page='.$current_page;
			$html->clear();
		}
		
		if (empty($this->data))
		{
			$this->notice[] = '%no data found.';
			return false;
		}
		
		return $this->data;
	}
	
}


?>

*1:大規模クロールはなるべくapiもあるtwitter側で!