はじめに (対象読者・この記事でわかること)

この記事は、Node.js と Puppeteer を利用した自動テストやスクレイピングを行っているエンジニア、もしくはこれから学び始めようとしているプログラマを対象としています。
Puppeteer の page.screenshot() で JPEG 形式の画像を保存しようとした際に、保存後のファイルが「開けません」あるいは「破損しています」と表示されるケースに遭遇したことはありませんか? 本記事を読むことで、以下が理解・実践できるようになります。

  • JPEG が開けない典型的な原因とその根本的メカニズム
  • puppeteer のオプション設定や画像エンコード方式の正しい指定方法
  • 実際のコード例と共に、問題を再現・検証し、確実に正常な画像を取得する手順

この問題は、環境設定やバイナリの互換性に起因することが多く、原因を突き止めるのに時間がかかりがちです。そこで、私が遭遇した事象と解決までの流れを具体的に共有し、同様の障害に直面した際のトラブルシューティングの指針を提供します。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。

  • 基本的な JavaScript/Node.js の文法と npm の使用経験
  • Puppeteer のインストールと簡単な操作(page.gotopage.screenshot など)
  • JPEG 形式の画像が何か、簡単なエンコード/デコードの概念

背景と問題の概要

Puppeteer は Chrome/Chromium の DevTools Protocol を利用してブラウザ操作を自動化できる強力なツールです。その page.screenshot() メソッドは、デフォルトで PNG を出力しますが、type: 'jpeg' を指定すれば JPEG 形式でも取得可能です。

しかし、以下のようなケースで JPEG が正常に開けないことがあります。

  1. 画像が破損している
    Windows のフォトビューアや macOS のプレビューで「画像が破損しています」と表示される。
  2. ファイルサイズが異常に小さい
    実際に保存されたファイルが数バイトしかなく、開けない。
  3. 特定環境(例: Alpine Linux + musl)でのみ発生
    同じコードでも OS や Node のビルド方式で結果が変わる。

この現象は、主に 画像エンコード時のパラメータ設定ミスChromium の実体が正しく起動できていない ことが原因です。特に quality オプションの不正な値や、deviceScaleFactor が 0 に設定されているケースでは、Chromium が内部的に JPEG を生成できず、空のバイナリが書き出されます。

具体的な手順と実装方法

以下では、問題の再現手順と、確実に正常な JPEG を取得するためのベストプラクティスをステップごとに示します。コード例は Node.js v20 系Puppeteer v22 で検証済みです。

ステップ 1 ― 環境の整備と基本的なサンプル

まずは最低限の環境を構築し、正常系のサンプルを走らせてみましょう。

Bash
# プロジェクトディレクトリ作成 mkdir puppeteer-jpeg && cd puppeteer-jpeg # npm 初期化 npm init -y # puppeteer インストール(headless Chrome バイナリも同時に取得) npm i puppeteer@22

index.js に以下を書き込みます。

Js
// index.js const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch({headless: true}); const page = await browser.newPage(); // 任意のページへ遷移 await page.goto('https://example.com', {waitUntil: 'networkidle2'}); // JPEG でスクリーンショット取得(quality は 80 が推奨) await page.screenshot({ path: 'example.jpeg', type: 'jpeg', quality: 80, // 0〜100 の整数 fullPage: true }); await browser.close(); console.log('Screenshot saved as example.jpeg'); })();

ターミナルで実行します。

Bash
node index.js

この段階で生成された example.jpeg正常に開く ことを確認してください。ここがベースラインです。

ステップ 2 ― よくある設定ミスとその検証

次に、問題を引き起こしやすい設定を意図的に変更し、どのような結果になるか確認します。

2‑1. quality に不正な値を入れる

Js
quality: 150 // 0〜100 の範囲外

このまま実行すると以下のようなエラーメッセージが出ます(Puppeteer の内部バリデーション)。

Error: quality should be between 0 and 100

2‑2. type を省略したまま quality を設定する

Js
await page.screenshot({ path: 'bad.jpeg', quality: 80 // type がデフォルトの png になるため無視される });

結果として PNG が保存されますが拡張子が JPEG のため、画像ビューアが破損と判断します。

2‑3. deviceScaleFactor が 0

Js
await page.setViewport({width: 800, height: 600, deviceScaleFactor: 0});

この状態でスクリーンショットを撮ると、内部的に 0 DPI の画像が生成され、ファイルサイズが数バイトになるケースがあります。

ステップ 3 ― 正しい設定で安定させるコツ

以下のポイントを抑えると、環境に依存せずに JPEG が取得できます。

設定項目 推奨値 / 注意点
type 'jpeg' を必ず指定
quality 70〜90 がバランス良好。整数で範囲外はエラーになる
fullPage 必要に応じて true(ページ全体)か false(ビューポート)
deviceScaleFactor 1 以上(2 で Retina 表示)
omitBackground true にすると透過部分が白くなるが、JPEG では透過は不可
encoding デフォルトは binarybase64 が必要なら明示

実装例(安定版)

Js
await page.setViewport({width: 1280, height: 800, deviceScaleFactor: 1}); await page.screenshot({ path: 'stable.jpeg', type: 'jpeg', quality: 85, fullPage: false, omitBackground: false });

ハマった点やエラー解決

問題 1: Docker(Alpine)上で JPEG が破損

  • 原因: glibc が無い Alpine イメージでは Chrome の内部 JPEG エンコーダが期待通り動作しないことがある。
  • 対策: node:20-alpine を使用する場合は apk add --no-cache chromiumapk add --no-cache nss freetype harfbuzz ca-certificates ttf-freefont を追加し、puppeteer.launch({executablePath: '/usr/bin/chromium-browser', args: ['--no-sandbox']}) とする。もしくは debian 系ベースのイメージに切り替える。

問題 2: page.screenshot が Promise を返さずプログラムがハング

  • 原因: headless: false のまま CI 環境で実行し、ディスプレイサーバが起動していない。
  • 対策: puppeteer.launch({headless: true, args:['--no-sandbox','--disable-setuid-sandbox']}) を必ず指定。

問題 3: JPEG が 0KB になる

  • 原因: await page.waitForTimeout(0) のように、ページの描画が完了する前にスクリーンショットを取得。
  • 対策: await page.waitForSelector('body') もしくは await page.waitForNetworkIdle({idleTime: 500}) を入れる。

解決策のまとめ

  1. 必ず type: 'jpeg'quality の整数範囲指定 を行う。
  2. deviceScaleFactor が正しいかビューポート設定を明示的に行う。
  3. Docker 環境では glibc ベースのイメージを使用、もしくは必要なライブラリを追加。
  4. ページロード完了を待つために waitForNetworkIdle / waitForSelector を活用。

上記を実装すれば、Puppeteer で取得した JPEG が「開けない」状態になることはほぼ防げます。

まとめ

本記事では、Puppeteer の page.screenshot() で JPEG が開けない問題の原因と、確実に正常な画像を取得するための設定・実装手順を解説しました。

  • 原因は主に type の未指定、quality の範囲外、deviceScaleFactor の誤設定、そして環境依存(Docker/Alpine)のエンコーダ不具合です。
  • 解決策type: 'jpeg'、適切な quality、正しいビューポート設定、そして必要なら OS ライブラリの追加です。
  • ベストプラクティスとして、ページロード完了を待ち、エラーハンドリングを徹底することで、安定した画像取得が可能になります。

この記事を通して、読者はトラブルシューティングのフローを習得し、実務で Puppeteer を安心して活用できるようになるはずです。次回は、取得した画像を Sharp でリサイズ・最適化する方法を取り上げる予定です。

参考資料