この記事でわかること

スクレイピングやWebシステムのテスト自動化などを検討されている方向けに、数あるWeb自動化ツールの中から、処理の柔軟性、学習コストの低さなどのバランスがよく将来性にも期待できる「Puppeteer(パペッティア)」について、基本的な使い方からブラウザ操作を自動化する際の勘所を紹介します。

目次

  • Puppeteerとは
  • Puppeteerでできること
  • Puppeteerをおすすめする理由
  • よく使う処理の実装
  • Puppeteerの実装を補助する便利なツール
  • サンプルコード

Puppeteerとは

Chrome(Chromium)を制御するAPIを提供するNodeライブラリです。

Puppeteerでできること

ブラウザで手動で実行できることはほとんどできます。

  • ブラウザ操作の自動化
  • 値の取得
  • スクリーンショット、PDFの生成

など、
これらを組み合わせて、スクレイピング、Webシステムのテスト、RPAなどに活用できます。

Puppeteerをおすすめする理由

  • GoogleChromeチームが開発
    ブラウザの改修に追従し切れずに開発がストップするといったリスクは極めて低いと思っています。 これはテストツールとしてシステム開発に導入する際には非常に重要なことです。
  • ヘッドレスChromeで動作
    SeleniumなどもヘッドレスChromeで動作させることは可能ですが、PuppeteerはヘッドレスChromeで動作することが前提で開発されています。
  • 環境構築が簡単
    Node.js(npm)が使えるようになっている前提ですが、適当なフォルダで以下コマンドを実行するだけでPuppeteerが使えるようになります。
npm install puppeteer
Chromiumを内包しますので、Seleniumのように使っているブラウザとドライバーのバージョン違いでエラーとなるといったこともありません。(システムにインストールされているChromeを利用するpuppeteer-coreという軽量のライブラリもあります)
  • 実行速度が変えられる
    実行速度を変えることで、より人の操作に近付けることができます。
    これだけが理由ではありませんが、クローリング対策されているサイトもPuppeteerなら突破できる場合があります。

よく使う処理の実装

