CopyButton

2026年5月31日日曜日

WiFi ルーター買い替え記録:接続台数約30台でも安定するモデルの選び方【WiFi 7 選び方】

マンション1フロア・接続台数約30台の家庭で WiFi ルーターの買い替えを検討した記録です。月に数回ハングアップする6年選手を卒業するにあたって、WiFi 世代の選び方・カタログ速度の落とし穴・最終2択の比較まで全部書きます。同じように「接続台数が増えてきてルーターが不安定」と感じている方の参考になれば。

この記事でわかること

  • 接続台数が多い家庭でルーターがハングアップする本当の原因
  • WiFi 5 / 6 / 6E / 7 の違いと「どの世代を選ぶべきか」の判断基準
  • カタログの「〇〇 Mbps」が1台あたりの速度ではない理由
  • Buffalo WXR-11000XE12 と ASUS TUF Gaming BE9400 の実用的な比較

月に数回のハングアップに限界がきた

使っていたのは TP-Link Archer A10(2018年発売)。購入からおよそ6年経ちます。最近になって月に数回、突然全く通信できなくなるようになりました。

しかも始末が悪いのが、ハング中は管理画面にもアクセスできないこと。ブラウザで 192.168.x.x を開いても無反応。スマホからも当然つながらない。解決策は電源ケーブルを物理的に抜いて強制再起動するだけ。2024年にファームウェアの更新も止まり、今後改善される見込みはありません。

ファームウェア更新が止まった時点で改善の見込みがなくなり、いよいよ買い替えを決断しました。

知らない間に接続台数が約30台になっていた

買い替えを考えてまず試みたのが、接続台数の棚卸しです。数えてみると——

  • スマホ・PC・タブレット:家族4人分。自分は仕事用・プライベート用・開発用と複数台持ち。子供も学校用 PC・個人 PC・スマホをそれぞれ持っており、この時点でかなりの数になる
  • 開発用端末:Claude Code 用 PC、GPU 処理用 PC、Mac、Raspberry Pi 複数台、アプリテスト用 iPhone・Android
  • 家電・IoT:テレビ、レコーダー、AVアンプ、体重計、スマートスピーカー、SwitchBot の電球・温度計・亀のヒーター……

合計すると約30台。子供が大きくなってタブレットやゲーム機が増えたこともあり、気づいたらこの数字になっていました。

Archer A10 はなぜ今まで持ったのか

Archer A10 の公称接続台数は 48台。台数だけ見れば余裕のはずでした。

ではなぜハングアップが起きるのか。推定原因は台数ではなく「処理能力の枯渇」です。

  • 2018年当時のエントリー帯 CPU/RAM が、常時30台近くの接続を安定して捌ける設計になっていない
  • FW 更新停止でメモリリーク等が放置されたまま蓄積している
  • 月複数回のハングアップは「コネクション管理テーブルが消費し尽くされる」典型パターン

つまり台数の問題というより、古い世代のハードウェアが現代の使われ方に追いついていないのが本質です。

WiFi 世代を整理する

買い替えにあたって、まず世代の違いを整理しました。ざっくりまとめると以下の通りです。

世代 規格 周波数帯 多台数への対応
WiFi 5 802.11ac 2.4 + 5 GHz △ 多台数は苦手
WiFi 6 802.11ax 2.4 + 5 GHz ◎ OFDMA で大幅改善
WiFi 6E 802.11ax 2.4 + 5 + 6 GHz ◎ 6GHz 帯で干渉フリー(壁に弱い点に注意)
WiFi 7 802.11be 2.4 + 5 + 6 GHz ◎ MLO(複数帯域同時使用)が目玉

わが家の環境で実際に効く差は WiFi 5 → WiFi 6 の変化です。WiFi 6 で導入された OFDMA という技術が多台数の同時通信を大きく改善します。WiFi 6E はさらに 6GHz 帯を追加し干渉を減らせますが、壁に弱いため設置場所の工夫が必要です。

カタログの「〇〇 Mbps」は1台あたりの速度ではない

ルーターを比べると「AXE11000」「11,000 Mbps」といった数字が目に飛び込んできます。これは全帯域の理論値合計で、1台のデバイスが体験できる速度とは全く別物です。

多台数環境では「ピーク速度」より「全体キャパシティ」で見るのが正しい。Archer A10(約2,600 Mbps)と今回の買い替え候補(約9,000〜11,000 Mbps)では、処理余裕の桁が違います。スペックシートの数字だけで比べる落とし穴にはまらないよう注意が必要です。

最終2択まで絞った過程

条件を整理すると、自然と候補は絞られていきました。

  • WiFi 6 以上(OFDMA 必須)
  • 多台数接続に余裕のあるキャパシティ
  • 予算4万円以内
  • EasyMesh / AiMesh 対応(将来の拡張に備えて)

最終的に残ったのがこの2台です。

機種 WiFi 世代 総キャパシティ メッシュ 発売時期 公称台数 Amazon 実勢価格 FW サポート
ASUS TUF Gaming BE9400(選択) WiFi 7 約9,400 Mbps AiMesh(対応機種多数) 2025年12月 56台 約30,000円
Buffalo WXR-11000XE12 WiFi 6E 約10,800 Mbps EasyMesh(対応機種限定) 2023年4月 60台 約40,000円

TUF Gaming BE9400 を選んだ理由

結論から言うと、ASUS TUF Gaming BE9400 に決めました。

決め手は3点です。

① WiFi 7 + AiMesh の組み合わせが今後も使いやすい
WiFi 7 は MLO(複数帯域の同時使用)が目玉ですが、今の端末では恩恵を受けるには端末更新が必要です。しかし AiMesh の生態系は ASUS の幅広いラインアップに対応しており、後からサテライット追加の選択肢が広い。EasyMesh はオープン規格とはいえ実態は対応機種が絞られます。

② 発売時期の差がサポート期間に直結する
WXR-11000XE12(2023年4月)と TUF BE9400(2025年12月)では発売に2年半の差があります。前のルーターで FW 更新が止まって痛い目を見た経験から、サポート残存期間はかなり重視しました。

③ 1万円の価格差
Amazon 実勢で WXR-11000XE12 は約40,000円、TUF BE9400 は約30,000円です。WXR の 4×4 MIMO・10G WAN という優位点は魅力ですが、宅内が全室 WiFi 運用で現有端末が WiFi 5/6 主体という環境では過剰スペック。その差を1万円で埋めるには至りませんでした。

選外になった機種

今回の比較で検討したすべての機種と除外理由をまとめます。

機種 WiFi 世代 総キャパシティ メッシュ 参考価格 除外理由
ASUS RT-BE92U WiFi 7 約9,700 Mbps AiMesh 約37,600円 TUF BE9400 と同世代・同機能系統なのに約8,000円高い。WAN 10G は現環境では過剰
ASUS RT-AX86U Pro/J WiFi 6 約5,700 Mbps AiMesh 約38,700円 WiFi 7 候補と価格が近いのに1世代古い。選ぶ理由がなくなった
TP-Link Deco XE75 Pro WiFi 6E 約5,400 Mbps OneMesh 約15,800円 価格は魅力だが総キャパシティが他候補の半分以下。約30台の多台数環境には余裕が少ない
TP-Link Archer A10(現行) WiFi 5 約2,500 Mbps なし FW 更新停止・処理限界。今回の買い替え対象

運用方針:まず1台でスタート

設置環境の話をすると、マンション1フロア4部屋で、中央のクローゼットに LAN コネクタを壁面埋め込み済みです。これのおかげでルーターを室内のほぼ中央に置けるのが強みです。

弱点は仕事部屋が一番端にあること。有線 LAN は引いていないため、電波が若干弱い状況です。まずは TUF Gaming BE9400 1台で運用を開始し、電波が足りない場合は AiMesh 対応機をサテライットとして仕事部屋の近くに追加する予定です(ワイヤレスバックホールで対応可能)。

ASUS TUF Gaming BE9400 のパッケージ外観
パッケージ。ロゴもイラストも何もない、シンプルの極み。
開封した ASUS TUF Gaming BE9400 本体、6本のアンテナ
開封するとシンプルな外箱とは対照的に、6本アンテナのカニが登場。
ASUS TUF Gaming BE9400 背面ポート、Gaming port の表示
背面ポート。「Gaming port」と書かれた LAN ポートがあり、ここに繋いだデバイスはパケットが優先的に流れる仕様。

よくある質問

Q. WiFi 7 は今買うべきですか?

A. 値段次第では選択肢に入ります。今回は WiFi 6E の上位機種より安い WiFi 7 ルーターが見つかったため選びましたが、主目的は MLO ではなく、発売の新しさ・AiMesh 対応・価格です。端末が WiFi 7 に揃うまでは WiFi 7 機能そのものの恩恵は限定的です。

Q. WiFi 6E の 6GHz 帯は使ったほうがいいですか?

A. 2.4GHz・5GHz 帯と比べて干渉が少ない反面、壁や床で大きく減衰します。同じ部屋・隣の部屋では威力を発揮しますが、端から端の通信には 5GHz 帯を使うほうが安定する場合があります。端末側の自動バンドステアリングに任せるのが現実的です。

Q. 公称接続台数は信用できますか?

