はもちくわ

コードについて自分なりの解釈を書いてます。

【PHP・MySQL】独学用Webサイトで失敗した話(クローラーを選別する方法)

独学でPHPを勉強しています。
今回は、独学ならではの失敗談です。
PHPを学習するなら、フリーサーバーでお金をかけずにWebサイトを作ってみるのが一番だと思います。とはいえ、本番環境のフリーサーバー上で、公開しながら作成するのは、非効率な上、作成中のエラー表示がダダ漏れになるので、ローカル環境に開発環境を準備して作成するという流れになると思います。
私もMAMPでローカルに開発環境を作っています。
みなさんがどのように環境設定しているのか知らないので、ひょっとしたら、当たり前のことなのかもしれませんが、本番環境と開発環境のフォルダ配置は一緒にし、さらに、そのフォルダに開発環境のルートも通しておくと、追加・変更の連動が楽になるので大変便利です。

今回の失敗の主原因はこの開発環境と本番環境の違いによるものだったのですが、、、とても簡単に発見できた挙動のはずなのに、作成と確認が自分だけだったため、発見がとても遅れ、さらに、エラーの確認用コードを放置していたため、変数の内容がずっと出てしまっていたという、、、お恥ずかしいことになりました。

例えるなら、、、いたずらで背中に落書きをされていて、しばらく気づかずに生活している人と同じです。

ここまでの大失敗は初めてです。恥ずかしすぎて、言い訳したくなったので、無理やり記事にしたという経緯です。

少し前に、GoogleアナリティクスみたいなものをPHPで作ってみるという記事を書きました。そのとおり、自分のサイトで試しに作ってみたのです。
まだ現在作成中ではありますが、初めてサイト分析するので知らないことばかりです。
なので動かしてみて、その都度対応するようにしました。
まずは、スーパーグローバル変数で得られる情報から、IPアドレス、ホスト名、ユーザーエージェント、参照元URL(リファラ)を拾って集計してみることにしました。次のコードは、参照元URLを得るためのコードです。これを得たい情報分、書けばいいです。とても簡単なコードです。

    if(isset($_SERVER['HTTP_REFERER']) ){ // リファラ
      $refe = $_SERVER['HTTP_REFERER'];//ここはこのまま使わないでエスケープ処置をしてください。
    } else{ // なければスラッシュ
      $refe = '/';
    }

 

XSS対策】スーパーグローバルなどの外部からの情報は偽装、細工される恐れがあるので、変数に入れる前にhtmlspecialchars(エスケープする文字列, ENT_QUOTES,自分にあったエンコード)エスケープしておく必要があります。だいたいどの本でも必ず初めの方に紹介されているのでよく読んで導入しましょう。

 

期待する集計結果は、同じ人がどのページを何回見ているのか?を、わかるようにするのが目標です。それで個人の断定をするわけではありませんし、そもそもそんなことは事件でも無い限りできませんが、同じ環境の人が何回見ているのか?は、わかるかもしれないです。

この目標を実現させるために、まずはIPアドレス別に集計することにしてみました。
すると、自宅からのアクセスは同じ集計になっているみたい。でも、スマホからのアクセスは場所と時間によってIPアドレスが変わるようで、別集計になってしまう。
であれば、ホスト名かユーザーエージェントで区別するのか?、、、と内容を見てみると、
ホスト名には「モバイルである」事がわかるだけで、個を特定するものは無さそう。しかもIPアドレスと同じようにホスト名も統一されているわけではない。
では、ユーザーエージェントはどうなっているのか見ると、スマホの機種と使っているブラウザなどのweb環境が表示されている。
それならば、条件分岐にスマホの機種が該当したらと入れるか?、、、いやいや、同じ機種を持っている人はどうするんだ??、、、そもそもアクセス数が少ないわけだから、スマホ機種別にカウントしても自分かどうかはわかるのではないか?、、、など、いろいろ考えながら、IPアドレスを見比べてみると、ドットで4分割された数字の1番目と2番目が一緒であることがわかりました。
調べてみましたが、電話番号のようにどの地域がどの番号とかいうようなものではないみたい。ただ、「自分」のものとして集計するなら、このIPアドレスの一部一致とスマホ機種の一致の2つをセットで集計すれば、そこそこ絞れるのではないかと考え、条件分岐はその2項目としました。これがあとあと間違っていたんですが、それは後で書くとしまして、ここからホスト名別の集計に変えました。条件分岐して自分と判定したときは、同じホスト名を変数に入れて集計ループにかけるようにしたんです。IPアドレスにしなかったのは特に意味は無いのですが、なんとなくIPアドレスは取っておいたほうが良さそうな気がしたので。

ただ、とにかくアクセス数が少ないサイトなので、ある程度時間をかけて状態を見なければわかりません。

そうこうしているうちにポチポチとアクセスがあり、嬉しくなって見てると、ホスト名に「bot」とある。これはHTMLの勉強で見たことあるやつだ。名前も含めて調べてみるとやはり「クローラー」らしい。そして、同じ会社のクローラーでもIPアドレスは全然違うので、IPアドレス別に集計すると、新規訪問者数がガンガン増えてしまうことになりました。
これはホスト名を見ると、どこのクローラーかが、明記されているので、クローラー別に集計できるように条件分岐を設定しました。