最低限これだけ押さえておけばブラウザの自動操作が可能という処理の実装を抜粋して紹介します。
より詳しい説明が必要な場合は公式リファレンス(https://pptr.dev/)を参照ください。

  • Puppeteerモジュールの読み込み
    下記のようにrequireを1行書きます。
const puppeteer = require('puppeteer');
 
(async() => {
  // ここに処理を書く
})();
  • URLを開く
    新規にブラウザを起動し、指定したURLを開きます。
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://www.tvkingdom.jp/');
  • クリックする
    リンク、ボタン、ラジオボタン、チェックボックスなどに対してマウスのクリック操作を行います。
    パラメータのselectorは、HTML要素を示すCSSセレクターの文字列になります。詳しい指定方法は後述の「【共通】selectorの指定方法」で説明しています。
await page.click(selector);
  • 文字を入力する
    テキストボックス、テキストエリアなどに対して、文字入力操作を行います。
await page.type(selector, '入力するテキスト');
  • 選択する
    ドロップダウンリストおよびリストの選択を行います。
await page.select(selector, 'optionのvalue値');
  • テキストを取得する
    スクレイピングでは画面に表示されているテキストを取得することになると思います。
    方法としては、要素を取得し、その要素のプロパティにアクセスしinnerTextの値を取得します。
const element = await page.$(selector);
const text = await (await element.getProperty('innerText')).jsonValue();
  • iframe内の要素を操作する
    iframe内の要素は、pageオブジェクトのメソッドでは操作することが出来ません。
    以下のように取得したframeオブジェクトのメソッドにより操作します。
const element = await page.$(selectorFrame);
const frame  = await element.contentFrame();

await frame.click(selector);
  • 待機する
    ブラウザの自動操作とは、前述の各種ブラウザ操作を連続して行っていくことになりますが、何も考えずにただ操作を書いていくだけだとまずまともに動きません。操作によりページの遷移が発生したりするので、適切に「待つ」ことが必要となります。
    待機の方法もいろいろ用意されていますが、以下に挙げる待機方法を押さえておけば大抵はいけます。
  • ページの読み込みが完了するまで待つ
    リンククリクなどの操作によりページの読み込みが発生する場合に、操作を行った後にページの読み込みが完了するまで待ちます。
    await Promise.all([
      page.waitForNavigation(),
      page.click(selector)
    ]);
  • 要素が現れるまで待つ
    先ほどとは逆で、操作を行う前に操作対象の要素がレンダリングされるまで待ちます。
    await page.waitForSelector(selector);
    await page.click(selector);
  • ウィンドウが表示されるまで待つ
    ボタンクリックなどにより別ウィンドウを開くような場合、前述の2種類の待機方法だけでは正しく待つことが出来ません。
    以下で取得したnewPageのメソッドを呼び出すことで別ウィンドウを操作することが出来ます。
    const [newPage] = await Promise.all([
      browser.waitForTarget(t => t.opener() === page.target()).then(t => t.page()),
      page.click(selector)
    ]);
  • 指定した時間待つ
    上記3つの待機方法でうまく行かない場合の最終手段です。
    時間指定は、マシンスペック、ネットワークトラフィックなどの影響受けますのでどれだけ待てば大丈夫という確実性がありません。なのである程度余裕を持った待機時間を設定するようにしましょう。ミリ秒で指定します。
    await page.waitFor('500');
  • 【共通】selectorの指定方法
    要素を特定する方法はいくつかありますが、CSSセレクターによる指定方法を覚えておけば十分です。
    CSSセレクターは非常に柔軟な指定が可能ですので、詳しく知りたい場合はこことかを参照ください。
  • id属性がある場合
    idはページ内でユニークな値が設定されていますので、最も確実です。
    このようなHTMLの場合、
    <input type="submit" value="ログイン" id="login">
    selectorの指定は以下となります。
    '#login'
  • class属性で一意に決まる場合
    ページ内の複数の要素で使われているclassではなく、一意に特定できるclassの場合はこちら方法が有効です。
    このようなHTMLの場合、
    <span class="PHOgFibMkQJ6zcDBLbga8">検索</span>
    selectorの指定は以下となります。
    '.PHOgFibMkQJ6zcDBLbga8'
  • name属性で一意に決まる場合
    ページ内で一意なname属性が指定されている場合はこちらの方法が有効です。
    このようなHTMLの場合、
    <input type="submit" name="login_button" value="ログイン">
    selectorの指定は以下となります。
    'input[name="login_button"]'
  • 上記以外
    id、class、nameで特定できない場合は、Chromeの開発者ツールで確認したCSSセレクターを利用します。
以下のような文字列がクリップボードにコピーされますので、そのままselectorに指定します。
’body > header > div > h1 > a > img’

Puppeteerの実装を補助する便利なツール

サンプルコード

実生活で使えないかと考えて作成したスクレイピング処理を一部抜粋して紹介します。
テレビ番組表のサイトにて指定したキーワードで番組表を検索し、ヒットする番組があれば通知するというものです。
テレビ番組表は1週間先までしかありませんので、それ以上先の番組を録画しようと思っていてもすっかり忘れていて見逃すといった事がよくあり、その対策として考えた処理です。
Try Puppeteerに貼り付ければ動きますので参考にしていただければと思います。
実際は毎日自動実行されるよう仕掛けて、キーワードにヒットする番組があったらメールを送るという運用をしています。

const keyword = 'オリンピック';

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://www.tvkingdom.jp/', {waitUntil: 'domcontentloaded'});

// 地域選択
const selectorArea = '#area-selector';
await page.waitForSelector(selectorArea);
await page.select(selectorArea, '23');

// 地上波選択
const selectorPlatform = '#stationPlatformId';
await page.waitForSelector(selectorPlatform);
await page.select(selectorPlatform, '1');

// キーワード入力
const selectorSearch = '#condition_keyword';
await page.waitForSelector(selectorSearch);
await page.type(selectorSearch, keyword);

// 検索ボタンクリック
const selectorSearchButton = 'body > div.headerMenu.pdT5 > div > table > tbody > tr > td:nth-child(5) > input';
await page.waitForSelector(selectorSearchButton);
await Promise.all([
  page.waitForNavigation(),
  page.click(selectorSearchButton)
]);

// 結果取得
const elmList = await page.$$('.utileList');         // 結果リスト
for (const elm of elmList) {
  const elmTitle = await elm.$('div > h2 > a');    // 番組タイトル
  const elmTime = await elm.$('div > p');          // 放送時間、チャンネルなど
  if (elmTitle === null) {
    console.log('キーワード「' + keyword + '」に一致する番組は見つかりませんでした。');
  } else {
    const textTitle = await (await elmTitle.getProperty('innerText')).jsonValue();
    const textTime = await (await elmTime.getProperty('innerText')).jsonValue();
    console.log(textTitle + '\n' + textTime + '\n');
  }
}

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

最後に

ここで紹介したのはPuppeteerのごく一部の機能になります。 Puppeteerを使ったブラウザ自動操作の一助になれば幸いです。


リンク

puppeteer公式(GitHub)
https://github.com/puppeteer/puppeteer

puppeteer APIリファレンス
https://pptr.dev/

CSSセレクターについて
https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Selectors

テレビ王国
https://www.tvkingdom.jp/