A. 「接続できる台数の上限」と「安定して通信を捌ける台数」は別物です。公称値はアソシエーションの上限であり、常時通信する台数が増えると CPU/RAM の処理限界が先に来ます。特に古い世代のエントリーモデルは、台数が増えると早く限界を迎えます。

Q. メッシュ追加のタイミングはどう判断しますか?

A. 仕事部屋など特定の場所でだけ速度低下・接続切れが続く場合が追加の目安です。まず1台で運用してみて、問題があった場所に限定して中継機を置くのが無駄のない方法です。

※ 本記事の価格・スペックは執筆時点(2026年5月)の情報です。購入前に最新情報をご確認ください。

まとめ

接続台数が増えてルーターが不安定になってきたら、単純な台数スペックより「世代のアップグレードによる処理能力の底上げ」を優先して選ぶのがポイントです。カタログの合算 Mbps ではなく、OFDMA 対応の有無と世代差が多台数環境では効いてきます。

WiFi 7 は端末側も揃わないと目玉機能は使えませんが、価格が WiFi 6E 上位機と逆転してきたケースでは検討する価値があります。発売時期とサポート期間も重要な選択軸です。

この記事が参考になったら X(Twitter)でシェアしてもらえると喜びます。

今回購入・比較検討したルーター

  • ASUS TUF Gaming BE9400 — WiFi 7 / AiMesh 対応 / 2025年12月発売。約30,000円で最新世代を選ぶなら。
  • Buffalo WXR-11000XE12 — WiFi 6E / 4×4×3 = 12ストリーム / 10Gbps 有線ポート。多台数に特化した上位機。

このブログを書いた人のアプリ

本ブログの著者が作った iOS 読書管理アプリ わたしのほんやさん を App Store で公開しています。本棚をシンプルに管理したい方はぜひ。

App Store で見る →

関連記事

※ この記事は Claude Code を使った自動更新を試しています。

2026年5月27日水曜日

HyperFrames を Linux サーバーで動かした構築記録【GPU 不要・日本語フォント豆腐問題の解決まで】

Linux サーバーで動画をプログラマブルに生成したいエンジニア向けです。オープンソースの動画レンダリングエンジン HyperFrames を使い、HTML/CSS/GSAP で書いたアニメーションを headless Chrome + FFmpeg 経由で MP4 に変換する環境を、sudo 権限なし(一般ユーザー)の Ubuntu サーバーに構築した記録を全部書きます。最大の詰まりポイントだった日本語フォント豆腐問題(テキストが□□□になる)の解決策まで含めています。

この記事でできること

  • HTML/CSS/GSAP で書いたアニメーションを MP4 動画に自動変換できる
  • GPU なし・Docker なしで Linux サーバーに動画生成環境を構築できる
  • 日本語テキストが豆腐(□□□)になる問題の原因と解決方法がわかる

使ったもの

  • HyperFrames — HTML/CSS → MP4 変換エンジン(OSS、Apache 2.0)
  • nvm — Node.js バージョンマネージャー(sudo なしでインストール可)
  • Node.js 22 — HyperFrames の実行環境(22 以上が必須)
  • FFmpeg — 動画エンコーダー(静的バイナリで sudo なしインストール)
  • Python 3 — 呼び出し元ラッパースクリプト用

背景

HyperFrames を知ったきっかけは X(Twitter)のタイムラインでした。「GPU なし・Docker なしで HTML を動画に変換できる OSS」という投稿が英語圏のエンジニアを中心にかなり広まっていて、試してみようと思いました。

欲しかったのは SSH 経由で他のスクリプトから MP4 動画を自動生成する仕組みです。GPU を占有したくない、Docker を入れたくない、という縛りの中で HyperFrames は選択肢として合理的でした。HTML/CSS という既存の Web 技術でアニメーションを記述でき、CPU レンダリングで動き、一般ユーザー環境に収まる点が決め手でした。

環境: Ubuntu 22.04、sudo 権限なし(一般ユーザー)。

手順

1. Node.js 22+ のインストール(nvm 経由)

HyperFrames は Node.js 22 以上が必須です。多くの Ubuntu 環境では apt で入る Node.js が古いため、nvm でユーザーレベルにインストールします。

$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
$ source ~/.bashrc
$ nvm install 22 && nvm alias default 22
$ node -v
v22.x.x

詰まりポイント: nvm でインストールした node~/.nvm/versions/node/v22.x.x/bin/ に置かれます。SSH 経由のスクリプト実行では ~/.bashrc が読み込まれないため PATH にこのパスが含まれません。SSH 経由で呼び出すスクリプトでは冒頭に以下を追加してください。

export NVM_DIR="$HOME/.nvm"
source "$NVM_DIR/nvm.sh"

2. FFmpeg のインストール(静的バイナリ)

sudo なしで FFmpeg を入れるため、静的ビルド済みバイナリを取得して ~/.local/bin/ に配置します。

$ mkdir -p ~/.local/bin
$ cd /tmp
$ curl -L https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz \
    | tar xJ
$ cp ffmpeg-*-static/ffmpeg ~/.local/bin/
$ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
$ source ~/.bashrc
$ ffmpeg -version | head -1

3. HyperFrames のインストール

$ npm install -g hyperframes
$ hyperframes --version

4. Chrome Headless のインストール

HyperFrames はフレームのキャプチャに headless Chrome を使います。以下のコマンドで自動ダウンロードします。

$ hyperframes browser ensure

nodeffmpeg が PATH に通った状態で実行する必要があります。which node && which ffmpeg で両方の場所が表示されることを確認してから実行してください。

5. 日本語フォント問題の解決(最大の詰まりポイント)

HTML テンプレートに font-family: 'Noto Sans JP' を指定して動画を生成すると、日本語テキストが豆腐(□□□)になります。HyperFrames のログには「Fetched 1116 font face(s) for "Noto Sans JP" from Google Fonts」と出るのに、です。

原因

Google Fonts は Noto Sans JP を Unicode の範囲(サブセット)ごとに分割して配信します。HyperFrames がキャッシュしていたのは Latin サブセット(81KB)のみで、日本語グリフを含む CJK サブセット(1 ウェイトあたり 4〜5MB)はダウンロードされていませんでした。

headless Chrome はシステムフォントを直接参照するため、TTF をシステムフォントディレクトリに直接インストールするのが解決策です。

解決手順

以下のように curl で TTF を直接取得します。User-Agent をブラウザに偽装しないと woff2 サブセット版が返るため豆腐が解決しません。

$ mkdir -p ~/.local/share/fonts

# User-Agent をブラウザに偽装して TTF を取得(必須)
$ curl -L -A 'Mozilla/5.0 (X11; Linux x86_64)' \
    'https://fonts.gstatic.com/s/notosansjp/v56/-F6ofjtqLzI2JPCgQBnw7HFowAA.ttf' \
    -o ~/.local/share/fonts/NotoSansJP-Regular.ttf

# フォントキャッシュを更新
$ fc-cache -f ~/.local/share/fonts/

# 確認
$ fc-list | grep "Noto Sans JP"

fc-list の出力に NotoSansJP が表示されれば成功です。URL 中の版番号(v56)は変わる場合があります。最新の URL は Google Fonts の「Download family」から取得した zip を ~/.local/share/fonts/ に展開する方法でも対応できます(こちらの方が URL に依存しないため安定です)。

ビフォー・アフター

フォント修正前と修正後を並べました。

修正前 — 日本語テキストが□□□(豆腐)になっている
修正後 — 日本語テキストが正常に表示される

6. Python ラッパースクリプトの設計(オプション)

他のスクリプトから SSH 経由で呼び出す場合は、HyperFrames を薄くラップした Python スクリプトを用意すると便利です。設計のポイントは以下の 3 点です。

  • --prompt "テキスト" を受け取り、内部で HTML コンポジションを生成して HyperFrames に渡す(呼び出し元を HTML スキーマから隔離)
  • スタイル(fade / slide / motion)を引数で切り替え可能にする
  • fcntl.flock で同時実行を排他制御する(複数プロセスから呼ばれても安全)

呼び出し側は「SSH 1 コマンドで動画ファイルが返ってくる」インターフェースだけ知っていればよく、内部の HTML テンプレートが変わっても呼び出し側を修正する必要がありません。

完成システム

呼び出し元スクリプト(SSH)
▼ --prompt "テキスト" --output video.mp4
Python ラッパー(HTML 生成 + ロック)
HyperFrames(Node.js + Chrome + FFmpeg)
MP4 動画(720p / 30fps)

daemon なし、コマンド実行のみ。SSH 1 コマンドで動画ファイルが返ります。

よくある質問

Q. nvm: command not found になります

A. SSH 経由で実行すると ~/.bashrc が読み込まれないため発生します。スクリプト冒頭に source "$HOME/.nvm/nvm.sh" を追加してください。

Q. hyperframes browser ensure が失敗します

A. nodeffmpeg が PATH に通っているか確認してください。which node && which ffmpeg で両方の場所が表示されれば OK です。

Q. 日本語以外のフォント(中国語・韓国語など)も豆腐になります

A. 同様の原因です。対象フォントの完全版 TTF を ~/.local/share/fonts/ にインストールして fc-cache -f を実行してください。

Q. 動画が真っ黒になります

A. Chrome がフレームを描画する前にキャプチャされている場合があります。コンポジションの delay 設定を増やすか、CSS アニメーションの開始タイミングを見直してください。

Q. Node.js 20 でも動きますか?

