Linux Mintで狙った位置を確実に撮るPuppeteerスクリーンショットの術

contents 7
  1. 01 導入/背景
  2. 02 技術的な話
  3. 03 おまけ:ふつうに撮る手順(Linux Mint)
  4. 04 設計上のトレードオフ
  5. 05 実運用での留意点
  6. 06 まとめ
  7. 07 参考文献

導入/背景

ウェブ画面の特定位置を正確にスクリーンショットとして保存したい場面は珍しくありません。UIの差分確認、デザインレビュー、バナーの掲出位置の確認、あるいはサポートのための再現画像作成など、用途は幅広いです。ところが、人手での操作は再現性が低く、ピクセル単位の位置合わせも難しいのが実情です。本稿では、Puppeteerによる自動化と、ブラウザだけで完結する小さな補助ツールを組み合わせ、狙った位置を簡潔に、かつ再現性高く撮影する手順を解説します。日々の運用で役立つ地味な工夫が、後の工数削減に効きます。

技術的な話

Puppeteerとは

PuppeteerはNode.jsでChromeやChromiumブラウザを自動操作するライブラリです。ページを開く、クリック、スクリーンショット撮影などを、プログラムから指示できます。人間の手作業と違って毎回同じ条件で実行できるため、テストや画像生成の自動化でよく使われています。

本節は「コピペでOK」を基本方針とします。以下の順に示します。

  1. HUD表示+クリックでY座標をコピー(ブックマークレット、トグル式)
  2. URLとY座標を指定して撮影するPuppeteerスクリプト(Node.js)

Puppeteerスクリーンショット実行例

こちらは実際にスクリプトを実行し、撮った画像です。

1. HUD表示+クリックでコピー(ブックマークレット、トグル式)

以下をブックマークのURL欄に貼り付けて保存します。ブックマーク実行で左上にHUD(Y/H/VH)が表示され、任意の位置をクリックすると現在のスクロール量(window.scrollY)をコピーします。Shift+クリックで80px差し引き(固定ヘッダー想定)。もう一度ブックマークを押すとHUDと待受けが解除されます。リンク等の既定動作は抑止します。

javascript:(()=>{const M_KEY='__ys_hud_manager__';if(window[M_KEY]){const{hud,onScroll,onResize,onClick}=window[M_KEY];window.removeEventListener('scroll',onScroll);window.removeEventListener('resize',onResize);document.removeEventListener('click',onClick,true);if(hud){hud.remove()};delete window[M_KEY];return};const hud=document.createElement('div');Object.assign(hud.style,{position:'fixed',top:'8px',left:'8px',zIndex:2147483647,padding:'4px 8px',background:'rgba(0,0,0,.75)',color:'#fff',font:'12px/1.4 monospace',borderRadius:'4px',pointerEvents:'none',whiteSpace:'pre'});document.documentElement.appendChild(hud);let lastMsgAt=0;const metrics=()=>%60Y:${Math.round(window.scrollY)} H:${document.documentElement.scrollHeight} VH:${window.innerHeight}%60;const render=()=>{if(Date.now()-lastMsgAt>1200){hud.textContent=metrics()}};const copyText=(t)=>{try{navigator.clipboard.writeText(String(t));hud.textContent=%60Copied Y: ${t}%60}catch(e){prompt('Copy Y:',String(t));hud.textContent=%60Shown in prompt: ${t}%60}lastMsgAt=Date.now();setTimeout(render,1300)};const onScroll=()=>render();const onResize=()=>render();const onClick=(e)=>{e.preventDefault();e.stopPropagation();if(e.stopImmediatePropagation)e.stopImmediatePropagation();const off=e.shiftKey?80:0;const y=Math.round(window.scrollY-off);copyText(y)};window.addEventListener('scroll',onScroll,{passive:true});window.addEventListener('resize',onResize);document.addEventListener('click',onClick,true);render();window[M_KEY]={hud,onScroll,onResize,onClick};})()

