Vercel上でPuppeteerを動かす


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

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

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

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

その他参考: VercelでLINE BOTを動かす 2020年5月版

実装

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

vercelはAWS Lambda上で動いてる模様なので、動作するChromium Binaryもchrome-aws-lambdaを使うと良いっぽいです。

また、バイナリはchrome-aws-lambdaを利用するということで、バイナリが梱包されてない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 [email protected]
  • 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)にアクセスしてタイトルを取得してきます。


その他: 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動かしたい場合もあるかもしれないので誰かの参考になれば幸いです。