この記事は『CRESCO Advent Calendar 2020』 19日目の記事です。

はじめに

こんにちは!

SEC(システムズエンジニアリングセンター)の odashima です。

昨年に引き続き、今年もアドベントカレンダーの執筆ができて嬉しいです。

(今年ももう残すところ僅かですね・・・。)

今年はブラウザの自動操作を行う機会が多く、中でも puppeteer というライブラリを使用することが一番多かったのでまとめてみます。

ブラウザ自動操作は特にテスト工程での工数削減に大きく貢献できる技術ですが、敷居が高いと思っていませんか?

今回は puppeteer で手軽にブラウザ操作の自動化を行ってみましょう!

puppeteerとは?

puppeteer とは、プログラムから API で Chromeブラウザ を制御できる Node.js のライブラリです。

制御には CDP (Chrome DevTools Protocol) を使用できるため、コマンドラインAPI といった開発者モードで使用できる API をそのまま puppeteer 上で使用できます。

A reference of convenience functions available in the Chrome DevTools Console.

また npm からインストールするだけで操作する Chromium も同時にインストールされるため、手軽に環境構築が行えます。

その他の特徴としては、

  • ヘッドレス(GUIなし)で実行できる
  • SPA などのスクリプトから動的に生成されるページも問題なく操作できる

といった点があります。

とりあえず試してみたいという方は以下のサイトでブラウザから実行してみることができます。

Run IT Note: There’s no need to require(‘puppeteer’) in the editor. It’s done for you! Protip: you can use async/await directly in the editor. Note: The playground doesn’t support require() calls. Protip: you drag and drop a .js file onto the code editor.

seleniumとの違い

「ブラウザを自動で操作する」というと、一番に selenium を想像される方が多いと思いますが、

ここで selenium との違いを見てみましょう。

対応ブラウザの比較

selenium puppeteer
Google Chrome (Chromium) ✅ ✅
Firefox ✅ ✅
Safari, Opera (Webkit) ✅
Internet Explorer ✅
Edge ✅

使用可能なブラウザの比較ですが、ここでは selenium に軍配が上がります。

puppeteer は当初 Chromium のみのサポートでしたが、2020年4月(puppeteer 3.0) から Firefox もサポートしました。

一見すると、対応ブラウザの多い selenium のほうが優秀に見えますが、スクレイピングやシンプルなハッピーパス確認といったケースでは、ブラウザ依存の確認はあまり必要ないため用途に合わせて選ぶと良いと思います。

また、対応ブラウザが多いが故に selenium は設定可能項目が多く調べるのが大変・・・

といった欠点があります。

特に selenium を使用していて個人的に不便と感じた部分は、普段使用しているブラウザ(ここでは Google Chrome とします)が自動でアップデートされた際に selenium 側で使用しているドライバのバージョン更新をする必要がある点です。

上記問題について、現在は webdriver-manager というパッケージで簡単に管理できるようですが、手軽に使用する上では考えることを減らしたいですよね。

今回のようなライトな使用を目的としている場合は puppeteer がオススメです。

導入

前提として Node.js が動く環境とします。

本記事で取り上げるバージョンは以下になります。

Node v12.18.3
puppeteer@5.5.0
Chromium(818858)

※ puppeteer@3.0.0 以降は Node v10.18.1 以降の環境が必要となります。

上述した通り puppeteer は環境構築の手軽さが特徴です。

$ npm install puppeteer

上記コマンドを実行するだけで puppeteer 及び 最新の Chromium をインストールしてくれます。

ローカルにある Chrome を使用して実行したいという方は以下のコマンドで puppeteer 本体のみをインストールすることもできます。

$ npm install puppeteer-core

さっそく動かしてみる

導入が完了したらさっそく動かしてみましょう!

const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
try {
await page.goto('https://www.cresco.co.jp/');
await page.screenshot({ path: './image.png' });
} catch (err) {
// エラーが起きた際の処理
} finally {
await browser.close();
}
})();

上記コードは、

  1. ブラウザ立ち上げ
  2. ページ作成
  3. 弊社トップページへアクセス
  4. 画面のスクリーンショットを保存
  5. ブラウザ終了