A. HyperFrames は Node.js 22 以上が公式要件です。nvm install 22 && nvm use 22 で切り替えて使用してください。

※本記事の手順・コードは執筆時点(2026 年 5 月)で動作確認していますが、ライブラリやハードのバージョンが変わるとそのままでは動かない場合があります。動かない場合は コメント欄でお知らせください。

まとめ

HyperFrames を sudo なし Ubuntu サーバーに構築しました。詰まりポイントの本命は日本語フォント豆腐で、Google Fonts のサブセット配信仕様と headless Chrome のシステムフォント参照の組み合わせが原因でした。curl の User-Agent 偽装で TTF を取得してシステムフォントに置くことで解決できます。

この記事が役に立ったら X(Twitter)でシェアしてもらえると喜びます。

HyperFrames で作ったブログプロモーション動画

せっかくなので、このブログ「ON THE HAND」のプロモーション動画を HyperFrames で作りました。過去記事の画像+フェードトランジション+テキストキャプションを組み合わせた 30 秒の動画です。日本語テキストもフォント修正後は正常に表示されています。

ON THE HAND ブログのプロモーション動画(HyperFrames で生成・30 秒)

このブログを書いた人のアプリ

本ブログの著者が作った iOS 読書管理アプリ わたしのほんやさん を App Store で公開しています。本棚をシンプルに管理したい方はぜひ。

App Store で見る →

関連記事

参考

※この記事は Claude Code を使った自動更新を試しています。

2026年5月15日金曜日

ブログ更新を Claude Code で自動化する仕組み【テーマ選定・画像・週次 cron の試行錯誤を全部書く】

毎週コンスタントにブログ記事を出すために、テーマ選定・本文生成・画像連動・自動投稿まで Claude Code に任せる仕組みを 1 日で組み上げた。「LLM が知らない直近トレンド」をどう拾うか、「2 つの Claude Code セッションを git 経由で連携させる」とどう設計したか、「毎週金曜の cron 自動投稿」をどう安定させるか、3 つの試行錯誤の経緯と最終形を全部書く。Claude Code を使うと、これだけの仕組みが 1 日で動くところまで立ち上がる。

ノートPC、手書きメモ、マイコンボード、コーヒーカップが並ぶ作業デスクの水彩イラスト

この記事でできること

  • ブログのテーマ選定を Claude Code で自動化するアプローチがわかる
  • 画像生成のような重い処理を別の Claude Code セッションと git 連携させる方法がわかる
  • cron で headless 実行するときのハマりどころと回避策がわかる
  • 「Claude Code に任せる範囲」と「人間が判断する範囲」の線引きの考え方がわかる

使ったもの

  • Claude Code — Anthropic の CLI 版エージェント。本記事の主役
  • Blogger(投稿先)+ Blogger API v3
  • GitHub Pages — 画像配信
  • Python — テーマ偵察スクリプトと MCP サーバ
  • WSL2 + systemd cron — 毎週金曜 18:00 起動

最初にやったこと: 既存記事を Claude Code に読ませて文体を学習させる

このブログは 2015 年から書いてきて記事が約 80 本ある。新しい記事を AI に書かせるとき、これまでの文体・構成・カテゴリ傾向と揃っていないと 「いつもと別人が書いた感」が出る。検索からたどり着いた読者にも、定期読者にも違和感を与える。

そこで最初にやったのは、既存記事を Claude Code に全部読ませて、文体ガイドとトピック傾向をまとめさせること。具体的にはこうした:

  1. Blogger API(posts.list + posts.get)で全公開記事の HTML をローカルにバックアップ
  2. Claude Code に「あなたが今後この同じ著者として記事を書くために、文体の特徴・よく使う章立て・カテゴリ分布・避けるべき書き方を分析して、再利用できるようにまとめて」と依頼
  3. 出力は memory(Claude Code がプロジェクト固有の知識を持続的に保持する仕組み)に保存

抽出された知見の例:

  • 言い回しの癖: 「最短手順」「全部書く」「詰まりポイント」のような実用記事ボキャブラリ、固有のユーモアの分量感(1〜2 文に収める)
  • 章立てのよく使う形: リード → 使ったもの → 手順 → 詰まりポイント → まとめ → 関連記事
  • 14 のカテゴリ分布: 電子工作 / Raspberry Pi / Arduino / IoT / AI / 3Dプリンタ / 製品レビュー / ルンバ / ガジェット改造 / 音声合成 など
  • 避けるべき書き方: 過剰な絵文字、シリアスすぎて読み手を選ぶ語り口、「〜と思います」の連発
  • 過去 8 年分の記事タイトル全リスト: 「同じテーマを再度書かない」ための重複チェック源

これらが memory に入っていることで、Claude Code は新規記事を書くときに 「過去の自分の延長として」書ける。新規生成記事と過去記事の間の連続性が保たれる。memory はセッションをまたいで残るので、毎回プロンプトに長文の文体ガイドを貼り付ける必要がない。

なぜ自動化したのか

毎週コンスタントに記事を出したい。一方で、毎回ゼロから「最近何が話題か」を調べるのは時間が取れない。

Claude Code に「電子工作系で最近話題のテーマで書いて」と素直に頼むと、LLM の学習時点で知っているテーマから拾ってきてしまう。直近 1 年のトレンドや自分の興味とは微妙にズレる。

解決アプローチは 「直近のトレンド」「自分の興味」「過去記事との重複回避」を構造化した情報として外から流し込むこと。LLM 単体で完結させるのを諦めて、Python スクリプトで事前に下調べし、その結果を Claude Code への入力として渡す。

全体の流れ

毎週金曜 18:00 cron
BlogGen 担当 Claude Code セッション
テーマ偵察 → 記事生成 → 画像連動 → ドラフト投稿
RSS 20サイト + SNS
テーマスコアリング
別マシン Claude Code
画像生成サービス
GitHub Pages
画像配信
Blogger API でドラフト投稿
人間が中身を確認 → 公開ボタン

ハマり 1: テーマ選定 — 「LLM が知らない直近トレンド」をどう拾うか

ここで一番悩んだ。文体は memory で揃えられても、「何について書くか」を決めるのはまた別の問題。

最初の試行: WebSearch だけ → キーワードが偏る

「電子工作系で最近話題のテーマで書いて」と Claude Code に頼んで、WebSearch を数回叩いてもらう運用から始めた。やってみるとキーワードが偏る。「Raspberry Pi」「ESP32」「Home Assistant」のような LLM がよく知っている定番に収束する。直近の新製品(Pico 2 W、XIAO ESP32-S3、SCD41 など)にうまくフォーカスできず、過去にも書いたようなネタが繰り返される。

原因: WebSearch クエリ自体が LLM 知識空間内の単語に依存する。新しい単語は知らないのでクエリに含まれず、結果も新しいトレンドを拾わない、という鶏卵問題。

解決策: 構造化された「直近 1 年のトレンド」を外部から流し込む

国内 10 + 海外 10、計 20 サイトの RSS フィードを Python で定期取得して、直近 1 年分の記事タイトルからキーワードを抽出・スコアリングする research_topics.py を作った。20 サイトの内訳:

国内サイトだけだと国内ネタに偏るので、海外 10 を併用して「日本未紹介の海外先行ネタ」を拾えるようにした。海外で話題になっているのに国内サイトでは未紹介のテーマは「日本語先行紹介」枠として候補入り。

スコアリング式

各キーワードに以下のスコアを付ける:

score = frequency × (1 + cross_source × 0.4) × cross_region_bonus × affiliate_fit × novelty × sns_boost
  • frequency: 直近 1 年でそのキーワードが何記事に出てきたか
  • cross_source: 何サイトで言及されているか(広く話題なら高)
  • cross_region: 国内・海外の両方で言及されていれば +bonus(信頼性が高いトレンド)
  • affiliate_fit: Amazon で購入できる物理製品が紐づくテーマか(マイコン・センサー・部品系は高、純ソフトウェアは低)
  • novelty: 過去記事のタイトル一覧(Blogger API で取得)と照合して、すでに書いたテーマは下げる
  • sns_boost: 後述

SNS シグナル: 「自分も触れたガジェット」を 2 倍ブースト

世間で話題なテーマと、自分が SNS で言及したことがあるテーマは別物。前者だけだと「自分の興味」を反映しないし、自分が試したことがないハードを書くと内容も薄くなる。

そこで XFacebook の公式アーカイブ(個人データダウンロードで取得できる ZIP)を sns_data/ 以下に展開しておき、過去 1 年の自分の投稿テキストからキーワードを抽出。外部 RSS で言及があり、かつ自分も SNS で触れたキーワードはスコアを 2 倍ブースト

結果として上位に来るのは、「世間で盛り上がっている × 自分も触ったことがある」テーマ。経験ベースで深く書ける。

SNS アーカイブは月〜四半期に 1 回手動で再取得して上書きする運用。API スクレイピングではなく公式アーカイブを使うことで、X / Facebook の API 仕様変更で壊れないし、画像も同梱されるので記事の写真素材にも流用できる。

NG ワードフィルタ: 「ガジェットのハック」と「ライフハック」を区別する

もう一つのハマり: 「ライフハック」「生活ハック」のような汎用ワードが上位に紛れ込む。スマホアプリの組合せや時短テクなど、本ブログの読者が期待していない汎用ネタ。