安全上の注意/免責

  • ブックマークレットは「開いているページの権限」で動きます。機密情報を含むページ(社内管理画面、銀行、認証中のタブ等)では実行しないでください。
  • 掲載コードは短いので、実行前に必ず全文を自分で確認してください。ブックマークに保存した後も、内容が意図どおりか見直すと安心です。
  • 当スニペットはクリップボードへの書き込みを行います(ブラウザが許可を求める場合があります)。
  • 本コードの利用は自己責任でお願いします。実行によって生じたいかなる損害についても、筆者は責任を負いません。

2. URLとY座標を指定して撮影するPuppeteerスクリプト(Node.js)

次のファイルを shot.js として保存し、npm i puppeteer を行ったディレクトリで実行します。node shot.js <url> <y> [out] の形式で、URLとY座標(px)を指定できます。出力ファイル名を省略すると、YYYYMMDD_HHMMSS_<title>.png で自動命名します。

// shot.js
// shot.js
const puppeteer = require('puppeteer');
const {setTimeout: delay} = require('timers/promises');

// Usage: node shot.js <url> <y> [out]
// - <url>: target URL (required)
// - <y>: scroll position in px (optional)
// - [out]: output filename (optional). If omitted, it becomes
//           YYYYMMDD_HHMMSS_<title>.png based on the page title.

