PHPでアメーバブログのRSSから記事内容を取得して、記事内の画像を取得するプログラムを作る
PHPで作るシリーズ 第一弾
最近PHPでできることが増えてきたので、PHPの記事が増えてきました。
今回は、ちょっとまとまったプログラムの作成です。アメーバブログのRSSをPHPで取得して、そのRSSにある記事のURLをリスト化してそのURLを元にアメーバブログの記事のHTMLを取得し、そのHTMLの中から記事本文にある画像のみをURLのリストを作成するというプログラムです。 今後もPHPを使って作るシリーズを展開していく予定です。
まずはロジックを考える
プログラムを作るには、まずどういった流れで作っていくかをロジックとして考えてから創りだすとスムーズだったりします。
今回の流れ
- RSSリストを作る
- RSSリスト分繰り返す (foreache)
- RSSのURLからRSS(XML)を取得する
- XMLを分析してURLを抽出する
- URLリスト分繰り返す(foreache)
- 記事のURLからHTMLをCSSセレクタで扱えるようになるライブラリを使って取得
- オブジェクト形式になったHTMLから本文中の画像タグを抽出する
- 画像タグからURLを抽出して配列に保存する
- 完了
こういった流れのプログラムにしようと思ったのですが、これだと繰り返しこのプログラムを実行したい場合には、取得済みなものが重複して取得時間もそれなりにかかるので。取得済みリストを保存する形にして 重複分を回避するようにします。また、作っている途中に幾つか問題も出てきたので、その対策も盛り込んで行きます。
RSSリストを作る
$rss_url_list = array( "http://rssblog.ameba.jp/xxxxxxxxxxx/rss20.xml", //memo "http://rssblog.ameba.jp/xxxxxxxxxxx/rss20.xml", //memo );
まずは、RSSの数の分だけ繰り返すようにするために RSSのURLリストを配列にして foreache を使えるようにします。配列にはキーの指定は省略して、値のみでOKです
取得済みが有るか無いかチェックして、ある場合は取得しておく
//環境設定 $link_db_path = "./logs/link.db"; $img_db_path = "./logs/img.db"; //dbの取得 $link_db = unserialize(file_get_contents($link_db_path)); $img_db = unserialize(file_get_contents($img_db_path)); if (empty($link_db)) { $link_db = array(); } if (empty($img_db)) { $img_db = array(); }
今回作るプログラムを定期的に実行して、画像のリストを収集していく場合に、重複は必要ないので。重複が起きないように取得済みのものをテキストファイルに保存しておきます。その保存してあるテキストファイルを取り出すところの部分です、テキストファイルには配列のまま保存する形で行っています。 serialize を使うことで配列は文字列に変換してテキストファイルに保存することが出来ます。unserialize を行うことで文字列から配列に戻すことが出来る。
if の部分では、file_get_contents で取得出来ていない時には空の値になりますので $link_db と $img_db が未定義の変数となるので。取得できてない時には空の配列とするために それぞれ array() で空の配列にしておきます。これをしておかないと、後で登場する部分で上手く動かなくなります。
RSSのリストから記事のURLを抽出する
foreach ($rss_url_list as $urls) { $rss = simplexml_load_file($urls); foreach($rss->channel->item as $item){ $link = $item->link; //オブジェクトを配列に変換 $links = (array)$link; //配列に格納 $url[] = $links[0]; } }
simplexml_load_file でXML(RSS)を取得してオブジェクトにします。そのオブジェクトを使って、foreacheで記事のURLを抽出します。一旦 $link に入れて配列に変換して。URLのリストを$urlにキー省略で、値だけを入れていってます。
ちなみに$urlの中身
$url = array( "記事URL1", "記事URL2", );
中身はこんな感じになってます。
今回取得した記事URLリストと今までに取得した記事URLリストを比較して重複を取り除く
$url_no_overlap = array_diff( $url , $link_db );
今回取得したものが $url で今までに取得したものが $link_db を比較して重複を取り除きます。もし、今までに取得したものが無かった場合に、$link_db が存在しないのでそこでエラーが出てしまいます。なので、先ほど $link_db や $img_db に空の配列を入れておきました。$img_db も後ほど同じような処理をします。
array_diff で配列の中の値を両方で比較して重複を取り除きます。ちなみに array_diff_key では、配列のキーを対象に比較して重複を取り除くものとなります。
重複を取り除いた取得すべき記事のURLがなかった場合
//URLリストがなければ終了 if (empty($url_no_overlap)) { exit; }
もし、重複が無い記事リストが無かったらこの後の処理は不要なので、ここでプログラムを終了させるものです。exit;を使うことで、途中のプログラムもその時点で終了させることが出来ます。
取得済みリストと重複がないリストを一つにする
//dbと重複無しを足して、ファイルに保存 $link_db = array_merge($link_db, $url_no_overlap); file_put_contents($link_db_path, serialize($link_db));
処理スべきURLのリストがあれば、今まで取得したリストと結合してやります。保存済みの配列に取得した重複のないリストの配列を結合します。そのあとに、その配列を serialize して文字列に置き換えて。 取得済みリストを上書きして更新します。
これから取得するURLが存在するかのチェックをする
//URLリストから存在するかチェックして、存在するものを配列に保存する。 foreach ( $url_no_overlap as $value) { $response = @file_get_contents($value, NULL, NULL, 0, 1); if ($response !== false) { $url_no_error[] = $value; } }
重複していないもがここまでやって来ましたが、その中にあるURLに存在しないURLが混ざっている場合があります、その時は404のエラー等で後の処理がうまく行かなくなるのでこの時点で排除します。 @file_get_contents($value, NULL, NULL, 0, 1); で存在するかどうかのチェックを行っています。存在していない場合にはNULLとなり空の返り値になるので、空ではない場合だけの生きたURLリストに置き換えています。
もう一度URLリストが存在しなくなった場合
if (empty($url_no_error)) { exit; }
またしても登場しましたが、この時点でこのあとの処理が存在しない場合は、ここでプログラムを終了させます。ここまでの処理で、URLリストから記事のURLリストを取り出しの一連の処理は完結です。
HTMLの取得をし、必要な部分を取り出す作業
require_once "simple_html_dom.php"; //URLのリストからHTMLを取得し、HTMLからIMGのURLを取得してリストにする foreach ($url_no_error as $value) { $html = file_get_html($value); foreach ($html->find('div.articleText img') as $content ) { $content = $content->src; $imgurl[] = $content; } }
ここからはURLリストをもとに、HTMLを分析してimgタグのURL部分を取り出す作業が始まります。HTMLをCSSのセレクタ等で簡単に絞り込めるようにする Simple HTML DOM というライブラリを使って行います。これはHTMLをオブジェクトに変換して階層化します。ライブラリを利用するために、require_once でライブラリを読み込んでいます
このライブラリは file_get_html(URL) という形で、URLからHTMLを取得してオブジェクトにすることが出来ます。そのオブジェクトを $html に入れて foreache で回します。
$html->find の部分でセレクタで要素を指定できます jQueryでのセレクタと同じ使い方でおなじみです。そして、imgタグを抽出して、srcにある画像のURLを取り出します。それを $imgurl という配列に保存しておきます、キーは指定なしです。
不要な画像を除去する
//不要な画像の排除を行う foreach ($imgurl as $value) { if ( !stristr($value , 'stat.ameba.jp/blog/ucs/img') and !stristr($value , 'emoji.ameba.jp') ) { $img_url_list[] = $value; } }
アメブロには画像で、絵文字を挿入できますそういったものは不要なので。画像のURLのリストから排除します。
取得済みのURLを除去する
//重複の除去 $img_url_list_no_overlap = array_diff( $img_url_list , $img_db );
記事URLのリストでも行った array_diff を使って、取得済みのimgのURLリストと今取得してきたURLリストを照らしあわせて重複を削除する工程です。
取得済み画像リストを更新する
//画像のリストを結合して保存 $img_db = array_merge($img_db, $img_url_list_no_overlap); file_put_contents($img_db_path, serialize($img_db));
こちらでは、今取得してきたリストと取得済みのリストを結合して、配列をテキスト化して保存する工程です。これも 記事URLのリストと同じことを行っています
これで一応完結ですが、そのまま画像URLリストを使って画像をサーバーにダウンロードすることも可能です。PHPのダウンロードの方法では、同じファイル名があると上書きしてしまいます。なので、上書きをしないシステムもしくは、重複させないシステムを作る必要があります。
ダウンロード周りの作成は、次回に続く!
今回作成したプログラムの小話
今回はRSSを取得してそこからHTML、HTMLから画像のURLってのをつくろうとこのプログラムを作っていたんですが、特に苦労した点は存在しないURLを排除するところです。あの箇所で最初はHTML全体を取ってくる方法でやってたいのですが、かなり処理時間がかかりました。そこで、ヘッダーだけをチェックしてURLが有効かどうかをチェックする方法に変えただけでかなり、処理時間が短縮されました。またコードの実行の順序を変えるだけで、さらに処理時間が短くなりました。最初は何十秒も処理に時間がかかっていたのですが、改良を重ねていくと、初回は半分ぐらいの時間で完了出来るようになって。2回目以降に限っては 10倍以上早く完了できるようになっていました。同じ結果を得るにも、こんなに時間の差が出るなんてプログラムは奥が深いですね。あとは自分が意図していたものが出来上がった時は、とっても達成感がありますね。
今回作ったのはアメブロ専用なものですが、要所要所はいろんなことに応用できる部分だったりします。配列の保存とかは、データベースを使わない簡易的な方法としてはとても重宝するし、HTMLを取得して分析とかは使い方次第で、ビックデータを作ってそれを分析することで何かに利用できたりとか
PHPで何かを作り始めるには、とてもいい勉強になったかと思います。これからもPHPで色々作っていきます!
今回作成したコード全体はこちら
require_once "simple_html_dom.php"; //環境設定 $link_db_path = "./logs/link.db"; $img_db_path = "./logs/img.db"; //dbの取得 $link_db = unserialize(file_get_contents($link_db_path)); $img_db = unserialize(file_get_contents($img_db_path)); if (empty($link_db)) { $link_db = array(); } if (empty($img_db)) { $img_db = array(); } $rss_url_list = array( "http://rssblog.ameba.jp/xxxxxxxxxxx/rss20.xml", //memo "http://rssblog.ameba.jp/xxxxxxxxxxx/rss20.xml", //memo ); foreach ($rss_url_list as $urls) { $rss = simplexml_load_file($urls); foreach($rss->channel->item as $item){ $link = $item->link; //オブジェクトを配列の変換 $links = (array)$link; //配列に格納 $url[] = $links[0]; } } //配列から重複を削除 $url_no_overlap = array_diff( $url , $link_db ); //URLリストがなければ終了 if (empty($url_no_overlap)) { exit; } //dbと重複無しを足して、ファイルに保存 $link_db = array_merge($link_db, $url_no_overlap); file_put_contents($link_db_path, serialize($link_db)); //URLリストから存在するかチェックして、存在するものを配列に保存する。 foreach ( $url_no_overlap as $value) { $response = @file_get_contents($value, NULL, NULL, 0, 1); if ($response !== false) { $url_no_error[] = $value; } } //URLリストがなければ終了 if (empty($url_no_error)) { exit; } //URLのリストからHTMLを取得し、HTMLからIMGのURLを取得してリストにする foreach ($url_no_error as $value) { $html = file_get_html($value); foreach ($html->find('div.articleText img') as $content ) { $content = $content->src; $imgurl[] = $content; } } //不要な画像の排除を行う foreach ($imgurl as $value) { if ( !stristr($value , 'stat.ameba.jp/blog/ucs/img') and !stristr($value , 'emoji.ameba.jp') ) { $img_url_list[] = $value; } } //重複の除去 $img_url_list_no_overlap = array_diff( $img_url_list , $img_db ); //画像のリストを結合して保存 $img_db = array_merge($img_db, $img_url_list_no_overlap); file_put_contents($img_db_path, serialize($img_db));
コードのライセンスは CC0 でOKです。自由に使ってください、サーバーのcronを使えば自動でどんどん情報を収集していってくれますよ
COMMENT