PHP+SimpleXMLElementでTwitterのスクレイピング
via. PHPでTwitterのBotを作ってみる - yuyarinの日記
取得したHTMLから目的の情報だけを取り出す。取り出したい情報は
- ステータス番号 ($status_number)
- ユーザ名 ($username)
- メッセージ ($word)
- @先 ($at)
の4つ。
PHPでTwitterのBotを作ってみる - yuyarinの日記
うまいやり方が分からなかったので、strpos()とsubstr()で目的の情報が含まれる部分を愚直に取り出した後、preg_match()で正規表現マッチングして情報を抜き出す。これをwhileループで回す
そういえば,スクレイピングってちゃんとしたことないなーと思ったのと,DOM::loadHTML - 「PHPで街を育てる」の続きの続きの続き - Do You PHP はてなを思い出して,PHP5なら素のPHPでXPathとか使えるのかとか思い,せっかくだからXPathの練習がてらTwitterスクレイピングのクラス書いてみた.
最初はボットまで実装しようとしたけど,だったらServices_Twitter使っちゃえばいいじゃんとか考えて,でもAPI使うことの何がいやかって,制限にひっかかる*1のが嫌で,そもそもAPI使うならスクレイピングする必要ないじゃんとかうだうだ色々考えてしまって.
なので今回は,API使わないで取得できる,public_timeline と userのtimelineと,user with friends の3つのみ取得可能.
というかPHP5専用コード書いたのがはじめてで,例外とアクセス修飾子の使い方が正しいのかはわからないw*2 ついでにfactoryパターンもちゃんとわかってないので,なんか変なとこあったら教えてください>< *3
使い方
流れ
コード
<?php require_once 'Twitter_Scrape.php'; try { $Twitter =& Twitter_Scrape::factory(TW_FRIENDS, 'sotarok'); $body = $Twitter->getTimeline(); $timeline = $Twitter->parse($body); var_dump($timeline); } catch (Exception $e) { echo $e->getMessage() . "\n"; }
結果
[sotaro@centos php]$ php twitter_bot.php array(20) { [0]=> array(5) { ["id"]=> string(9) "622329362" ["name"]=> string(8) "shimooka" ["body"]=> string(73) "[B!]m-takagiの実験室(12:41) http://www.m-takagi.org/ 神のサイト." ["permalink"]=> string(46) "http://twitter.com/shimooka/statuses/622329362" ["date"]=> string(25) "2008-01-21T03:43:52+00:00" } [1]=> array(5) { ["id"]=> string(9) "622328542" ["name"]=> string(8) "ukstudio" ["body"]=> string(38) "@forestk Gの人、はじめてみた!" ["permalink"]=> string(46) "http://twitter.com/ukstudio/statuses/622328542" ["date"]=> string(25) "2008-01-21T03:43:33+00:00" } [2]=> array(5) { ["id"]=> string(9) "622327832" ["name"]=> string(5) "rhaco" ["body"]=> string(76) "greeでアバタ使いたいなー、でも携帯で使う気はないなー" ["permalink"]=> string(43) "http://twitter.com/rhaco/statuses/622327832" ["date"]=> string(25) "2008-01-21T03:43:13+00:00" } [3]=> ...
コード Twitter_Scrape.php
流れは,
parseでかえってくる値は,
body の内容の取得の部分ですが,replyがあったりURLが含まれててaタグがあると,子ノードが発生しちゃってそのままだとうまくとれなかったので,asXMLで取得してタグ除去してるんだけど,ここがあんま綺麗じゃないなーと.チャイルドまで全部テキストで取得する方法はないものでしょうか.なんかありそうですが知らないので適当(ぁ
実行環境は
<?php require_once 'HTTP/Request.php'; define('TW_PUBLIC' , 100); define('TW_USER' , 101); define('TW_FRIENDS' , 102); /** * Twitter_Scrape * * @author Sotaro KARASAWA <sotaro.k /at/ gmail.com> * @version 0.0.1 * @access public */ class Twitter_Scrape { protected $username = ""; protected $timeline; public $contents; public function __construct() { } public static function &factory ($timeline, $username = null) { $c = new Twitter_Scrape(); $c->timeline = $timeline; if ($c->timeline != TW_PUBLIC) { if ($username === null) { throw new Exception('Exception : USERNAME is required'); } } $c->username = $username; return $c; } public function getTimeline () { switch ($this->timeline) { case TW_PUBLIC: $url = 'public_timeliness'; break; case TW_USER: $url = $this->username; break; case TW_FRIENDS: $url = $this->username . '/with_friends'; break; default: return false; } return $this->_getTimeline('http://twitter.com/'. $url); } protected function _getTimeline($url) { $Request = new HTTP_Request($url); if(PEAR::isError($Request->sendRequest())) { throw new Exception ('Exception : false to get timeline'); } $body = $Request->getResponseBody(); return $body; } /** * parse * * @param string $body HTML * @return array $contents */ public function parse ($body) { // TwitterのHTMLのせいで,@ つけないとwarning出まくり $DOM = @DOMDocument::loadHTML($body); $XML = simplexml_import_dom($DOM); $arr = $XML->xpath('//tr[@class="hentry"]'); $i = 0; foreach ($arr as $val) { $content = $val->xpath('td[@class="content"]'); // application と repliesも取得しようと思ってたんだけど,途中でめんどいことに気づいてやめた $this->contents[$i++] = array( 'id' => substr((string)$val['id'], 7), 'name' => (string)$content[0]->strong->a, 'body' => trim(strip_tags($content[0]->span[0]->asXML())), 'permalink' => (string)$content[0]->span[1]->a[0]['href'], 'date' => (string)$content[0]->span[1]->a[0]->abbr['title'], //'application' => (isset($content[0]->span[1]->a[1])) ? (string)$content[0]->span[1]->a[1] : null , //'replies' => (isset($content[0]->span[1]->a[2])) ? substr((string)$content[0]->span[1]->a[2], 12) : null, ); } return $this->contents; } }
で,
最後の最後でSimpleXMLElementのおいしさがわかったので,それは今度まとめます.
すべての子ノードにオブジェクトとしてアクセスできる点がすごく楽な点だと思います.たぶんtoStringでノードテキストが取得できるのね.だからStringキャストが必要になるというわけです.
参考にしたサイト・資料
修正した
- タイトルちゃんとした.
- constractのはぢい間違い修正
*1:TwitterIrcGateway使ってるから,常に制限いっぱいカツカツなはず
*2:しかもトップレベルのExceptionはcatchしないほうがいいの?