これらは NG ワードとして明示的にフィルタした。「電子工作系の改造ネタ」と「単なる便利ライフハック」を区別する基準を文字列ベースで明示する必要があった。人間にとっては自明でも LLM の判断は揺れるので、ルールを文字列で固定する方が安定する。

22 種類の記事タイプ分類でローテーション

もう一段: 22 種類の「記事タイプ」分類を作った。マイコン+センサー型ばかり連発しないようにするため。例(一部):

説明
Aマイコン+センサー(Pico/ESP32 + I2C 温湿度・CO2 → MQTT 等)
B既製ガジェット改造・API ハック(SwitchBot / Echo / AirTag 等)
C3D プリンター + 機構自作
DAI × エッジ(Ollama on Pi、ローカル音声・画像認識)
Mレトロ・エミュレーション
N時計・タイムキーパー(Word clock、Nixie 時計、E-ink カレンダー)
POS 移行・デュアルブート・サーバ化(Asahi Linux、Proxmox 等)
T開発環境・IDE・ワークフロー(Node-RED、WSL2、Claude Code 等。本記事の型)
他 14 型(ロボット、無線・通信、電源、オーディオ、ストレージ、レビュー、ウェアラブル、自作 PC など)

正規表現でキーワードと型を対応付けた分類器を組んでおき、直近 5 記事でどの型が多いかresearch_topics.py 出力に含める。Claude Code は CLAUDE.md の指示で「直近 5 件のうち 2 件以上が同じ型なら、別の型を選ぶ」運用になっている。マイコン+センサーの記事を 2 週連続で出さないための仕組み。

最終的な出力フォーマット

スクリプトは毎回こんな感じの候補リストを出す(簡略例):

# 直近 5 記事の型分布: A×2, B×3 → A,B 以外を優先

# 投稿候補テーマ Top 8

1. ✨🌐💰 XIAO ESP32-S3 で CO2 モニタを ESPHome から Home Assistant に流す【型 A】
   根拠: 国内 4 サイト × 海外 6 サイト言及 / SNS で 3 回触れた / アフィリエイト適性高

2. 🌐💰 ファミコン互換機を Wipeout 移植機にする【型 M レトロ】
   根拠: 海外 5 サイト言及(国内未紹介)/ アフィリエイト適性中

3. 💰 Asahi Linux で Mac mini を Linux サーバ化する【型 P OS 移行】
   ...

(以下省略)

各候補に ✨ SNS シグナル / 🌐 国内外横断 / 💰 アフィリエイト適性のフラグが付き、根拠と該当する記事タイプも併記される。Claude Code はこの中から、直近の型分布と被らない 1〜3 件を選んで深掘り(Step 1-C のハック手法調査)に進む。

「外部から構造化情報を流し込む × LLM が選ぶ」という分業にしたことで、テーマ選定がそれなりに動くようになった。

ハマり 2: 画像をどう用意するか

記事のカバー画像(OG image)は SNS シェア時のサムネイルに使われるので、ないと CTR が大きく落ちる。LLM 単体では画像生成できないので、外部サービスとの連携が必要。

選択肢を試した: Blogger admin で手動アップロード(API 自動化と相性悪い)、xAI Grok API(月額課金)、ローカル GPU で SDXL を回す(採用)。最終的に GTX 1660 SUPER 搭載 PC に ComfyUI を立ててローカル生成、生成画像は GitHub Pages 経由で配信する構成に落ち着いた。

ローカル GPU 画像生成サーバ自体の構築過程と、参照画像から実機の見た目に寄せたり水彩イラストに変換したりする話は 前記事「ブログ用 AI 画像をローカル GPU で生成する」に詳しく書いた。本記事ではその先 — 「画像生成サービスを進化させたくなったとき、Claude Code をどう使ったか」に絞る。

ハマり 3: 2 つの Claude Code セッションを git で連携させる

これが一番面白かった。記事を書く側(BlogGen)と画像を生成する側(ImgGen)はそれぞれ 別のリポ・別のマシン・別の責務。それぞれに Claude Code セッションが付く。

でも連動はしたい。たとえば「ImgGen 側に --style illustration オプションを増やしたい」と思ったとき、その仕様は呼出側の BlogGen も知っている必要がある。

採った方法: git の仕様書ファイルを「両者が見る contract」にする

