APIとして使いやすい(動くサービスが作りやすい)TwitterのAPIを使って
昨年から少しずつ、Twitter-bot(自動投稿プログラム)を作成していました。
せっかくなので、ここにまとめておきたいと思います。
札幌市水道凍結情報配信bot
「水道凍結情報」-さっぽろお天気ネット-で公開されている情報を
WEBページから取得し、毎晩Twitterに配信するというbotです。
一定期間、一定地域にしかニーズはありません。
が、これも勉強です。
スクレイピング処理
今回スクレイピングしたかったHTMLはこんなソースです。
<tr> <td> <p class=center>中央区</p> </td> <td> <img src="../images/freeze3.gif" alt="注意"> <span class=sfont>注意</span> </td> <td> <img src="../images/freeze2.gif" alt="念のため"> <span class=sfont>念のため</span> </td> </tr>
ここから class=center と class=sfont の中身を3つセットで取り出したい。
取り出すために必要になる処理は大雑把にいうと以下となります。
◇ html情報を文字列として取り出す
◇ Tidy関数を使用し、文字列にストアされたドキュメントをパースする
◇ パースしたTidyオブジェクトを操作して、html情報を切り出す
◇html情報を文字列として取り出す
fopen関数を使って指定URLの情報を取得します。
//GetHttpSource class GetHttpSource { public function get_html($url){ if (($fp = fopen($url, "r")) == FALSE) { $this->status = "エラーが発生しました。"; return; } $str = fgets($fp); while (! feof($fp)) { $str = $str . fgets($fp); } fclose($fp); $data = mb_convert_encoding($str,"utf-8","auto"); return $data; } }
[プロキシを介してインターネットに接続している場合]
fsockopenを使います。
$proxy_name = 'your_proxy'; $proxy_port = 8888; $proxy_cont = ''; $proxy_fp = fsockopen($proxy_name, $proxy_port); if ( $proxy_fp == false ) { echo "Error!!"; return false; } // プロキシ情報を付加してファイル取得 fputs($proxy_fp, "GET $proxy_url HTTP/1.0\r\nHost: $proxy_name\r\n\r\n"); while(!feof($proxy_fp)) {$proxy_cont .= fread($proxy_fp,4096);} fclose($proxy_fp); // プロキシ情報を除去してデータとして格納する $data = mb_convert_encoding(substr($proxy_cont, strpos($proxy_cont,"\r\n\r\n")+4),"utf-8","auto"); return $proxy_cont;
◇Tidy関数を使用し、文字列にストアされたドキュメントをパースする
PHP: Tidy – Manualを参考に。
Tidy 関数 parseString() と cleanRepair() を使用します。
// HTMLの取得 $ca = new GetHttpSource(); // 自作クラス $data = $ca->get_html(INIT_URL); // Tidy関数config $config = array('indent' => TRUE, 'output-xhtml' => TRUE, 'wrap' => 200); // 取得したHTMLファイルの内容をUTF-8の文字コードで$configで指定した設定で、tidyオブジェクトを作成 $tidy->parseString($data, $config, 'UTF8'); // パースされたマークアップに設定に基く誤りの修正を行う $tidy->cleanRepair();
◇パースしたTidyオブジェクトを操作して、html情報を切り出す
ノードとして保持されているTidyオブジェクト内の情報を、再帰的に読み込み、
必要な情報だけを取り出します。
当日(当夜)予想と翌日(日中)予想を判断するのは「読み込まれる順番」しかなかったので、
class変数に取得した値を突っ込んでいって全てが埋まったらarrayに格納という手段をとっています。
class Scraping { private $center= ""; private $sfont1 = ""; private $sfont2 = ""; private $result = array(); private $result_date = ""; // ############ 各地区の予測を取得 public function scraping_data(tidyNode $node) { $this->doScraping($node); return $this->result; } private function doScraping(tidyNode $node) { if(isset($node->id)) { // $node->id が TABLEタグだったらそのなかにデータあり if($node->id == TIDY_TAG_TD ) { $chileNodes = $node->child ; foreach( $chileNodes as $item ){ if(isset($item->id) && $item->id == TIDY_TAG_P ) { if (isset($item->attribute['class'])){ // class属性値がcenterだったら if (stristr ($item->attribute['class'] ,"center") !==FALSE){ // 区 $this->center = trim( $item->child[0]->value); } } } if( isset($item->id) && $item->id == TIDY_TAG_SPAN ) { if (isset($item->attribute['class'])){ // class属性値がsfontだったら if (stristr ($item->attribute['class'] ,"sfont") !==FALSE){ if( $this->sfont1 == "" ){ // 夜から朝方の予想 $this->sfont1 = trim( $item->child[0]->value ); } else { // 日中の予想 $this->sfont2 = trim( $item->child[0]->value ); } } } } } // 情報セットが取れたらリストに格納 if( strlen($this->center) && strlen($this->sfont1) && strlen($this->sfont2) ) { $this->result[] = array("center" => $this->center ,"sfont1" =>$this->sfont1 , "sfont2" =>$this->sfont2 ); //echo "OK"; // クリアする $this->center = ""; $this->sfont1 = ""; $this->sfont2 = ""; } } } // 子供のノードが存在すれば、再帰的にdoScraping()を繰り返す if($node->hasChildren()) { foreach($node->child as $c) { $this->doScraping($c); } } } }
おまけ(日付の取得)
このソースから日付も取り出すことにしました。
<h2>札幌市内の水道凍結予報</h2> <h3>各区の水道凍結指数</h3> <p class=comment>2009年02月24日 16時00分発表</p> <p class=clear></p>
スクレイピング処理はこんな感じになります。
class Scraping { // ############ 日付を取得する public function scraping_date_data(tidyNode $node) { return $this->getDate($node); } private function getDate(tidyNode $node) { if(isset($node->id)) { // $node->id が Pタグだったらそのなかに日付データあり if($node->id == TIDY_TAG_P ) { // $node->id の class属性値がcenterだったら if (stristr ($node->attribute['class'] ,"comment") !==FALSE){ return trim( $node->child[0]->value ); } } } // 子供のノードが存在すれば、再帰的にdoScraping()を繰り返す if($node->hasChildren()) { foreach($node->child as $c) { $result = $this->getDate($c); if(strlen($result)) return $result; } } } }
これでスクレイピング処理が完了。
これをTwitterAPIを使用して、投稿します。
長くなったので続きは次回。