Playwright録画→Reels自動投稿|全コード付き解説

Playwright活用

※ この記事にはアフィリエイトリンクが含まれています。リンク経由で購入しても読者の皆さんに追加費用は発生しません。収益は本サイトの運営費に充てています。

Playwrightで操作を録画→ffmpegでMP4変換→Instagram Reels APIで投稿。この3ステップで、ブラウザ自動操作のデモ動画をSNSに自動投稿できる仕組みを作りました。実際に投稿したReelsはPlaywrightがInstagramにログインして自己紹介を自動入力する35秒のデモ動画です。

こんな方に読んでほしい記事です:

  • PlaywrightのrecordVideo機能を実用的に使いたい
  • ブラウザ自動操作のデモ動画をSNSで共有したい
  • Instagram Reels APIで動画投稿する実装コードが欲しい
  • 前回のInstagramプロフィール自動設定の続きを追いたい

なぜPlaywrightの録画機能でReelsを作ろうと思ったのか

最初はDALL-E 3でカルーセル画像(図解スライド5枚)を作ってInstagramに投稿しました。結果は散々でした。DALL-E 3は日本語テキストの描画が壊滅的で、図解として全く読めない。

それなら実際の操作画面を見せた方がいい。PlaywrightにはrecordVideoという画面録画オプションがあります。テスト用途で使われることが多い機能ですが、「操作のデモ動画を撮影してSNSに投稿する」というコンテンツ制作ツールとして使えるのでは?と考えました。

Playwrightの画面録画の仕組みと設定コード

Playwrightの録画機能(公式ドキュメント)は、browser.newContext()のオプションで有効化します。特別なライブラリは不要です。

基本的な録画設定

const context = await browser.newContext({
  // モバイルエミュレーション(Instagram用)
  userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0...)",
  viewport: { width: 390, height: 844 },
  isMobile: true,
  hasTouch: true,

  // ここが録画設定
  recordVideo: {
    dir: "drafts/videos",          // 保存先ディレクトリ
    size: { width: 390, height: 844 }, // 録画サイズ
  },
});

これだけで、このcontextで開いたページの操作がすべて動画として記録されます。出力形式はWebM。録画を止めるにはcontext.close()を呼びます。

注意点:context.close()を忘れると動画が保存されない

Playwrightの録画はcontext.close()のタイミングでファイルに書き出されます。browser.close()だけだと動画が不完全になることがあるので、必ず先にcontextをcloseするのがポイントです。

// ✅ 正しい順番
await context.close();  // ← ここで動画が書き出される
await browser.close();

// ❌ これだと動画が不完全になる場合がある
await browser.close();  // contextのcloseがスキップされる

「映える」デモ動画を撮るための工夫

テスト用の録画と、SNS投稿用の録画は要件が全然違います。正直、最初に撮った動画は134秒のうち大半が「Save your login info?」ダイアログの静止画面で使い物になりませんでした。2回目で以下の工夫を入れて35秒のコンパクトな動画に仕上げました。

工夫1:タイピングを1文字ずつ見せる

// 通常のfill()は一瞬で入力される→動画的に面白くない
await field.fill(username);  // ← 一瞬で完了

// type()でdelayを設定すると1文字ずつ入力される→映える
for (const char of username) {
  await field.type(char, { delay: 80 });  // 80msごとに1文字
}

自動化が目に見える瞬間がSNSでは一番インパクトがある。特に「文字が勝手に入力されていく」シーンは再生を止めて見てしまう効果があります。

工夫2:ダイアログは即座にevaluate()で閉じる

// waitForTimeout()で待つと無駄な静止画面が長くなる
// evaluate()で即座に閉じる
await page.evaluate(() => {
  const btns = document.querySelectorAll('div[role="button"]');
  for (const btn of btns) {
    const t = btn.textContent?.trim() || "";
    if (t === "Not now" || t === "後で") {
      btn.click();
      return;
    }
  }
});

ダイアログが表示された瞬間に閉じるので、動画には一瞬だけ映って消える。「あ、ダイアログも自動で閉じてる」という印象を与えつつ、無駄な待ち時間を排除できます。

工夫3:パスワード入力は高速で

パスワードは伏せ字(●●●)で表示されるので、1文字ずつ見せても意味がない。fill()で一瞬で入力します。ユーザー名はゆっくりタイピング、パスワードは瞬時に入力。このメリハリが動画のテンポを良くします。

工夫4:シーンの構成を事前に設計する

今回の35秒動画の構成は以下の通りです。

秒数 シーン 見どころ
0〜3秒 ログインページ表示 Instagramの画面が映る
3〜8秒 ユーザー名タイピング 文字が1文字ずつ入力される
8〜10秒 パスワード+ログイン 瞬時に入力→ボタン押下
10〜18秒 ログイン完了→遷移 ダイアログを自動で閉じる
18〜28秒 自己紹介文をタイピング Bio欄に文字が入力されていく
28〜35秒 完成プロフィール表示 フクロウアイコン+自己紹介が映る

ぶっちゃけ動画制作のプロからすれば素人レベルだと思いますが、「コードで生成した動画」という文脈ではこれで十分なインパクトがあります。

失敗した1回目の動画と改善

