【PHPチュートリアル】Guzzle6を使ったWebクローラーを自作してみよう その2

Guzzle6を使ったスクレイピング その2 Guzzle6
この記事は 約11 分で読めます。

前回の記事で少しはクローラーについて理解して頂けたでしょうか?

【PHPチュートリアル】初心者でも作れるスクレイピングクローラーの作り方 - その1
PHPでクローラーってどうやるの?よく聞かれます。 特にアフィリエイターの人とか良く聞かれますね、PHP覚えるとっかかりにでもなれば良いと思っているので、スクレイピングの方法を紹介したいと思います。 恐らくこの講座は長くなると思うので3...

少しはクローラーの流れを掴んで貰ったかと思います、ね?意外と簡単ですよね。
実際にはブラウザからのアクセスじゃないと弾くとか表示が違うとか色々細かい問題も出てくると思います。
初心者でも作れるクローラー講座の第2回目では、Guzzleの使い方を深く知ってもっと色々出来るようになれば、そう言った問題も少しは回避出来るようになるかと思います。

PR

Guzzleの基本的なクローラーの作り方と操作方法

前回ではYahooJapanのトレンドキーワードを一つ拾う時にX-Pathで拾ってみました。
しかし、実際にはもっと色々な部分を拾ったり、アンカー要素全部欲しい時とかもあります。
なので今回はもう少しクローラーについて知識を深めて行きましょう。

実はGuzzleのクローリング機能と言うのはSymfonyの機能の一部を利用しています。
結構前から知っていたのですがGuzzleの基本的な使い方として知って置けば良いかな程度で考えていたのですが、ライブラリインストール時に違和感が出るので説明しておきます。

クローラーは厳密にはSymfony/DomCrawlerと言うライブラリを使用して行っています。
Symfonyと言う大型フレームワークは有名だからPHPでフレームワークを触って開発しているような人は知っていると思います。

技術的な情報が欲しい時はGuzzleで検索するよりもSymfony/DomCrawlerで検索した方が情報が沢山出てくると思います。
クローラー開発に行き詰まった時は、このキーワードでググるべきです。

DomCrawlerの使い方

Symfony/Domcrawlerの基本的な使い方は日本ユーザー会のフォーラムにあります。

https://docs.symfony.gr.jp/symfony2/components/dom_crawler.html

前回と少し被りますがGuzzleをスタートさせる時にはお決まりのお作法コードを使います。

<?php

require 'vendor/autoload.php';
use GuzzleHttp\Promise;
use GuzzleHttp\Pool;
use GuzzleHttp\Clinet;


$client = new \GuzzleHttp\Client(
	[
		'cookies' => true,
		'base_uri' => 'https://www.yahoo.co.jp',
		'headers' => [
			'User-Agent' => 'Mozilla/5.0 (Windows NT 6.1; rv:38.0) Gecko/20100101 Firefox/38.0',
		]
	]
);

$response = $client->request('GET', 'https://www.yahoo.co.jp');
$crawler = new Symfony\Component\DomCrawler\Crawler((string)$response->getBody());

上記を使用し取得したURLに対してDomcrawlerを起動して情報をスクレイピングします。
最終行の$response->getBody()が実行された時点でソースコードが$crawlerに全て格納されています。

ページ内のAタグ全てを取得する場合

$ankerText = $crawler->filter('a')->each(function ($node) {
	return $node->text();
})

AタグのURLを全部取得する場合

$ankerText = $crawler->filter('a')->each(function ($node) {
	return $node->attr('href');
})

同じように画像のURLを取得する場合は $node->attr(‘src’);で取得出来ます。

N番目のH2タグの情報を取得する場合

$crawler->filter('body > h2')->eq(0);

eq(0)の部分が欲しい要素の順番になります(N番目の部分)

ページ内のAタグのURLを全て取得

$ankerText = $crawler->filter('a')->each(function ($node) {
	return $node->attr('href');
});

上記の場合はクロージャと言って無記名関数を利用した手法です。
これを使う事によってページ内にあるH2タグを全て取得する事が出来ます。

文字列でAタグを指定して取得する場合

$link = $crawler->selectLink('公式サイトへGo!')->link();

要素は分からないけど、そこのタグが欲しい!と言う場合はLinkメソッドを利用して、文字列指定も出来ます。

FormのAction要素(送信先URL)を取得する場合

$uri = $form->getUri();
$method = $form->getMethod();
$values = $form->getValues();
$files = $form->getFiles();

1行目のgetUriでActionの送信先のURLを取得します。
2行目のmethodGETPOSTかを判別して取得します。
3行目のgetValuesはFormに用意された要素を全て取得します。name属性の値など
4行目はimageファイルなどの送信の必要性を調査してくれます。

スクレイピングで並列処理を行う

次はGuzzle6を使って同時に欲しいURLの情報を一気に取得します。
どう言う事かと言うと、同時10アクセスでも20アクセスでも複数のクライアントを立ち上げて同時に処理出来ると言う事です。

使い所としては、ページ内にある全てのリンク先が404になってないかの有効性を確認したり、リンク先に自サイトのURLがあるか?と言う事を調べる時にも役立ちます。
または、掲載している画像のURL(src)が404になって表示されない等の状況を調べる事も出来ます。

