PHPでスクレイピング! HTMLからタグの情報を取得する方法

最近ブームのスクレイピング。
大体Pythonで取得するものだ~という情報になっている。
「だから他の言語ユーザもPythonやろうぜ!」みたいな流れなんだけど、実はPHPでもスクレイピングができる

きっとPythonの方が要領よく、効率よく、メモリも使わず、高速だとか、そういうこともあるのだろう(無調査)から、ちゃんとやりたいのであれば、Pythonをオススメしておく。
なので、コチラは今すぐやりたくて、新しい言語を覚えるよりも、サクッと今できるPHPでやりたい人だけに向けた記事です。

スクレイピングについて

ウェブスクレイピングとは、サイトから情報を抽出する技術のこと。
ウェブクローラーやウェブスパイダーともいうらしいですよ。
「ウェブクローラーってそうなの?」て気もするけど、そうらしいですよ。

そしてスクレイピングに必要になるのは、ザックリ言えば主に2つ。
1.ページを読む。必要ならログインする。
2.ページ内容を解析する。

1のログインとかページを読む方法については、いずれ書こうとは思うけども、いつになるか分からないので、他のサイトをご参照ください。
たくさんあるから大丈夫!

こちらは2のページ内容を解析する方法、
つまり『HTMLソースから、タグの情報を取得する方法』について書いておく。

自分で解析しようとしてみた

最初は自分でタグを解析してみようとしていた。
タグの情報を取得しようとすると出てくるのが大体『preg_match』だからである。

    /** 
     * タグのを情報取得
     * @param $html_source  対象文章
     * @param $tag          タグ名
     * @param $key          $key=$key_value
     * @param $key_value    $key=$key_value
     */
    // タグ情報取得 下記例<input ~ name="mail"~>
    function getTagInfomation($html_source, $tag='input', $key='name', $value='mail')
    {
        // タグを探す
        $pattern = '/(<'.$tag.'.*'.$key.'=\s*[\"|\'].*'.$value.'.*[\"|\'].*>)/i';
        preg_match_all($pattern, $html_source, $match);
        // タグがない
        if (count($match) == 0) {
            return '';
        }

        // 重複タグを削除する
        $tags = [];
        foreach($match as $m_key => $m_val){
            if(!in_array($m_val[0], $tags)){
                $tags[] = $m_val[0];
            }
        }

        // タグ内情報を取得する
        $infomation = [];
        $pattern = '/ (.*?=\s*[\"|\'].*?[\"|\']|checked|selected)/i';
        foreach ($tags as $t_key => $t_val) {
            preg_match_all($pattern, $t_val, $infomation[]);            
        }
        // tag情報がない
        if (count($infomation) == 0) {
            return '';
        }

        // 情報を分かりやすく配列化
        $return = [];
        $pattern = '/(.*?)=\s*[\"|\'](.*?)[\"|\']|(checked)|(selected)/i';
        foreach ($infomation as $i_key => $i_val) {
            foreach ($i_val[1] as $iv_key => $iv_val) {
                preg_match($pattern, $iv_val, $temp);
                if(!isset($temp[2])){
                    $temp[2] = $temp[1];
                }
                $return[$i_key][trim($temp[1])] = $temp[2];
            }
        }
        return $return;
    }

ただコレ『textarea』とか、タグに囲まれた情報については取れない。
だから別途作らなきゃいけないのが、面倒だったし上に正規表現のパターンが微妙に上手く行かなくて、時間がかかるかかる。
もうどれだけかかるんだというレベル。

そして探しに探した結果、何か便利な機能があった…無知は罪とはこのことか…。
なので、こちらのソースはもはや無用の産物であるが、自力でタグ情報を取得しようと使う人もいるかもと、記載しておく。

『DOMDocument』と『DOMXPath』を使う方法

HTMLタグの情報を取得しようと四苦八苦していたら『DOMDocument』が現れた。
これでDOM情報が取れる。

そして『DOMDocument』だけで使うものだと、アレだコレだと四苦八苦していたら『DOMXPath』が現れた。
まるでJavaScriptの様に情報が取得できる。

この時点で、そもそも自分はタグの情報ではなく、値がほしかったのであったことを思い出す。

組み合わせてから、しばしアレコレしていたら…できたー!!
結果がコレ。

    /** 
     * タグの値を取得
     * @param $html_source  対象文章
     * @param $tag          タグ名
     * @param $key          $key=$key_value
     * @param $key_value    $key=$key_value
     */
    function getTagValue($html_source, $tag='input', $key='name', $key_value='token')
    {
        $domDocument = new DOMDocument();
        @$domDocument->loadHTML($html_source);
        $xpath = new DOMXPath($domDocument);
        
        // '//ul[@class="menu"]'
        switch($tag){
            case 'input':
                $pattern = '//input[@'.$key.'="'.$key_value.'"]/attribute::value';
                break;
            case 'input-selected':
                $pattern = '//input[@'.$key.'="'.$key_value.'" and @selected]/attribute::value';
                break;
            case 'input-checked':
                $pattern = '//input[@'.$key.'="'.$key_value.'" and @checked]/attribute::value';
                break;
            case 'select':
                // /attribute::valueをつけない場合、挟まれた値が帰ってくる
                $pattern = '//select[@'.$key.'="'.$key_value.'"]/option[@selected]/attribute::value';
                break;
             case 'textarea':
             default:
                $pattern = '//'.$tag.'[@'.$key.'="'.$key_value.'"]';
                break;
        }
        $tag_nodes = $xpath->query($pattern);
        return isset($tag_nodes->item(0)->nodeValue) ? $tag_nodes->item(0)->nodeValue : '';
    }

短っ!
これで出来るとは…自分の苦労のアレさよ…

因みに
『case 'input-selected':』は『ラジオボタン』
『case 'input-checked':』は『チェックボックス』
であるが、実はコレらについては、未テスト。

あんまり使ってるサイトがなくて…テストケースを作るのが手間で…
なので、後日この2点については変動する可能性があるので、ご注意くだされ。

まとめ

バージョンで多少の差はあれど、PHP5から使えるので『DOMDocument』と『DOMXPath』を使った方がいい。
自分で作るとすっごい手間! 疲れる!
場合によっては、上手く取れないこともある様子なので、そこは頑張って!

コメント

タイトルとURLをコピーしました