【大阪 / 横浜】インフラ / サーバーサイドエンジニア募集中!

【大阪 / 横浜】インフラ / サーバーサイドエンジニア募集中!

【2024年2月~】25年卒 エンジニア新卒採用の募集を開始!

【2024年2月~】25年卒 エンジニア新卒採用の募集を開始!

【導入実績 500社以上】AWS 構築・運用保守・監視サービス

【導入実績 500社以上】AWS 構築・運用保守・監視サービス

【CentOS 後継】AlmaLinux OS サーバー構築・移行サービス

【CentOS 後継】AlmaLinux OS サーバー構築・移行サービス

【WordPress 専用】クラウドサーバー『ウェブスピード』

【WordPress 専用】クラウドサーバー『ウェブスピード』

【格安】Webサイト セキュリティ自動診断「クイックスキャナー」

【格安】Webサイト セキュリティ自動診断「クイックスキャナー」

【低コスト】Wasabi オブジェクトストレージ 構築・運用サービス

【低コスト】Wasabi オブジェクトストレージ 構築・運用サービス

【予約システム開発】EDISONE カスタマイズ開発サービス

【予約システム開発】EDISONE カスタマイズ開発サービス

【100URLの登録が0円】Webサイト監視サービス『Appmill』

【100URLの登録が0円】Webサイト監視サービス『Appmill』

【中国現地企業に対応】中国クラウド / サーバー構築・運用保守

【中国現地企業に対応】中国クラウド / サーバー構築・運用保守

【YouTube】ビヨンド公式チャンネル「びよまるチャンネル」

【YouTube】ビヨンド公式チャンネル「びよまるチャンネル」

Node.jsからヘッドレスGoogle Chromeを使ってみる

こんにちは。
開発チームのワイルド担当、まんだいです。

以前、Google Chrome 59でヘッドレスモードが標準搭載されたのでいじってみる | 株式会社ビヨンドというエントリーで記事を書いたところ、予想以上にアクセスや反響をいただきました。
Google Chromeのバージョンアップは、裏で勝手にアップデートされるので普段はあまり意識しないかも知れませんので、これからもマメにチェックしていきたいなと思います。

今回は、Node.jsから、Google Chromeをヘッドレスモードで起動し、chrome-remote-interfaceモジュールを経由して制御してみたいと思います。

サンプルプログラムが動かない!

前回の記事では、コマンドライン上でGoogle Chromeをヘッドレスモードで起動し、画面をキャプチャしたりする方法を見てみましたが、シェルスクリプトだけでは他のシステムとの連携も取りにくいので、Node.jsから起動させ、そのままコンテンツを取得する部分をやってみます。

基本的には、ヘッドレス Chrome ことはじめ | Web | Google Developersの記事を参考に進めていたのですが、どうにもうまく動かなかったので、こちらのコードをベースにしつつ、少し改造してみたのが、以下のソースです。

const ChromeLauncher = require('chrome-launcher');
const CDP = require('chrome-remote-interface');

const url = 'https://beyondjapan.com';

var launcherKill = (client, launcher, args = null) => {
    launcher.kill();
    console.log(args);
}

ChromeLauncher.launch({
    port: 9222,
    chromeFlags: [
        // '--disable-gpu',   // 必要に応じて有効化
        '--headless',
        // '--no-sandbox',    // 必要に応じて有効化
    ],
}).then(launcher => {
    CDP(client => {
        const {Page, DOM} = client;
        Promise.all([
            Page.enable(),
            DOM.enable(),
        ]).then(() => {
            Page.navigate({url : url});
            Page.loadEventFired(() => {
                DOM.getDocument((error, params) => {
                    if (error){
                        console.log(params);
                        launcher.kill();
                        return;
                    }
                    const opts = {
                        nodeId : params.root.nodeId,
                        selector : 'a'
                    };
                    DOM.querySelectorAll(opts, (error, params) => {
                        if (error){
                            console.log(params);
                            launcher.kill();
                            return;
                        }
                        var promises = [];
                        var getDomAttribute = (DOM, nodeId, count) => {
                            return new Promise((resolve, reject)=>{
                                const opts = {
                                    nodeId : nodeId
                                };
                                DOM.getAttributes(opts, (error, params) => {
                                    if (!error) console.log(count, params.attributes[1]);
                                    resolve();
                                })
                            })
                        };
                        console.log(params.nodeIds.length);
                        params.nodeIds.forEach(elm => {
                            promises.push(getDomAttribute(DOM, elm, promises.length));
                        });
                        Promise.all(promises).then(()=>{
                            launcher.kill();
                        })
                    })
                })
            })
        })
    }).on('error', error => {
        console.error(error)
        launcher.kill();
    })
})

 