1回目に撮った動画は134秒。サムネイルを抽出して確認したところ、67フレーム中50フレーム以上が「Save your login info?」ダイアログの静止画面でした。ダイアログをwaitForTimeout(2000)で待っていたのが原因です。2回目ではevaluate()で即座に閉じる方式に変更し、35秒に短縮。録画する前にシーン構成を設計して、waitの秒数を最小限にするのが鉄則です。

ついでに:Threadsへの投稿も同時に行う

動画と同じ内容をThreadsにはテキスト投稿しました。ここで1つハマりポイントがあって、bashのCLI引数で複数行テキストを渡すとWindows環境で最初の1行しか投稿されない問題が発生。原因はbashの引数処理で改行が切られること。テキストファイル(.txt)に書き出して、ファイルパスを引数として渡す方式に変更して解決しました。

# ❌ 最初の1行しか投稿されない
npx tsx src/publish/threads.ts '1行目
2行目
3行目'

# ✅ ファイル経由なら全文投稿される
npx tsx src/publish/threads.ts post.txt

地味ですが、複数行テキストをCLIで扱うときはファイル経由が安全です。

WebMからMP4への変換——Instagram Reelsの要件

Playwrightの録画出力はWebM形式ですが、Instagram Reels APIはMP4しか受け付けません。ここで登場するのがffmpegです。

ffmpegとは

ffmpegは動画・音声の変換、編集、ストリーミングを行うオープンソースのコマンドラインツールです。YouTubeやニコニコ動画など多くの動画サービスのバックエンドでも使われています。GUIの動画編集ソフトと違い、コマンド1行で変換処理が完結するのでスクリプトとの相性が抜群。今回のようなパイプライン(Playwright録画→変換→アップロード)に組み込むならffmpeg一択です。

インストールは公式サイトからバイナリをダウンロードするか、macOSならbrew install ffmpeg、Windowsならwinget install ffmpegで入ります。

Instagram Reelsの動画要件

項目 要件
形式 MP4(H.264コーデック)
アスペクト比 9:16(縦長)
推奨解像度 1080 x 1920
最大長さ 90秒

ffmpegの変換コマンド

ffmpeg -i recording.webm \
  -vf "scale=1080:1920:force_original_aspect_ratio=decrease,\
       pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black,setsar=1" \
  -c:v libx264 -preset fast -crf 23 \
  -pix_fmt yuv420p -an -movflags +faststart \
  output.mp4

ポイントは3つ。

  • scale=1080:1920:9:16のアスペクト比にリサイズ。force_original_aspect_ratio=decreaseで比率を維持し、padで余白を黒く埋める
  • -pix_fmt yuv420p:Instagramが要求するピクセルフォーマット。これがないとアップロード時にエラーになる
  • -an:音声なし。Playwrightの録画には音声がないので明示的に除外

Instagram Reels APIでの動画投稿——実装コード

変換したMP4をInstagram Reels APIで投稿します。画像投稿と同じく2ステップですが、動画は処理時間が長いので待機が必要です。

Step 1: コンテナ作成

const createRes = await fetch(
  `https://graph.instagram.com/v22.0/${userId}/media`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    media_type: "REELS",       // ← REELSを指定
    video_url: videoPublicUrl, // 公開URLが必要
    caption: "キャプション",
    access_token: token,
  }),
});
const { id: containerId } = await createRes.json();

重要なのはvideo_urlにローカルファイルパスは使えない点。公開URLが必要なので、先にWordPressのメディアライブラリにアップロードしてそのURLを使います。

Step 2: 動画処理を待って公開

// 動画の処理に時間がかかる(30秒程度待つ)
await new Promise(r => setTimeout(r, 30000));

const publishRes = await fetch(
  `https://graph.instagram.com/v22.0/${userId}/media_publish`, {
  method: "POST",
  body: JSON.stringify({
    creation_id: containerId,
    access_token: token,
  }),
});

画像投稿では5秒待ちで十分でしたが、動画は30秒は待つ必要があります。処理が完了していない状態で公開APIを叩くとエラーになります。正直、ステータスをポーリングする方が確実ですが、30秒固定待ちでも今のところ問題なく動いています。

全体のパイプラインと技術スタック

今回構築した「録画→変換→投稿」のパイプラインをまとめます。

Playwright (recordVideo)
  │ WebM出力
  ▼
ffmpeg
  │ MP4変換 (1080x1920, H.264)
  ▼
WordPress REST API
  │ メディアアップロード(公開URL取得)
  ▼
Instagram Reels API
  │ コンテナ作成→公開
  ▼
Instagram Reels として公開 🎬

関連書籍

今回の実装に関連する書籍を紹介します。

まとめ:Playwrightは「テスト」だけのツールじゃない

Playwrightの録画機能はE2Eテストのデバッグ用として紹介されることがほとんどです。でも「ブラウザ操作のデモ動画を自動生成してSNSに投稿する」という使い方は、エンジニアのコンテンツ制作ツールとして正直かなり強い。

  • コードを書くだけで再現性のあるデモ動画が作れる
  • 画面構成やタイミングをコードで制御できる
  • モバイルエミュレーションで9:16の縦動画が自然に撮れる
  • DALL-E 3の図解画像より圧倒的にリアルでインパクトがある

「自動化の過程を見せる」コンテンツは、文章よりも動画の方が伝わる。Playwrightを使っているなら、テストだけでなくコンテンツ制作にも活用してみてください。

進展があればまたこのブログで報告します。

コメント

タイトルとURLをコピーしました