# はじめに
これはジーズアカデミーAdvent Calendar 2020の記事です!
序章 -俺はスクレイピングやるぜ、PHPで。-
だいたいスクレイピングって言うと、相場はpython
って決まってます。
けど、普段PHPを触っているボクは、PHPでスクレイピングしたいんです。
で、PHPでスクレイピングって言うとだいたいphpQuery
というものが言及されがちです。
確かに、jQueryみたいに操作できるのでかんたんです。
けど、これ動的サイトにはうまく利用できないんですね。
だから、他の方法が必要になってきます。
今回の対象。
某海外の買い物サイト(一応名前は伏せます。)
以下の様に
最初に、すべての情報が取得されて一覧表示されたらphpQueryでもいけるのですが、
コレだとできません。
対処方法
以下のツール利用で行きます。
- selenium-server-standalone
- seleniumはのWebブラウザの操作を自動化してくれるやつ。
- Selenium Google Chrome Driver
- seleniumで、Chromを動かせるようにするやつ。
- facebook-webdriver
- facebook-webdriverで上記の自動化をPHPを用いてできる様にするやつ
という感じです。
で、今回やることのイメージは、
- seleniumのブラウザで、スクレイピングしたいサイトにアクセス。
- 情報を取得したいページに行って、一旦一番下までスクロールさせる(スクロールすれば、全部の情報が表示されている)
- 一番下までスクロールされた = スクレイピングしたい情報がすべて表示されているので、それをスクレイピング!!!!
なんというハック!
ツールの準備
今回の作業用ディレクトリに移動
$ mkdir selenium
$ cd selenium
selenium-server-standaloneをインストール。
homebrew
を利用。
brew install selenium-server-standalone
Selenium Google Chrome Driverをダウンロード
ダウンロードしたら、seleniumディレクトリにぶち込む。
facebook-webdriverダウンロード
$ php composer.phar require facebook/webdriver
もし、Could not open input file: composer.phar
とかの場合は、以下の参考になるかも。
サーバー起動
$ selenium-server -port 4444 &
$ selenium-server -port 4444 &
[1] 33415
C02SY1XFGTFJ:selenium username$ 18:04:14.512 INFO [GridLauncherV3.launch] - Selenium build info: version: '3.11.0', revision: 'e59cfb3'
18:04:14.513 INFO [GridLauncherV3$1.launch] - Launching a standalone Selenium Server on port 4444
2018-05-24 18:04:14.624:INFO::main: Logging initialized @441ms to org.seleniumhq.jetty9.util.log.StdErrLog
18:04:14.882 INFO [SeleniumServer.boot] - Welcome to Selenium for Workgroups....
18:04:14.882 INFO [SeleniumServer.boot] - Selenium Server is up and running on port 4444
で、下のようになればおk
$ jobs
[1]+ Running selenium-server -port 4444 &
PHPファイルの用意
試しに、test.php
を用意。
<?php
require './vendor/autoload.php';
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverExpectedCondition;
// 今回の買い物サイト用に準備する変数。
// 検索したいワードを$wordに入れる。この例だとamazonと検索した場合
$word = 'amazon';
$title = " ********** "; // ここはhtmlのtitleに表示される文言を記述。そのサイトによって異なるので、一旦ブラウザでアクセスして確認してください。
// chrome利用用意
$options = new ChromeOptions();
// headlessモードで対応
$options->addArguments(['--headless']);
// ブラウザのサイズを指定
$options->addArguments(["window-size=1024,2048"]);
$host = 'http://localhost:4444/wd/hub';
$capabilities = Facebook\WebDriver\Remote\DesiredCapabilities::chrome();
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
$driver = Facebook\WebDriver\Remote\RemoteWebDriver::create($host, $capabilities);
//スクレイピングしたいアドレスを入力
$driver->get('https://www.******/');
$browserLogs = $driver->manage()->getLog('browser');
// 検索したい語をsendKeys()に入れて、submitする。ここの'q'はサイトによって異なる。
$element = $driver->findElement(WebDriverBy::name('q'));
$element->sendKeys($word);
$element->submit();
// このサイトでは、商品画像を全部表示するため、画面を少し右に移動。場合によっては不要。Y軸を動かしたい場合は、第2引数の数字を変える。
$driver->executeScript("window.scrollTo(300, 0);");
$driver->wait(15)->until(
WebDriverExpectedCondition::titleIs($title)
);
// もし、$title(htmlの<title>が想定どおりじゃない)がなければ、なにかした失敗していると判断
if ($driver->getTitle() !== "$title") {
throw new Exception('fail');
}
// 以下取得したい要素を指定してあげる。
// 今回は、検索結果のURL、写真、名前を取得したいので以下の用になっている。
// cssSelectorはサイトに寄って異なる。cssSelectorの中身を変えてね。
$itemUrls = $driver->findElements(WebDriverBy::cssSelector('div.c2iYAv > div.cRjKsc > a'));
$photos = $driver->findElements(WebDriverBy::cssSelector('.c5TXIP .c2iYAv .cRjKsc .c1ZEkM'));
$productNames = $driver->findElements(WebDriverBy::cssSelector('.c5TXIP .c3KeDq .c16H9d'));
$items = [];
// 商品あるかチェック
if (count($photos) < 0) {
throw new Exception('no item.');
}
//。今回は上から10件だけ取得して、情報を$item配列に格納する。
foreach ($itemUrls as $k => $v) {
if ($k === 10) {
break;
}
$items[$k]['$itemUrl'] = $v->getAttribute('href');
}
foreach ($photos as $k => $v) {
if ($k === 10) {
break;
}
$items[$k]['photoUrl'] = $v->getAttribute('src');
}
foreach ($productNames as $k => $v) {
if ($k === 10) {
break;
}
$items[$k]['titleName'] = $v->getText();
}
print_r($items);
echo "\n";
// ↓コメントアウト外せば、どこをスクレイピングしているかとスクリーンショットで確認できる↓
// $file = "sumple_chrome.png";
// $driver->takeScreenshot($file);
$driver->close();
※selenium-webdriverの使い方はこちら。
リンク
かけたら、
$ php test.php
で実行。
いい感じにできました。
終了したらkillする。
$ jobs
[1]+ Running selenium-server -port 4444 & (wd: ~/selenium)
$ kill %1
$ jobs
[1]+ Exit 143 selenium-server -port 4444 (wd: ~/selenium)
まとめ
PHPでも動的サイトをスクレイピングできるんだよ!