Heroku上でpuppeteer-coreを動かす(Docker編)


puppeteer-heroku-buildpack を使ってるサンプルはそれなりに出てくるけど、Dockerを使ってる例はあまり出てこなかったのでメモ。

Heroku上でDockerコンテナを動かす

1コンテナを適当に動かすだけのHTTPサーバーなら、だいたいなんでも動く。 $PORT で指定されたポート番号でHTTPサーバーが起動しているだけでよい。

あたりに詳しいことは書いてある。

Pythonの http.serverするだけのものをまずはデプロイしてみる。

Dockerfile
FROM python:3.8-alpine
heroku.yml
build:
  docker:
    web: Dockerfile
run:
  web: python -m http.server $PORT

最初の1回だけ、アプリの設定が必要

$ heroku create
$ heroku stack:set container

デプロイ

git pushするだけ。めっちゃかんたん。

$ git push heroku master

puppeteer-coreとGoogle Chrome入りのDockerイメージを作ってデプロイ

puppeteerはChromiumをダウンロード機能があるが、どうせならGoogle Chrome (Stable)を使ったほうがよいだろう。(偏見)

そこで、Chromiumのダウンロード機能のないpuppeteer-coreと、Google Chromeを1つのDockerイメージに詰め込んでデプロイしてみる。

Dockerfile
# ref: https://github.com/puppeteer/puppeteer/blob/main/.ci/node12/Dockerfile.linux
FROM node:12-slim

# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
# installs, work.
RUN apt-get update \
    && apt-get install -y wget gnupg \
    && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get update \
    && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
    --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

ENV PUPPETEER_EXECUTABLE_PATH /usr/bin/google-chrome-stable
ENV PUPPETEER_PRODUCT chrome

RUN mkdir /app
WORKDIR /app
COPY package.json /app/package.json
RUN npm install
COPY index.js /app/index.js
CMD npm start

index.jsのほうは、HTTPサーバーを適当に作って、 ?q=hogehoge みたいなパラメータが指定されたらhogehogeをGoogle検索してその検索結果画面のテキストを表示するだけの、超てきとうなもので。

index.js
const http = require("http");
const url = require("url");
const puppeteer = require("puppeteer-core");

//create a server object:
http
  .createServer((req, res) => {
    const requestUrl = url.parse(req.url, true);
    let keyword = requestUrl.query["q"];
    if (!keyword) {
      res.write("request with ?q=xxxx");
      res.end();
    } else {
      puppeteer
        .launch({
          args: ["--no-sandbox", "--disable-setuid-sandbox"],
          executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
        })
        .then(async (browser) => {
          const page = (await browser.pages())[0] || (await browser.newPage());

          // ここから自動操作
          await page.goto("https://google.com/");
          await page.type("input[name='q']", requestUrl.query["q"]);
          await Promise.all([
            page.waitForNavigation(),
            page.keyboard.press("Enter"),
          ]);

          let text = await page.evaluate(() => document.body.textContent);
          await page.close();

          res.write(text);
          res.end();
        });
    }
  })
  .listen(process.env.PORT);

package.jsonは特に何のひねりもなく、 npm init --yes npm install puppeteer-core したくらいのもの。

package.json
{
  "name": "heroku-docker-playground",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/YusukeIwaki/heroku-docker-playground.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/YusukeIwaki/heroku-docker-playground/issues"
  },
  "homepage": "https://github.com/YusukeIwaki/heroku-docker-playground#readme",
  "dependencies": {
    "puppeteer-core": "^5.2.1"
  }
}
heroku.yml
build:
  docker:
    web: Dockerfile
run:
  web: npm start

デプロイ時にdocker buildが行われるので結構時間がかかるが、待っていれば自然にHTTPサーバーが起動する。

こんな感じで、Google件察結果テキストがずらーーーっと出たら動作疎通OK。

たぶん playwrightも動くだろう

puppeteer-coreが動くなら、playwright-coreも同様に動くだろう。

playwrightはDockerfileがマイクロソフトから公開されているので、それを参考にすればよい。
https://github.com/microsoft/playwright/blob/master/docs/docker/Dockerfile.bionic

完全に余談だが、Azure Functionsでplaywrightが現時点では直感的には使えないので、「とりあえずHTTPのリクエストを受けてplaywrightを動かしたいだけなんだ!」という人にHerokuはかなりおすすめできる。