BlogGen 担当
記事生成・呼出側スクリプト・仕様書ホスト
ImgGen 担当
別マシン・画像生成サービス本体・実装側
┃ ┃
git 上の仕様書(docs/<feature>_spec.md
両者が見る contract

具体的な連携サイクル:

  1. 段階 1(仕様起票): BlogGen 側のセッションが新機能の引数名・期待動作・ワークフロー JSON 仕様を spec に書いて push
  2. 段階 2(実装): ImgGen 側のセッション(GPU PC 上)がリポを pull、実装、push back
  3. 段階 3(検証): BlogGen 側が pull、実テスト、生成画像の品質確認
  4. 段階 4(フィードバック): BlogGen 側が実測値(推奨パラメータ)を spec に追記、ステータスを ✅ に更新

良かった点:

  • 進行が止まらない: ImgGen 側の実装を待つ間、BlogGen 側は他の検証や記事生成に進められる
  • API ズレが起きにくい: spec が contract として機能するので「引数名が微妙にズレている」が起きにくい
  • 各セッションが自分のリポだけ意識すればよい: 認知負荷が低い
  • 記録が git history に残る: どのフェーズが・いつ・どちらのセッションから動いたか追跡可能

ハマりポイント:

  • 同時編集の上書き事故: 一度、BlogGen 側が spec を push する直前に ImgGen 側が別の修正を push していたのに気づかず、古い SHA で PUT して上書きしかけた → 直後の commit で復元
  • 教訓: gh CLI で spec を更新するときは PUT 直前に必ず gh api repos/.../contents/foo --jq .sha最新 SHA を取り直す
  • 引数名のリネーム時は後方互換 alias を必ず残す(呼出側が壊れる)

ハマり 4: 毎週金曜 cron + 権限ホワイトリスト

毎週金曜 18:00 JST に cron で Claude Code を --permission-mode acceptEditsheadless 実行。指定したプロンプトを 1 回実行して終了する。

最初の試行: cron 起動時に サイレントタイムアウト。原因は権限。Claude Code は対話的セッションでは「この Bash コマンド実行していい?」と都度聞くが、headless 実行ではユーザー入力ができないため、許可されない操作で詰まる。

解決策: .claude/settings.local.jsonpermissions.allow に必要な Bash パターンと WebFetch ドメインを事前に許可登録。「ホワイトリスト方式」で必要な操作を一つひとつ追加していく運用。

運用上の注意: 新しいツール呼出が必要になるたびにホワイトリストの追加が要る。cron ログ(logs/weekly_draft_*.log)を週次で眺めて「許可されなくて詰まった操作」を見つけたら settings.local に追記する。「最初に全部読んで漏れなく追加」より「動かして・壊れて・直す」のサイクルのほうが結局速かった。

例: scp の複合パターン Bash(scp take@host:* *):* がパターン途中にある形だと無効になる。Bash(scp take@host:**) のように :* を末尾に置く形に直す、というローカルなハマりも出てくる。Claude Code の /doctor コマンドで警告が出るので気付ける。

ハマり 5: Claude Code に任せる範囲 vs 人間が判断する範囲

「全部 AI に任せる」のは怖い。一方で「全部人間がやる」のは元の問題(時間が取れない)に戻る。線引きが要る。

採った設計:

フェーズ 担当
RSS / SNS からのトレンド抽出スクリプト
候補テーマからの 1 件選定Claude Code
記事 HTML 本文生成Claude Code
薬機法・景表法 NG ワードチェックClaude Code
参照画像の検索 → 取得Claude Code
AI 画像生成別セッション
GitHub Pages への画像 pushClaude Code
Blogger API でドラフト投稿Claude Code
最終的な公開ボタン人間
事実関係・タイトルのチェック人間

キーは 「ドラフトで止める」こと。Blogger API でドラフト投稿はするが、公開ボタンは押さない。人間が中身を見て、文体・事実関係・タイトルを最終チェックしてから公開する。

これにより「変な記事が出ても可逆」になる。Claude Code が試行錯誤するときの心理的安全性が確保できる。実際、テーマ選定スクリプトの初期版が「ライフハック」を量産していた時期や、画像生成パラメータ調整中も、ドラフトで止めていたおかげで安心して試せた。

よくある質問

Q. ドラフト生成に失敗したらどうなりますか?

A. cron が weekly_draft.sh を起動して Claude Code が走るが、途中で詰まったり権限が足りなかったりするとログにエラーが残ってドラフトは作られない、というだけ。次の金曜にまた走るだけなので、致命的な事故にはならない。

Q. なぜ Claude Code を 2 セッションに分けたのですか?1 セッションで両方できないのですか?

A. 物理的に別マシン(一方は GPU 搭載 PC、一方は普段使いの Linux PC)なので、それぞれの環境に張り付いた Claude Code セッションが触れる範囲が違います。別セッションで動かすことで、それぞれ自分のリポだけ意識すればよく、認知負荷が下がります。git の仕様書を contract にするとこのスタイルが成立します。

Q. 全自動公開はやらないのですか?

A. やりません。「人間が最後に見る」前提にすることで、AI による生成を試行錯誤しやすくなる、という設計です。誤情報・薬機法・景表法などのリスクを完全には自動チェックできないため、最後の関門は人間に残します。

Q. テーマ選定スクリプトはどのくらいのペースで再実行されますか?

A. cron 起動時、つまり週 1 回。RSS と SNS アーカイブから直近 1 年分のキーワード分析をその場で計算するので、結果は毎週変わります。SNS アーカイブは月〜四半期に 1 回、手動で再ダウンロードして上書きする運用です。

Q. 立ち上げにどのくらいかかりましたか?

A. 丸 1 日です。RSS 偵察スクリプト・MCP サーバ・Blogger API 連携・cron 設定・CLAUDE.md(NG ワードや文体ガイド含む)まで、CLI で対話しながら Claude Code が書き上げました。普通なら仕様策定・実装・デバッグで数週間かかる規模の仕組みが、対話するだけで一気に動くところまで進みます。

Q. 文体や記事構成は AI とどうやって揃えるのですか?

A. 過去記事を Blogger API でローカルに取得し、Claude Code に文体の特徴・章立て・カテゴリ傾向を分析させて memory に保存しました。memory はセッションをまたいで残るので、新規記事を書くたびに毎回プロンプトに長文の文体ガイドを貼り付ける必要がありません。Claude Code は「過去の自分の延長」として記事を組み立てます。

※本記事の手順は執筆時点(2026 年 5 月)で動作確認していますが、Claude Code・Blogger API・各種 OSS のバージョン更新によりそのままでは動かない場合があります。動かない場合は コメント欄でお知らせください。

まとめ

Claude Code でブログを自動化するときに効いた設計判断は次の 4 つに集約できる:

  1. 過去記事を AI に読ませて memory に文体・カテゴリ傾向を保存: 「過去の自分の延長として書く」連続性を最初に作る
  2. 「LLM 単体で完結」を諦める: 直近トレンド・自分の興味・過去記事一覧を Python で構造化して外から流し込む。LLM の知識だけで書こうとすると定番に収束する
  3. 2 つのセッションを git の仕様書で連携: 別マシン・別リポをまたぐ作業で「進行が止まらない」「API ズレが起きにくい」という効果が大きい。spec を contract にする発想は、Claude Code 以外の文脈でも応用が効く
  4. 公開のゲートは人間が押す: ドラフトで止めることで「失敗が可逆」になり、AI による生成を試行錯誤しやすくなる。完全自動化を目指さないことが結果的に自動化を進める

そして強調したいのは 立ち上げの速さ。RSS 偵察スクリプト・MCP サーバ・Blogger API 連携・cron 設定・CLAUDE.md(NG ワードや文体ガイドを含む)まで、丸 1 日で Claude Code が書き上げた。普通なら数週間かかる仕様策定・実装・デバッグが、CLI 越しに対話するだけで一気に進む。「やりたいことを言葉で説明する → ほぼ即時に動く実装が出る」サイクルが回せるので、設計判断の試行錯誤コストが劇的に下がる。

立ち上げ後は、毎週金曜の夜にドラフトが 1 本上がってきて、土日に確認して公開、というリズムになっている。完全自動化を目指さず「人間が最後に見る」前提にすることで、安心して任せる範囲を少しずつ広げられる。

この記事が役に立ったら X(Twitter)でシェアしてもらえると喜びます。

関連記事

参考

※この記事は Claude Code を使った自動更新を試しています。

2026年5月11日月曜日

GTX 1660 SUPER で SD1.5 ControlNet 環境を構築した話:VRAM ではなく RAM が詰まった

GTX 1660 SUPER(VRAM 6GB)に SD1.5 + ControlNet 環境を構築した記録。詰まったのは VRAM 不足ではなく「RAM が ~26GB に跳ね上がる OOM」「fp16 の全黒 NaN 画像」「math SDPA 強制で 3.2x 遅延」の 3 点だった。さらに後から「全ステップ計測値がスピルによる誤計測だった」という誤算も発覚した。原因から修正まで全部書く。

この記事でできること

  • VRAM 6GB の GPU で ControlNet が動く SD1.5 構成がわかる
  • GTX 16xx 系(TU116)の fp16 NaN 問題と回避策がわかる
  • sequential_cpu_offload が「VRAM ではなく RAM を食う」仕組みがわかる
  • 最終的な速度: Hyper-SD15 1-step ~5 秒/枚、フル 25-step ~1 分/枚の実測値が得られる

使ったもの

背景:SDXL の ControlNet を 1 時間 13 分で廃止した

もともと別環境(SDXL ベース)で ControlNet を実装しようとしていた。2026-04-30 21:40 に実装が完走したのだが、22:53 には廃止 commit を打っていた。1 時間 13 分。

原因は RAM だった。SDXL で enable_sequential_cpu_offload を使うと VRAM ピークは ~4GB に収まる。しかし、この仕組みではモデル全体が RAM に常駐する。そこに ControlNet(fp32 で ~5GB)を追加すると:

構成 VRAM ピーク RAM 常駐 結果
SDXL fp32 + FaceID ~4GB ~21GB ✅(24GB 環境)
+ ControlNet を追加 ~4GB(変わらず) ~26GB ❌ OOM

VRAM は問題なかった。ControlNet 追加のコストは RAM 側に出る。「VRAM 6GB に収まっているのになぜ OOM?」という不可解さは、sequential_cpu_offload の仕組みを理解しないと解けない。

sequential_cpu_offload の仕組みを整理する

diffusers の enable_sequential_cpu_offload は UNet の ~700 サブモジュールを 1 つずつ VRAM に転送→推論→RAM に戻す、を毎ステップ繰り返す。モデル全体は RAM に常駐し続ける。CPU↔GPU のピンポンが 1 ステップに 700 回あり、Python GIL で 1 コア 100% 張り付く構造だ。

SD1.5 は全体 ~4GB (fp32)。VRAM 6GB に収まるので sequential_cpu_offload が不要になる。これが SD1.5 別環境を立てた動機だった。

詰まった問題 3 つ

① fp16 で全黒 NaN 画像が出る(GTX 16xx 系の既知問題)

5 構成でマイクロベンチを実行した:

構成 step 時間 出力
T1: SD1.5 fp16、CN なし、math SDPA 15.8 s/step 全黒 1224 bytes(NaN)
T3: + ControlNet 1 個、math SDPA 22.6 s/step NaN
T4: + flash + mem-efficient SDPA 7.1 s/step NaN(速くなったが)
fp32 に変更(SDPA デフォルト) 後述 ✅ 正常生成

SDPA の種類を変えても NaN は消えなかった。原因は GTX 1660 SUPER(TU116)が Tensor Core を非搭載であること。attention layer の fp16 softmax で underflow/overflow が起きる。AUTOMATIC1111 が GTX 16xx 向けに --no-half フラグを公式提供しているのと同じ問題だ。fp32 に変えると即解決した。

② math SDPA 強制が 3.2x 遅延の原因だった

T3(22.6 s/step)と T4(7.1 s/step)の差。別環境から流用したコードが原因だった:

# 別環境から引き継いだコード(fp16 NaN 対策として書かれたもの)
torch.backends.cuda.enable_flash_sdp(False)
torch.backends.cuda.enable_mem_efficient_sdp(False)
torch.backends.cuda.enable_math_sdp(True)

このコードを削除して PyTorch のデフォルト自動選択に任せることで 3.2x 改善できる。ただし flash SDPA は fp16/bf16 専用なので、fp32 化すると math kernel に戻る。「強制コードを削除 + fp32 化」が正しい組み合わせだ。

③ 全計測値がスピルによる誤計測だった(後から判明)

環境構築後、vram_peak_mb メタデータを生成画像に出力するようにして計測したところ、fp32 + ControlNet 1 個で VRAM ピークが 6,726 MB に達していた。GTX 1660 SUPER の WSL2 実効上限は ~6,100 MB。

全部スピルしていた。初期の計測値(13.7 s/step / フル 25-step 5分55秒)は全部 WSL2 がホスト RAM にスピルした状態での値だった。

対策は enable_model_cpu_offload(モデル単位、5〜10 回の CPU↔GPU 入れ替え):

構成 --offload none(スピルあり) --offload model(現行 default) 改善率
フル 25-step + 1 CN 584.7s / VRAM 6,726 MB 61.5s / VRAM 5,298 MB 9.5x
Hyper-SD15 4-step + 1 CN 62.9s / VRAM 6,680 MB 11.1s / VRAM 5,146 MB 5.7x
Hyper-SD15 1-step 32.4s / VRAM 6,680 MB 4.9s / VRAM 5,106 MB 6.6x
Multi-CN (2 個) 25-step 884.6s / VRAM 8,124 MB 680.1s / VRAM 6,694 MB 1.3x(まだスピル)

1 CN 構成では VRAM が 5.1〜5.3GB に収まりスピルなし → 9.5x の高速化。Multi-CN(2 個)は --offload model でも 6.7GB でスピルが残るため改善が限定的だ。

SD1.5 の低 step 蒸留:SDXL Lightning に相当するものは?

SDXL で使っていた Lightning は SD1.5 版が存在しない。ByteDance は SDXL 専用にしかリリースしていないからだ。SD1.5 の対応手法:

  • LCM-LoRA--lcm): 4-step。~10 秒/枚
  • Hyper-SD15 CFG-distilled--hyper {1,2,4,8}): 1-step で ~5 秒。negative prompt 無効
  • Hyper-SD15 CFG-preserved--hyper-cfg {8,12}): 8-step で ~4.5 分。negative prompt 有効、過飽和しにくい

