Chrome97の登場に備えてPuppeteerを勉強しておく

## はじめに

[booci bocci](https://boccibocci.com)を運営している@shoji-kaiです。
唐突ですが、日常生活の中でも定型化されているブラウザ作業ってありますよね？
私の場合でいうと、

- 隔週で生協に食パンと卵を注文する
- 給料が振り込まれたら、別の口座に定額を振替する
- IPOがBB(ブックビルディング)されたら、いちおう申し込んでおく

などなど。ですが、よく忘れて締め切りに間に合わないことが度々あります。

そこで以前から、Seleniumとか使ってここらへんの処理を自動化したいなと思っていたのですが、実装するのが面倒でそのままになっておりました。
そんなときにTwitterを眺めていたところ以下の記事を見つけました。

https://www.publickey1.jp/blog/21/chrome_97devtoolswebpuppeteer.html

Chrome97の登場は来年1月予定とのことで、実際のものを使ってみないと分かりませんが、これはかなり期待できます。
Puppeteerへのエクスポートもできるらしいので、お手軽にいろいろとカスタマイズもできそうです。
そんなわけで、ちょっとPuppeteerを勉強してみようかなと思った次第です。

ちなみにですが、SeleniumにもSelenium IDEという似たようなものがあるのですが、以前試したときに使いづらく感じて、それ以降調べていません。（今だともう少し使いやすくなっているのかもしれません)

今回はPuppeteer初挑戦ということで、[Quick Start](https://developers.google.com/web/tools/puppeteer/get-started)に沿って基礎を学習していきたいと思います。

## そもそもPuppeteerとは

いちおう簡単に説明すると、Webブラウザの操作を自動化するフレームワークになります。
Puppeteerと似たようなものに、SeleniumやCypressなどがあります。
Puppeteerがサポートしているブラウザは基本的にChrome/Chromiumのみですが、Firefox Nightlyも実験的サポートされています。
開発元はGoogleです。

## Puppeteerのインストールの前に

PuppeteerにはNode.jsが必要です。
私は、VoltaでNode.js, Yarnのバージョン管理をしているので、そこの手順も説明しておきます。
Node.jsのバージョン管理に関しては個人のお好みですが、私個人としてはVoltaを教えてもらって気にっているのでシェアしておきます。
環境は、Mac(Homebrew)を想定しております。それ以外の方は、適宜置き換えてお読みください。

### Voltaのインストール

```
brew install volta
export VOLTA_HOME="$HOME/.volta"
export PATH="$VOLTA_HOME/bin:$PATH"
```

### Node.js, Yarnのインストール

```
volta install node@14
volta install yarn
```

### pakcage.jsonの初期化

```
npm init -y
```

### プロジェクトにNode.js, Yarnのバージョンを固定

このためだけにVoltaを使っていると言っても過言ではありません。
nodeだけでなくyarnやnpmなどもバージョン固定できるのが便利です。

```
volta pin node@14
volta pin yarn
```

```json:package.json
{
  "name": "gsw-puppeteer",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "volta": {            # <= これ
    "node": "14.18.1",
    "yarn": "1.22.17"
  }
}
```

## クイックスタート

### Puppeteerのインストール

```
yarn add puppeteer  # OR `npm i puppeteer`
```

### Puppeteerの使い方

#### 例1. スクリーンショットを撮る

```javascript:example.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({ path: 'example.png' });

  await browser.close();
})();
```

```
% node example.js
% ls example.png
example.png
```

#### 例2. PDFを作成する

```javascript:hn.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://news.ycombinator.com', {
    waitUntil: 'networkidle2',
  });
  await page.pdf({ path: 'hn.pdf', format: 'a4' });

  await browser.close();
})();
```

```
% node hn.js
% ls hn.png
hn.pdf
```

#### 例3. ページのコンテキストでスクリプトを評価する

```javascript:get-dimensions.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');

  // Get the "viewport" of the page, as reported by the page.
  const dimensions = await page.evaluate(() => {
    return {
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight,
      deviceScaleFactor: window.devicePixelRatio,
    };
  });

  console.log('Dimensions:', dimensions);

  await browser.close();
})();
```

```
% node get-dimensions.js
Dimensions: { width: 800, height: 600, deviceScaleFactor: 1 }
```

### デフォルトの設定

* 1. Headlessモード

デフォルトはHeadlessモードです。
ブラウザを起動したい場合はheadlessオプションをfalseに設定します。

```
const browser = await puppeteer.launch({ headless: false }); // default is true
```

* 2. Chrome/Chromium

デフォルトはバンドルされたバージョンのChromiumを起動します。
デフォルト以外のバージョンのChrome/Chromiumを使いたい場合はexecutablePathオプションにパスを設定します。

```
const browser = await puppeteer.launch({ executablePath: '/path/to/Chrome' });
```

* 3. ユーザープロファイル

実行ごとにユーザープロファイルを新規作成します。

## デモ

試しにデモとしてPuppeteerからTweetするプログラムを書いてみました。

```javascript:tweet.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    ignoreHTTPSErrors: true,
    args: [`--window-size=1440,900`],
    defaultViewport: {
      width:1440,
      height:900
    }
  });
  const page = await browser.newPage();
  await page.goto('https://twitter.com/login');
  await page.waitForTimeout(5000);
  await page.type('input[type="text"]', '<ユーザーID>');
  await page.waitForTimeout(2000);
  await page.keyboard.press('Enter');
  await page.waitForTimeout(2000);
  await page.type('input[type="password"]', '<パスワード>');
  await page.waitForTimeout(2000);
  await page.keyboard.press('Enter');
  await page.waitForTimeout(4000);
  await page.click('a[href^="/compose/tweet"]');
  await page.waitForTimeout(2000);
  await page.keyboard.type('Tweeted by Puppeteer');
  await page.waitForTimeout(2000);
  await page.keyboard.down('Meta');
  await page.keyboard.press('Enter');
  await page.keyboard.up('Meta');
  await page.waitForTimeout(10000);
  await browser.close();
})();
```

動画も撮ってみました。(Headlessモードはオフで実行しています)

<iframe width="560" height="315" src="https://www.youtube.com/embed/Usd-5EImSQI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## おわりに

あまり触る機会は少ないのですが、ブラウザ操作系のツールは見ていて面白いので結構好きです。
スクレイピング系の技術は使い所も広いので、今後もちょこちょこ記事を書いていければと思っています。
それでは、こちらの記事がどなたかのお役に立てれば幸いです。