処理の流れとしては、

  1. chrome-launcherモジュールを利用して、Google Chromeを起動
  2. 起動できたら、chrome-remote-interfaceモジュールを利用して、DevToolsプロトコルに接続
  3. DevToolsクライアントオブジェクトから、指定のURLへ移動し、データを取得
  4. DOMから、全Aタグを取得して、hrefの値を標準出力に表示する

となっています。
ちゃんと調べきれていないのですが、49行目辺りのDOM.getAttributesメソッドが非同期で動いてそうで、forEachを回した直後にGoogle Chromeを停止するとDOMの内容が読み取れない現象が発生しました。
今回は、Promiseを使って、全てのDOMが読み取れたのを待ってから、Google Chromeを停止するようにしています。

 

エラーが出た

スクリプトを起動すると、以下のようなエラーが出た場合は、Google Chromeの起動オプションを少しいじる必要があります。

No usable sandbox! Update your kernel or see https://chromium.googlesource.com/chromium/src/+/master/docs/linux_suid_sandbox_development.md for more inform
ation on developing with the SUID sandbox. If you want to live dangerously and need an immediate workaround, you can try using --no-sandbox.

 

Google Chromeのサンドボックス環境を利用するためのライブラリに、Google Chromeの実行ファイルへのパスがハードコーディングされているバージョンがあるらしく、サンドボックスが利用できない状態の様子。
正しくこのエラーを回避するには、sandbox/linux/suid/sandbox.cc にハードコードされているパスを変更してビルドし直す必要があるようです。

また、緊急の場合やその場しのぎでいい場合は、起動時のオプション「--no-sandbox」をアンコメントすることで、起動できるようになります。

この場合、サンドボックスが利用しない環境でGoogle Chromeを起動することになるので、セキュリティリスクが高くなることにご注意ください。

 

さて、何に使う?(まとめ)

DevToolsからGoogle Chromeに直接干渉できるので、今までよりリアルなスクレイピングができるようになりそうです。

JavaScriptの動作も含めた試験もやりようによってはできそうなので、こういった試験もコードベースでバックグラウンドで自動実行できるのは、便利になるのではないでしょうか。

ウェブサイトの構成上、curlなどでは監視しきれなかったコンテンツも正常に稼働しているか監視できる仕組みも作れそうです。

以上です。

この記事がお役に立てば【 いいね 】のご協力をお願いいたします!
0
読み込み中...
0 票, 平均: 0.00 / 10
1,602
X facebook はてなブックマーク pocket
【2024.6.30 CentOS サポート終了】CentOS サーバー移行ソリューション

【2024.6.30 CentOS サポート終了】CentOS サーバー移行ソリューション

【2024年2月~】25年卒 エンジニア新卒採用の募集を開始いたします!

【2024年2月~】25年卒 エンジニア新卒採用の募集を開始いたします!

【大阪 / 横浜】インフラエンジニア・サーバーサイドエンジニア 積極採用中!

【大阪 / 横浜】インフラエンジニア・サーバーサイドエンジニア 積極採用中!

この記事をかいた人

About the author

萬代陽一

ソーシャルゲームのウェブ API などの開発がメイン業務ですが、ありがたいことにマーケティングなどいろんな仕事をさせてもらえています。
なおビヨンド内での私の肖像権は CC0 扱いになっています。