【Laravel】スクレイピングでWEBページの中身を抽出する方法|Symfony dom-crawlerの使い方と便利なメソッド一覧

laravel-prograshi(プロぐらし) Laravel
記事内に広告が含まれていることがあります。

Symfony dom-crawlerのインストール

インストール

SymfonyのDom-crawlerをインストールします。

ターミナルで以下のコマンドを実行します。

composer require symfony/dom-crawler
hint

上記コマンドでメモリエラーが発生する場合は次のコマンドを使ってください。

COMPOSER_MEMORY_LIMIT=-1 composer require symfony/dom-crawler

COMPOSER_MEMORY_LIMITという環境変数を設定することで、php.iniの環境変数memory_limitを一時的に-1にしてインストールを行います。

-1は無制限という意味です。

インストールが完了すると、composer.jsonとcomposer.lockファイルが更新されます。

合わせて読みたい

composer.jsonとcomposer.lockの違いについては下記をご参考ください。

(参考)【Laravel】composer require, install, updateの違い|composer.jsonとcomposer.lockの違いと役割


composer.json

    "require": {
        (省略)
        "olssonm/l5-very-basic-auth": "^6.4",
        "symfony/dom-crawler": "^5.3"
    },

composer.lock

        {
            "name": "symfony/dom-crawler",
            "version": "v5.3.7",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/dom-crawler.git",
                "reference": "c7eef3a60ccfdd8eafe07f81652e769ac9c7146c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c7eef3a60ccfdd8eafe07f81652e769ac9c7146c",
                "reference": "c7eef3a60ccfdd8eafe07f81652e769ac9c7146c",
                "shasum": ""
            },
            "require": {
                "php": ">=7.2.5",
                "symfony/deprecation-contracts": "^2.1",
                "symfony/polyfill-ctype": "~1.8",
                "symfony/polyfill-mbstring": "~1.0",
                "symfony/polyfill-php80": "^1.16"
            },
            "conflict": {
                "masterminds/html5": "<2.6"
            },
            "require-dev": {
                "masterminds/html5": "^2.6",
                "symfony/css-selector": "^4.4|^5.0"
            },
            "suggest": {
                "symfony/css-selector": ""
            },
            "type": "library",
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\DomCrawler\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Eases DOM navigation for HTML and XML documents",
            "homepage": "https://symfony.com",
            "funding": [
                {
                    "url": "https://symfony.com/sponsor",
                    "type": "custom"
                },
                {
                    "url": "https://github.com/fabpot",
                    "type": "github"
                },
                {
                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
                    "type": "tidelift"
                }
            ],
            "time": "2021-08-29T19:32:13+00:00"
        },


dom-crawlerの使い方

ここでは例としてページのHTMLコードを直接渡して欲しいデータを取得します。

コードはコントローラやヘルパなどに記載します。

       use Symfony\Component\DomCrawler\Crawler;

        $html = <<<'HTML'
        <!DOCTYPE html>
        <html>
            <body>
                <div id="first-wrapper" >
                    <p class="greeting">Hello World!</p>
                </div>
                <div id="second-wrapper">
                    <p class="program">Laravel</p>
                </div>
                <p>Crawler!</p>
            </body>
        </html>
        HTML;

       $crawler = new Crawler($html);

       dump($crawler);

上記処理は、new Crawler($html)で、HTML要素をクローリングしてオブジェクトとして扱えるようにしています。

tips

<<<は改行を含んだ値を変数に代入する時に使います。

<<<の後ろに識別子を記述し、終端に識別子;を記述します。

PHPの記述でヒアドキュメント構文(Hear Doc.)と呼ばれるものです。<<<EOT と EOT;など3文字の英単語が使われることも多いです。上記ではHTMLとしています。

(参考)<<<EOTとは?ヒアドキュメントについて


dump($cralwer)でクローリングした中身を表示します。

