PHPではてブスクレイピング+α

はてブPHPスクレイピングしてみた。

  • タグ検索結果からサイト名とURLを取得
  • 被ブクマ数を取得
  • XML形式で出力

というシンプルな内容です。

スクレイピングには、PHP凡庸スクレイピングライブラリを作ってみたで紹介されているライブラリを少々いじって利用。また、被ブクマ数取得にはAPIを使った。(参考サイト:はてなブックマーク件数取得API PHP版サンプル


ソース:

HatebScraper.Class.php

<?
class HatebScraper{
  
  function __construct($url="", $encoding="", $noQuery=""){
    
    (!empty($url)) ? $this->url = $url : $this->errors[] = "URLを設定してください";
  
    switch (true) {
      case empty($encoding)                  : $this->errors[] = "URLの文字エンコーディングを設定してください"; break;
      case eregi("SHIFT(-|_)JIS", $encoding) : $this->encoding = "SJIS-win"; break;
      case eregi("EUC-JP", $encoding)        : $this->encoding = "eucJP-win"; break;
      case eregi("UTF-8", $encoding)         : $this->encoding = "UTF-8"; break;
      case eregi("ISO-2022-JP", $encoding)   : $this->encoding = "ISO-2022-JP"; break;
      case eregi("JIS", $encoding)           : $this->encoding = "JIS"; break;
      case eregi("ASCII", $encoding)         : $this->encoding = "ASCII"; break;
      default                                : $this->errors[] = "不明なエンコーディングです";
    }
    
    if (empty($noQuery)) {
      if (!empty($_GET["tag"])) {
        $keyword = mb_convert_encoding($_GET["tag"], $this->encoding, "UTF-8");
        $this->keyword = rawurlencode($keyword);
        $this->url    .= $this->keyword;
      }
	  else {
        $this->errors[] = "検索キーワードを入力してください";
      }
	
	  if( !empty($_GET['page']) ){
		$this->page = 25 * $_GET['page'];
		$this->url .= "&of=" . $this->page;		
	  }

   	  switch($_GET['sort']) {
		case 'hot'  :
		case 'eid'  :
		case 'count':
						$this->url .="&sort=" . $_GET['sort'];
						break;
		default		: 	//do nothing
		}
    }
  }

  
  // HTMLを取得して、SimpleXMLObjectに変換
  function retrieve(){
    $html = $this->getHTML();
    
    //SimpleXMLオブジェクトに変換
    $this->xml = $this->html_to_simplexml($html);
  }
  
  // 要素を抽出
  function pickUpElement($xpath=""){
    (empty($xpath)) ? $this->errors[] = "対象の要素をXPath式で指定してください" : "";
    // 配列をセット
    foreach ($this->xml->xpath($xpath) as $item){
      $items[] = (string) str_replace("&amp;", "&", $item->asXML());
    }

    return $items;
  }

  // いったん、別のエンコードに変換したキーワードを再度UTF-8に変換し直す
  function convertKeyword(){
    $keyword = rawurldecode($this->keyword);
    $keyword = mb_convert_encoding($keyword, "UTF-8", $this->encoding);
    
    return $keyword;
  }
  
  // HTMLを取得
  function getHTML(){
    
    $client = new HTTP_Client();
    $client->setDefaultHeader(array('User-Agent' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'));
    $client->get($this->url);
    $result = $client->currentResponse();
    
    switch($this->encoding){
      case "SJIS-win" : $this->encoding_html = "shift_jis"; break;
      case "eucJP-win": $this->encoding_html = "euc-jp"; break;
      default         : $this->encoding_html = $this->encoding;
    }
    
    $html = $result["body"];
    $html = preg_replace("/".$this->encoding_html."/i", "utf-8", $html);
    $html = mb_convert_encoding($html, "UTF-8", $this->encoding);
    
    // コメントを削除
    $html = preg_replace("/<!--([^-]|-[^-]|--[^>])*-->/", "", $html);
    $html = str_replace("&", "&amp;", $html);
    
    if (preg_match("/<title>(.*)<\/title>/i", $html, $match)){
      $this->title = $match[1];
    }
    
    return $html;
  }
  
  // SimpleXMLオブジェクトに変換
  function html_to_simplexml($html_){
    
    // DOMDocumentの文字化け対策
    $html_ = preg_replace('/<title>/i',
                          '<meta http-equiv="content-type" content="text/html; charset=utf-8"><title>',
                          $html_);
    
    @$dom = new DOMDocument("1.0", "utf-8");
    @$dom->loadHTML($html_);
    
    // DOMをSimpleXMLへ変換
    $ret = simplexml_import_dom($dom);
    
    $str = $ret->asXML();
    
    // XML宣言付与
    if (true !== preg_match('/^<\\?xml version="1.0"/', $str)) {
      $str = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $str;
    }
    
    $ret = simplexml_load_string($str);
    
    return $ret;
  }
  
  // エラーがあった場合、エラーメッセージを含む文字列を返す
  function isError(){
    if (count($this->errors) > 0) {
      foreach($this->errors as $error) {
        $msg .= "<li>{$error}</li>\n";
      }
      
      echo "<ul>". $msg ."</ul>";
    }
	else {
      return false;
    }
  }
}

?>


hateb.php

<?php

//$_GETにtag, page, sortを指定可能

require_once "HTTP/Client.php";
require_once "XML/RPC.php";
require_once "HatebScraper.Class.php";

$scraper = new HatebScraper("http://b.hatena.ne.jp/t?tag=", "UTF-8");

if ( empty($_GET["tag"]) ) { echo "no tag"; exit; }

// XMLに変換して格納(その際に不必要なタグを除去)
$scraper->retrieve();

// 要素を抽出して、変数に代入
$titles = $scraper->pickUpElement('//a[@class="bookmark"]');
$urls = $scraper->pickUpElement('//a[@class="bookmark"]/@href');
$count  = count($titles);

//Get Hateb User
$params = array();

for($i=0;$i<$count;$i++) {
//strip href part
$urls[$i] = strstr($urls[$i], '"');
$urls[$i] = substr($urls[$i], 1, (strlen($urls[$i])-2) );

$params[$i] = new XML_RPC_Value($urls[$i], 'string');
}


$msg = new XML_RPC_Message('bookmark.getCount', $params);
$cli = new XML_RPC_Client('/xmlrpc', 'b.hatena.ne.jp');

$resp = $cli->send($msg);

if (!$resp) {
    echo 'Communication error: ' . $cli->errstr; exit;
}

if( !$resp->faultCode() ) {
    $data = XML_RPC_decode( $resp->value() );
}
else {
    var_dump($resp->faultCode());
    var_dump($resp->faultString()); exit;
}
	
$sites = array();

for($i=0;$i<$count;$i++) {
	$sites[$i] = array(
						'title' => $titles[$i],
						'url'	=> $urls[$i],
						'user'	=> $data[$urls[$i]]
					 );
}

// キーワードをUTF-8に戻す
$scraper->keyword = $scraper->convertKeyword();


// Output XML
header("Content-Type: text/xml;charset=utf-8");
echo '<?xml version="1.0"?>'."\n";
echo "<results>";

if ($scraper->isError() !== false) {
  $scraper->isError();
}
else {
  $msg = '';
  
  for ($i=0; $i<$count; $i++){
    $msg .= '<result id="'. ($i + 1) .'">';
    $msg .= "<title>". strip_tags($sites[$i]['title'])."</title>";
	$msg .= "<url>".strip_tags($sites[$i]['url'])."</url>";
	$msg .= "<user>".strip_tags($sites[$i]['user'])."</user>";
    $msg .= "</result>";
  }
  
  echo html_entity_decode($msg);
}

echo "</results>";
?>

hateb.php?tag=Tag&page=1&sort=count

みたいに実行できます。
APIがなくてもスクレイピングを駆使すれば色々作れそう。