php で xpath
結構前に、Liner NoteさんのPHP汎用スクレイピングライブラリを作ってみたで公開されているソースを元に改造したxpathルーチンを作ったのでおいときます。いろいろあって作ってから1年ぐらい経過したけど。。。
ライセンスは、派生元が修正BSDなんで修正BSDでおながいします。
ダウソする↓
http://rtilabs.net/files/2009_09_02/simple_scraper.zip
メリット。
firefox xpatherぽい値を返してくれるよう最善の努力をしている。
つまり、努力賞ってことね。
<? // //htmlをxpathでぬはぬはするルーチン //FireFoxのxpatherコンパチを目指して挫折しています。 // //派生元 http://note.openvista.jp/2008/php-scraing-library/ //ライセンスは、派生元が修正BSDなんで修正BSDでおながいします。 // //このソフトウェア上記 scraper.Class.php の内容を改変したものが含まれています。 class SimpleScraper { //読み込んだHTML var $XML = FALSE; //おまけ ファイルまたは、HTTP GET public function LoadURL($url,$encode = NULL) { return $this->LoadHTML(file_get_contents($url),$encode); } //HTTP POST public function LoadURLPost($url,$data,$encode = NULL) { $context = array( "http" => array( "method" => "POST" ,"header" => array( "Content-Type: application/x-www-form-urlencoded" ,"Content-Length: ".strlen($data) ) ,"content" => $data ) ); return $this->LoadHTML(file_get_contents($url, false, stream_context_create($context)),$encode); } //文字列から読み込む public function LoadHTML($html,$encode = NULL) { //UTF-8にする if ($encode == NULL) { $html = mb_convert_encoding($html,'UTF-8',$this->getContentType($html)); } else if ($encode = 'pass') { //nop } else { $html = mb_convert_encoding($html,'UTF-8',$encode); } // コメントを削除 $html = preg_replace("/<!--.*?-->/iusm", "", $html); $html = preg_replace("/&/iusm", "&", $html); //scriptタグを消滅させる $html = preg_replace("/<script[^>]*?>.*?<\/script>/iusm", "", $html); $html = preg_replace("/<noscript[^>]*?>.*?<\/noscript>/iusm", "", $html); //styleタグを消滅させる $html = preg_replace("/<style[^>]*?>.*?<\/style>/iusm", "", $html); //DOMDocumentの文字化け対策1 charsetを消す $html = preg_replace('/<meta[^>]*?charset[^>]*?>/uis', '', $html); //DOMDocumentの文字化け対策2 headの直後に utf8のcharsetを入れる $html = preg_replace('/<head[^>]*?>/uis', '<meta http-equiv="content-type" content="text/html; charset=utf-8">', $html); //DOCTYPE が付いていないならつける if (!preg_match("/<!DOCTYPE /ius",$html)) { $html = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' ."\r\n". $html; } $html = preg_replace('/xml:lang="ja"/uis','',$html); //DOMに読み込ませる $dom = new DOMDocument("1.0", "utf-8"); @$dom->loadHTML($html); // DOMをSimpleXMLへ変換 $ret = simplexml_import_dom($dom); $html = $ret->asXML(); // XML宣言付与 if (true !== preg_match('/^<\\?xml version="1.0"/uis', $html)) { $html = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $html; } //xmlnsの消去 ネームスペースの関係でうまく動作しないことがある //xmlnsだけ消すのは大変だから、htmlタグ自体を交換しちゃうよ! $html = preg_replace("/<html [^>]*?xmlns=[^>]*?>/uis",'<html>',$html); $this->XML = simplexml_load_string($html); return ($this->XML !== FALSE); } //xpathで取得した結果をテキストでもらう //xpather の Textに相当する内容 public function XPathText($xpath) { assert($this->XML !== FALSE); $retNodes = array(); $isAttribute = $this->IsAttributeXPath($xpath); $arr = $this->XML->xpath($xpath); if (!is_array($arr)) { return array(); } foreach($arr as $node) { if ($isAttribute) { $h = (string)$node; } else { $h = preg_replace('/<.*?>/uis' , '' , (string)$node->asXML()); } //& -> &asm; となってしまうので戻す. $retNodes[] = $this->EscapeCodeReverse($h); } return $retNodes; } //xpathで取得した結果をInnerHTMLでもらう //xpather の InnerHTMLに相当する内容 public function XPathInnerHTML($xpath) { assert($this->XML !== FALSE); $retNodes = array(); $isAttribute = $this->IsAttributeXPath($xpath); $arr = $this->XML->xpath($xpath); if (!is_array($arr)) { return array(); } foreach($arr as $node) { if ($isAttribute) {//属性だと取れません $retNodes[] = NULL; } else { $tagName = $node->getName(); $h = preg_replace('/^<'.$tagName.'[^>]*?>(.*?)<\/'.$tagName.'[^>]*?>$/uis' ,'\1', (string)$node->asXML() ); //& -> &asm; となってしまうので戻す. $retNodes[] = $this->EscapeCodeReverse($h); } } return $retNodes; } //xpathで取得した結果をOuterHTMLでもらう //InnerHTMLがあるなら OuterHTMLもほしいぢゃん。 public function XPathOuterHTML($xpath) { assert($this->XML !== FALSE); $retNodes = array(); $isAttribute = $this->IsAttributeXPath($xpath); $arr = $this->XML->xpath($xpath); if (!is_array($arr)) { return array(); } foreach($arr as $node) { if ($isAttribute) {//属性だと取れません $retNodes[] = NULL; } else { $retNodes[] = $this->EscapeCodeReverse((string)$node->asXML()); } } return $retNodes; } //xpathで指定したデータを削除する. public function XPathRemove($xpath) { foreach( $this->XPathNode($xpath) as $node ) { $node->parentNode->removeChild($node); } return TRUE; } //xpathで取得した結果をそのままNodeでもらう public function XPathNode($xpath) { assert($this->XML !== FALSE); return $this->XML->xpath($xpath); } //文字コード判別 private function getContentType($html) { $matches = array(); preg_match("/<meta.*?charset=(?:\"')?([0-9A-Za-z\-_]+)(?:\"')?/uis",$html ,$matches ); if (! isset($matches[1]) ) { mb_detect_order("sjis-win,eucjp-win,euc-jp,UTF-8"); return mb_detect_encoding($html); } else { return $matches[1]; } } //属性を求めるxpathですか? private function IsAttributeXPath($xpath) { // /span/@class みたいなヤツ? if ( preg_match('/\/@[^\/]+$/uis',$xpath) ) { return true; } return false; } //XMLの使用上エスケープされてしまう文字列を元に戻す private function EscapeCodeReverse($text) { $text = preg_replace("/>/uis","<",$text); $text = preg_replace("/</uis",">",$text); $text = preg_replace("/&/uis","&",$text); return $text; } }; ?>
使い方
<? //このソースはサンプルなので、 //ソースで echo なんてサンプルソースしかできないダーティな技をたくさん利用していますwww //ライブラリのロード require_once('simple_scraper.inc'); //構築 $ss = new SimpleScraper(); //読み込み 文字コードは自動推測してUTF-8に変換してくれます。 $ret = $ss->LoadURL("http://www.yahoo.co.jp/"); if (!$ret) { echo "パースできませんでした。"; exit; } //textだけよこせ echo "<h2>textだけよこせ</h2>"; $nodes = $ss->XPathText("//table"); foreach($nodes as $n) { echo htmlspecialchars($n,ENT_QUOTES) . "<br /><br />"; } //innerHTMLでよこせ echo "<h2>innerHTMLでよこせ</h2>"; $nodes = $ss->XPathInnerHTML("//table"); foreach($nodes as $n) { echo htmlspecialchars($n,ENT_QUOTES) . "<br /><br />"; } //OuterHTMLでよこせ echo "<h2>OuterHTMLでよこせ</h2>"; $nodes = $ss->XPathOuterHTML("//table"); foreach($nodes as $n) { echo htmlspecialchars($n,ENT_QUOTES) . "<br /><br />"; } //meta の Description値 echo "<h2>meta の description値</h2>"; $r = $ss->XPathText("//meta[@name='description']/@content"); echo $r[0]; ?>
バグがあったらごめんね。
EDFはバグを恐れない。