5. TIDY関数について
前章まででHTMLファイルの取得ができました。この章ではいよいよ、実際のスクレイピング処理のプログラムを作り始めます
なお、スクレイピング処理は、「ある規則性を持ったHTMLファイルを、その規則性を解析し、必要な部分を抜き出すプログラミングを作成し、実行すること」です。規則性はサイトごとに違うので、自分の取得したいサイトに合わせたプログラミングが必要です。
このサイトでは、幾つかの例を実際に試してみることにより、自分で自分の取得したいサイトのスクレイピング処理ができることを目的としています。サイトが違えば、当然規則性も違いますので、取得したいサイトにあわせてプログラムを工夫する必要があります。
今回のターゲットは「はてなブックマークのトップページの最近の人気エントリー」の情報を取得することにします。はてなブックマークのトップページの情報を取得するプログラムは前の章で書きましたので、このhatena.phpをコピーしてhatena_toplink.phpにリネームしてスクレイピング処理の部分を記載してみましょう。
幾つかやり方があり、最もはじめに思いつくのは、全文を検索し、該当する部分を正規表現で抜き出してみる方法だったりするのですが、ここでは、TIDY関数を用いてプログラミングをしてみます。大事なのは自分でいくつも同じようなプログラムを作ってみてプログラムに慣れることだと思います。
前章同様に先ず、プログラムを記載して、それから説明いたします。(少し長いのでインラインではなく別ウィンドウで開きます。
ではプログラムを見てみましょう。まず、このプログラムを実行した結果を想像してみてください。
59~61行目までがHTMLファイルを取得する部分で、前回同様です。22~26行目に記載されたget_html()関数を利用しています。61行目終了後には$dataにはHTMLファイルのソースが入っていることになります。追加されたのは、62~80行目の部分と、28~58行目に新たに加わったdump_nodes_title()関数になります。
HTMLファイルの解析をこのdump_nodes_title()関数で行っています。
63~65行目はtidy関数の設定をしている箇所です。スクレイピング処理のおまじないとして覚えておくとよいでしょう。より詳しく知りたい方は PHP: tidy_parse_string - Manual を参照してください。
さて、今HTMLファイルは22行目からの get_html() 関数で取得したのですが、取得したHTMLの文字コードは現在、一律にUTF-8になっているはずです。なぜなら23行目の http_request() 関数で取得した値を24行目の mb_convert_encoding() で一律にUTF-8に変換しているからです。
それを踏まえて、68行目を見てみましょう。68行目に記載されている、
$tidy = tidy_parse_string($data, $config, 'UTF8');
の部分は、「$dataの文字列を$configという設定にしたがってUTF8でtidy関数でパースしてその結果を$tidyというオブジェクトにいれます」ということが記載してあります。この$tidyというオブジェクトを解析することでHTMLの内容をスクレイピングするのです。
70行目に記載されている
$tidy->cleanRepair();
は $tidy オブジェクトを整形するためのおまじないその2として覚えておきましょう。
では、73行目以降は何をしているのでしょう。73行目は作成した$tidyオブジェクトのbody(<body>~</body>まで)を dump_nodes_title() 関数に渡して、その結果を $urls に入れなさいと記載しています。
$urls = dump_nodes_title($tidy->body());
この dump_nodes_title() の中でHTMLファイルを解析するのです。dump_nodes_title() は解析するHTMLファイルや解析しようとする内容によって異なるわけなので、php によるスクレイピングとは、つまり「この dump_nodes_title() に相当する部分を自分で作成すること」になります。
ここで51~55行目を見てみましょう。この部分は $node->hasChildren() が TRUEならば、その子要素を再度 dump_nodes_title()に渡すと書かれています。つまり、再帰処理がされているのです。再帰処理とはプログラミングでよく利用される言葉ですが、関数の中で自分自身を呼び出す(再帰的処理とも言う)行為のことです。つまり73行目で実行しようとした dump_nodes_title() は $tidy->bodyの子要素がなくなるまで73行目には戻ってこずに dump_nodes_title() 内で子要素がなくなるまで繰り返されるのです。つまり、BODYタグの中身を全てチェックするまで戻ってこないってことです。この再帰処理により、取得したHTMLファイルの中身を洩れなくチェックすることができるのです。
では実際に何をどうチェックしているのかを見て行きましょう。
細かい部分はプログラムをいくつも書きながら覚えていけばよいと思うので、チェックするポイントのみに言及していきます。
29行目で $urls という配列がセットされていなければ $urls を配列として宣言します。配列がセットされるのは、1回目の実行時のときだけとなります。
30行目はおまじないとして覚えておくとよいでしょう。
33~47行目の部分が実際のスクレイピング処理に相当する部分です。
33行目の $node->id が TIDY_TAG_A ならばというのは、想像するとおりtidyオブジェクトのノードのidが<A>タグならばということになります。つまり、<A>タグ以外のものはこの処理の対象外とし、<A>タグならば、ifの中身を実行するわけです。やっていくと次第に分かるでしょう。ちなみに、<A>タグ以外のタグはどうやって指定するのかは PHP: Tidy 関数 - Manual:表 321. tidy タグ定数 を参照してみてください。殆どの場合、タグの名前と一致しているので分かりやすいと思います。主なものを以下に記載しておきます。
- TIDY_TAG_A : <A>タグ
- TIDY_TAG_H1 : <h1>タグ
- TIDY_TAG_IMG : <img>タグ
- TIDY_TAG_SCRIPT : <script>タグ
- TIDY_TAG_META : <META>タグ
ということで、33行目で<A>タグを取得した場合、更に35行目に進みます。35行目では、取得した$nodeのアトリビュート属性をチェックしています。はてなのトップページに記載されているホットエントリーのブックマークにはすべて<A>タグのclass属性に"bookmark"と指定されているので、更に37行目でこれを抜き出しています。こうしないと、全ての<A>タグを処理対象としてしまうからです。
このあたりの何を処理対象とするか、またはしないかがスクレイピング処理では肝要です。
現在38行目で取得している対象の文字列はきっと、下記のような形式になっていると思われます。
<a href="http://ryouchi.seesaa.net/" class="bookmark">りょーち</a>このとき、「りょーち」の文字を取得するには、
$node->child[0]->valueこのとき、「http://ryouchi.seesaa.net/」の文字を取得するには、
$node->attribute['href']になります。 今 $node は<a>~</a>の部分になるのですが、そのノードの子要素の0番目の値が<a>~</a>で囲まれた部分になります。
<A>タグのhref属性を取得するには、その attribute['href'] を見ればよいことになります。
ここで、attributeというものが出てきましたが、これはとてもよく使いますので是非覚えましょう。
今、tidyオブジェクトに変換されたタグが下記のようになっていたとします。
<img src="hoge.gif" alt="fig1" width="100" height="70" />この場合、先ず、TIDY_TAG_IMGでIMGタグの情報を取得したとき、各アトリビュートは下記のように取得します。
- src : $node->attribute['src'];
- alt : $node->attribute['alt'];
- width : $node->attribute['width'];
- height : $node->attribute['height'];
詳細は PHP: Tidy 関数 - Manual:表 322. tidy 属性定数 を参照してください。
なお、実際に動作するサンプルは下記になります。
ここまで説明すれば、もうかなり、TIDYでのスクレイピングをマスターしたと思われます(多分)。今回はここまでですが、最後にスクレイピングのコツを記載しておきます。
その1:HTMLソースをよく読んで規則性を把握する
何はなくとも、まず、取得したい情報はどういう規則を持っているかを把握することが肝要です。把握できれば、TIDY_TAG_タグ名と$node->attribute['属性名']でなんとかできそうですよね?
その2:文字コードを統一しておく
今回はUTF-8に文字コードを統一してプログラムを記載しましたが、取得時、表示時にも文字コードを統一しておけば、文字コードによるトラブルはある程度防げそうです。
その3:よく利用する処理は関数化しておく
今回利用した get_html() や dump_nodes_title() のようによく利用する処理は関数化しておけば、別のものを利用するときに、処理が異なる部分だけ修正すればすみます。これはプログラミングの基本なので、処理を関数化しておくくせを身につければ後々楽にコーディングできそうです。
その4:print_r関数を使って配列の情報を可視化する
print_r()関数はphpに標準でついている配列を分かりやすく表示する関数です。これを更に拡張して、print_d()関数をこんな感じで作ってもよいでしょう。
function print_d($array){ echo ''; print_d($array); echo ''; }
こうしておけば、配列の中身を確認したいときに、
print_d($tidy);などとして、中身を簡単に確認することができます。
次章では、幾つかサンプルを用意してサンプルを元に解説していきます。サンプルは今後追加していく予定です。