ヘッドレス Chrome Node API 「Puppeteer」

## Puppeteer
[Puppeteer](https://github.com/GoogleChrome/puppeteer) は DevTools プロトコルでヘッドレス Chrome または Chromium を制御するための高水準 API を提供する Node ライブラリです。
ヘッドレスではないフルの Chrome または Chromium を使用することもできます。

## インストール

### Yarn
Puppeteer をインストールするために、JavaScript パッケージマネージャーのひとつである Yarn をインストールします。

#### macOS
```bash
brew install yarn
```
nvm を利用している場合は `--without-node` オプションを付けて Node.js の依存インストールを除外します。

#### Windows
```bash
cinst -y yarn
```

#### CentOS
```bash
curl --silent --location https://rpm.nodesource.com/setup_8.x | bash - && \
yum -y install nodejs gcc-c++ make yum-utils && \
yum-config-manager --add-repo https://dl.yarnpkg.com/rpm/yarn.repo && \
yum -y install yarn
```

### Puppeteer
```bash
yarn add puppeteer
```
`yarn` コマンドで Puppeteer をインストールします。最新版の 1.3.0 がインストールされました。

```bash
mkdir -p Workspace/puppeteer && cd $_
```

## ブラウザの起動 puppeteer.launch()
### 通常の起動
```js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = (await browser.pages())[0];
  await page.setViewport({ width, height });
  await page.goto('https://www.example.com');
})();
```
### 情報バーの非表示、ウィンドウの最大化、シークレットモードでの起動
```js
const puppeteer = require('puppeteer');
const headless = false;
const slowMo = 10;
const width = 1280, height = 800;
const args = [
  '--start-fullscreen',
  '--disable-infobars',
  '--incognito',
];

(async () => {
 const browser = await puppeteer.launch({ headless, slowMo, args });
  const page = (await browser.pages())[0];
  await page.setViewport({ width, height });
  await page.goto('https://www.example.com');
})();
```

### API
https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md
#### ブラウザの起動 puppeteer.launch
```javascript
puppeteer.launch();
```
|パラメータ|説明|
|---|---|
|`headless`|ブラウザを[ヘッドレスモード](https://developers.google.com/web/updates/2017/04/headless-chrome)で実行するかどうか。 `devtools` オプションが `true` でない限りデフォルトは `true` 。|
|`slowMo`|Puppeteer の動作を指定されたミリ秒単位で遅らせる。何が起こっているかを見ることができて役に立つ。|
|`devtools`|各タブに DevTools パネルを自動的に開くかどうか。このオプションが `true` だと `headless` オプションは `false` に設定される。|
|`dumpio`|ブラウザの process stdout と stderr を `process.stdout` と `process.stderr` にパイプするかどうか。デフォルトは `false` 。|
|`timeout`|ブラウザインスタンスの開始を待つ最大時間（ミリ秒単位）。デフォルトは `30000` （30秒）。タイムアウトを無効にするには `0` を渡す。|
|`args`|ブラウザインスタンスに渡す追加の引数。 Chromiumフラグの一覧は[ここ](https://peter.sh/experiments/chromium-command-line-switches/)で見られる。|

###### [List of Chromium Command Line Switches](https://peter.sh/experiments/chromium-command-line-switches/)
|オプション|説明|
|---|---|
|`--no-startup-window`|起動時にブラウザウィンドウを自動的に開かない（バックグラウンドアプリをホスティングする目的で Chrome を起動するときに使用される）|
|`--no-referrers`|HTTP-Referer ヘッダーを送信しない|
|`--incognito`|ブラウザをシークレットモードで直接起動する|
|`--no-default-browser-check`|デフォルトのブラウザチェックを無効にする<br>デフォルトのブラウザ情報バーが表示されないようにするUI/ブラウザテストに役立つ|
|`--hide-scrollbars`|スクリーンショットのスクロールバーを非表示にする|
|`--disable-infobars`|一時廃止された [^disable-infobars] が復活した [^disable-infobars2] |
|`--enable-automation`|ユーザーにブラウザが自動テストによって制御されていることを通知する|

[^disable-infobars]: https://chromium.googlesource.com/chromium/src/+/d869ab3350d8ebd95222b4a47adf87ce3d3214b1
[^disable-infobars2]: https://bugs.chromium.org/p/chromium/issues/detail?id=820453#c6

### ブラウザの終了 browser.close()
```
browser.close()
```
Chromium と開かれている場合はそのページすべてを閉じます。 Browser オブジェクト自体は破棄されたものとみなされ、もう使用できない。

## フォーム入力
```js
await page.type('input[name="id"]', 'joe');
```
```js
await page.type('input[name="id"]', 'joe', {delay: 100});
```

## フォーカス
```js
let button = await page.focus('#login_button');
```

## クリック
```js
await page.click('#login_button');
```
```js
let button = page.$('#login_button');
await button.click();
```

##
```js
let items = await page.$$('#list .item');
let item = await items[2].$('.name');
```
`querySelectorAll`

## スクリーンショット
```js
await page.screenshot({path: 'screenshot_all.png'});
```
```js
await let side_nav = page.$('#side_nav');
await side_nav.screenshot({path: 'screenshot_side_nav.png'});
```

## value 値の取得
```js
let login_id = await page.$eval('input[name="login_id"]', el => el.value);
```

## テキストの取得
```js
let text = await page.$eval('.classname', item => {return item.textContent;});
```


## 遷移待機
```js
page.click('a[href="index.php"]');
await page.waitForNavigation({timeout: 60000, waitUntil: "domcontentloaded"});
```
```js
await page.click('a[href="login.php"]');
await page.waitFor('a[href="mypage.php"]', {timeout: 120000});
```

## プルダウンの選択
```js
await page.select('select[name="color"]', 'red');
```
```js
await page.select('select#colors', 'red', 'green', 'blue');
```
`option` の `value` が `red` のものを選択する。
https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pageselectselector-values

```js
const scrapingData = await page.evaluate(() => {
    const dataList = [];
    const nodeList = document.querySelectorAll("td.date");
    nodeList.forEach(_node => {
        dataList.push(_node.innerText);
    })
    return dataList;
});
```

## BASIC 認証
```js
const user = 'user';
const pass = 'P@ssw0rd';
await page.setExtraHTTPHeaders({Authorization: `Basic ${new Buffer(`${user}:${pass}`).toString('base64')}`});
```

## ページ遷移
```js
await page.goto('https://example.com/', {waitUntil: 'domcontentloaded'});
```
```js
let waitUntil = 'domcontentloaded';
await page.goto('https://example.com/', {waitUntil});
```

### waitUntil
|オプション|内容|
|---|---|
|`load`            |`load` イベントが発生するとナビゲーション完了と見なす。デフォルト値。|
|`domcontentloaded`|`DOMContentLoaded` イベントが発生するとナビゲーション完了と見なす。|
|`networkidle0`    |500ミリ秒間にネットワーク接続が0個以下であればナビゲーション完了と見なす。|
|`networkidle2`    |500ミリ秒間にネットワーク接続が2個以下であればナビゲーション完了と見なす。|
https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagegotourl-options

### waitFor
https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitforselectororfunctionortimeout-options-args

### waitForNavigation
```js
await page.waitForNavigation({waitUntil:'domcontentloaded'});
```
https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitfornavigationoptions

### waitForSelector
```js
await page.waitForSelector('#contents');
```
https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitforselectorselector-options

### waitForXPath
https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitforxpathxpath-options

### waitForFunction
https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitforfunctionpagefunction-options-args

## スタイルシート操作
```js
await page.evaluate(({}) => {$('#slider').css('display', 'block');},{});
```

## ユーザーエージェントの指定
```js
const userAgent = '';
page.setUserAgent(userAgent);
```

## デバイスの指定
```js
const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
const iPhone = devices['iPhone X'];

puppeteer.launch().then(async browser => {
  const page = await browser.newPage();
  await page.emulate(iPhone);
  // other actions...
});
```
使用可能なデバイスのリストは [DeviceDescriptors.js](https://github.com/GoogleChrome/puppeteer/blob/master/DeviceDescriptors.js) で確認できる。

https://qiita.com/ukyo/items/287169de85576dfed608
https://qiita.com/tomi_shinwatec/items/a68cf7840c3da002c6e0