Playwright で Webサイトのスクリーンショットを自動化する方法
Playwright と JavaScript で Webサイトのスクリーンショットを自動化します。1ページの取得、ページ全体、モバイル表示、リトライ付きの URL バッチを扱います。
14分で読めますウェブサイトキャプチャ作成者:Temposhot Team
Playwright は Microsoft が提供するブラウザ自動化ライブラリです。Node.js、Python、Java、.NET から Chromium、Firefox、WebKit を操作できます。この記事では Node.js パッケージを使ってスクリーンショットのスクリプトを作成します。1ページの取得から始めて、失敗時にリトライする URL バッチまで扱います。
Playwright が適したツール
スクリーンショットがより大きな技術プロセスの一部である場合に、スクリプトの準備に見合う価値があります。
- デプロイのたびに CI でキャプチャを実行する
- URL の一覧をサイトマップ、データベース、または API から取得する
- ファイルを固定のフォルダ構成またはストレージバケットに保存する
- スクリーンショットの工程が、テスト、スクレイピング、PDF 生成と並んで動く
画像を必要とする人がコードを書かない場合や、URL の一覧が毎週変わる場合は、ノーコードツールの方が速いことが多いです。そのトレードオフは末尾で扱います。
この記事の実例
例は一つの作業を目標に段階的に組み立てます。サイトのサイトマップに記載された 200 ページを、デプロイのたびにデスクトップサイズとスマートフォンサイズで取得し、失敗したページを報告します。この作業には、スクリーンショットのスクリプトを難しくする要素がすべて含まれています。長い一覧、表示の遅いページ、遅延読み込みの画像、Cookie バナー、ときどき発生するタイムアウトです。
この作業の数値は見積もりです。実際の所要時間は、サイト、ネットワーク、マシンによって変わります。
スクリプトを保守する負担
スクリプトは無料ですが、保守が不要なわけではありません。サイトのリニューアルにより、Cookie バナー、準備完了の判定に使う要素、スクロールするコンテナが変わることがあります。変更のたびに、バッチの一部が失敗します。
Node.js か他の言語か
スクリーンショット API は、4 つの言語バインディングでほぼ同じです。Python の page.screenshot(path=..., full_page=True) は、Node.js の fullPage: true と同じ動作をします。既存のパイプラインで使われている言語を選ぶと、スクリーンショットの工程を周辺のコードと同じリポジトリと CI ジョブに置けます。
この点は計画の段階で見積もります。デプロイのたびに実行するバッチは、保守の負担を短期間で回収できます。年に 2 回だけ実行するバッチは、その間に何かが変わっているため、初回で失敗することが多いです。
スクリーンショットスクリプト用に Playwright をインストールする
mkdir site-shots && cd site-shots
npm init -y
npm install -D playwright
npx playwright install chromium
最後のコマンドは、インストールされた Playwright のバージョンに対応する Chromium のビルドをダウンロードします。以下のスクリプトは .mjs 拡張子を使います。そのため、バンドラーや追加の設定がなくても、トップレベルの await を使えます。
バージョンとブラウザを固定する
Playwright の各リリースは、特定のブラウザビルドを前提とします。playwright パッケージをアップグレードした後は、npx playwright install chromium を再実行します。実行しないと、実行ファイルが存在しないというメッセージで失敗します。
package-lock.json をコミットして、すべてのマシンで同じバージョンをインストールできるようにします。Linux の CI ランナーでは、npx playwright install --with-deps chromium を使うと、ブラウザに必要なシステムライブラリもインストールされます。
screenshot コマンドは、バッチに入れる前に 1 つの URL を確認するのに役立ちます。--device と --wait-for-selector も受け付けるため、表示の遅いページをターミナルからテストできます。
Webサイトのスクリーンショットを 1 枚取得する
// one.mjs
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
await page.goto('https://example.com');
await page.screenshot({ path: 'example.png' });
await browser.close();
node one.mjs で実行します。ブラウザはヘッドレスで起動します。つまり、ウィンドウを開かずにページを描画します。この仕組みは ヘッドレスブラウザ の項目で説明しています。
追加のオプションを指定しない場合、ファイルは 1920×1080 ピクセルのちょうどの ビューポートのスクリーンショット になります。
再現可能なキャプチャにするオプション
変化がないページを 2 回キャプチャすると、同じ画像になるべきです。いくつかのスクリーンショットオプションを使うと、よくあるノイズを取り除けます。途中で止まったアニメーション、点滅するテキストカーソル、マシンごとに異なるピクセル密度です。
await page.screenshot({
path: 'example.jpg',
type: 'jpeg',
quality: 80,
animations: 'disabled',
caret: 'hide',
scale: 'css',
mask: [page.locator('.ad-slot'), page.locator('.live-chat')],
});
mask は、一致する各要素の上に箱を描きます。ローテーションする広告、ライブのタイムスタンプ、チャットウィジェットによって実行のたびに見た目が変わるのを防ぐ、最も簡単な方法です。
空白または誤ったキャプチャをデバッグする
空白の画像、エラーページ、ボット確認が写った画像は、ブラウザが何を見ていたかを確認する必要があります。headless: false と slowMo オプションを指定して起動すると、各ステップを観察できます。または、トレースを記録してトレースビューアで開きます。
トレースには、各操作のページのフィルムストリップと、ネットワークリクエストが表示されます。フィルムストリップではページが完全に描画されているのに、キャプチャが空白になる場合は、フェードインが終わる前にスクリーンショットを取得したことが多いです。animations: 'disabled' で解決します。
ページ全体のスクリーンショットを取得する
fullPage: true を追加すると、スクロール可能なドキュメント全体をキャプチャします。
await page.screenshot({ path: 'example-full.png', fullPage: true });
幅はビューポートの幅のまま、高さがページに合わせて伸びます。非常に長いページは非常に高い画像になり、画像ビューアによっては大きく縮小されます。固定ヘッダーのあるページでは、Playwright がスクロールして複数回に分けて撮るのではなく、ドキュメントを 1 回のパスで取得します。そのため、ヘッダーは上部に 1 回だけ表示されます。
代わりに要素 1 つだけをキャプチャするには、ロケーターに対して screenshot を呼び出します: page.locator('main').screenshot({ path: 'main.png' })。
固定領域をキャプチャする
clip は、ページ上の矩形を CSS ピクセルで保存します。指定は x、y、width、height です。ページの最初の 1,200 ピクセルだけが重要な場合に向いています。例えば、ファーストビューとその次のセクションを含むランディングページです。
await page.screenshot({ path: 'hero.png', clip: { x: 0, y: 0, width: 1920, height: 1200 } });
短いページの下端を超える clip は、ページの端で切り取られます。同じ clip を複数のページで使う場合は、画像の高さを確認します。
対象 URL をバッチスクリプトに入れる
URL はテキストファイルに保存します。こうすると、一覧を変更するためにコードを編集する必要がなくなります。
// batch.mjs
import { readFile, mkdir } from 'node:fs/promises';
import { chromium } from 'playwright';
const urls = (await readFile('urls.txt', 'utf8'))
.split('\n').map((l) => l.trim()).filter(Boolean);
await mkdir('shots', { recursive: true });
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
for (const [i, url] of urls.entries()) {
const name = `shots/${String(i + 1).padStart(3, '0')}.png`;
await page.goto(url, { waitUntil: 'load', timeout: 30000 });
await page.screenshot({ path: name, fullPage: true });
console.log('saved', url, name);
}
await browser.close();
001.png のようなゼロ埋めの名前にすると、Finder で一覧の順序どおりにファイルが並びます。
サイトマップから一覧を作る
実例の作業では、一覧は sitemap.xml から取得します。実行の開始時に一度取得し、すべての loc エントリを抽出します。そうすると、urls.txt を編集しなくても、新しいページがキャプチャされます。
const xml = await (await fetch('https://example.com/sitemap.xml')).text();
const urls = [...xml.matchAll(/<loc>(.*?)<\/loc>/g)].map((m) => m[1].trim());
位置だけでなくページごとに名前を付ける
001.png のようなインデックス名は、サイトマップにページが追加されると壊れます。後続のファイルがすべて 1 つずつずれるためです。URL のパスから作った名前は、実行をまたいでも安定します。例えば /pricing/teams は pricing-teams.png になります。レビューで一覧の順序が重要な場合のみ、インデックスを接頭辞として残します。
末尾のスラッシュの有無のように、2 つの URL が同じ名前になることがあります。実行前に重複する名前を確認し、インデックスを追加して解決します。
大規模なサイトでは、サイトマップが複数のサイトマップを指すインデックスに分割されています。その場合は 1 階層下のリンクをたどり、PDF などのファイルの URL は除外します。ファイルはページとして描画されないためです。
大きなバッチのために並行処理を追加する
数百の URL を 1 ページずつ処理すると、時間がかかります。数ページを並列で実行します。各ページは専用のページオブジェクトを持ちます。メモリを抑えるため、並列数には上限を設けます。
const LIMIT = 4;
const queue = [...urls.entries()];
async function worker() {
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
while (queue.length) {
const [i, url] = queue.shift();
await page.goto(url);
await page.screenshot({ path: `shots/${i + 1}.png`, fullPage: true });
}
await page.close();
}
await Promise.all(Array.from({ length: LIMIT }, worker));
ノートパソコンでは、ワーカー 4 つが妥当な出発点です。上限を引き上げるのは、メモリ使用量を確認した後にします。自分が管理していないサイトでは、リクエストの頻度を抑えるため、上限を低く保ちます。
ワーカー数が実行時間に与える影響
マシンまたはサイトがボトルネックになるまでは、実行時間はワーカー数にほぼ比例して短くなります。ページあたり 4 秒と仮定すると、200 ページは、ワーカー 1 つで約 13 分、4 つで約 3.5 分、8 つではそれより少し短くなります。
その点を超えると、追加のワーカーはメモリ負荷とサーバー負荷を増やすだけです。ワーカー数を増やしたときにスクリーンショットがタイムアウトし始める場合は、サイトまたはマシンが飽和しています。その場合はワーカー数を減らします。
リクエストの頻度を抑える
ページを読み込むたびに、スクリプト、画像、フォントも読み込まれます。そのため、ワーカー 4 つでは 1 つのサーバーに毎秒数十件のリクエストが送られることがあります。自分が管理していないサイトでは、ワーカー数を 1 または 2 にして、ページ間に 1 から 2 秒の待機を入れます。大きなバッチは、サイトの混雑時間帯を避けて実行します。レスポンスが 429 Too Many Requests になったら停止します。
ページとコンテキスト
上のワーカーは 1 つのブラウザコンテキストを共有するため、すべてのページが Cookie を共有します。あるページが同意の Cookie を設定すると、後のページはバナーを飛ばします。その結果、キャプチャの内容がワーカーの順序によって変わります。すべてのキャプチャを初期状態から始める必要がある場合は、各ワーカーに browser.newContext() で専用のコンテキストを与えます。
モバイルまたはタブレットのスクリーンショットを取得する
Playwright には、ビューポート、ユーザーエージェント、タッチ対応、ピクセル比率をまとめて設定するデバイス記述子が用意されています。
import { chromium, devices } from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext({ ...devices['iPhone 13'] });
const page = await context.newPage();
Cookie とストレージを分けるため、デバイスごとに 1 つのコンテキストを使います。タブレットの場合は、同じ devices の一覧から iPad の項目を選ぶか、768×1024 などの単純なビューポートを設定します。
ピクセル比率とファイルサイズ
スマートフォンの記述子は deviceScaleFactor を 2 または 3 に設定します。倍率 3 の場合、幅 390 ピクセルのスマートフォンのビューポートは、幅 1,170 ピクセルの画像を保存します。長いページは高さが数万ピクセルに達することがあります。これは倍率 1 の同じページの 9 倍のピクセル数です。
レイアウトを確認する目的なら、スクリーンショットオプションの scale: 'css' で CSS ピクセル 1 つにつき画像ピクセル 1 つを維持します。スマートフォンの画面で鮮明に見える必要がある場合だけ、デバイスの倍率を維持します。
モバイルレイアウトが読み込まれたことを確認する
サイトによっては、サーバー側のユーザーエージェントでレイアウトを決めるものと、CSS のビューポート幅で決めるものがあります。記述子はその両方を設定しますが、単純な viewport の変更では幅だけが変わります。スマートフォンのキャプチャで横長のデスクトップレイアウトが縮んで表示される場合は、単純なビューポートから完全なデバイス記述子に切り替えます。
Safari に近い描画のための WebKit
多くのスクリプトではデフォルトが Chromium ですが、実機の iPhone のレイアウトは WebKit で描画されます。npx playwright install webkit でインストールし、Safari 固有の描画が重要な場合は webkit.launch() で起動します。
遅延読み込みのコンテンツを扱う
多くのページは、画像がビューポートに入ったときにだけ読み込みます。読み込み直後にページ全体をキャプチャすると、下の方の空の枠が写ることがあります。スクリーンショットの前に、ページを 1 回スクロールします。
await page.evaluate(async () => {
for (let y = 0; y < document.body.scrollHeight; y += window.innerHeight) {
window.scrollTo(0, y);
await new Promise((r) => setTimeout(r, 200));
}
window.scrollTo(0, 0);
});
遅延読み込み の項目で、この現象の仕組みを説明しています。
画像のデコードが終わるまで待つ
スクロールすると画像の取得が始まりますが、画像が届く前にスクリーンショットが実行されることがあります。スクロールの後、ページのすべての画像が完了を報告するまで待機します。
await page.waitForFunction(() =>
[...document.images].every((img) => img.complete && img.naturalWidth > 0)
);
壊れた画像の URL は完了しないため、欠けたファイルがあるページではこの待機がタイムアウトすることがあります。タイムアウトを捕捉してそのままキャプチャを保存し、レポートにそのページを記録します。
適切な待機戦略を選ぶ
waitUntil オプションは、goto がいつ返るかを決めます。
| 値 | 返るタイミング |
|---|---|
load | load イベントが発生したとき。デフォルトです。 |
domcontentloaded | HTML の解析が終わったとき。画像の読み込みより前です。 |
networkidle | 500 ミリ秒以上ネットワークリクエストがないとき。 |
Playwright の公式ドキュメントは、テストでの networkidle の使用を推奨していません。ポーリングや解析を行うページは、ずっと静かにならないことがあるためです。より確実なパターンは、ページの準備完了を示す要素を待つことです。例えば await page.locator('main img').first().waitFor() です。
200 ページに対して 5 秒の固定待機を入れると、ワーカー 1 つでは約 17 分の待機時間が加わります。それでも、6 秒を必要とする 1 ページでは失敗します。ロケーターの待機は、ページの準備ができた時点ですぐに返ります。フォントの場合は await page.evaluate(() => document.fonts.ready) で対応できます。
リトライを追加する
ネットワークは失敗することがあり、一部のページは 1 回タイムアウトした後で正常に読み込まれます。キャプチャを小さなリトライ用の関数で包みます。
async function capture(page, url, path, tries = 3) {
for (let attempt = 1; attempt <= tries; attempt++) {
try {
await page.goto(url, { timeout: 30000 });
await page.screenshot({ path, fullPage: true });
return true;
} catch (error) {
console.warn(`attempt ${attempt} failed for ${url}: ${error.message}`);
}
}
return false;
}
false を返した URL を集めて failed.txt に書き出すと、それらの URL を個別に再実行できます。
変わらない原因はリトライしない
リトライは、タイムアウトや切断された接続に対しては有効です。404 または 500 を返すページには効果がありません。欠けた 20 ページを 3 回ずつリトライすると、数分が無駄になります。page.goto はレスポンスオブジェクトを返します。そのため、response.status() を確認し、ステータスが 400 以上の場合はリトライしないようにします。
レポートには、URL の隣にステータスを記録します。サイトマップの 404 の一覧は、それだけでも有用です。古いサイトマップのエントリを示すからです。
クラッシュしたページから復旧する
重いサイトでは、ページがクラッシュすることがあります。その後、同じページオブジェクトでの goto はすべて例外を投げます。リトライ用の関数内で、エラー後にページを閉じて、次の試行用に新しいページを開きます。ワーカーは動き続け、1 つの不良な URL によってキュー全体が失敗することはなくなります。
実用的なフォルダ構成
site-shots/
urls.txt
batch.mjs
shots/
2026-03-24/
desktop/001.png
mobile/001.png
failed.txt
実行ごとに日付のフォルダを作ると、過去のキャプチャが残ります。デバイスのサブフォルダを作ると、並べて見比べやすくなります。
CI の実行でアップロードするもの
CI では、ジョブの終了後にランナーが削除されるため、スクリーンショットはビルドアーティファクトとしてアップロードする必要があります。日付のフォルダ、失敗一覧、そして各ページの URL、ステータス、ファイル名、所要時間を含む小さな JSON レポートをアップロードします。
1 回の実行と前回の実行を比較する
2 つの日付フォルダは、違いを確認する人がいて初めて役に立ちます。数ページだけの場合は、対応するファイルを Compare Images で開きます。スライダーとピクセル差分の表示があります。数百ページの場合は、Playwright Test の toHaveScreenshot アサーションが、各キャプチャを保存済みの基準画像と比較し、しきい値を超える差分があると失敗します。
ノートパソコンでの実行と CI での実行の差は、フォントが最も多く原因になります。サイトのフォールバックフォントがない Linux ランナーでは、テキストの幅が変わるため、改行位置とページの高さが変わります。
よくある間違い
awaitを付け忘れる。 ファイルが書き出される前にスクリプトが終了します。- クラッシュした後に 1 つのページを使い回す。
gotoが target closed のエラーを投げたら、新しいページを開きます。 - すべてのページで
networkidleに頼る。 到達しないページがあり、その行はすべてタイムアウトします。 - Cookie バナーをキャプチャしてしまう。 ロケーターで同意ボタンをクリックするか、
page.addStyleTagでバナーを非表示にします。 - サイトの利用規約を無視する。 自分が管理していないサイトでは、利用規約と robots のルールを確認し、リクエストの頻度を低く保ちます。
Cookie バナーを確実に閉じる
バナーはサイトごとに異なるため、一覧全体に 1 つのセレクターが通用することはほとんどありません。既知の同意ボタンを短い一覧で試します。各ボタンに 2 秒のタイムアウトを設定します。見つからない場合は、CSS でバナーのコンテナを非表示にします。
const consent = page.getByRole('button', { name: /accept|agree|reject all/i });
if (await consent.first().isVisible({ timeout: 2000 }).catch(() => false)) {
await consent.first().click();
}
同意ボタンをクリックすると、そのコンテキストの以降の Cookie が変わります。ワーカーごとに 1 つのコンテキストを使う場合は問題ありません。コンテキストを共有する場合は、順序によってキャプチャが変わる理由がもう 1 つ増えます。
Playwright のスクリーンショットスクリプトの代替手段
- Puppeteer は、Chrome と Firefox 向けに似た API を提供します。Puppeteer で Webサイトのスクリーンショットを自動化する方法 の記事を参照してください。
- Playwright Test はスクリーンショットを基準画像と比較します。Playwright のスクリーンショットテストガイド を参照してください。
- ホスト型のスクリーンショット API は、ブラウザを代わりに実行し、画像を HTTP で返します。
- Temposhot Website Capture は、URL の一覧をコードなしで Mac 上でローカルに実行します。デバイスは Desktop 1920×1080、Tablet 768×1024、Mobile 390×844 から選べます。範囲は Viewport または Full page を指定できます。最大 10 ブラウザを同時に実行でき、行ごとの Retry でリトライできます。
スクリプトのオプションとノーコードでの対応設定
この記事で扱ったほとんどの設定には、Website Capture で直接対応する設定があります。違いは、その選択を誰が行うかです。コードを書く開発者か、URL の一覧を貼り付けられる人か、です。Website Capture にはスケジューラーとサインインのサポートがないため、CI の実行や、ログインの背後にあるページはスクリプトに残ります。
FAQ
Playwright には表示されるブラウザウィンドウが必要ですか?
いいえ。ブラウザは既定でヘッドレスで起動します。デバッグ中にページを見たい場合は、chromium.launch に headless: false を渡します。
Playwright は PNG ではなく JPEG で保存できますか?
はい。スクリーンショットオプションで type: 'jpeg' と、0 から 100 の quality 値を設定するか、.jpg のファイル名を使います。
ページ全体のスクリーンショットが途中で切れるのはなぜですか?
ドキュメントではなく、内側の要素の中でスクロールするページがあります。その要素はロケーターのスクリーンショットでキャプチャするか、キャプチャの前に CSS の高さを変更します。
Playwright でログインの背後にあるページをキャプチャできますか?
はい。一度サインインし、context.storageState({ path: 'auth.json' }) でセッションを保存します。以降のコンテキストは storageState: 'auth.json' で作成します。このファイルはセッションの Cookie を含むため、バージョン管理に含めないでください。
広告やタイムスタンプが毎回のキャプチャで変わるのを防ぐには?
それらのロケーターを mask オプションに渡します。Playwright は各要素の上に単色の箱を描くため、誤った差分なしに実行結果を比較できます。
CI のスクリーンショットがノートパソコンと違って見えるのはなぜですか?
フォント、ピクセル比率、ロケールは通常異なります。ランナーに同じフォントをインストールし、scale: 'css' を設定して、コンテキストの locale と timezoneId を固定すると、日付と数値の形式が一致します。
参考資料
ノーコードでWebサイトのスクリーンショットを自動化する
Temposhot Website Capture は、貼り付けた URL の一覧を受け取り、Mac 上ですべてのページをキャプチャします。保守するスクリプトは不要です。Webサイトのスクリーンショット自動化 を参照するか、Website Capture のクイックスタート から始めてください。
playwrightwebsite-captureautomationjavascript
このページで使用する用語: full page screenshot, playwright, puppeteer, screenshot api, screenshot automation, viewport screenshot
繰り返し撮影するスクリーンショットを、完成したデータに仕上げます
Temposhotは、タイマーまたはクリックごとに撮影し、結果をPDF、画像、ZIP、MP4で書き出します。
関連記事
- Macでウェブサイトのスクリーンショットを一括で撮影する方法MacでTemposhotを使い、ウェブサイトのスクリーンショットを一括で撮影します。URLリストを貼り付け、デスクトップ・タブレット・モバイルのプリセットを選び、並列撮影を実行して監査やレポート用に書き出せます。
- PuppeteerでWebサイトのスクリーンショットを自動化する方法JavaScriptとPuppeteerでWebサイトのスクリーンショットを自動化します。1ページのキャプチャ、フルページ撮影、URLの一括処理、モバイル表示幅を、再利用可能なスクリプトで扱います。
- ノーコードでWebサイトのスクリーンショットを自動化する方法ノーコードでWebサイトのスクリーンショットを自動化する方法を選びます。URL一覧からのキャプチャ、スクリーンショットAPIを使うオートメーションプラットフォーム、監視ツール、ブラウザーの補助ツールを扱います。
- 2026 年版: Mac 向け Xnapper の代替アプリ比較Mac 向け Xnapper の代替アプリを、繰り返し撮影、スクロールキャプチャと録画、無料の背景、クロスプラットフォームのガイド、標準搭載アプリの観点で比較します。