(async () => {
  const argv = process.argv.slice(2);
  if (argv.includes('--help') || argv.includes('-h')) {
    console.log('Usage: node shot.js <url> <y> [out]\n  <url>: target URL (required)\n  <y>: scroll Y in px (optional)\n  [out]: output filename (optional). If omitted, it becomes YYYYMMDD_HHMMSS_<title>.png');
    process.exit(0);
  }

  const urlArg = argv[0];
  const yArg = argv[1] !== undefined ? Number(argv[1]) : undefined;
  const outArg = argv[2];

  if (!urlArg) {
    console.error('URL is required.\nUsage: node shot.js <url> <y> [out]');
    process.exit(1);
  }

  const url = urlArg;
  // outPath will be decided after the page title is available
  let outPath;

  let browser;
  try {
    browser = await puppeteer.launch({
      headless: true,
      args: [
        // CI/コンテナ環境での起動安定化(必要ない環境では無視されます)
        '--no-sandbox',
        '--disable-setuid-sandbox',
      ],
    });

    const page = await browser.newPage();
    await page.setViewport({ width: 1600, height: 1200 });
    await page.goto(url, { waitUntil: 'networkidle2', timeout: 60_000 });

    if (!Number.isNaN(yArg) && yArg !== undefined) {
      // ページの最大スクロール量にクランプしてから移動
      await page.evaluate((y) => {
        const max = Math.max(0, document.documentElement.scrollHeight - window.innerHeight);
        const yy = Math.max(0, Math.min(max, Math.round(y)));
        window.scrollTo(0, yy);
      }, yArg);
    }

    await delay(200);
    // Decide output filename: use arg/ENV or auto-generate: YYYYMMDD_HHMMSS_Title.png
    if (!outPath) {
      const title = (await page.title()) || 'untitled';
      const safeTitle = title
        .replace(/[\n\r\t]/g, ' ')
        .replace(/[\\/:*?"<>|]/g, '')
        .replace(/\s+/g, ' ')
        .trim()
        .replace(/ /g, '_')
        .slice(0, 60);
      const pad = (n) => String(n).padStart(2, '0');
      const d = new Date();
      const name = `${d.getFullYear()}${pad(d.getMonth()+1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}_${safeTitle || 'untitled'}`;
      outPath = outArg || process.env.OUT || `${name}.png`;
    }

    await page.screenshot({ path: outPath, fullPage: false });
    console.log(`Saved: ${outPath}`);
  } catch (err) {
    console.error('shot.js error:', err);
    process.exitCode = 1;
  } finally {
    if (browser) {
      await browser.close();
    }
  }
})();

実行例:

node shot.js https://example.com 1200 example.png

Y座標は前節の手段で取得すると効率が良いです。Puppeteer側では最大スクロール範囲にクランプしてから移動しているため、ページの長さが想定より短い場合でも安全です。

おまけ:ふつうに撮る手順(Linux Mint)

Puppeteerを使わない通常の撮影手順を簡潔にまとめておきます。

  • キーボード

    • PrtScn: 全画面撮影
    • Alt + PrtScn: アクティブウィンドウ
    • Shift + PrtScn: 範囲選択
  • Chrome/Chromium で撮る(DevTools 標準)

    1. F12 または Ctrl+Shift+I で開発者ツールを開く
    2. Ctrl+Shift+P(Run command)→「screenshot」(日本語にしている方はカタカナで)
    3. Full size / Visible / Node から選んで保存
    • ヒント: DevTools を別ウィンドウ(Dock side: undock)にするとページがフル幅のまま撮影できます。
  • コマンドラインから撮る

    • gnome-screenshot(入っていれば)
      gnome-screenshot -d 5 -f ~/Pictures/screenshot.png
      gnome-screenshot -a -d 5 -f ~/Pictures/selected.png
    • Mintの他エディションでよくあるコマンド
      mate-screenshot -d 5 -f ~/Pictures/screenshot.png
      xfce4-screenshooter --fullscreen --delay 5 --save ~/Pictures/xfce4-screenshot.png
      ※ xfce4-screenshooter は —fullscreen / —window / —region のいずれか指定が必須。
  • Chrome/Chromium をヘッドレスで

    google-chrome --headless --disable-gpu --window-size=1600,1200 \
      --screenshot=output.png https://example.com
    chromium --headless --disable-gpu --window-size=1600,1200 \
      --screenshot=output.png https://example.com
    • ブラウザUIやスクロールバーが入らない綺麗なキャプチャ。特定のY位置からの撮影は未対応。
  • 撮影後のトリミング

    • GUI: Pix / GIMP
    • CLI: ImageMagick
      convert screenshot.png -crop 800x600+100+100 cropped.png

設計上のトレードオフ

  • 位置の決め方
    • 固定ピクセル(本稿の<y>)は単純で再現性が高い一方、レスポンシブレイアウトでは崩れに弱いです。
    • 要素基準(CSSセレクタで目的要素へスクロール)に拡張すれば、レイアウト変化に強くなりますが、セレクタの管理コストが増します。
  • 表示の安定化
    • ネットワーク待機をnetworkidle2と短い待機(例: 200ms)で組み合わせて、遅延描画の揺らぎを抑えています。
    • 画像や広告などの外部要素が多いページでは、さらに明示的な待機(特定セレクタのwaitForSelector)が有効です。
  • 実行環境
    • コンテナ/CIでの安定動作のため--no-sandboxを付けています。不要な環境では削除して構いません。

実運用での留意点

  • 画面サイズは要件に合わせて固定(例: 1600x1200)するのが無難です。レビュー文化がある組織では、撮影基準の明文化が効果的です。
  • フォントやレンダリングの差異が差分に影響するため、OSやヘッドレスのバージョンを固定します。
  • 重要なビューはフルページ撮影の併用も検討します。遅延読み込みが強いページでは段階スクロール+複数撮影が安定します。

まとめ

狙った位置のスクリーンショットを再現性高く得るには、位置取得と撮影を分けて設計するのが近道です。ブラウザ側でY座標を素早く取得し、Puppeteerで同一条件のビューポートと待機条件を整えて撮影すれば、手戻りの少ない運用が実現します。小さな自動化の積み重ねが、最終的にはレビュー効率と品質の安定につながります。

ここまでこだわったのは、ただただスクロールバーが入るのが嫌だからという、しょうもない理由が燃料です。特に不揃いなのは好きではなく、4:3の比率は見やすいと思います。美しくあれ。

参考文献

share Twitter Facebook