前回のPHP と Goutte ではじめる超絶簡単クローラー入門の記事が予想の35倍くらいストックされたので続編を。
今回は Cookie を引き回してログインしたページの情報をかっさr取得してみましょう。
題材はかの有名な SNS プラットフォーム、mixi なんていかがでしょうか。
API が公開されてない(たぶん)足あと一覧を攻略してみましょう。ニックネームでも集めてみましょうか。
はじめに
前回同様、composer つかってサクッと下地を作りましょう。
composer require fabpot/goutte
touch app.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Goutte\Client;
$cli = new Client();
このへんまで来たら準備完了ですね。
みなさんバッチリ mixi にログインしてるかと思うのでログアウトしておいてください。
ねらいをさだめる
ログアウトすると https://mixi.jp/ つまりトップページにリダイレクトされたかと思います。
(LINE の友達募集コミュニティって時代を感じますね!)
ログインフォーム。
こちらにメールアドレスとパスワードを挿入して submit したいわけです。
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Goutte\Client;
$cli = new Client();
$top = $cli->request('GET', 'https://mixi.jp');
$loginForm = $top->filter('form')->form();
$loginForm['email'] = 'mojibakeo@example.com';
$loginForm['password'] = 'PASSSWOOOORDDD';
$cli->submit($loginForm);
フォームに狙いを定めて form() すると Symfony\Component\DomCrawler\Form オブジェクトが取得できるのであとは簡単。
email と password の値をセットして $cli->submit()
の引数に渡すだけ。
これだけで $cli
にログイン状態が有効な Cookie がセットされるので、こちらを使い回せばログインユーザーとしてアクセスできます。なにこれちょうべんり。
ためしに自分のニックネームを取得してみる
ログイン済みページ右上にあるこれが手っ取り早そうです。
一番最初の .nickname
がこの要素にあたるようなので以下のように書けますね。
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Goutte\Client;
$cli = getLoggedInClient();
$home = $cli->request('GET', 'http://mixi.jp/home.pl');
echo $home->filter('.nickname')->first()->text();
// 見やすさのためにログイン部分を function 化
function getLoggedInClient() {
$topUrl = 'https://mixi.jp';
$cli = new Client();
$top = $cli->request('GET', $topUrl);
$loginForm = $top->filter('form')->form();
$loginForm['email'] = 'mojibakeo@example.com';
$loginForm['password'] = 'PASSSWOOOORDDD';
$cli->submit($loginForm);
return $cli;
}
どうでしょう。ニックネームが表示されたのではないでしょうか。
足あとページにアクセス
そしてついに本題です。ニックネームなんぞ API 経由で取得するべきですが、足あとに関しては公開されていません(ぱっと見)。
総アクセス数が切ないという衝撃事実 はさておき、このようなページ構成になっております。
Chrome Developer Tools で確認したところ、このような DOM 構造になっていました。
足あと表示部分が .logListAre.visitorList で、ニックネームが .nickname なのでこのように絞り込めばさくっとニックネームを集められそうですね。
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Goutte\Client;
$cli = getLoggedInClient();
$visitor = $cli->request('GET', 'http://mixi.jp/list_visitor.pl');
$visitorList = $visitor->filter('.visitorList');
$visitUsers = [];
$visitorList->filter('.nickname')->each(function($nickname) use (&$visitUsers) {
$user = trim($nickname->text());
// $visitUsers に存在しないニックネームだけ追加
if (!in_array($user, $visitUsers)) {
$visitUsers[] = $user;
}
});
print_r($visitUsers);
function getLoggedInClient() {
$topUrl = 'https://mixi.jp';
$cli = new Client();
$top = $cli->request('GET', $topUrl);
$loginForm = $top->filter('form')->form();
$loginForm['email'] = 'mojibakeo@example.com';
$loginForm['password'] = 'PASSSWOOOORDDD';
$cli->submit($loginForm);
$cli->getCookieJar()->all();
sleep(2);
return $cli;
}
こんな感じで足あとつけたひとのニックネーム一覧が取得できるはずです。
2ページ目(mixi プレミアムなユーザーならそれ以上)も取得したい
普通に for でまわして
// 抜粋
$visitor = $cli->request('GET', 'http://mixi.jp/list_visitor.pl?page=$i');
のような形でも実現できるのですが、これだと getLoggedInClient が2回も呼ばれてしまいます。
それはつまり1ページから取得したいだけなのに2ページ分のアクセス、10ページなら20アクセスになってしまいますね。
裏口からこそっと行ってるなりに、お行儀よくしたいところです。
Cookie 情報をキャッシュする
現時点でも相当に都合の良いオンナ感ある Goutte ですが、以下の一行だけで Cookie を取り出すことができます。
$cookies = $cli->getCookieJar()->all();
そして以下の1行で取り出した Cookie をセットすることができます。
$cli->getCookieJar()->updateFromSetCookie($cookies);
なんてつごうのいい子…(言いたいだけ)
Cookie を使いまわして省エネアクセスする
まずこれまで使ってた getLoggedInClient
を超雑に Cookie 使い回し対応します。
function getLoggedInClient(&$cookies = null) {
$cli = new Client();
if (!is_null($cookies)) {
$cli->getCookieJar()->updateFromSetCookie($cookies);
return $cli;
}
$topUrl = 'https://mixi.jp';
$top = $cli->request('GET', $topUrl);
$loginForm = $top->filter('form')->form();
$loginForm['email'] = 'mojibakeo@example.com';
$loginForm['password'] = 'PASSSWOOOORDDD';
$cli->submit($loginForm);
$cookies = $cli->getCookieJar()->all();
sleep(2);
return $cli;
}
// 参照渡しやめろっていうのはごもっともで、普通にやるならクラス化して protected/private な $cookies プロパティを定義して使いたいですが本題からそれるので今回はこれで勘弁してください
これで関数側の準備ができましたね。
あとは1ページ目だけを取得するコードをちょろっと書き換えるだけです。
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Goutte\Client;
$cookies = null;
$visitUsers = [];
for ($i = 1, $last = 2; $i <= $last; $i++) {
$cli = getLoggedInClient($cookies);
$url = sprintf('http://mixi.jp/list_visitor.pl?page=%s', $i);
$visitor = $cli->request('GET', $url);
$visitorList = $visitor->filter('.visitorList');
$visitorList->filter('.nickname')->each(function($nickname) use (&$visitUsers) {
$user = trim($nickname->text());
// $visitUsers に存在しないニックネームだけ追加
if (!in_array($user, $visitUsers)) {
$visitUsers[] = $user;
}
});
sleep(2); // 高速連続アクセスとか迷惑なので sleep しましょう
}
print_r($visitUsers);
function getLoggedInClient(&$cookies = null) {
$cli = new Client();
if (!is_null($cookies)) {
$cli->getCookieJar()->updateFromSetCookie($cookies);
return $cli;
}
$topUrl = 'https://mixi.jp';
$top = $cli->request('GET', $topUrl);
$loginForm = $top->filter('form')->form();
$loginForm['email'] = 'mojibakeo@example.com';
$loginForm['password'] = 'PASSSWOOOORDDD';
$cli->submit($loginForm);
$cookies = $cli->getCookieJar()->all();
sleep(2)
return $cli;
}
mixi プレミアムなひとが一体何ページ目までアクセスできるのか知りませんが、$last
の値を変えてあげれば好きなページ数までクロールできそうですね。
でも高速連続アクセスとか超迷惑なので sleep しましょう。お行儀よく。
さいごに
Facebook が好きです。でも、mixi の方がもっと好きです。
だからみんなもっと mixi つかおう(提案)