ポーズ探索は --hyper 1(5 秒/枚)で大量に回し、人物写真の仕上げは --hyper-cfg 8(negative prompt 有効)が使いやすい。

2 環境の役割分担(確定版)

SD1.5 環境 SDXL 環境
主な用途 ポーズ・構図の探索 最終出力・FaceID portrait
速度(フル 25-step) ~1 分/枚(61.5s 実測) ~16 分/枚
速度(低 step 蒸留) Hyper 1-step ~5 秒 Lightning 8-step ~6-7 分
解像度 512×768 (native) 1280×720 / 832×1216
ControlNet OpenPose / Canny / Depth ✅ ❌(RAM 制約で廃止済み)

推奨ワークフロー: SD1.5 で --hyper 1(5 秒/枚)を使ってポーズ・構図を大量に試し、確定したら SDXL 環境に参照画像ごと渡して最終出力を得る。SD1.5 の OpenPose 骨格画像は SDXL の ControlNet モデルと latent space が違うので直接は使えないが、元の参照画像を IP-Adapter 入力として渡す形で橋渡しできる。

よくある質問

Q. GTX 1660 SUPER で fp16 を使えないか?

A. 現状は fp32 一択です。TU116 は Tensor Core 非搭載で attention layer の fp16 softmax が underflow/overflow します。AUTOMATIC1111--no-halfComfyUI--force-fp32 と同根の既知問題です。RTX 3060 以降(Ampere)なら fp16 が使えて flash SDPA で 1-2 s/step が期待できます。

Q. SDXL に ControlNet を追加する方法はないか?

A. FaceID を使わない構成(txt2img + ControlNet のみ)なら RAM ~19GB で動作可能です。FaceID と ControlNet の同時利用は RAM ~26GB を超えるため SD1.5 に分担するのが現実的な選択です。

Q. Multi-ControlNet(2 個同時)はどれくらい時間がかかる?

A. --offload model でも VRAM 6.7GB でスピルが残るため ~11 分/枚です。1 個の時と比べて改善が限定的なのはスピルが完全には解消しないからです。

※本記事の手順・数値は 2026 年 5 月時点の環境(diffusers 0.38、GTX 1660 SUPER、WSL2 + Ubuntu 22.04)で確認しています。ライブラリや GPU ドライバのバージョンが変わると動作が変わる場合があります。動かない場合はコメント欄でお知らせください。

まとめ

GTX 1660 SUPER(VRAM 6GB, RAM 24GB)に SD1.5 + ControlNet 環境を構築した。3 つの落とし穴(fp16 NaN、math SDPA 強制遅延、VRAM スピルによる誤計測)を経て、最終的に Hyper-SD15 1-step ~5 秒/枚・フル 25-step ~1 分/枚が出る構成になった。SDXL 環境(16 分/枚)との使い分けでポーズ探索を高速化できる。

この記事が役に立ったら X でシェアしてもらえると喜びます。

このブログを書いた人のアプリ

本ブログの著者が作った iOS 読書管理アプリ わたしのほんやさん を App Store で公開しています。本棚をシンプルに管理したい方はぜひ。

App Store で見る →

参考

※この記事は Claude Code を使った自動更新を試しています。

2026年5月10日日曜日

X API を Python で使う【2026年版・無料廃止・認証の罠・tweepy v1.1+v2 使い分け】

この記事は Python で X(旧 Twitter)API に自動投稿を組み込もうとしている開発者向けです。2026年5月時点の X API は無料プランが廃止されており、認証まわりの罠が2つある。実際にハマった体験をもとに、tweepy v1.1 と v2 の使い分けまで手順を全部書く。

この記事でできること

  • Python + tweepy で X に自動投稿(テキスト+画像)できる
  • 2026年の X API 料金体系(無料プラン廃止・従量課金)がわかる
  • 401・402 エラーで詰まったときの対処法がわかる
  • 月のコストをざっくり試算できる

使ったもの

  • tweepy(Python X API ラッパー)
  • Python 3.11+
  • X Developer Portal アカウント(要クレジットカード登録)

X API の現状(2026年5月)

結論から言うと、無料プランは存在しない

以前は Free tier があり「個人ブログのボット程度なら無料でいける」という記事が多かった。が、現在 X Developer Portal を開くと Basic プランへの登録しか選べず、従量課金が走る。

料金体系

  • URL 付きツイート 1 件 = 0.20 USD(URL なしは別体系・安い)
  • 月次 Spend Cap はデフォルトで無制限。設定しないと青天井でチャージされ続けるので危険
  • 上限に達すると次のサイクル開始まで API がブロックされる

Spend Cap を手動で設定しておくことを強く推奨する。設定しないと使いすぎても自動では止まらない。

実際に Spend Cap を設定後、テスト投稿 4 件目で上限に達し以下のエラーが出た:

Your enrolled account has reached its billing cycle spend cap.
API requests will be blocked until the next cycle begins on 2026-06-10.

Spend Cap は Developer Console → Billing → Spend limit から設定・変更できる。上限に達しても即時引き上げ可能。

ハマりポイント 1:401 の罠(初期設定後のトークン再作成)

アプリを最初から「Read and Write」で作成したにもかかわらず、API 呼び出しで 401 が返ってきた。Developer Portal 上はトークンが有効に見えるのに、実際のリクエストが弾かれる。

原因は特定できなかったが、初期設定直後は Access Token & Secret を一度 Regenerate(再作成)すると解消する

対処

  1. Developer Portal → App → Keys and tokens
  2. 「Access Token & Secret」の「Regenerate」をクリック
  3. 新しいトークンをコードに反映

初期セットアップ後に 401 が出たら、まずトークンを再作成してみるのが最短の解決策。X のドキュメントにはこの手順が目立つ形で書かれていないため、詰まりやすい。

ハマりポイント 2:402 の罠(クレジット未入金)

トークン再発行後に再度 API を叩いたら、今度は 402 が返ってきた:

Your enrolled account does not have any credits to fulfill this request.

無料プランがないので、クレジット残高ゼロだとリクエストが弾かれる。Developer Console でクレジットカードを登録して最小額チャージすれば解消する。

実装:tweepy v1.1 と v2 の使い分け

現時点の tweepy で画像付きツイートを投稿するには、2 つの API バージョンを同時に使う必要がある

  • ツイート投稿:tweepy v2 Client(OAuth 2.0)
  • 画像アップロード:tweepy v1.1 API(v2 には media upload が存在しない)
import tweepy

# ---- 画像アップロード(v1.1)----
auth = tweepy.OAuth1UserHandler(
    "{API_KEY}", "{API_SECRET}",
    "{ACCESS_TOKEN}", "{ACCESS_TOKEN_SECRET}"
)
api = tweepy.API(auth)
media = api.media_upload(filename="/path/to/image.png")
media_id = str(media.media_id)

# ---- ツイート投稿(v2)----
client = tweepy.Client(
    consumer_key="{API_KEY}",
    consumer_secret="{API_SECRET}",
    access_token="{ACCESS_TOKEN}",
    access_token_secret="{ACCESS_TOKEN_SECRET}"
)
response = client.create_tweet(
    text="記事タイトル\nhttps://example.com/your-post",
    media_ids=[media_id]
)
print(response.data["id"])

v2 だけで完結させようとすると media_upload が見つからずエラーになる。v1.1 で画像をアップして media_id を取り、それを v2 の create_tweet に渡すのが現時点での正解だ。

コスト試算

URL 付きツイート 0.20 USD/件として:

  • 週 3 投稿 × 4 週 = 月 12 件 → 2.40 USD/月
  • 週 3 投稿 × 5 週 = 月 15 件 → 3.00 USD/月

月 3 USD は「安い」とは言い切れない。X への自動投稿は新しいコンテンツを生み出すわけでも、投稿以外の機能を使えるわけでもない。あくまで「既存のブログ記事を X 上でリーチを広げるための配信コスト」だ。

この費用が見合うのは、X 経由の流入がアフィリエイト収益や広告収益に繋がるケースに限られる。個人ブログで収益化していない場合、月 3 USD を垂れ流し続けるだけになる可能性がある。「X に自動投稿したい」という動機だけで導入する前に、自分のブログに収益経路があるかどうかを確認しておくのが現実的だ。

毎日投稿するような用途だと月 20〜30 USD になるので特に注意。

関連アイテム

  • Raspberry Pi 5 4GB(Amazonで確認)— 自動投稿を 24 時間稼働させたいなら低消費電力の常時稼働マシンとして

よくある質問

Q. 無料で X API を使う方法はありますか?

A. 2026年5月時点では Free tier が存在しないため、クレジットカード登録と課金が必須です。従量課金なので月の投稿数を絞れば数ドル以内に収まります。

Q. 401 エラーが消えません

A. 初期設定後に 401 が出ることがあります。Developer Portal → App → Keys and tokens で Access Token & Secret を「Regenerate」してください。権限変更の有無に関わらず、再発行すると解消します。

Q. 画像なしのテキストツイートも 0.20 USD かかりますか?

A. 画像の有無は料金に影響しません。料金が変わるのは URL の有無です。URL なしのツイートは URL 付き(0.20 USD/件)より安い課金体系になっています。

