6. 例1.はてなフォトライフ

前章まででスクレイピングについての殆ど一通りの内容を理解できたかと思います。あまり難しくないと思いますのですんなり入っていけると思います。

このあとは、いろいろ自分で好きにやってもらえばよいのですが、どういう勉強でも、例題を幾つかこなしていくことが大事だと思うので、ここからは、スクレイピング処理に関する幾つかのサンプルを用意して解説を進めていくことにしてみます。
何れにしろ、基本は前章までで学んだ事柄の応用です。いくつも作っていくうちに自分自身のプログラミングパターンを見つけることができると思います。

今回は「はてなフォトライフの画像を動的にスクレイピング処理」について解説していきます。

はてなフォトライフ はユーザがサーバに画像をアップロードし、それをみんなで見たりすることができる写真共有サイトです。なので、画像がいくつも表示されます。
今回は画像情報をハンドリングする方法を中心に進めていきましょう。
とはいえ、画像といっても、ハンドリングする項目はあくまでもHTMLファイルのスクレイピングなので、そんなに難しくはありません。画像を表示するタグを取得して、自前のサーバに表示してあげるというだけです。

また、今まで作成したサイトでは、取得するURIが固定だったため、今ひとつインタラクティブ性に欠けていました。ここでは、取得するURIも動的に変更できるようなプログラミングについての基本も学んでみましょう。

例によって、ソースを先に提示し、解説を進めていきます。

ではプログラムを見てみましょう。共通で利用する関数などを「 hatena_fotolife_init.php 」においてみました。ソースの一部を外部ファイルとして出しておいても結構な行数になってしまいましたが、途中にコメントなどを記載しているため行数が増えているので、実質的なソースは思ったより長くないはずです。
前回までと異なる部分は、プログラムの中で、パラメータを元に、取得するURLを変化させている部分です。27行目の if($_GET['id']<>"") の部分は、このプログラム内で、GETメソッドで渡されたidという変数がNULLでなければ(つまり何かidが渡されていれば)$target_URL(取得するHTMLファイルのURL)をこれにしなさい、という感じで記載されています。

今回は内部関数を3つ作成しました。

  • dump_nodes_fotolife()関数 (49~82行目)
  • dump_nodes_page()関数 (85~111行目)
  • print_navi()関数 (113~135行目)

メインの部分は、138~177行目になります。メインの部分を見てみましょう。取得したいURL($target_URL)を27~40行目で計算し、143行目でお馴染みのget_html()関数でHTMLファイルを取得します。
取得したデータを元に148行目でtidyオブジェクトを作成します。
150行目でcleanRepair()した後、153行目でdump_nodes_fotolife()関数に $tidy->body() オブジェクトを渡します。

tidyオブジェクトが渡されたdump_nodes_fotolife()関数はIMGタグの存在をチェックします(54行目)。
IMG タグの中に /images/fotolife/ の文字列が見つかれば(57~59行目)、ユーザがアップロードした写真のサムネールと見做し、次の処理へ進みます。
ユーザのアカウント名を取得する方法は、取得したIMGタグのSRCのアトリビュートを参照しています。アカウント名取得のロジックは21~23行目に記載しています。IMGタグは相対パスで書かれているので、12行目で指定した INIT_URL をSRCの属性値に付与し、絶対パスを取得します。
これを繰り返し、全てのサムネールのURLとユーザの名前のペアを取得し、配列として153行目の $urls に配列を戻します。

次にページ情報を取得します。ページ情報は現在何ページを表示しているかという情報です。多分これはスクレイピングしなくても計算できると思いますが、今回は勉強ということなので、これもスクレイピング処理で求めてみましょう。

156行目のdump_nodes_page()関数に再度 $tidy->body() オブジェクトを渡します。85行目からのdump_nodes_page()関数では、渡されたtidyオブジェクトを元に、Aタグを探します(89行目)。Aタグの中で、ページ情報が記載されている部分の href 属性には 「./userlist?of=」という値が設定されていますので、これに該当するものをチェックします。このときの $node の値は 「 <a href="./userlist?of=150">4</a> 」という形式になっています。この4の部分を取得するには、

$node->child[0]->value
で、取得できます。更に、rtrimで終端文字列を排除します。ちなみに、何故終端文字列を排除しなくてはいけないかというのは実はよくわかりませんが、排除せずに以降の処理を進めていくと表示が崩れてしまったので、試行錯誤でこのようにしました。
このあたりはいろいろ考えながら実装していくひとつのオモシロさなのかも知れません。
99行目で取得したページ情報を$pageという配列に格納し、配列を戻します。
ページ情報も無事取得でき、162行目のページナビゲーションを表示する print_navi() 関数を直ぐに実行してもよさそうなのですが、158行目と160行目で怪しげな計算をしています。
はてなフォトライフのナビゲーションは例えば、10ページを表示している場合は、
<prev 6 7 8 9 11 12 13 14 15 next>
のように、10というページ数は表示されないようなのです。なので、この10も表示するために $page 配列に現在のページ ($id + 1) をわざわざpushしています。ちなみに、何故現在のページが ($id + 1) であるかは30行目、36行目あたりを見てください。
更に sort($page,SORT_NUMERIC); でソートしている理由は、ここで取得しているpage情報は 「 <prev 6 7 8 9 11 12 13 14 15 next> 」という順番で配列に格納されているので、これの後にpushすると「 <prev 6 7 8 9 11 12 13 14 15 next> 10 」という感じになってしまいます。なので、数値としてソートします。
しかし、数値としてソートすると今度は「 <prev next> 6 7 8 9 10 11 12 13 14 15 」となってしまいます。これは出力の際に修正することにし、この配列の状態で162行目の print_navi($page); を実行します。

113~135行目の print_navi() 関数は渡された配列を取得し、foreachで回しています。$itemを$pageとして計算していますが、$itemが「<prev」の時、「next>」の時、それ以外の時で、格納する変数を分けて、最後にくっつけて表示しています。

あとは残りの画像を表示する部分ですが、やっと153行目で取得した $urls 配列を利用することができます。
$urlsの配列の数だけforeachし、ユーザ名と画像のURLをHTMLファイルに展開しています。175行目は10枚ごとに強制改行を入れているだけです。テーブルなどで表示すると美しく表示されると思われます。

実際に動作するサンプルは下記になります。

はてなfotolifeの情報を取得(別ウィンドウで開きます)