<?php
$url = [
	'https://ranking.rakuten.co.jp/daily/',
	'https://ranking.rakuten.co.jp/daily/p=2/',
	'https://ranking.rakuten.co.jp/daily/p=3/',
	'https://ranking.rakuten.co.jp/daily/p=4/',
];
foreach ($url as $key => $value) {
	$promises[] = $client->requestAsync('GET', $value);
}

$responses = \GuzzleHttp\Promise\all($promises)->wait();

foreach ($responses as $key => $response) {
	$crawler = new Symfony\Component\DomCrawler\Crawler((string)mb_convert_encoding($response->getBody(),'UTF-8',"auto"));
	// ここにDomcrawlerの解析内容を記述する
}

上記は楽天の人気商品ランキング(デイリー)のデータを取得する際のサンプルコードです。
4ページ目まで一気に取得していますので、このプログラムを実行すると同時に4クライアント立ち上がり、一気に4つのページにアクセスし情報を取得してきます。
※実際には楽天APIを使った方が早いです(笑

【2018年保存版】楽天APIの使い方と商品抽出するサンプルコード
作業でちょこっと楽天APIを触る機会があったのでメモメモ っつか楽天APIにSDKがあるけど、あんなややこしいの使わなくてもPHPで簡単に使えるよね? あんなの使ったら余計にややこしくなるし処理が重くなるじゃん?って事でFile_get...

通信環境によって変わりますが大体2秒程度で全てのページを取得する事が出来ます。

並列処理プログラム解説

はじめの$urlからの連想配列でURLリストを生成しセットします。
その後のForeachの部分でrequestAsyncメソッドを呼び出し$promisesの配列にセットします。

$promises[] = $client->requestAsync('GET', $value);

この時点ではまだクローラーは実行されていません。

$responses = \GuzzleHttp\Promise\all($promises)->wait();

上記の行でwait()メソッドを実行した時点で同時にクライアントが一気に立ち上がり$responsesに結果が格納されて行きます。
尚、ページの表示速度やボリュームによってバラバラなので必ず1から順番にデータが格納されていくわけではありません。(非同期(async処理)だからです)

非同期のマルチクライアントの手法は他にも3種類ぐらいやり方がありますが、私が主に使ってて取り回しの良い方法が上記になりますので、その他の手法にも興味がある人は調べてみて下さい。

Guzzle6を使った小技

レスポンスヘッダーだけを取得

$res = $client->request('GET', 'https://www.yahoo.co.jp');
echo $res->getStatusCode();

ベーシック認証を通す

$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://yahoo.co.jp/login', [
    'auth' => ['user', 'pass']
]);

Cookieを有効にする

$client = new \GuzzleHttp\Client(['cookies' => true]);
$jar = new \GuzzleHttp\Cookie\CookieJar;
$res = $client->request('GET', 'http://httpbin.org/cookies', [
    'cookies' => $jar
]);

上記はCookieが必要なサイトで使用する場合、例えばログインした情報を持ちながら会員認証が必要なページをスクレイピングする場合などにCookieを有効にします。

Formデータ送信(ログインしたい時などに使用)

$res = $client->request('POST', 'http://httpbin.org/post', [
[
 'form_params' => [
 'user' => 'ユーザー名',
 'password' => 'パスワード',
 ]
]
]);

Method指定の部分をPOSTにする事でデータの送信や画像などのファイルのアップロードも出来ます。もちろんログインの際にはCookieを有効にして情報を持ち回らないと別のページにアクセスしたい場合などは蹴られてしまいますから、Cookieを有効にしないとダメです。

ヘッダー指定/設定 環境変数も設定出来ます

$client->request('GET', '/get', [
    'headers' => [
        'User-Agent' => 'macintosh safari',
        'Accept'     => 'application/json',
        'X-Foo'      => ['Bar', 'Baz']
    ]
]);

ブラウザやリファラーなどのヘッダーを送信したい場合は上記のようにして設定します。
これが無いとアクセスを拒否されるような事もありますので、そんな場合はChromeやSafariなどのブラウザ情報を送信してやるとうまく行く場合もあります。
スマホ専用ページなどにアクセスする場合は、もちろんiPhoneなどのスマホの環境変数をヘッダーとして送信すればOKです。

まとめ

スクレイピングは普通にブラウザからアクセスする場合よりも数段早いです。
なぜ、早いかと言うと、Guzzle6を使ったアクセスではソースコードだけの取得になるからです。
画像やCSSやJSなどのファイルはソースとして記述されているだけで実際には目的のURLにあるHTMLのファイルのソースだけを取得してますから、クローラーの方が取得が早いと言うわけです。

更にクローラーは同時に10でも30でもクライアントを立ち上げてアクセスしに行きますから人間が操作する何百倍もの速度で処理を完了させます。

レンタルサーバーでクローラーを運用する際に注意して欲しいのは、同時にアクセスが可能だからと言って並列処理を一気に1000クライアントとか2000クライアントとか立ち上げて回すと、さすがにレンタルサーバー屋さんから警告されたり怒られたり、最悪の場合は契約解除されてしまったりしますから、サーバー負荷に注意しながら運用するべきです。

次回の講座では、クローラーを使ったデータを基にサイトを構築するコツを紹介しています。
20万ページ超の大規模サイト構築のヒント

コメント

トップへ戻る