LoginSignup
0
0

Laravel 8.x/PHP7.4 環境で Laravel Dusk の ChromeDriver を使用して Web スクレイピングしてみた

Last updated at Posted at 2024-02-22

概要

Laravel でヘッドレスブラウザを使用して Web スクレイピングをしたいのですが、 Selenium(php-webdriver) や php-phantomJS などより、Laravel 公式パッケージでブラウザテストの自動化およびテスティング API の Laravel Dusk を使用してWebスクレイピングできないか調べてみました。

現在の環境が PHP7.4 環境ですので、必然的にLaravel のバージョンは 8.x となります。PHP と Laravel のバージョン対応表は こちら

Laravel 8.x プロジェクトの作成

> composer create-project laravel/laravel=8.* scraping
> cd scraping

Laravel Dusk のインストール

プロジェクトに Laravel/Dusk Composer 依存パッケージを追加します。
Laravel が 8.x のため dusk 7.x 系はインストールされず、v6.25.2 がインスールされました。

> composer require --dev laravel/dusk
Cannot use laravel/dusk's latest version v7.12.3 as it requires php ^8.0 which is not satisfied by your platform.

> composer show -i | findstr dusk
You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.
laravel/dusk                       v6.25.2  Laravel Dusk provides simple end-to-end testing and browser automation.

Laravel dusk をインストールします。

> php artisan dusk:install  

ChromeDriver をインストールします。

> php artisan dusk:chrome-driver
ChromeDriver binaries successfully installed for version 114.0.5735.90.

ChromeDriver サイトの更新停止

ChromeDriver のバージョン 114 がインストールされたのですが、残念ながら dusk 6.x のままでは ChromeDriver サイトの更新停止により 115 以降に更新できません。現在のクライアントの Chrome ブラウザのバージョンが 122 ですのでこのままではバージョンの不一致により ChromeDriver エラーとなり正常に動作しません。

ChromeDriver - WebDriver for Chrome - Downloads

chromedriver.png

この赤文字の情報から新たなダウンロードサイトは JSON endpoints になります。ちなみに dusk7.x はこの JSON endpoints での更新をサポートしています。

  < 115 : https://chromedriver.storage.googleapis.com/index.html
 >= 115 : https://googlechromelabs.github.io/chrome-for-testing/

dusk7.x → dusk6.x クラスの差し替え

dusk6.x 環境でもなんとか ChromeDriver を最新に上げたいなーとソースを斜め読みしながら dusk 6.x と dusk 7.x のソースと比較してみたところ ChromeDriverCommand クラスが ChromeDriver の更新処理を担っていることがわかりました。

dusk 7.x の ChromeDriverCommand クラスとこのクラスが依存する OperatingSystem クラスを dusk 6.x のクラスと差し替えます。

ChromeDriverCommand.php, OperatingSystem.php
dusk7.x  dusk6.x
  /vendor/laravel/dusk/src/Console/ChromeDriverCommand.php
  /vendor/laravel/dusk/src/OperatingSystem.php

throw 式構文のダウングレード

差し替えにあたって、dusk7.x の ChromeDriverCommand クラスは PHP7.4 では記述できなかった PHP8.0 以降の throw 式構文による Null 合体演算子で記述されているため、4 ケ所ほど以下のようにダサーく修正します。

dusk7.x - ChromeDriverCommand.php
@@ -138,2 +138,4 @@
-        return $milestones['milestones'][$version]['version']
-            ?? throw new Exception('Could not determine the ChromeDriver version.');
+        if (is_null($milestones['milestones'][$version]['version'])) {
+            throw new Exception('Could not determine the ChromeDriver version.');
+        }
+        return $milestones['milestones'][$version]['version'];

再度 ChromeDriver の更新

再度 ChromeDriver をアップデートしたところ、最新の ChromeDriver 122 にすることができました。オリジナルのソース改変はあまりやりたくないのですが、dusk6.x はもう更新されないだろうと汗。

> php artisan dusk:chrome-driver
ChromeDriver binary successfully installed for version 122.0.6261.57.

使い方

dusk6.x の ChromeDriver を使い Web スクレイピングしてみます。

Command クラスの雛型を生成します。

> php artisan make:command ScrapingCommand
Console command created successfully.

試しに Yahoo! Japan トップページの主要ニュースをスクレイピングしてみましょう!
(利用規約に該当する場合は速やかに訂正いたします。見た感じ大丈夫のようですが)

yahoo.png

ScrapingCommand.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverExpectedCondition;
use Facebook\WebDriver\WebDriverBy;
use Laravel\Dusk\Chrome\ChromeProcess;
use Exception;

/**
 * ScrapingCommand class
 */
class ScrapingCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:scraping';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        $targetUrl = "https://www.yahoo.co.jp/";

        $process = (new ChromeProcess())->toProcess();
        try {
            if ($process->isStarted()) {
                $process->stop();
            }
            $process->start();

            $options = (new ChromeOptions())->addArguments([
                '--disable-gpu',
                '--headless',
                '--window-size=1200,1000',
                '--no-sandbox'
            ]);
            $capabilities = DesiredCapabilities::chrome()->setCapability(ChromeOptions::CAPABILITY, $options);

            $driver = retry(5, function () use ($capabilities) {
                return RemoteWebDriver::create('http://localhost:9515', $capabilities, 50000, 60000);
            }, 5000);

            // scraping start
            $driver->get($targetUrl);

            // waiting for 'footer' id load
            $driver->wait(10, 1000)->until(
                WebDriverExpectedCondition::visibilityOfElementLocated(
                    WebDriverBy::id('footer')
                )
            );

            // Yahoo! top topics
            $topics = $driver->findElements(
                WebDriverBy::cssSelector('#tabpanelTopics1 ul a')
            );
            foreach ($topics as $topic) {
                $url =  $topic->getAttribute("href");
                $title = $topic->findElement(
                    WebDriverBy::cssSelector('h1 > span')
                )->getText();

                var_dump($url . " : " . $title);
            }
        } catch (Exception $e) {
            Log::error($e->getMessage() .  "\n");
        } finally {
            $process->stop();
        }
    }
}

Laravel のコマンド実行をします。

execute
> php artisan command:scraping

string(86) "https://news.yahoo.co.jp/pickup/6492295 : 松野・高木氏ら5人 政倫審出席へ"
string(85) "https://news.yahoo.co.jp/pickup/6492279 : 1月の貿易収支 1兆7583億円の赤字"
string(88) "https://news.yahoo.co.jp/pickup/6492292 : 外務省 元徴用工訴訟巡り厳重抗議"
string(88) "https://news.yahoo.co.jp/pickup/6492294 : 西山ファーム 元副社長の身柄確保"
string(85) "https://news.yahoo.co.jp/pickup/6492290 : 榊容疑者 映画監督の立場悪用か"
string(88) "https://news.yahoo.co.jp/pickup/6492289 : 罰則ないカスハラ防止条例 効果は"
string(88) "https://news.yahoo.co.jp/pickup/6492293 : ペット死体巡り批判 市が対応変更"
string(88) "https://news.yahoo.co.jp/pickup/6492299 : 仲里依紗 激変も「今の私が自分」"

おわりに

最近はHTMLソースを見ただけではなにをやってるかわからない、ヘッドレスブラウザによるスクレイピングじゃないとNGな動的サイトが増えてきていますね。

次は Chrome DevTools Protocol(CDP) でやってみたいですね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0