ここまで作成して、しばらく様子を見ていました。すると、自分とクローラーは順調に集計がされていましたが、新規訪問者は、いつまで経っても0更新です(笑)
まあ、ポートフォリオみたいなサイトだからしょうがないと諦めていたのですが、、、、問題が発覚します。
それはスマホで自分のサイトを開いた時です。ガッツリSQLで例外処理がされて、メインページに移れません。。。しかも、開発中に使っていたチェック用のコードが入れっぱなしになっており、余分な情報が表示されている!!
おそらく、いままで気づかなかったということは、いつもと違うコードの流れになったと言うことだろう。となれば、自分判定用に条件分岐していた、スマホ用のIPアドレスの一部は、いつも同じでなく、違うこともあり、結果、自分のスマホが新規訪問者の扱いになり、新規訪問者用の集計コードの流れに行ったらエラーになった??。。。とあたりをつけました。ということは、、、、新規訪問者はカウントされずに、このエラー画面を見ていたことになるのではないか!?(笑)
もし、それが本当なら、いくら訪問者が少ないと言ってもこれは恥ずかしい!!!!
自宅に帰って確認すると、やはり、新規訪問者用のSQL文が間違っていたのと、開発用のMySQLではNull設定にしていたカラムが本番用ではNull不可設定でした。これじゃあ、SQL文が正常だったとしても、例外処理になっていたことになります。。。
これは、IPアドレスが私の想像を超えて動的であり、それがたまたま条件分岐からずれたので気づきましたが、それがなければ気づかずにずっと流れていましたね。。。一方で、条件分岐にはまらない「別の環境を持つ人」が確認すれば、一発で判明する状態。独学じゃなければ回避できたかもね。。。
なんでもそうですが、確認は複数人でやったらいいですね。ホントに(笑)
いい勉強になった。これからは気をつけよう。。。

 

下にページ訪問者が自分かクローラーかを分けるコードをまとめておきます。実際はPHPで判定したあとにMySQLのデータベースに保存していますが、そこは省略します。

//訪問者の情報取得

    //botはクロール回数が多いのでbot別に集計するための配列
    $bot_list = array('semrush.com','msn.com','linkfluence.com','google.com','line-apps.com','DotBot');//確認できているbotの文字列

    if(isset($_SERVER['REMOTE_ADDR'])){
      $ip_add = $_SERVER['REMOTE_ADDR'];//IPアドレス
      $host_name = gethostbyaddr($_SERVER['REMOTE_ADDR']);//ホスト名
    }else{
      $ip_add = '/';//情報なければスラッシュ
      $host_name = '/';
    }

    if(isset($_SERVER['HTTP_REFERER']) ){ // リファラ
      $refe = $_SERVER['HTTP_REFERER'];
    } else{ // なければスラッシュ
      $refe = '/';
    }

    if(isset($_SERVER['HTTP_USER_AGENT']) ){ //ユーザーエージェント
      $u_agent = $_SERVER['HTTP_USER_AGENT'];
    } else{ // なければスラッシュ
      $u_agent = '/';
    }

    //自分のモバイル判別
    if(mb_strpos($ip_add,'IPアドレスの一部')!== false && mb_strpos($u_agent,
    'スマホの機種名などユーザーエージェントで判別できる文字列')
    !== false){
      $host_name = "my_mobile";//自分とわかる名前に変更しておく
    }

    //botに該当する文字列を判別があった場合はホスト名を変える
    foreach ($bot_list as $key => $bot){
      //ホスト名に文字列があったら ホスト名をbot文字列に変える
      if(mb_strpos($host_name,$bot)!== false){
        $host_name = $bot;
      }
      //ユーザーエージェントに文字列があったら ホスト名をbot文字列に変える
      if(mb_strpos($u_agent,$bot)!== false){
        $host_name = $bot;
      }
    }
    

このあと、連想配列に入っているデータとホスト名を比べて、新規か既存かを判断して集計する感じです。データとホスト名を比べる前に集計を同じにするデーターのホスト名を一致させておけば、コードを変えずにループさせるだけで良いのでこうしてます。ホスト名とかIPアドレスを収集することが目的ならこの方法はダメですけどね。あとは、 CSVファイルなんかでログファイルを作って更新するか、MySQLで集計用のテーブル作って更新するかはそれぞれ変えればいいです。
先に書いてますが、私はMySQLでテーブル作って運用していますが、今回、間違ってしまったのは、そこの新規訪問者の場合のSQL文でした。カラム名をなるべく直接入力しないでもいいようにコード書いてますが、肝心な変数名を単純に記入ミスしてました。。。
これをデータベースに繋げて保存するまでをfunctionにしておいて、各ページにインクルードさせて実行。ページから飛ばすときに番号で分けておいて、どのページに誰が来たかわかる仕組みにしました。

現在滞在時間を調べる方法を追加中。タイマー機能を持ったクラス作って、コンストラクタとデストラクタで表現できないかな?って試している最中です。

 

やってみてわかりましたが、私のページはクローラーしか来てない(笑)
そして、Googleアナリティクスはちゃんとクローラーはカウントしてない!!ということがわかりました。すごいなぁ。Google