という処理です。

コードを見るだけで何となくどんな処理をしているのか想像がしやすいと思います。

puppeteer でできる操作については、多くの記事ですでにまとめられているので今回は割愛しますが、以下記事などが参考になると思います。

puppeteer https://github.com/GoogleChrome/puppeteer/releases を触ってみて自分がよく使う書き方を載せたらこれから始めてみようかなって人に需要あるんじゃないかと書いてみます

ただし puppeteer@5.5.0 では page.waitFor が 非推奨(deprecated) されています。

代替として page.waitForTimeout を使用すると良さそうです。

try {
await page.goto('https://www.cresco.co.jp/');
await page.waitForTimeout(6000); // ここを追加
await page.screenshot({ path: './page.png' });
} catch (err) {

先ほどのコードを一部上記のように改変すると弊社トップページのスライドショーが切り替わっていることが確認できます。(もちろんその分実行時間は長くなりますが・・・)

他にも page.screenshot() をする際に、以下のような設定をすることで全画面のスクリーンショットも可能です。

await page.screenshot({ path: './fullPage.png', fullPage: true });

ブラウザのオプション

puppeteer.launch() をする際に、引数にオプションのオブジェクトを渡すことでブラウザの設定をすることもできます。

const browser = await Puppeteer.launch({
// executablePath: '/usr/bin/chromium-browser', /* サーバで実行する場合や、ローカルの Chrome を使用する場合設定 */
headless: false, /* ヘッドレスモードで起動するかどうか。デバッグ段階では false を設定することで動きを目視で確認できる */
slowMo: 1000, /* 各操作の前に入れる遅延(ms)を設定 */
defaultViewport: {
/* 画面のサイズを設定。スクリーンショットを撮る場合は設定しておいたほうが良い */
width: 1920,
height: 1080,
},
timeout: 10000, /* ブラウザの開始を待つ最長時間(ms)を設定。タイムアウトを無効にする場合 0 を設定*/
args: [
// '--no-sandbox', /* サーバで実行する場合 */
// '--disable-setuid-sandbox', /* サーバで実行する場合 */
'--incognito', /* ブラウザをシークレットモードで起動 */
],
});

全てのプロパティについては公式の API にまとめられています。

Headless Chrome Node.js API. Contribute to puppeteer/puppeteer development by creating an account on GitHub.

処理が失敗(タイムアウトなど)したら再試行したい場合

まとめてテストケースを実行したりすると、数ケースタイムアウトしたりすることがあります。

対策としては、以下のような関数を定義して処理をラップしてあげると良いと思います。

function retryWhenRejected(func, retryCount) {
let promise = Promise.reject().catch(() => func());
for (let i = 0; i < retryCount; i++) {
promise = promise.catch((err) => func());
}
return promise;
}

呼び出し側

const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
try {
// 失敗した場合2回まで再試行する(合計3回まで)
retryWhenRejected(async () => {
await page.goto('https://www.cresco.co.jp/');
await page.screenshot({ path: './image.png', fullPage: true });
}, 2).catch(err => {
// 再試行しても失敗した場合の処理
});
} catch (err) {
// エラーが起きた際の処理
} finally {
await browser.close();
}
})();

さいごに

今回は手軽なブラウザ操作自動化を目的に puppeteer の導入~使用までをまとめました。

今回紹介した puppeteer ですが、2020年現在でも TypeScript への対応や クロスブラウザ対応 (Firefox) と活発的に開発が続けられています。

そんな puppeteer ですが、今年1月に彗星の如く登場した playwright というライブラリに存在を脅かされています。。。

ざっくり紹介すると Google で puppeteer を開発していたメンバーが Microsoft へ移動し、単一の API で主要ブラウザエンジン(Chromium, Firefox, Webkit)をサポートするライブラリとして開発されているようです。

そんな playwright について、明日のエンジニアブログで benishouga さんが記事を投稿されますので、是非あわせて確認してみてください!

最後まで閲覧いただきありがとうございました。