2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ブラウザをサーバーで動かし、Playwrightと人の両方がブラウザ操作できるようにする

Last updated at Posted at 2023-02-19

やりたいこと

  • PlaywrightまたはPuppeteerを使って、ブラウザ操作してクローリングしたい。
  • そのブラウザは、サーバーやクラウドで動かしたい。
  • 本記事では、ブラウザはサーバー/クラウドで、Puppeteer/Playwrightはローカルで動かす構成を目指します。
  • サーバーで動くブラウザを、クライアントでも人が見て操作したい(reCAPTCHA突破等)。

構成図

試行錯誤の結果、最終的な構成は以下の図のようになりました。
おおまかなイメージ図です。等幅フォントで見てね。

┏━━━━━━━━━━━━━━━━━━━━━━┓     ┏━━━━━━━━┓
┃  サーバー/クラウドのDockerコンテナ   ┃     ┃ クライアント ┃
┃                   ┏━━┻━━━┓ ┃        ┃
┃┌──────┐  ┌────┐PORT ┃      ┃ PORT ┌─────┐┃
┃│Xvfb    │←─┤Vnc   │←──╂──────╂─╂─┤ Vnc   │┃
┃│ 仮想Display├─→│ Server├───╂──────╂─╂→│ Viewer │┃
┃└───┬──┘  └────┘5900 ┃      ┃ 5900 └─────┘┃
┃   ↑│              ┃  SSH   ┃ ┃        ┃
┃   │↓              ┃Portforward ┃ ┃        ┃
┃┌──┴───┐PORT┌────┐PORT ┃      ┃ PORT ┌─────┐┃
┃│ Google  │←─┤Reverse │←──╂──────╂─╂─┤Playwright│┃
┃│  Chrome ├─→│ Proxy ├───╂──────╂─╂→│/Puppeteer│┃
┃└──────┘9222└────┘19222 ┃      ┃ 9222 └─────┘┃
┃   (Remote Debugging Port)     ┗━━┳━━━┛ ┃        ┃
┃                      ┃     ┃        ┃
┗━━━━━━━━━━━━━━━━━━━━━━┛     ┗━━━━━━━━┛

ただし、 本記事ではDockerコンテナもローカルで動かすにとどめています。

Dockerfile作成

Step 1:Google Chromeをインストール

このページのDockerfileをアレンジしました(感謝)。
変更点として、DockerでPuppeteerは動かしませんので、nodeも入れないようにしました。

作成中のDockerfile