Q. tweepy 以外のライブラリを使えますか?

A. requests で直接 OAuth を組んでも動きますが、tweepy を使うと認証まわりのボイラープレートを省けます。現時点では tweepy が最もメンテされているため推奨します。

※本記事の手順・コードは執筆時点(2026 年 5月)で動作確認していますが、X API の料金体系・tweepy の仕様は変更される可能性があります。動かない場合は コメント欄でお知らせください。

まとめ

2026年の X API は実質有料。「無料でボットを動かす」のは過去の話になった。ただし月数件の投稿なら数ドルで収まる。認証でハマる罠は 2 つあって、どちらもドキュメントで見つけにくい。tweepy で画像付き投稿するには v1.1 と v2 を組み合わせる必要があるのも現状の制約だ。

この記事が役に立ったら X(Twitter)でシェアしてもらえると喜びます。

このブログを書いた人のアプリ

本ブログの著者が作った iOS 読書管理アプリ わたしのほんやさん を App Store で公開しています。本棚をシンプルに管理したい方はぜひ。

App Store で見る →

関連記事

参考

※この記事は Claude Code を使った自動更新を試しています。

Text to Speech が OS の音声をミュートする問題を直した話【iOS audio session 衝突・2026年版】

この記事は iOS アプリ開発者向けです。7年前のアプリを近代化したあと、AdMob・カメラ・Text to Speech が audio session を奪い合って「起動直後に音楽がミュートされる」「読み上げ後に音楽が戻らない」が同時発生した。原因は iOS の Text to Speech が shared session を deactivate しない Apple 既知バグ(FB14444620)で、ワークアラウンド1行で解決。Build 19〜29 にわたる 11 回の試行錯誤を全部書く。

わたしのほんやさん App Store 掲載画像

この記事でわかること

  • AdMob・カメラ・音声読み上げが audio session を奪い合う構造と解決策
  • Apple 既知バグ FB14444620(音声読み上げが shared session を deactivate しない)のワークアラウンド
  • コードを直しても実機を再起動しないと確認できなかった話

前提:何を直したアプリか

わたしのほんやさんは2017年リリースの iOS 読書管理アプリ。v3.0.0 でコードベースを Xcode 26 / Swift 6 / SPM に全面移行したあと、v3.1.0 では Amazon アフィリエイト URL の現行化・AdMob 再有効化・Firebase Analytics 追加という3本立ての小規模リリースを予定していた。

実機で AdMob の動作確認をしようとしたところ、7年間ほぼ触っていなかった音声読み上げ・カメラまわりの問題が一気に露出した。結果として Build 19〜29 まで 11 サイクルを回すことになった。Build 19〜24 でカメラ・権限まわりの問題を片付け、Build 25〜29 が audio session との格闘だった。

audio session の三つ巴

症状は2つ同時に出た。

  • 起動直後にシステムで再生中の音楽がミュートされる
  • バーコードスキャンで本のタイトルを読み上げた後、音楽が再開しない

登場人物は3つ。全員が同じ AVAudioSession.sharedInstance() を取り合っている。

コンポーネント audio session への干渉
GADMobileAds.start()(AdMob) SDK 内部で .soloAmbient に上書きする(2020年代 AdMob の既知挙動)
AVCaptureSession(カメラ) デフォルトで audio session を自動設定しようとする
AVSpeechSynthesizer(読み上げ) speak() のたびに shared session をアクティブ化するが、終了後に deactivate しない

試行錯誤の記録(Build 25〜29)

Build 試した対処 結果
25 AVCaptureSession.automaticallyConfiguresApplicationAudioSession = false 効果なし
26 起動時に .ambient + audioSessionIsApplicationManaged = true を AdMob 前に設定 効果なし
27 didFinishsetActive(false, .notifyOthersOnDeactivation) 効果なし
28 speak() 前に shared session を事前アクティブ化 逆効果(症状悪化)
29 synthesizer.usesApplicationAudioSession = false 解決

原因:Apple 既知バグ FB14444620

Apple の公式ドキュメントは「setActive(false, .notifyOthersOnDeactivation) を呼べば他アプリが再開する」と書いている。これは正しい。しかし AVSpeechSynthesizer自分が session をアクティブ化した事実を覚えておらず、終了後に deactivate を呼ばない。公式ドキュメントに記載はないが、Apple Developer Forums で Apple エンジニアが認め、ワークアラウンドを提示している既知問題(FB14444620)だ。

解決策:synthesizer を shared session から切り離す

// synthesizer が shared audio session を触らないようにする
synthesizer.usesApplicationAudioSession = false

// 読み上げ終了後に他アプリへ「再開してよい」シグナルを送る
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer,
                       didFinish utterance: AVSpeechUtterance) {
    try? AVAudioSession.sharedInstance().setActive(
        false, options: .notifyOthersOnDeactivation)
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer,
                       didCancel utterance: AVSpeechUtterance) {
    try? AVAudioSession.sharedInstance().setActive(
        false, options: .notifyOthersOnDeactivation)
}

usesApplicationAudioSession = false で synthesizer が独自の private session を使うようになり、shared session への干渉が消える。

Build 25〜28 の対処はいずれも Claude Code が「これで直るはず」と判断してコミットしたものだが、実機では一度も音楽が戻らなかった——コードが間違っていたからだ。Build 29 のコードを入れた後も最初は同じ症状が続いたが、実機を再起動したところ正常に動いた。audio session の状態が OS に残留していたためで、再起動でクリアされた形だった。コードが正しくても、実機を再起動しないと確認できない——これが最終的な教訓だった。

その他の修正

カメラ許可後にプレビューが出ない

AVCaptureSession.startRunning() はメインスレッド禁止なので専用バックグラウンドキューで呼んでいたが、その後の setupCameraLayout()(プレビュー UIView の再配置)も同じキューで呼ばれていた。UIKit の操作はメインスレッドのみなので、これが原因でプレビューが出なかった。

sessionQueue.async {
    self.captureSession.startRunning()
    DispatchQueue.main.async {
        self.setupCameraLayout()   // UI 操作はメインスレッドに戻す
    }
}

初回起動の連続ポップアップ

7年前にリリースしたころ、iOS のユーザー許諾ポップアップはほぼカメラ権限だけだった。その後 Apple は ATT(広告トラッキング許諾)・通知許諾・位置情報と、ユーザー同意を必要とするケースを年々追加してきた。古いアプリをそのまま動かし続けていると気づきにくいが、近代化して初めて実機を触ると4つのダイアログが一気に出る——これはコードのバグではなく、iOS のマイグレーションによる「許諾負債」の露出だ。

今回出た4つ:カメラアクセス(バーコードスキャン)、ATT(AdMob の広告トラッキング)、位置情報(近くのリアル図書館を検索する機能)、通知許諾——これが初回起動で連続して表示されていた。対処の構造:

  • 通知許諾:2回目起動以降に延期(初回はアプリの価値を理解していない)
  • 位置情報:図書館検索ボタンを押したタイミングでのみリクエスト
  • ATTapplicationDidBecomeActive で呼ぶ(カメラ権限と衝突しないタイミング)

よくある質問

Q. Text to Speech で音楽が止まる問題、iOS のバージョンは関係しますか?

A. FB14444620 は iOS 17 以降で特に顕在化しています。AdMob SDK のバージョンとの組み合わせに依存する部分もあるため、まず usesApplicationAudioSession = false を試してください。

Q. usesApplicationAudioSession = false にすると何か副作用はありますか?

A. synthesizer が private session を使うため、他のオーディオとのカテゴリ調整が不要になります。読み上げ中に BGM を混在させたい場合は調整が必要ですが、「読み上げ後に音楽を戻す」だけが目的なら副作用はほぼありません。

Q. Build 29 のコードを適用しても症状が続く場合は?

A. 実機を完全に再起動してから確認してください。audio session の状態が OS に残留しており、アプリの再起動だけでは解消しないことがあります。

※本記事の手順・コードは執筆時点(2026年5月)の Xcode 26 / iOS 17 / AdMob SDK 11.x 環境で動作確認しています。ライブラリのバージョンが変わると挙動が変わる可能性があります。動かない場合はコメント欄でお知らせください。

まとめ

v3.1.0 の動作確認で実機を触ったら 11 回の TestFlight 提出になった。根本原因は Text to Speech が shared audio session を deactivate しない Apple 既知バグ(FB14444620)で、usesApplicationAudioSession = false の1行で解決した。7年前の設計をそのまま移植した結果、iOS 17 + AdMob の新しい組み合わせで初めて顕在化した形だった。

この記事が役に立ったら X(Twitter)でシェアしてもらえると喜びます。

このブログを書いた人のアプリ

本ブログの著者が作った iOS 読書管理アプリ わたしのほんやさん を App Store で公開しています。本棚をシンプルに管理したい方はぜひ。

App Store で見る →

関連記事

参考

※この記事は Claude Code を使った自動更新を試しています。

2026年5月8日金曜日

CLI で自力実装した Hub-Worker 構成と Managed Agents API を比較してみた

Anthropic が Managed Agents API を公開したので、自前の Hub-Worker 構成と並べてみたら構造がほぼ同じだった。Claude Code CLI を tmux で 8 インスタンス並走させて自力で組んだものが、Anthropic の公式基盤と同じ設計になっていたというのは、なんとも不思議な感覚だ。

