SimpleXmlとSimpleTag比較:twitterのhomeをスクレイピングしてみた。

事例としては、

<div id="content">

なタグを抽出とか

<div class="hentry">

なタグを全部抽出について比較します。

id は1つ、class は 複数 という違いがあるので、そのあたりも真面目に考えてみました。
なお、class 抽出のxpath式については CSSのセレクタをXPathに変換する 2007-02-05 - nazonoDiary からもらいました。

書き方

こんな感じで書けます。

idで抽出するとき、以下が等価になります。

SimpleXML:$xml->xpath('//div[@id="content"]') 
SimpleTag:getInAndParameter($tag, 'div',  'id', 'content');

classで抽出するとき、以下が等価になります。

SimpleXML:$xml->xpath('//tr[contains(concat(" ",@class," ")," hentry ")]') 
SimpleTag:getInAndParameter($tag, 'tr',  'class', 'hentry');

twitterからタイムラインを抜いてみる。

例として http://twitter.com/goungounスクレイピングしてタイムラインを抽出します。

SimpleXml で抽出するなら。

$xml = html_to_simplexml($html, false);
$timelines = array();
$timelines = array_merge($timelines, $xml->xpath('//div[@id="content"]//div[contains(concat(" ",@class," ")," hentry ")]'));
$timelines = array_merge($timelines, $xml->xpath('//table[@id="timeline"]//tr[contains(concat(" ",@class," ")," hentry ")]'));

SimpleTag で抽出するなら。

SimpleTag::setof($tag, $html, 'body');
$tag_div_content    = getInAndParameter($tag, 'div', 'id', 'content');
$tag_table_timeline = getInAndParameter($tag, 'table', 'id', 'timeline');
$timelines = array();
$timelines = array_merge($timelines, getInAndParameter($tag_div_content,    'div', 'class', 'hentry'));
$timelines = array_merge($timelines, getInAndParameter($tag_table_timeline, 'tr',  'class', 'hentry'));

実際に作ったもの。緑のバーをクリックすると見れます。
http://goungoun.dip.jp/app/open/rhaco-sample/simpletag003.php?name=goungoun

SimpleTag、getInAndParameter関数の実装

  • getInしてforeachで回してます。
  • id の時は、ヒットしても1タグなので、それを条件に処理を抜けるようにしてます。
  • それ以外のときは、再帰で全てのタグを抽出してます。
  • 再帰するか?正規表現使うか?も指定できます。
<?php
/**
 * タグ名 が $tagName で パラメータ名 が $parameterId で パラメータ値 が $value のタグを全て抽出する。
 *
 * @param SimpleTag $tag      配列でも可
 * @param string $tagName     抽出条件 タグ名
 * @param string $parameterId 抽出条件 パラメータ名
 * @param string $value       抽出条件 パラメータ値
 * @param string $recursive   再帰抽出するか?
 * @param string $value_rex   パラメータ値を正規表現で評価するか?
 * @return array              SimpleTagの配列
 */
function getInAndParameter($tag, $tagName, $parameterId, $value, $recursive=null, $value_rex=null)
{
	if ($recursive === null) {
		$recursive = ($parameterId === 'id' ? false : true);
	}
	if ($value_rex === null) {
		$value_rex = false;
		if ($parameterId === 'class') {
			$value_rex = true;
			$value = '/(^|\s)' . $value . '($|\s)/';
		}
	}
	if (is_array($tag)) {
		$ret = array();
		foreach ($tag as $item) {
			$ret = array_merge($ret, getInAndParameter($item, $tagName, $parameterId, $value, $recursive, $value_rex));
			if (!$recursive && count($ret) === 1) break;
		}
		return $ret;
	}
	return _getInAndParameter($tag, $tagName, $parameterId, $value, $recursive, $value_rex);
}

function _getInAndParameter($tag, $tagName, $parameterId, $value, $recursive, $value_rex)
{
	$ret = array();
	foreach ($tag->getIn($tagName) as $item) {
		if (
		  (!$value_rex && $item->getParameter($parameterId) === $value) ||
		  ($value_rex  && preg_match($value, $item->getParameter($parameterId)))) {
			$ret[] = $item;
			if (!$recursive) return $ret;
		}
		$ret = array_merge($ret, _getInAndParameter($item, $tagName, $parameterId, $value, $recursive, $value_rex));
		if (!$recursive && count($ret) === 1) return $ret;
	}
	return $ret;
}