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

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

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

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

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

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

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

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

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

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

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

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

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

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

【200ヶ国以上に対応】グローバル eSIM「ビヨンドSIM」

【200ヶ国以上に対応】グローバル eSIM「ビヨンドSIM」

【中国への旅行・出張・駐在なら】中国SIMサービス「チョコSIM」

【中国への旅行・出張・駐在なら】中国SIMサービス「チョコSIM」

【グローバル専用サービス】北米・中国でも、ビヨンドのMSP

【グローバル専用サービス】北米・中国でも、ビヨンドのMSP

【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,853
X facebook はてなブックマーク pocket
【2025.6.30 Amazon Linux 2 サポート終了】Amazon Linux サーバー移行ソリューション

【2025.6.30 Amazon Linux 2 サポート終了】Amazon Linux サーバー移行ソリューション

この記事をかいた人

About the author

萬代陽一

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