【Node.js】Puppeteerでページ内の動画ファイルを一括ダウンロードする

#JavaScript
にメンテナンス済み
記事のトップ画像

Puppeteerを使って、ページ内にある <video> の動画URLを取得し、Node.jsから一括でダウンロードする方法を紹介します。

Puppeteerはヘッドレスブラウザを操作するため、JavaScriptの実行後にコンテンツが生成されるページでも情報を取得できます。

チェック

動画の多くは著作権で保護されています。ダウンロードは、自分が権利を持つ動画、またはサイトの利用規約で許可されている範囲に限ってください。また、短時間に大量のアクセスをしないよう配慮してください。

動作環境

ツールバージョン
Node.js18 以上(fetch を使用)
Puppeteer20 以上
npm i puppeteer

ソースコード

動画のURLだけをPuppeteerで取得し、ダウンロード自体はNode.jsの fetch で行います。ブラウザを閉じたあとに処理できるため、シンプルに書けます。

import fs from 'node:fs/promises';
import puppeteer from 'puppeteer';

// 保存先(相対パス)
const DOWNLOAD_PATH = 'downloads';

// 接続先
const ADDRESS = 'https://example.com';

const browser = await puppeteer.launch();

try {
  const page = await browser.newPage();
  await page.goto(ADDRESS, { waitUntil: 'networkidle2' });

  // 動画のURLを取得
  const srcs = await page.evaluate(() =>
    Array.from(document.querySelectorAll('video'))
      .map((video) => video.currentSrc || video.src)
      .filter(Boolean)
  );

  console.log(`${srcs.length}件の動画を検出しました。ダウンロードを開始します`);

  await fs.mkdir(DOWNLOAD_PATH, { recursive: true });

  // 動画をダウンロード
  for (const [i, src] of srcs.entries()) {
    const response = await fetch(new URL(src, ADDRESS));
    if (!response.ok) {
      console.error(`${src} の取得に失敗しました (${response.status})`);
      continue;
    }

    await fs.writeFile(`${DOWNLOAD_PATH}/${i}.mp4`, Buffer.from(await response.arrayBuffer()));
    console.log(`${i}.mp4 saved`);
  }
} finally {
  await browser.close();
}

.mjs で保存するか、package.json に "type": "module" を指定して実行してください。

ポイントは次のとおりです。

  • video.currentSrc を使うと、<video src> だけでなく <source> 要素で指定された動画のURLも取得できます
  • new URL(src, ADDRESS) で、相対パスのURLも絶対URLに解決できます
  • 大きな動画をメモリに載せたくない場合は、response.body を stream.pipeline でファイルに書き込む方法もあります

スクロールしないと表示されないページの場合

スクロールして初めて動画が読み込まれるページでは、page.goto のあとにスクロール処理を入れます。

await page.goto(ADDRESS);

// 下へ向かって10回スクロール
for (let i = 0; i < 10; i++) {
  await page.evaluate(() => window.scrollBy(0, 1000));
  await new Promise((resolve) => setTimeout(resolve, 200));
}

動画がvideo要素のsrcに設定されていない場合

遅延読み込みなどで、video 要素に直接 src が設定されていないページもあります。その場合は、開発者ツールでURLがどこに保持されているか(data-src 属性など)を確認し、page.evaluate の中身を調整してください。

動画のタイトルをページ上から取得して、ファイル名に使いたい場合は、page.evaluate の返り値をオブジェクトにします。

return Array.from(document.querySelectorAll('video')).map((video) => ({
  src: video.currentSrc || video.src,
  title: video.closest('article')?.querySelector('h1')?.textContent ?? '',
}));

ストリーミング配信(HLS / DASH)の動画には使えない

video.src が blob: から始まる場合、その動画はHLSやMPEG-DASHなどで分割配信されており、この方法ではダウンロードできません。