FROM debian:bullseye

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/*

Step 2:Xvfb(仮想Display)とVNCサーバーをインストール

Google Chromeは--headlessオプションをつけずに起動し、Xvfb(仮想Display)に表示させることにします。
そのDisplay画面をクライアントで表示・操作したいので、リモートデスクトップ接続用にVNCサーバーもインストールします。

Dockerfileに以下のapt-get installを加えます。


    && apt-get install -y xauth xvfb x11vnc x11-apps \

Step 3:SSH Serverのインストールと設定

セキュリティーの理由により、SSHポートフォワーディングを使用します。

  • 構成図の5900ポートと9222ポートが、SSHログインしたユーザーのみに開放されます。
  • ポートを通じた通信が暗号化されます。

Dockerfileに以下のapt-get installを加えます。


    && apt-get install -y openssh-server \

また、DockerfileでSSHの設定も行います。


RUN mkdir /root/.ssh \
    && chmod 700 /root/.ssh
COPY ./id_ed25519.pub /root/.ssh/authorized_keys
RUN chmod 600 /root/.ssh/authorized_keys
COPY ./sshd_config /etc/ssh/

公開鍵(本記事では id_ed25519.pub)と設定ファイル(sshd_config)は事前に用意しておきます。

sshd_configは以下を設定します。

  • 公開鍵認証を許可する:PubkeyAuthentication yes
  • パスワード認証を禁止する:PasswordAuthentication no
  • SSHポートをデフォルトから変える:Port 2222
  • (本記事では設定しませんが必要に応じて)GatewayPorts clientspecified

参考までに、以下に sshd_config 全体を載せます。


#       $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $

# This is the sshd server system-wide configuration file.  See
# sshd_config(5) for more information.

# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin

# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented.  Uncommented options override the
# default value.

Include /etc/ssh/sshd_config.d/*.conf

Port 2222
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::

#HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
#HostKey /etc/ssh/ssh_host_ed25519_key

# Ciphers and keying
#RekeyLimit default none

# Logging
#SyslogFacility AUTH
#LogLevel INFO

# Authentication:

#LoginGraceTime 2m
#PermitRootLogin prohibit-password
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10

PubkeyAuthentication yes

# Expect .ssh/authorized_keys2 to be disregarded by default in future.
#AuthorizedKeysFile     .ssh/authorized_keys .ssh/authorized_keys2

#AuthorizedPrincipalsFile none

#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody

# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
#HostbasedAuthentication no
# Change to yes if you don't trust ~/.ssh/known_hosts for
# HostbasedAuthentication
#IgnoreUserKnownHosts no
# Don't read the user's ~/.rhosts and ~/.shosts files
#IgnoreRhosts yes

# To disable tunneled clear text passwords, change to no here!
PasswordAuthentication no
#PermitEmptyPasswords no

# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication no

# Kerberos options
#KerberosAuthentication no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
#KerberosGetAFSToken no

# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes
#GSSAPIStrictAcceptorCheck yes
#GSSAPIKeyExchange no

# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication.  Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
UsePAM yes

#AllowAgentForwarding yes
#AllowTcpForwarding yes
GatewayPorts no
X11Forwarding yes
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
PrintMotd no
#PrintLastLog yes
#TCPKeepAlive yes
#PermitUserEnvironment no
#Compression delayed
#ClientAliveInterval 0
#ClientAliveCountMax 3
#UseDNS no
#PidFile /var/run/sshd.pid
#MaxStartups 10:30:100
#PermitTunnel no
#ChrootDirectory none
#VersionAddendum none

# no default banner path
#Banner none

# Allow client to pass locale environment variables
AcceptEnv LANG LC_*

# override default of no subsystems
Subsystem       sftp    /usr/lib/openssh/sftp-server

# Example of overriding settings on a per-user basis
#Match User anoncvs
#       X11Forwarding no
#       AllowTcpForwarding no
#       PermitTTY no
#       ForceCommand cvs server

Step 4:Reverse Proxyの設定

Google Chromeに--remote-debugging-port=9222オプションをつけてリモートデバッグ有効で起動し、9222ポートのエンドポイントに疎通させたいのですが、ここで問題が発生しました。

  • Chromeに--headlessオプションをつけて起動すると、Dockerコンテナの外からも疎通する
  • Chromeに--headlessオプションをつけずに起動すると、Dockerコンテナの外から疎通しない

セキュリティーの理由から、--headlessをつけないと外からの疎通を許さないようです。
本記事では、セキュリティーはSSHポートフォワーディングで担保されているとして、--headlessオプションをつけずに起動しても疎通するようにします。

そのために、構成図のようにリバースプロキシを導入します。
リバースプロキシのソースコードは以下の通りです(Go言語)。

reverse_proxy.go

package main

import (
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
)

func main() {
	loc := "/"
	proxyPass := "http://localhost:9222"

	url, err := url.Parse(proxyPass)
	if err != nil {
		log.Fatal(err)
	}
	http.Handle(loc, httputil.NewSingleHostReverseProxy(url))
	http.ListenAndServe(":19222", nil)
}

そしてDockerfileの最初の方に以下を加えて、reverse_proxyを導入します。


FROM golang:bullseye as go-build
WORKDIR /app
COPY ./reverse_proxy.go ./
RUN go build -o reverse_proxy reverse_proxy.go

FROM debian:bullseye
WORKDIR /app

# リバースプロキシ設定
COPY --from=go-build /app/reverse_proxy /app/

最終的なDockerfile

まずDockerfile以外に、事前に以下のファイルを用意しました。

  • id_ed25519.pub(前述)
  • sshd_config(前述)
  • reverse_proxy.go(前述)
  • cmd.sh

cmd.shの内容は以下の通りです。

cmd.sh

#!/bin/bash
set -eo pipefail

echo "service start."
service ssh start
service dbus start

echo "exec reverse_proxy."
exec /app/reverse_proxy &

echo "exec xvfb-run."
exec xvfb-run \
    --auth-file /tmp/xvfb-run \
    --server-args=":99 -screen 0 1024x768x24" \
    google-chrome \
    --no-sandbox \
    --disable-dev-shm-usage \
    --disable-gpu \
    --no-first-run \
    --no-default-browser-check \
    --remote-debugging-address=0.0.0.0 \
    --remote-debugging-port=9222 \
    about:blank &

echo "exec x11vnc."
exec x11vnc -display :99 -auth /tmp/xvfb-run -forever &

# Exit immediately when one of the background processes terminate.
echo "wait."
wait -n

そして最終的なDockerfileは以下の通りです。


FROM golang:bullseye as go-build
WORKDIR /app
COPY ./reverse_proxy.go ./
RUN go build -o reverse_proxy reverse_proxy.go

FROM debian:bullseye
WORKDIR /app

# リバースプロキシ設定
COPY --from=go-build /app/reverse_proxy /app/

# Google Chrome、SSH Server、Xvfb、VNCのインストール
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 \
    && apt-get install -y openssh-server \
    && apt-get install -y xauth xvfb x11vnc x11-apps \
    && rm -rf /var/lib/apt/lists/*

# SSH設定
RUN mkdir /root/.ssh \
    && chmod 700 /root/.ssh
COPY ./id_ed25519.pub /root/.ssh/authorized_keys
RUN chmod 600 /root/.ssh/authorized_keys
COPY ./sshd_config /etc/ssh/

# コマンド起動
COPY ./cmd.sh ./
RUN chmod +x ./cmd.sh
CMD ["/app/cmd.sh"]

動かしてみる

docker buildします。


docker build -t chrome-linux .

docker runします。


docker run --publish 2222:2222 chrome-linux

SSHポートフォワーディングを有効にするオプションをつけて、sshでdockerコンテナにログインします。


ssh -o ExitOnForwardFailure=yes -p 2222 \
    -L 9222:localhost:19222 -L 5900:localhost:5900 root@localhost

9222ポートへの疎通を確認します。


curl -I http://127.0.0.1:9222/json/list

以下のように200が返ればOKです。


HTTP/1.1 200 OK
Content-Length: 5
Content-Security-Policy: frame-ancestors 'none'
Content-Type: application/json; charset=UTF-8
Date: Sun, 19 Feb 2023 13:57:31 GMT

VNC Viewerを起動して、Dockerコンテナへのリモートデスクトップ接続を試みます。
本記事では TightVNC Viewerを使用します。

image.png

以下のように繋がりました。
リモートデスクトップ接続ですので、人がGoogle Chromeを操作できます。

image.png

このブラウザをPlaywrightから操作する場合は、launch()の代わりにconnectOverCDP()を呼びます。


const browser = await playwright.chromium.connectOverCDP("http://localhost:9222");
const context = browser.contexts()[0];
const page = await context.newPage();

このブラウザをPuppeteerから操作する場合は、launch()の代わりにconnect()を呼びます。


const browser = await puppeteer.connect({
    browserURL: "http://localhost:9222",
});
const page = await browser.newPage();
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?