2010ss〜2025ss の
hotsuits.jpトップページを自動で撮影し、1枚のPNG に並べる手順とスクリプトを公開します。
失効URLは Wayback Machine のスナップショットへ自動フォールバックします。
- スクショ取得: Puppeteer (Headless Chrome)
- 画像合成: ImageMagick(
montage) - 動作確認: macOS (Apple/Intel), Node v18/20
TL;DR
# 1) 準備
mkdir -p hotsuits_timeline && cd hotsuits_timeline
npm init -y >/dev/null 2>&1 && npm i puppeteer
brew list imagemagick >/dev/null 2>&1 || brew install imagemagick
# 2) 取得スクリプトを作る
$EDITOR capture_thumbs.mjs # 下のコードをコピペ保存
# 3) スクショ実行
node capture_thumbs.mjs
# 4) 合成(1枚PNGに)
magick montage hotsuits_shots/*.png \
-tile 6x -geometry 480x300+20+50 -background white \
-set label '%t' -font Helvetica -pointsize 20 \
-title "hotsuits.jp TOP (2010ss–2025ss)" \
Hotsuits_Top_Timeline.png
1. 前提
- Node.js: v18 以上(
fetchを使うため) - Homebrew(macOS)
- ImageMagick(
montageコマンドを使います)
Windows: Chocolatey や winget で ImageMagick を入れてください。
openの代わりにstart。
Linux:sudo apt-get install imagemagickなど。
2. 対象サイトと時系列リスト
今回は下記の hotsuits.jp を例にします。
時系列スラグ(2010ss〜2025ss)は配列で定義します。
https://www.hotsuits.jp/
2010ss / 2010aw / 2011ss / ... / 2025ss
3. スクリーンショット取得スクリプト(Puppeteer)
capture_thumbs.mjs を作成して、以下を丸ごとコピペ保存してください。
// capture_thumbs.mjs
import fs from 'fs/promises';
import path from 'path';
import puppeteer from 'puppeteer';
const base = 'https://www.hotsuits.jp/';
const slugs = [
'2010ss','2010aw','2011ss','2011aw','2012ss','2012aw','2013ss','2013aw',
'2014ss','2014aw','2015ss','2015aw','2016ss','2016aw','2017ss','2017aw',
'2018ss','2018aw','2019ss','2019aw','2020ss','2020aw','2021ss','2021aw',
'2022ss','2022aw','2023ss','2023aw','2024ss','2024aw','2025ss'
];
const outDir = 'hotsuits_shots';
const viewport = { width: 1280, height: 900, deviceScaleFactor: 2 };
const delayMs = 2500;
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
/** Wayback Machine の最新スナップショットURLを返す */
async function findWayback(targetUrl) {
try {
const api = `https://web.archive.org/cdx/search/cdx?url=${encodeURIComponent(targetUrl)}&output=json&filter=statuscode:200&collapse=digest`;
const res = await fetch(api);
const data = await res.json();
// data[0] はヘッダ行。2行目以降がレコード。timestamp は 2番目の要素。
if (Array.isArray(data) && data.length > 1) {
const last = data[data.length - 1];
const ts = last[1]; // timestamp
return `https://web.archive.org/web/${ts}/${targetUrl}`;
}
} catch {
// noop
}
return null;
}
async function capture() {
await fs.mkdir(outDir, { recursive: true });
const browser = await puppeteer.launch({
headless: true,
defaultViewport: viewport,
args: ['--no-sandbox','--lang=ja-JP']
});
const page = await browser.newPage();
await page.setUserAgent(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
);
page.setDefaultNavigationTimeout(60000);
for (const slug of slugs) {
const dest = path.join(outDir, `${slug}.png`);
let url = `${base}${slug}/`;
let usedWayback = false;
try {
const resp = await page.goto(url, { waitUntil: 'networkidle0' });
if (!resp || resp.status() >= 400) throw new Error(`HTTP ${resp?.status()}`);
} catch {
const wb = await findWayback(url);
if (wb) {
usedWayback = true;
await page.goto(wb, { waitUntil: 'networkidle0' });
} else {
console.error(`[SKIP] ${slug}: live & wayback not available`);
continue;
}
}
// フォント読み込み(対応ブラウザのみ)+描画安定待ち
await page.evaluate(() => (document.fonts && document.fonts.ready) ? document.fonts.ready : null);
await sleep(delayMs);
// 先頭に戻して撮影(全体が良ければ fullPage: true)
await page.evaluate(() => window.scrollTo(0, 0));
await page.screenshot({ path: dest }); // { path: dest, fullPage: true } にすると縦長キャプチャ
console.log(`[OK] ${slug} ${usedWayback ? '(Wayback)' : ''}`);
}
await browser.close();
}
capture().catch(e => { console.error(e); process.exit(1); });
実行
node capture_thumbs.mjs
- 正常なら
hotsuits_shots/2010ss.png… が並びます - 404/タイムアウトは Wayback Machine に自動フォールバック
- それもなければ
[SKIP]表示
4. 画像を1枚に合成(ImageMagick)
montage でグリッド整列+ラベル付与します。
%t は拡張子を除いたファイル名(=2010ss など)がラベルに入ります。
magick montage hotsuits_shots/*.png \
-tile 6x -geometry 480x300+20+50 -background white \
-set label '%t' -font Helvetica -pointsize 20 \
-title "hotsuits.jp TOP (2010ss–2025ss)" \
Hotsuits_Top_Timeline.png
macOS なら
open Hotsuits_Top_Timeline.png、Windows はstart, Linux はxdg-open。
5. そのまま使えるワンコマンド版(全部自動)
記事を丸ごと1発で動かしたい人向け。
コピペ→Enter で最後に Hotsuits_Top_Timeline.png まで出ます。
bash -c '
set -e
mkdir -p hotsuits_timeline && cd hotsuits_timeline
command -v node >/dev/null || { echo "Node.js v18+ が必要です"; exit 1; }
npm init -y >/dev/null 2>&1
npm i puppeteer >/dev/null 2>&1
if ! command -v magick >/dev/null; then
if command -v brew >/dev/null; then brew install imagemagick; else
echo "ImageMagick を入れてください(magick コマンドが必要)"; exit 1;
fi
fi
cat > capture_thumbs.mjs << "EOF"
import fs from "fs/promises";
import path from "path";
import puppeteer from "puppeteer";
const base = "https://www.hotsuits.jp/";
const slugs = ["2010ss","2010aw","2011ss","2011aw","2012ss","2012aw","2013ss","2013aw","2014ss","2014aw","2015ss","2015aw","2016ss","2016aw","2017ss","2017aw","2018ss","2018aw","2019ss","2019aw","2020ss","2020aw","2021ss","2021aw","2022ss","2022aw","2023ss","2023aw","2024ss","2024aw","2025ss"];
const outDir = "hotsuits_shots";
const viewport = { width: 1280, height: 900, deviceScaleFactor: 2 };
const delayMs = 2500;
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
async function findWayback(targetUrl) {
try {
const api = `https://web.archive.org/cdx/search/cdx?url=${encodeURIComponent(targetUrl)}&output=json&filter=statuscode:200&collapse=digest`;
const res = await fetch(api);
const data = await res.json();
if (Array.isArray(data) && data.length > 1) {
const last = data[data.length - 1];
const ts = last[1];
return `https://web.archive.org/web/${ts}/${targetUrl}`;
}
} catch {}
return null;
}
async function capture() {
await fs.mkdir(outDir, { recursive: true });
const browser = await puppeteer.launch({ headless: true, defaultViewport: viewport, args: ["--no-sandbox","--lang=ja-JP"] });
const page = await browser.newPage();
await page.setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36");
page.setDefaultNavigationTimeout(60000);
for (const slug of slugs) {
const dest = path.join(outDir, `${slug}.png`);
let url = `${base}${slug}/`;
let usedWayback = false;
try {
const resp = await page.goto(url, { waitUntil: "networkidle0" });
if (!resp || resp.status() >= 400) throw new Error(`HTTP ${resp?.status()}`);
} catch {
const wb = await findWayback(url);
if (wb) {
usedWayback = true;
await page.goto(wb, { waitUntil: "networkidle0" });
} else {
console.error(`[SKIP] ${slug}: live & wayback not available`);
continue;
}
}
await page.evaluate(() => (document.fonts && document.fonts.ready) ? document.fonts.ready : null);
await sleep(delayMs);
await page.evaluate(() => window.scrollTo(0, 0));
await page.screenshot({ path: dest });
console.log(`[OK] ${slug} ${usedWayback ? "(Wayback)" : ""}`);
}
await browser.close();
}
capture().catch(e => { console.error(e); process.exit(1); });
EOF
node capture_thumbs.mjs
magick montage hotsuits_shots/*.png -tile 6x -geometry 480x300+20+50 -background white -set label "%t" -font Helvetica -pointsize 20 -title "hotsuits.jp TOP (2010ss–2025ss)" Hotsuits_Top_Timeline.png
'
6. カスタマイズ
-
列数:
-tile 6x→-tile 5xなど -
サムネサイズ:
-geometry 480x300+20+50の480x300を調整 -
ラベル:
-set label '%t'はファイル名。2010 春夏などにしたければ撮影時のファイル名を変えるか、montage前にmogrify -set label "..."を使う -
全ページ縦長:
page.screenshot({ fullPage: true })に変更 -
高解像度:
deviceScaleFactor: 2を 1/3/4 に変更 -
タイトル除去:
-title "..."を外す
7. トラブルシュート
-
TypeError: page.waitForTimeout is not a function
→ 廃止API。上記コードはsleep()(setTimeout)で対応済み。 - 画面が崩れる / 画像が真っ白
→delayMsを 4000–6000 に。waitUntil: "networkidle0"維持。 - 日本語ラベルが文字化け
→-font Helveticaを macOS の日本語フォント(例:-font "Hiragino Sans W3")に変更。 - ImageMagick の警告
→magick -versionでインストール確認。ポリシー制限が強い場合はpolicy.xmlを見直し。
8. マナーと注意
- 対象サイトの 利用規約・robots.txt・トラフィック負荷に配慮してください(今回は数十ページ・1回実行想定)。
- 公開物に使う場合、権利表記や 出典明記 を推奨します。
9. まとめ
- Puppeteer でサクッとスクショ、ImageMagick で一発整列
- 失効URLも Wayback に自動切替
- ブランドやプロダクトの歴史を可視化するのに超便利
気に入ったらスターやLGTMお願いします。
改良版(PDF出力/縦長版/キャプションCSV対応)も需要があれば追記します!