はじめに
うれしくなりそうなひと
- クローラかいてるひと
- 今回は PHP & Goutte だけどあんまり言語関係ない話
- 一度だけじゃなくて継続的にデータを取得し続けたいひと
- 複数人でも困らない程度の清潔さを作っておきたいひと
かいてないこと
よくあるつらみ
- なんか動いてない
- 動いてないのどこ。。。
- エラーログみたけど
hoge.php
のN行目 ってどのページのどの部分だよ
- エラーログみたけど
- (おいかける)
- 「ああ、ここね、はいはい確かに構造かわってますね」
まじむり
せめてどうなってほしいか
- エラーでた瞬間にどのページで異常が起きてるか知りたい
- ただそれだけだ
おちついて考えてみよう
- なぜおれは1ファイル(or 1クラス)にすべて書こうとしたのか
- クローラのクラスに保存とか出力させてるから汚いんじゃないのか
もしかして
- 複数クラスに分けてあげるとクラス名わかるからストレスへるんじゃないのか
- クローラのクラスにはあくまでデータぬくだけの方がいいんじゃないのか
- サービスクラス的なところにデータのハンドリングまかせてみたら全体を俯瞰できそうなんじゃないのか
やってみた
- mojibakeo/php-crawler-app-sample
- 書いたもの
- TOPページのニュース一覧をひろう
- ログインしてタイムラインをひろう
構造
$ tree app/
app/
├── Crawlers
│ ├── AbstractCrawler.php
│ ├── AbstractLoginClient.php
│ └── Mixi
│ ├── Login.php
│ ├── My.php
│ └── Top.php
└── Services
└── Mixi
├── FetchNews.php
└── FetchRecentTimeLine.php
使い方
- 必要に応じて
AbstractLoginClient
を継承したログインロジックを書きます -
AbstractCrawler
に渡すと cookie を引き回してアクセスできます -
Crawlers
はあくまで取得する部分だけ覚えさせます -
Services
以下でハンドリングします
サンプルをさっと眺めましょう
ログイン
- ふつうですね
Login.php
/**
* @param Crawler $crawler
* @return Form
*/
protected function logic(Crawler $crawler): Form
{
$form = $crawler->filter('form[name="login_form"]')->form();
$form['email'] = $this->user['email'];
$form['password'] = $this->user['password'];
return $form;
}
タイムライン取得
My.php
protected function logic(array $parameters): array
{
$crawler = $this->createCrawler($parameters);
$newsListNodes = $crawler->filter('.newsList li');
if ($newsListNodes->count() === 0) {
return [];
}
$news = [];
$newsListNodes->each(function (Crawler $newListNode) use (&$news) {
$linkNodes = $newListNode->filter('a');
if ($linkNodes->count() === 0) {
return;
}
$linkNode = $linkNodes->first();
$title = trim($linkNode->text());
$url = $linkNode->attr('href');
$news[] = compact('title', 'url');
});
return $news;
}
ログインしてからタイムラインをとりにいく
- シンプルすぎて保存とかの処理書かれても見通しよさそう
FetchRecentTimeLine.php
/**
* FetchRecentTimeLine constructor.
* @param array $user
*/
public function __construct(array $user)
{
$this->user = $user;
}
/**
* @return array
*/
public function fetch(): array
{
$login = new Login($this->user);
$my = new My($login);
return $my->crawl();
}
運用してみて
- node list empty っていわれても StackTrace のクラス名の部分みるだけですぐにあたりつけられて楽
- いまのところこれ使ってクローラ運用するにあたって苦労してない
- 本気だすなら React でいうところの Containers/Components みたいな単位で分割するともっと調査時間短くなりそう
- 過剰な気もするけど
- 型にまもられてる気持ちだけでだいぶ精神が安定する
- PHP >= 7.1.0 さいこうですね
- 全然関係ないけどページネーション必要そうなページも対応できそうな感じで実装してみた
- 動作確認はしていない
結論
- 1ファイルに全部かくと追いかけるの大変だからページごとにクラス分けよう
- これそのまま使いまわしてもいいよ自己責任でね