この記事では以下 3 機能を自前構成と比較しながら整理する:

  1. Managed Agents API — エージェント定義・環境・セッション・イベントを一式管理するクラウド基盤
  2. Multiagent sessions — Coordinator が複数 Worker を並列・直列に束ねるマルチエージェント実行基盤(Research Preview)
  3. Dreams — 過去セッションのログを読んで memory store を自動整理・再構築する非同期ジョブ(Research Preview)

自前構成の概要

参考までに今の自前構成を簡単に説明する(詳しくはこの記事)。

  • Claude Code CLI インスタンス × 8 を tmux セッションで管理
  • Hub インスタンスがタスクを分解し、Worker インスタンスに GitHub Issues 経由で委譲
  • Worker へのトリガーは tmux send-keys ベースのスクリプト
  • ガバナンスルールは POLICY.md に明文化し、各インスタンスが自律参照
自前 Hub-Worker 構成
User(たけやす)
tmux send-keys
Hub(Claude Code CLI)
tmux: cl_orchestrator · POLICY.md でガバナンス
▼ GitHub Issues 起票 + tmux send-keys トリガー
BlogGen
ImgGen
ClaudeChat
… × 5
▼ 読み書き
WSL2 ファイルシステム / Git リポジトリ群

Managed Agents API の構造——並べてみると激似だった

Managed Agents API のドキュメントを読んで最初に思ったのは「あ、これ自分で作ったやつだ」だった。コアコンセプトを自前構成と対比すると:

Managed Agents API の概念 自前構成の対応物
Agent(モデル・system prompt・ツール定義) CLAUDE.md + settings.local.json でロールを定義した各 CLI インスタンス
Environment(コンテナ・パッケージ・ネットワーク設定) WSL2 + 各リポジトリの .venv・パス設定
Session(タスクを実行するエージェントの実行インスタンス) tmux セッション内の Claude Code プロセス
Events(アプリ↔エージェント間のやり取り) GitHub Issues への書き込み + tmux send-keys によるトリガー
Managed Agents API 構成
Your App
▼ Events API(SSE)
Session(primary thread)
Coordinator Agent
モデル / system prompt / tools / MCP を定義
▼ session threads(最大 25)委譲 1 レベルのみ
Worker
Agent 1
Worker
Agent 2
Worker
Agent 3
… 最大
20種
▼ 読み書き
共有コンテナ
ファイルシステム
Memory Store
(永続メモリ)

対応表と図を見比べると、設計はほぼ一致している。ただし実装上の差は大きい。Pros & Cons をまとめると:

観点 Managed Agents API 自前構成(Claude Code CLI)
初期ツール 自前定義が必要 Bash/Read/Write/MCP が最初から全部乗っている ✅
Linux 資産の活用 要別途設計 apt install や設定変更まで自律実行できる ✅
観測性 イベントストリームで全スレッドをリアルタイム把握 ✅ tmux 出力を目視か Monitor で追う
インフラ運用 Anthropic 管理。環境維持不要 ✅ WSL 維持が必要。ただし git から復元できる
料金 API トークン従量課金。大量実行でコスト跳ね上がりリスク Max プランの月額固定。ヘビーユースでも定額 ✅
移行コスト CLI 資産は移植不可。全面書き直し なし(現状維持)✅

Multiagent sessions——公式版が解決している問題

Multiagent sessions のドキュメントを読んで、自前構成との差が明確になった箇所がいくつかある。

ボールロスの検知。各エージェントは session thread(コンテキスト分離されたイベントストリーム)で動き、状態変化は session.thread_status_idle / session.thread_status_running などのイベントとして primary thread に集約される。自前構成では「誰も次のボールを投げない」状態は何も発火しない——これが最大の盲点だった。公式版はここが構造的に解決されている。

ツール確認の集約。子エージェントが確認待ちになると requires_action が primary thread に cross-post される。自前構成では各インスタンスの確認要求が分散していて、見落としが起きやすい。

なお {"type": "self"} を使うと Coordinator が自分のコピーを並列展開することもできる。スレッド上限・委譲深さ・アクセス要件は以下の表を参照。

観点 Multiagent sessions 自前構成(tmux + GitHub Issues)
ボールロス検知 thread_status_idle で構造的に検知 ✅ 誰も投げない状態は無音。目視かポーリングが必要
ツール確認の集約 requires_action が primary thread に一元集約 ✅ インスタンスごとに分散。見落としが起きやすい
コンテキスト引き継ぎ スレッド永続。追加指示で前の文脈が引き継がれる ✅ 同左(tmux セッションが生きている限り引き継ぐ)✅
委譲の柔軟性 1 レベル固定(深さ制限は基盤保証) POLICY.md の設計次第で柔軟に変更できる ✅
スケール上限 スレッド 25・エージェント 20 の固定上限 ハードウェアと Max プランの許す限り無制限 ✅
アクセス要件 Research Preview・別途アクセスリクエスト必要 今すぐ使える ✅

Dreams——メモリ整理の自動化

Dreams は memory store を非同期バッチで整理・再構築する機能(Research Preview・要アクセスリクエスト)。入力の store は一切変更しないので、気に入らなければ出力を捨てられる。

仕様の要点:

  • 入力:既存 memory store(必須)+ 過去セッション最大 100 件(任意)
  • instructions パラメータでキュレーションの方向付けが可能(最大 4,096 字。例:「コーディングの好みに絞れ、一時的なデバッグメモは無視しろ」)
  • 対応モデル:claude-opus-4-7 / claude-sonnet-4-6
  • 料金:通常 API トークン課金。入力セッション数・長さに比例
  • ベータヘッダーは managed-agents-2026-04-01,dreaming-2026-04-21 の 2 つが必要

自前構成では memory の更新は手動で、インスタンスが増えると追いつかなくなる。「既存 store がなくても空ストアを作ってセッションログだけを入力にできる」とドキュメントに書かれており、段階的な導入が現実的にできる点は大きい。

観点 Dreams 自前構成(手動 memory 管理)
実行トリガー cron や任意タイミングで自動実行。ユーザー不在でも動く ✅ 「学習して」と依頼したときのみ
スケール インスタンス数に依存せず同じフローで回せる ✅ インスタンスが増えると人間が追いつかなくなる
安全性 入力 store を変更しない。出力を確認してから差し替えられる ✅ 直接書き換えのため意図しない改変の検知が難しい
コスト セッション量に比例して課金増。大量処理でコスト跳ね上がりリスク Max プラン内に収まる(追加コストなし)✅
完了までの時間 数分〜数十分(非同期ジョブ) 会話の中で即時実行 ✅
アクセス要件 Research Preview・追加ヘッダー・アクセスリクエスト必要 今すぐ使える ✅

CLI で自力実装した経験が教えてくれたこと

自分でスクラッチから組んだことで、Managed Agents API のドキュメントを読んだとき「なぜこの設計なのか」がすぐ腑に落ちた。ロスターの 1 レベル制限・スレッドの永続性・primary thread への集約——どれも自前実装でぶつかった問題に対する答えだった。

「生成 AI を使える」は当たり前になりつつある。次に差がつくのは——エージェント間の責任境界の引き方、コンテキスト設計、障害検知と回復の設計など、実際に組んで壊してみないと身につかない経験値だと思う。早期クラウドアーキテクトが「AWS を知っていた」より「オンプレの失敗パターンをクラウドでどう回避するか」を語れた理由と同じだ。

自前構成を持っている今は、API への移行コストより現状の自由度と定額コストのほうが優先度が高い。ただ Worker を大幅に増やしたい・24 時間無人稼働を常態化させたい段階が来たら、その時点で移行を考えると思う。

まとめ

CLI で自力実装した Hub-Worker 構成と Managed Agents API を並べたら、設計がほぼ同じだった。Multiagent sessions が明確に勝っているのは「ボールロスの構造的な検知」と「ツール確認の一元集約」。Dreams は手動では追いつかないメモリ管理のスケール問題に応える機能で、空ストアからの段階的導入が可能。

この記事が参考になったら X(Twitter)でシェアしてもらえると喜びます。

よくある質問

Q. Managed Agents API は今すぐ使えますか?

A. API アカウントがあればデフォルトで有効です(ベータヘッダー managed-agents-2026-04-01 が必要)。Multiagent sessions と Dreams は Research Preview で別途アクセスリクエストが必要です。

Q. 自前構成から Managed Agents API への移行は大変ですか?

A. CLI の資産(Bash スクリプト・MCP サーバー・ポリシーファイル)は移植できないため、実質全面書き直しです。移行するなら新規プロジェクトとして設計し直すのが現実的です。

Q. Max プランのまま Managed Agents API は使えますか?

A. Managed Agents API は API 従量課金です。Max プランとは別に API の利用料が発生します。Dreams はセッション量に比例するため、スモールバッチでコストを確認してから本番規模に上げることをお勧めします。

※本記事の情報は執筆時点(2026 年 5 月)のものです。Managed Agents API はベータ機能のため仕様が変更される場合があります。最新情報は Anthropic 公式ドキュメント を参照してください。

このブログを書いた人のアプリ

本ブログの著者が作った iOS 読書管理アプリ わたしのほんやさん を App Store で公開しています。本棚をシンプルに管理したい方はぜひ。

App Store で見る →

関連記事

参考

※この記事は Claude Code を使った自動更新を試しています。