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

[puppeteer-heroku-buildpack](https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack) を使ってるサンプルはそれなりに出てくるけど、Dockerを使ってる例はあまり出てこなかったのでメモ。

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

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

* https://devcenter.heroku.com/articles/container-registry-and-runtime#dockerfile-commands-and-runtime
* https://devcenter.heroku.com/articles/build-docker-images-heroku-yml

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

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

```Dockerfile: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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/fe8033d2-c531-557d-0286-58a2b2ebdd98.png)


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

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

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

```Dockerfile: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。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/56ca2216-f1c0-c95b-e2f3-542dd2bce5f4.png)


### たぶん playwrightも動くだろう

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

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

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