Symfony\Component\DomCrawler\Crawler {#335 ▼
  #uri: null
  -defaultNamespacePrefix: "default"
  -namespaces: []
  -cachedNamespaces: ArrayObject {#325 ▶}
  -baseHref: null
  -document: DOMDocument {#336 ▶}
  -nodes: array:1 [▶]
  -isHtml: true
  -html5Parser: null
}


便利なメソッド

new Crawlerでクローリングした内容はメソッドを使って欲しい情報を絞り込むことができます。

基本的にはfilterメソッドなどで絞り込みをし、textメソッドやattrメソッドで値を抽出する形になります。

タグの絞り込み(filter系など)

メソッド内容
filter(‘タグ名’)指定したタグ名のデータを全て取得
filter(‘タグ名1 > タグ名2’)タグ名1の直下にあるタグ名2のデータを全て取得
filter(‘#id名’)指定したid名のデータを取得
filter(‘.クラス名’)指定したクラス名のデータを全て取得
filter(‘タグ名.クラス名’)指定したクラス名を持つ指定したタグのデータを全て取得
filterxPath(‘xPath’)xPathで要素を指定

クラス名で指定する場合は、CSSのセレクタと同じ指定方法が使えます。

XPathとは?

XPathとはHTMLのツリー構造に合わせて要素を指定する方法です。

例えば以下の場合、html > body 直下の2つ目のscriptタグを指します。

/html/body/script[2]

//を使って省略表記にすることもできます。

//script[2]


filter後の絞り込み

fiterの後に複数のタグがある場合は、続けて絞り込みをするメソッドが用意されています。

メソッド内容
first()1つ目のタグのデータ
eq(数値)指定した数値のデータ。1つ目のタグならeq(0)。
last()最後のタグのデータ
siblings()同じ階層にあるデータ
children()全ての子要素
ancestors()全ての親要素
nextAll()同じ階層の以降のタグのデータ
previousAll()同じ階層の以前のタグのデータ

children('p.common')のようにさらにタグを絞り込むこともできます。(例は取得したタグの中のクラス名commonを持つpタグを指定)


値を取得する

タグに囲まれた文字列(値)を取得する時は、text()を使います。

メソッド内容
text()タグの値を取得する。タグが複数ある場合は1つ目のタグの値。
attr(‘属性名’)指定した属性の値を取得する。attr(‘id名’)なら指定したid名の値を取得。

▼テキスト抽出の実例

body直下の1つ目のpタグの値を取得し、dumpで画面上に表示します。

        $crawler = new Crawler($html);
        $message = $crawler->filter('body > p')->eq(1)->text();
        dump($message);


指定した要素か確認する

matchesメソッド(※複数形)を使うと、指定した値と一致していればtrue、不一致ならfalseを返します。

メソッド内容
matches(‘指定’)指定した値と一致していればtrue、不一致ならfalseを返す。

指定方法はfilterと同じです。前方にfilterなどで絞り込んだ要素に対して使います。

$crawler->filter('body > div')->first()->matches('div#first-wrapper');


全ての値を取得する(eachメソッド)

text()を使うと最初のデータの値しか取得できません。複数のタグがある場合はeachメソッドを使います。

each( function( $変数, $インデックス番号用の変数 ){ 処理 } )


例:複数のデータを取得して配列に格納する

        use Symfony\Component\DomCrawler\Crawler;        

        $html = <<<'HTML'
        <!DOCTYPE html>
        <html>
            <body>
                <div id="first-wrapper" >
                    <p class="greeting">Hello World!</p>
                </div>
                <div id="second-wrapper">
                    <p class="program">Laravel</p>
                </div>
                <p>Crawler!</p>
            </body>
        </html>
        HTML;

        $crawler = new Crawler($html);

        $arr = $crawler->filter('div > p')->each(function($el){
            return ($el->text());
        });
        dump($arr);
point
$arr = $crawler->filter('div > p')->each(function($el){
     return ($el->text());
});

クローリングしたデータからfilterメソッドでdivタグ直下のpタグを取得しています。

eachを使って一つ一つのデータからtextメソッドで値を取得し$arrに代入しています。

▼出力

array:2 [▼
  0 => "Hello World!"
  1 => "Laravel"
]


例:複数のデータを連想配列として取得する

連想配列として取得する例です。

        $html = <<<'HTML'
        <!DOCTYPE html>
        <html>
            <body>
                <div id="first-wrapper" >
                    <p class="greeting">Hello World!</p>
                </div>
                <div id="second-wrapper">
                    <p class="program">Laravel</p>
                </div>
                <p>Crawler!</p>
            </body>
        </html>
        HTML;

        $crawler = new Crawler($html);

        $arr = $crawler->filter('div')->each(function($el, $i){
            $id = $el->attr('id'). $i;
            $text = $el->text();
            return [
               "id-" . $i => $id,
               "text-" . $i => $text,
            ];
        });
        dump($arr);
point
        $arr = $crawler->filter('div')->each(function($el, $i){
            $id = $el->attr('id'). $i;
            $text = $el->text();
            return [
               "id-" . $i => $id,
               "text-" . $i => $text,
            ];
        });

filter(‘div’)で全てのdivタグのデータを取得します。eachメソッドでdivを一つづつ抜き出し$elに代入します。合わせてインデックス番号を$iに格納しています。


▼出力

array:2 [▼
  0 => array:2 [▼
    "id-0" => "first-wrapper0"
    "text-0" => "Hello World!"
  ]
  1 => array:2 [▼
    "id-1" => "second-wrapper1"
    "text-1" => "Laravel"
  ]
]


参考

Symfony The DomCrawler Component

タイトルとURLをコピーしました