Vercel上でPuppeteerを動かす


試してみたら動かせました。調べてもそれっぽいの無さそうだったのでメモです。

## 参考にしてるコードとお断り

ググったらこちらのプルリクが当たったので、こちらをもとに書いてますが、現状の公式なやり方かはわかりません。

> https://github.com/vercel/now-examples/pull/207/files

その他参考: [VercelでLINE BOTを動かす 2020年5月版](https://qiita.com/n0bisuke/items/a5f49193fa68f28a7f25)

## 実装

### chrome-aws-lambda + puppeteer-coreを利用

vercelはAWS Lambda上で動いてる模様なので、動作するChromium Binaryも[chrome-aws-lambda](https://github.com/alixaxel/chrome-aws-lambda)を使うと良いっぽいです。

また、バイナリはchrome-aws-lambdaを利用するということで、バイナリが梱包されてない[puppeteer-core](https://www.npmjs.com/package/puppeteer-core)を使います。

```
$ yarn add chrome-aws-lambda
```

ここで最新バージョンの`chrome-aws-lambda`がインストールされますが、この時のバージョンにあわせて`puppeteer-core`もインストールしましょう。

僕の場合、chrome-aws-lambdaをインストールしたら3.1.1だったのでpuppeteer-coreもバージョン指定してインストールします。(なにも指定しないでインストールしたら`3.2.0`がインストールされて、デプロイ後に怒られました。

```
$ yarn add puppeteer-core@3.1.1
```

* package.jsonのdependenciesで確認

こんな感じでこの二つのモジュールのバージョンが合ってれば大丈夫です。

```
  "dependencies": {
    "chrome-aws-lambda": "^3.1.1",
    "puppeteer-core": "3.1"
  }
```

### プログラムの作成

プロジェクトのルートにapiフォルダを作成し、その中に任意のjsファイルを作成します。

`api/run-puppeteer.js`とします。

Node.jsなコードもLambdaぽい書き方になります。

```api/run-puppeteer.js
const chrome = require('chrome-aws-lambda');
const puppeteer = require('puppeteer-core');

module.exports = async (req, res) => {
  const { URL = 'https://twitter.com/n0bisuke' } = req.query;
  const browser = await puppeteer.launch({
    args: chrome.args,
    executablePath: await chrome.executablePath,
    headless: chrome.headless,
  });

  const page = await browser.newPage();
  await page.goto(URL); //URLにアクセス
  // 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,
      title: document.title,
      deviceScaleFactor: window.devicePixelRatio
    };
  });
 
  console.log('Dimensions:', dimensions); 
 
  await browser.close();
  
  //アクセスしたページのタイトルを取得
  res.send(`${URL}のページタイトルは「${dimensions.title}」だよー!`);
}
```

こんな感じになりました。

#### 補足

apiフォルダにjsファイルを作ってこんな感じに書くのが基本ぽいです。AWS Lambdaをちゃんと使ったことないけど、一つ上の階層でrequire/importしてる処理があるんでしょうね。

```
module.exports = (req, res) => {
  const { name = 'World' } = req.query
  res.send(`Hello ${name}!`)
}
```

参考: https://vercel.com/docs/runtimes#official-runtimes

### デプロイして試す

```
$ vercel
```

デプロイして、 `https://発行されたドメイン/api/jsファイル名`にアクセスします。
今回の場合は`https://発行されたドメイン/api/run-run-puppeteer`です。

`api/run-puppeteer.js`の中身参照ですが、アクセスすると、puppeteerが動いて指定したURL(デフォは`https://twitter.com/n0bisuke`)にアクセスしてタイトルを取得してきます。

> ![](https://i.gyazo.com/1620a02df288450d3a0c5ff06d829636.png)
> ![](https://i.gyazo.com/45242eeaa76f38e535d45c1972810644.png)

## その他: scheenshotの画像保存が出来なかった

これは設定問題かもしれないですが、ログにこんな感じのが出てたので、Vercel上でファイル書き込みは出来ないのかも。

```
"errorMessage":"EROFS: read-only file system, open 'example.png'
```

(出来そうだって話あれば教えて下さい！)

## 補足: 毎回デプロイが面倒なのでローカルで試す

`api/run-puppeteer.js`をこんな感じで書いてローカルでも試せました。


* ローカル環境
	* mac os catalina
	* Node.js v14.3.0

puppetterを追加します。

```
$ yarn add puppetter
```

`process.env.AWS_LAMBDA_FUNCTION_VERSION`があればVercel上での動作と判断してます。

```api/run-puppeteer.js
let chrome = {};
let puppeteer = {};

//puppeteer main process
const run = async (puppeteer, chrome={}, URL='https://twitter.com/n0bisuke') => {
  const browser = await puppeteer.launch({
    args: chrome.args,
    executablePath: await chrome.executablePath,
    headless: chrome.headless,
  });

  const page = await browser.newPage();
  await page.goto(URL);
  // 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,
      title: document.title,
      deviceScaleFactor: window.devicePixelRatio
    };
  });
  await browser.close();

  return dimensions;
}

if(process.env.AWS_LAMBDA_FUNCTION_VERSION){
  //Vercel
  chrome = require('chrome-aws-lambda');
  puppeteer = require('puppeteer-core');
}else{
  //Local Test
  puppeteer = require('puppeteer');

  const URL = `https://protoout.studio`;
  run(puppeteer,{},URL).then(res => console.log(res));
}

module.exports = async (req, res) => {
  const { URL='https://twitter.com/n0bisuke' } = req.query;
  const dimensions = await run(puppeteer, chrome, URL);
  res.send(`${URL}のページタイトルは「${dimensions.title}」だよー!`);
}
```

手元で実行

```
$ node api/run-puppeteer.js
```

`vercel dev`コマンドでも試せるかもですが、サーバー起動などで時間かかるのでこの手の処理だけならこっちの方が早いかなぁという感触です。

## まとめや所感など

最終形のコードはディレクトリごとあげておきました。

> https://github.com/n0bisuke/vercel-puppeeter-sample


chrome-aws-lambdaというのがあることを知れたのが収穫かも。あと、`process.env.AWS_LAMBDA_FUNCTION_VERSION`で判定してるけどもっとよいやり方ないのかな......

あとやはりファイル書き込みが出来ない(スクショ保存)のかも気になるので誰か分かる方いたらコメントを...笑

Herokuなどでも問題ないけどVercel上でもpuppeteer動かしたい場合もあるかもしれないので誰かの参考になれば幸いです。