Windowsタスクスケジューラ + Node.jsでnoteに記事を自動投稿する手順
個人ブロガーが毎日noteを自動更新するための最小実装

noteの定期更新は地味に時間を食う作業ですが、Node.js と Playwright をWindowsタスクスケジューラに登録すれば毎日自動で投稿させられます。私は note (@ai_fukugyo_ryuji) に毎日12時の自動投稿を1年近く運用していて、累計数百記事を無人で公開してきました。この記事では、その実装の最小構成と、最初に踏み抜いた3つの落とし穴を共有します。
必要な構成要素
自動投稿の最小構成は4つのファイルで完結します。第一に Node.js + Playwright の投稿スクリプト、第二に Windowsタスクから起動するための .bat ファイル、第三に投稿対象の記事Markdownファイル、第四に環境変数を入れた .env ファイルです。すべて1つのフォルダにまとめ、約1日で構築できます。
投稿スクリプトの最小実装
Playwright を使った note 自動投稿の核心部分はこのようになります。chromium.launchPersistentContext で永続セッションを維持し、初回だけ手動ログイン、以降はクッキー再利用で無人化します。
// scripts/post-note.mjs
import { chromium } from 'playwright';
import { readFile } from 'node:fs/promises';
import { config as loadDotenv } from 'dotenv';
import { notify } from './discord-notify.mjs';
loadDotenv();
const USER_DATA_DIR = process.env.NOTE_PROFILE_DIR;
const ARTICLE_PATH = process.env.NOTE_ARTICLE_PATH;
async function main() {
const article = JSON.parse(await readFile(ARTICLE_PATH, 'utf8'));
const ctx = await chromium.launchPersistentContext(USER_DATA_DIR, {
headless: true,
args: ['--no-sandbox']
});
const page = await ctx.newPage();
await page.goto('https://note.com/notes/new', { waitUntil: 'networkidle' });
await page.waitForSelector('[contenteditable="true"]', { timeout: 30000 });
await page.locator('input[placeholder*="タイトル"]').fill(article.title);
await page.locator('[contenteditable="true"]').first().fill(article.body);
// 公開ボタンクリック (UI変更で壊れる可能性あり、定期的な見直し必要)
await page.click('button:has-text("公開に進む")');
await page.waitForTimeout(2000);
await page.click('button:has-text("投稿する")');
await page.waitForURL(/note\.com\/.*\/n\//, { timeout: 60000 });
await notify('info', `noteに投稿成功: ${article.title}`, { url: page.url() });
await ctx.close();
}
main().catch(async (e) => {
await notify('error', `note投稿失敗: ${e.message}`);
process.exit(1);
});
初回だけ headless: false で起動して手動ログインを完了させると、USER_DATA_DIR にクッキーが保存され、以降は headless: true でも認証済み状態を維持できます。
バッチファイルとタスクスケジューラ登録
バッチファイルは Windows タスクスケジューラから起動するための薄いラッパーです。重要なのは改行コードと WorkingDirectory の指定で、ここを誤ると exit code 9009 で動きません。
@echo off
rem post-note.bat — 必ず CRLF 改行で保存
rem WorkingDirectory が自動でこのbat の置かれた場所になる場合は cd 不要
cd /d %~dp0
node scripts/post-note.mjs >> logs/note.log 2>&1
このバッチを PowerShell で登録します。ポイントは -WorkingDirectory の明示と、ジッタを入れて毎日同時刻のbot判定リスクを下げることです。
# 管理者PowerShellで実行
$action = New-ScheduledTaskAction \
-Execute 'cmd.exe' \
-Argument '/c C:\AI_WORK\note-bot\post-note.bat' \
-WorkingDirectory 'C:\AI_WORK\note-bot'
$trigger = New-ScheduledTaskTrigger -Daily -At 12:00
$trigger.RandomDelay = (New-TimeSpan -Minutes 20) # ジッタ ±20分
Register-ScheduledTask -TaskName 'Daily_Note_Post' \
-Action $action -Trigger $trigger \
-RunLevel Highest -User $env:USERNAME
踏み抜いた3つの落とし穴
1年運用する中で私がハマった代表的な問題が3つあります。
第1: bat の改行コード LF 問題。VS Code のデフォルト設定で bat を保存すると LF になり、'-' は内部コマンドまたは外部コマンドとして認識されていません エラーが連発します。.vscode/settings.json で "[bat]": { "files.eol": "\r\n" } を設定し、必ず CRLF 保存に強制します。
第2: persistent context のセッション失効。note のセッションは約 30-60日でクッキーが切れ、再ログインが必要になります。私は週1で page.url() を確認し、ログイン画面に飛ばされたら Discord で「再ログイン必要」と通知する仕組みを入れています。
第3: note のUI変更によるセレクタ破綻。button:has-text("投稿する") のような文字列ベースのセレクタは、note 側の文言変更で動かなくなります。月1で実機テストを走らせ、失敗したら即セレクタ更新するルーチンが必須です。私は2026年に2回、note のボタン文言変更で投稿停止を経験しました。
運用してみての所感
Windows タスクスケジューラ + Node.js + Playwright の3点セットは、個人開発の自動投稿基盤として最低限の機能を満たします。月コストは VPS や クラウド関数を使わないため 0円、運用工数は月1のセレクタ点検と再ログイン対応のみです。同じ仕組みで Threads・X・Qiita・Zenn の自動投稿も組めるため、メディア量産戦略の土台になります。
Discord webhook によるエラー通知設計は Discord webhookでエラー通知基盤を作る最短コース にまとめています。AIによる記事生成と組み合わせる構成は Claude + Gemini を組み合わせて記事を量産する設計 を参照してください。