Puppeteerで表示できない画面を取得しよう
さて問題です。Puppeteer とはなんでしょう?
Puppeteer とは、以下のようなことができるNode.jsのライブラリーです。
【Puppeteer でできること】
・ページのスクリーンショットやPDFの作成
・SPAをクロールしてプレレンダリングコンテンツを生成
・フォーム送信の自動化
などGoogle Chromeのブラウザでできることはほぼできるようです。
上記を駆使して、たとえば「ページ内の大量のデータから取得したいデータのみを抽出する」や「表示できないページを割り出す」などを行うことができ、普段の業務で自分で一つ一つ探して確認してたものを自動で行うことができるようになります。
※ 公式サイトは以下です
今回はこちらのライブラリーを使って、表示できない画面を探すツールを作りながら、
使用した各メソッドが何ができるメソッドなのかを説明していきたいと思います。
今回作ったもの
こちらのGitHubにて作成済みのものを公開しておりますので、よろしければクローンして動かしてみてください。
実装手順
それでは、実装を説明していきます。
要件
今回は、以下の要件でツールを作成しました。
① data.jsonに記載したwebページの中で表示できないものがあれば全部取得
② デバイスはSPでもPCでも選択して確認できるようにしたい
③ Puppeteerを使ってみる
①は、実務などで検証環境によってあるページとないページがある場合に、「どのページが存在するかを確認したいという場面で使用できたらいいな」という気持ちと、「コードがいまいちわからない人でも簡単に使えたらいいな」という気持ちから、JSONにURLを持たせるという要件を入れてます。
②は、サービスによってはスマホ画面とPC画面をサーバー側で出しわけしている場合に、URLだけでは対象のデバイスでの検証ができないことから要件に入れました。
③は完全にPuppeteerを習得して業務の中でもツールで楽できるようにしていきたいという気持ちがあり、筆者が使いたかったからという理由だけです。
手順① Puppeteerをインストールしよう
以下コマンドでPuppeteerをインストールします。
npm i Puppeteer
手順② 必要なファイルを作る
今回、処理はfind404.js
に記載し、URLなどはdata.json
に記載しようと思いますので、
以下の階層構造になるように★マークがついているファイルを作成します。
find404 ┳ node_modules
┣ data.json ★
┣ find404.js ★
┣ package-lock.json
┗ package.json
手順③ data.jsonに引用したい情報を記載する
今回、JSONに持たせたいデータは対象画面のURL
と対象デバイス
です。
今回は以下の様にします。
{
"targetPageUrl": [
"https://pptr.dev/next/",
"https://pptr.dev/next/hogeFuga",
"https://nodejs.org/ja/",
"https://developer.mozilla.org/ja/"
],
"targetDevice": {
"sp": "iPhone 12"
}
}
targetPageUrl
には配列にしたURLを記載します。
上記サンプルは2つ目のURL(https://pptr.dev/next/hogeFuga
)が、実際には存在しないページです。
targetDevice.sp
には対象デバイスを指定しています。
指定できるデバイスの値は以下ページを参照してください。今回はiPhone 12
を指定しています。
手順④ found404.jsに処理を書く
今回の完成系のコードは以下です。以下コードに沿って、Puppeteer
で用意されているメソッドを解説していきます。
const puppeteer = require('puppeteer');
const fs = require('fs');
const jsonPath = './data.json';
// 検索条件やURLをdata.jsonから取得する
const data = JSON.parse(fs.readFileSync(jsonPath));
// 使用するデバイスを取得
const device = data.targetDevice.sp ? puppeteer.KnownDevices[data.targetDevice.sp] : false;
// data.jsonカラ取得したURLのリスト
const targetUrl = data.targetPageUrl;
// 対象urlが指定されていなかったら処理を中止
const noTargetUrl = !targetUrl.length || targetUrl.some((url) => !url.length);
if (noTargetUrl) {
console.log('data.jsonのtargetPageUrlにurlを指定してください><');
return;
}
(async() => {
// 新規ブラウザをシークレットブラウザで立ち上げる
const browser = await puppeteer.launch({
headless: false,
timeout: 10000,
ignoreDefaultArgs: ['--disable-extensions'],
});
const context = await browser.createIncognitoBrowserContext();
// 新規タブを開く
const page = await context.newPage();
if (device) {
// 開くデバイスを指定
await page.emulate(device);
}
try {
const notFound = [];
for (url of targetUrl) {
// ページにアクセスする
const response = await page.goto(url);
// ページにアクセスできないあるいは404が帰ってきた時
if (!response || response.status() >= 400) {
notFound.push(url);
}
}
// 400番台のページをログ出力
console.log('==============================');
if (!notFound.length) {
console.log('すべてのページが存在します');
} else {
console.log('以下のページが表示できません。');
console.log('==============================');
notFound.forEach((url) => {
console.log(url)
});
}
console.log('==============================');
} catch(e) {
if (e instanceof Error) {
console.log(e.message);
} else {
console.log('予期せぬエラー');
}
} finally {
// チェックが終了したらブラウザーを閉じる
await browser.close();
}
})();
クラス
Puppeteer
には以下のようなクラスが用意されており、各Classに用意されたメソッドを用いて処理を実現します。
実際には以下の記載よりも多くのクラスが存在しますが、今回は本記事のコードで使用しているものをピックアップして記載しています。
Class名 | 概要 | コード上での使用箇所 |
---|---|---|
Puppeteer | Puppeteerのメインクラス | このクラスから直接生えているメソッドは今回は使用していません |
PuppeteerNode | ブラウザのfetchとダウンロードのためのクラスでrequire で取得されます |
1行目のpuppeteer で取得し、const browser = await puppeteer.launch({}) でブランチを立ち上げるのに使用しています |
Browser |
PuppeteerNode.launch() やPuppeteer.connect() で生成されるクラスで、シークレットブラウザを指定するためのメソッドやブラウザを閉じるためのメソッドが用意されています |
本記事のコードではconst context = await browser.createIncognitoBrowserContext(); の行で使用しています |
Page |
Browser.newPage() で生成されるクラスで、デバイスの指定やページ内の要素の取得やイベント実行などページとの対話を可能にするメソッドが用意されています |
本記事のコードでは、await page.emulate(device); でのデバイス指定や、const response = await page.goto(url); でのページへのアクセスに使用しています |
HTTPResponse |
Page クラスが受信するHTTPリクエストの応答です |
本記事のコードではif (!response ||response.status() >= 400) {} でページが存在するかどうかの判定に使用しています |
KnownDevices
次の項で紹介するPage.emulate()
の引数として使用できる、Puppeteer
が用意しているデバイスのリストです。
コード一行目のpuppeteer
に用意されてますので以下のようにして使用可能です。
const device = puppeteer.KnownDevices['iPhone 12'];
今回のコードでは、以下の行で設定を行っています。
// 使用するデバイスを取得
const device = data.targetDevice.sp ? puppeteer.KnownDevices[data.targetDevice.sp] : false;
Page.emulate()
上記で説明したKnownDevices
を使用して、デバイスを指定します。
Page
クラスのメソッドです。使い方は以下の通りで、ページを開いてから使います。
// 新規タブを開く
const page = await context.newPage();
await page.emulate(device);
上記で指定をすると以下のようにスマホ画面での実行がされます。(deviceには今回iPhone 12
を指定しています)
PuppeteerNode.launch()
PuppeteerNode
クラスのメソッドで、オプションなどを指定してブラウザのインスタンスの起動を行います。
以下は今回のコードですが、headless
オプションをfalse
に設定することでPage.emulate()
の項目に載せたGIFのように、ブラウザが開かれて動いている様子が確認できます。デフォルトはtrue
(headlessモード)です。timeout
オプションはブラウザ起動までの待機時間の最大をms単位で指定が可能で、0
を渡すとtimeoutの無効化ができます。
※ Windows実行時、Puppeteerでは、デフォルトで--disable-extensions
フラグが有効かどうかを確認し、有効な場合に起動に失敗するのですが、ignoreDefaultArgs: ['--disable-extensions']
を指定することで、回避して実行することが可能になります。
// 新規ブラウザをシークレットブラウザで立ち上げる
const browser = await puppeteer.launch({
headless: false,
timeout: 10000,
ignoreDefaultArgs: ['--disable-extensions'],
});
他にも指定できるoptionはあるのですが、今回は使っていないため割愛させていただきます。
Page.goto()
Page
クラスのメソッドで、以下のようにURLを指定してあげることで、指定したページに遷移します。
// ページにアクセスする
const response = await page.goto(url);
url以外にもオプションを引数に取ることができ、最大待機時間などを指定ができます。
また、本メソッドはPromise<HTTPResponse | null>
を返却するメソッドとなっており、
以下のようにページアクセス時のステータスを取得することが可能です。
// ページにアクセスできないあるいは404が帰ってきた時
if (!response || response.status() >= 400) {
notFound.push(url);
}
ステータス以外にも、レスポンスに関連するHTTPヘッダーを持つオブジェクトやレスポンスに対するHTTPリクエストなどの取得も可能です。
ページ内で使っている非同期通信のエラー検知にも役立ちそうかと思います。
まとめ
今回はページの存在チェックをして存在しない、あるいは見つからないページのURLをログで返してくれるツールを作りつつ、Puppeteerのクラスやメソッドについてツール内で使用したもの中心に説明をしてきました。
この記事をきっかけに、「Puppeteer試しにいじってみよう!」「意外と簡単に始められるね」と気軽に挑戦するきっかけにしていただけると嬉しいです。
今回ご紹介したクラスやメソッドだけでも、便利なツールの作成ができると思うのですが、他にも多くのクラスやメソッドが用意されてますので、ぜひ調べてお役立ちツールを作ってみてください!