Docker でヘッドレス Chrome を動かしてみた

HTML を取得後に JavaScript で DOM を操作してデータを表示するようなサイトでは curl で HTML を取得しても欲しいデータを取得できない。そこで、Chrome をヘッドレスで動かして完全にレンダリングされた後のデータを取得してみた。

目的

スクレイピングするときは、Web サーバーから HTML を取得するわけだが、(1)HTML を取得 → (2)ブラウザ側の JavaScript で DOM を操作してデータを表示、のような動きをするサイトでは、通常は(1)の段階の HTML しか取得できない。

ならば、(2)の実際にブラウザに表示されてる状態の HTML をどうやって取得するかという問題を解決するのが、ヘッドレスの Chrome らしい。

ヘッドレスの Chrome とは、Chrome のウィンドウは表示しないけど、HTML の取得やレンダリング、JavaScript の動作はGUI版の Chrome と同様に行う機能らしい。

実験

実際に試してみた。

設定

Alpine Linux ベースの PHP イメージに Chrome を追加する方式で動作環境を構築する。

数々の試行錯誤の結果、以下を考慮しないと動かないようだ。

  1. udev を導入する
  2. 日本語フォントを導入する
  3. 一般ユーザー権限で動かす
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
From php:7.4-alpine3.12

ENV USER=user
ENV UID=12345
ENV GID=23456

RUN apk add --update --no-cache \
chromium \
chromium-chromedriver \
udev \
zlib-dev \
&& rm -rf /var/cache/apk/*

RUN mkdir /noto

ADD https://noto-website.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip /noto

WORKDIR /noto
RUN unzip NotoSansCJKjp-hinted.zip && \
mkdir -p /usr/share/fonts/noto && \
cp *.otf /usr/share/fonts/noto && \
chmod 644 -R /usr/share/fonts/noto/ && \
fc-cache -fv

WORKDIR /
RUN rm -rf /noto

ENTRYPOINT tail -f /dev/null

RUN addgroup --gid "$GID" "$USER" \
&& adduser \
--disabled-password \
--gecos "" \
--home "/home/user" \
--ingroup "$USER" \
--no-create-home \
--uid "$UID" \
"$USER"
RUN mkdir /home/user && chown -R user.user /home/user
USER user
WORKDIR /home/user

CMD ['php', '-S', '0.0.0.0:3000']

確認

Docker コンテナを起動したら、chromium-browser が実行できるを確認する。

1
2
$ chromium-browser --version
Chromium 83.0.4103.116

続けて、HTML を取得してみる。

1
2
3
4
5
6
$ chromium-browser --headless --disable-gpu --dump-dom --no-sandbox https://www.yahoo.co.jp/ | head
[0728/074355.597564:WARNING:dns_config_service_posix.cc(341)] Failed to read DnsConfig.
[0728/074355.707217:WARNING:dns_config_service_posix.cc(341)] Failed to read DnsConfig.
<!DOCTYPE html>
<html lang="ja"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><title>
...

OK っぽい。以下はオプションの説明。

--headless
ヘッドレスで動かす
--disable-gpu
いずれなくなる予定のオプション
--dump-dom
取得した document.body.innerHTML を標準出力に表示する
--no-sandbox
サンドボックス外で起動する。セキュリティが低下するけど、Docker コンテナ内ではこれをつけないと動かないっぽい?

PHP からヘッドレス Chrome を操作する

composer.json

1
2
3
4
5
{
"require": {
"chrome-php/chrome": "^0.8.1"
}
}

PHP

Yahoo! から<title>タグに囲まれたテキストを取ってくるサンプル。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
require_once('./vendor/autoload.php');
use HeadlessChromium\BrowserFactory;

$browserFactory = new BrowserFactory('/usr/bin/chromium-browser');

$options = [
'headless' => true,
'noSandbox' => true ,
];
$browser = $browserFactory->createBrowser($options);

$page = $browser->createPage();
$page->navigate('https://www.yahoo.co.jp/')->waitForNavigation();

$pageTitle = $page->evaluate('document.title')->getReturnValue();
echo $pageTitle, PHP_EOL;

$browser->close();