Google Chrome ヘッドレスで遊ぼう！

## 概要

* Windows, Linux版Google Chromeのヘッドレス実行でスクリーンショットを取得。
* RHEL9系Linux(AlmaLinux, MIRACLE LINUX, CentOS, RockyLinux)でBash動く説明。
  * dnf(yum)あればおｋ
* macOSはHomebrewやらNodebrewで色々入れてある前提. 標準ターミナルのZshで動く説明。
* 各環境でスクショやHTMLでPDF帳票欲しかったのでまとめた感じです。

## この記事執筆時点での確認環境[2024/11/07]

* Google Chrome 130.0.6723.117
  * Windows 11 Pro 23H2（64 ビット）
  * macOS 15.1 Sequoia(Mac mini[M1]で確認)
  * AlmaLinux 9.4（どこぞのVPS環境）
* Node.js(npm,npx)
  * npm --version 10.9.0

## Chrome ヘッドレス モード

Chrome for Developers [Chrome ヘッドレス モード](https://developer.chrome.com/docs/chromium/headless?hl=ja) 参照.
`ヘッドレス モードは Chrome 59 以降、Mac と Linux でご利用いただけます。 Windows サポート Chrome 60 で導入されました。`
`Chrome 112 でヘッドレス モードが更新された`
……ということで、2023/04/04にリリースされたGoogle Chrome 112系でheadlessフラグのvalue値指定無しが使えなくなっているようです。

## Chrome 111以前の古いやり方
### Google Chromeバイナリを直接叩く、

* 前提：Windows版Google Chromeをインストール済みであること。
```PowerShell
cd C:\var\temp

# PNG形式で出力
"C:\Program Files\Google\Chrome\Application\chrome.exe" --headless=old --disable-gpu --window-size=1920,1080 --hide-scrollbars --lang=ja-JP --screenshot=C:\var\temp\hoge.png https://www.yahoo.co.jp/
# ->  426519 bytes written to file C:\var\temp\hoge.png

# PDF形式(A4縦)で出力
"C:\Program Files\Google\Chrome\Application\chrome.exe" --headless=old --disable-gpu --window-size=1920,1080 --hide-scrollbars --lang=ja-JP --no-pdf-header-footer --print-to-pdf=C:\var\temp\hoge.pdf https://www.yahoo.co.jp/
# ->  1342161 bytes written to file C:\var\temp\hoge.pdf
```

## Chrome112以降の推奨されたやり方
### Puppeteer（パペッティア）でchrome-headless-shellのバイナリを取得して実行.

 * 前提：node.js[npm]がインストール済みであること。

```PowerShell
npx --yes @puppeteer/browsers install chrome-headless-shell@stable
# Need to install the following packages:
# @puppeteer/browsers@2.4.1
# Downloading chrome-headless-shell 130.0.6723.116 - 105.6 MB [====================] 100% 0.0s
# chrome-headless-shell@130.0.6723.116 C:\var\temp\chrome-headless-shell\win64-130.0.6723.116\chrome-headless-shell-win64\chrome-headless-shell.exe

cd C:\var\temp\chrome-headless-shell\win64-130.0.6723.116\chrome-headless-shell-win64\

# PNG形式で出力
chrome-headless-shell.exe --disable-gpu --window-size=1920,1080 --hide-scrollbars --lang=ja-JP --screenshot=C:\var\temp\hoge.png https://www.yahoo.co.jp/
# -> 442004 bytes written to file C:\var\temp\hoge.png

# PDF形式(A4縦)で出力
chrome-headless-shell.exe --disable-gpu --window-size=1920,1080 --hide-scrollbars --lang=ja-JP --no-pdf-header-footer --print-to-pdf=C:\var\temp\hoge.pdf https://www.yahoo.co.jp/
# -> 1230406 bytes written to file C:\var\temp\hoge.pdf
```

# RHEL系Linuxでも確認.

```Bash
$ cat /etc/redhat-release
AlmaLinux release 9.4 (Seafoam Ocelot)

$ dnf install -y https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm
$ google-chrome --version
Google Chrome 130.0.6723.116

$ which google-chrome
/usr/bin/google-chrome

# とりあえずNoto Sans Japaneseフォント
$ curl -O https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip && unzip NotoSansCJKJP-hinted.zip -d ~/.fonts/
# IPAフォントはお好みで
# curl -O https://moji.or.jp/wp-content/ipafont/IPAexfont/IPAexfont00401.zip && unzip IPAexfont00401.zip -d ~/.fonts/
# フォントキャッシュクリア
$ fc-cache -fv

$ google-chrome --headless=old --no-sandbox --disable-setuid-sandbox --window-size=1920,1080 --hide-scrollbars --lang=ja-JP --screenshot=/tmp/hoge.png https://www.yahoo.co.jp/
# ->  532293 bytes written to file /tmp/hoge.png

$ google-chrome --headless=old --no-sandbox --disable-setuid-sandbox --window-size=1920,1080 --hide-scrollbars --lang=ja-JP --no-pdf-header-footer --print-to-pdf=/tmp/hoge.pdf https://www.yahoo.co.jp/
# ->  1174386 bytes written to file /tmp/hoge.pdf
# Old Headless mode will be removed from the Chrome binary soon. Please use the new Headless mode (https://developer.chrome.com/docs/chromium/new-headless) or the chrome-headless-shell which is a standalone implementation of the old Headless mode (https://developer.chrome.com/blog/chrome-headless-shell).
# headless=oldオプションだからchrome-headless-shell使えぃ！ と促される。

$ cd /usr/local/src
$ npx --yes @puppeteer/browsers install chrome-headless-shell@stable
Downloading chrome-headless-shell 130.0.6723.116 - 112.4 MB [====================] 100% 0.0s
chrome-headless-shell@130.0.6723.116 /usr/local/src/chrome-headless-shell/linux-130.0.6723.116/chrome-headless-shell-linux64/chrome-headless-shell
$ ln -s /usr/local/src/chrome-headless-shell/linux-130.0.6723.116/chrome-headless-shell-linux64/chrome-headless-shell /usr/local/bin/chrome-headless-shell
$ ls -l /usr/local/bin/ | grep "chrome"
lrwxrwxrwx  1 root root    109 Nov  7 11:47 chrome-headless-shell -> /usr/local/src/chrome-headless-shell/linux-130.0.6723.116/chrome-headless-shell-linux64/chrome-headless-shell

# warningが出るけど気にしない。
$ chrome-headless-shell --no-sandbox --disable-setuid-sandbox --window-size=1920,1080 --hide-scrollbars --lang=ja-JP --screenshot=/tmp/hoge.png https://www.yahoo.co.jp/
# -> 531620 bytes written to file /tmp/hoge.png

$ chrome-headless-shell --no-sandbox --disable-setuid-sandbox --window-size=1920,1080 --hide-scrollbars --lang=ja-JP --no-pdf-header-footer --print-to-pdf=/tmp/hoge.pdf https://www.yahoo.co.jp/
# -> 1127228 bytes written to file /tmp/hoge.pdf
ls -l /tmp | grep "hoge"
-rw-r--r-- 1 root root 1127228 Nov  7 11:51 hoge.pdf
-rw-r--r-- 1 root root  531620 Nov  7 11:51 hoge.png
```

# macOS Zshで確認。

* Google Chromeでは動く。chrome-headless-shellバイナリは動かない？

```Zsh
# 標準パスのGoogle Chromeバイナリを実行。
% "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless=old --no-sandbox --disable-setuid-sandbox --window-size=1920,1080 --hide-scrollbars --lang=ja-JP --no-pdf-header-footer --print-to-pdf=/tmp/hoge.pdf https://www.yahoo.co.jp/
% ls -la | grep hoge 
-rw-r--r--@  1 nqj26641  wheel  1877191 11  7 20:48 hoge.pdf

% sudo npx --yes @puppeteer/browsers install chrome-headless-shell@stable
% sudo ln -s /usr/local/src/chrome-headless-shell/mac_arm-130.0.6723.116/chrome-headless-shell-mac-arm64/chrome-headless-shell /usr/local/bin/chrome-headless-shell
% which chrome-headless-shell 
/usr/local/bin/chrome-headless-shell
# icudtl.dat not found in bundle ... NG
```


## Puppeteerで動かす場合.

「A4横」用紙設定で、PDF帳票をChrome Headlessにてブラウザレンダリング出力したい場合。
⇒Google Chrome本体のHeadlessモードに引数を当てるだけではムリ。

Chrome DevTools Protocolを利用した、Node.js「Puppeteer」やPython「Selenium」+WebDriverなどでChrome相当でオプション制御可能なのテストツールで出力が可能となる。
開発者ツール上で可能なフルページスクリーンショットなどにも対応しているので、こちらの方が利活用しやすいと思われる。

```PowerShell
npm --version
10.9.0

# Chromium欲しいのでpuppeteer-coreではなくpuppeteer全体.
npm i --save puppeteer puppeteer-extra puppeteer-extra-plugin-stealth

puppeteer --version
23.7.0
```

では、以下ファイルを作成して実行.
例：`node prtscr.js https://qiita.com/ qiitaScreenShot` のように実行する.


```prtscr.js
/**
 * Puppeteerでスクリーンショット(PNG/PDF)取得.
 * node prtscr.js 【URL】 【ファイル名(拡張子なし)】
 */

if (process.argv.length > 4) {
  console.log('arguments error.');
  return;
}

// 第3引数＝URLを取得.
const TARGET_URL = process.argv[2] ? process.argv[2] : 'https://yahoo.co.jp/';
const FILE_NAME = process.argv[3] ? process.argv[3] : 'tmpPrtScr';

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());

(exports.handler = async(event, context) => {
  const viewportWidth = 1920;
  const viewportHeight = 1080;

  const browser = await puppeteer.launch({
    headless: 'new',
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox'
    ],
    defaultViewport: {
      width: viewportWidth,
      height: viewportHeight
    },
  });
  const page = await browser.newPage();
  page.setViewport({width: viewportWidth, height: viewportHeight})
  await page.setExtraHTTPHeaders({
    'Accept-Language': 'ja'
  });

  const response = await page.goto(TARGET_URL, { waitUntil: 'networkidle2' });
  // await page.waitForNavigation({waitUntil: 'networkidle2', timeout: (1000 * 5)});

  if (response.status() == 200) {
    // PNGスクショの場合.
    await page.screenshot({
      path: FILE_NAME + '.png',
      fullPage: true,
    });

    // PDFの場合. A4横用紙.
    await page.pdf({
      path: FILE_NAME + '.pdf',
      displayHeaderFooter: false,
      format: 'a4', // 小文字
      landscape: true,
      // グレースケール/白黒帳票にはできない。
    });
    const pageTitle = await page.title();
    console.log(TARGET_URL + ' -> ' + pageTitle);

    await page.close();
  }
  await browser.close();
})();

```

---
* 何かあればコメント等ﾖﾛｼｸｵﾈｶﾞｲｼﾏｽ。