田中力磨 › 一人塾のGAS自動化 実録 › Threads自動投稿

Threadsの自動投稿——Geminiで生成し、採点し、翌日の投稿に学習させる

Geminiに投稿文を作らせ、自己採点させ、翌日の投稿に学習させる。GASだけで回している仕組みの記録。

2026-09-14 公開 / 滋賀県彦根市の一人塾「総合学習教室ブリッジ」で実際に動いている仕組みの記録 / Google Apps Script(GAS)

何を自動化したか

塾の運営をしながらThreadsを毎日更新するのは、続けようとするほど負担になる作業だった。文章を考える時間、投稿する時間、翻ってみて反応を見て次に活かす時間——どれも塾の仕事の合間に差し込むには重い。今はこの一連の流れをGoogle Apps Script(GAS)が担っている。Geminiに下書きを生成させ、田中力磨らしくない表現や関西弁が混ざっていれば自動で弾いて作り直させ、100点満点で自己採点させたうえで一番良い案を投稿する。投稿後は閲覧数・いいね数・返信数を取得し、フォロワーの増減と突き合わせて「何が伸びたか」を毎晩学習し、翌日以降の生成プロンプトに反映する。

2026-09-13時点で運用しているのは1日2回(08時・12時)の投稿。もともとは08/12/20時の3回体制だったが、投稿ログを見ると20時の回は3スロット中もっとも閲覧が伸びず(7〜8月平均で朝50・昼52に対し夜36)、直近32日のフォロワー純増は+1にとどまっていた。惰性で続けるより回数を絞って質に寄せる判断をし、宣伝ナイト(後述)や記事告知の期間だけ夜枠を復活させる形にした。数字が実測、それ以外の「体感で助かっている点」(文体チェックの手間がゼロになったこと、ネタ切れで詰まらなくなったこと)は体感であることをここに明記しておく。

仕組み(データの流れ)

1回の投稿が生成されてから公開されるまでの流れは次のとおり。

ステップ内容
1. テーマ決定過去の投稿ログから「閲覧数の平均が高いテーマ・型」を7割の確率で再利用し、3割は新しいテーマ・型を探索する。12時台は必ず代表が監修した本(国語の解答力の本)由来のネタを差し込む
2. 旬の話題取得Googleニュースの公開RSSから教育関連キーワードで見出しを取得し、テーマと自然に重なるものがあれば起点にする
3. 生成Geminiに人物プロファイル・バズ構造パターン・直近の書き出し(重複回避用)・前日の学習メモを渡して下書きを作らせる
4. 検査関西弁・固定挨拶・ハッシュタグなどの禁止表現を正規表現で検出。ヒットしたら理由を添えて再生成(最大6回)
5. 字数調整Threadsの上限500字を超えたら、まず既存の下書きを意味を保ったまま短縮させる。短縮が失敗した場合のみ字数指示付きで作り直す
6. 採点Geminiに100点満点で自己採点させ、90点に届いたら即採用。届かない場合は6回分の最高得点を採用
7. 画像生成・投稿本文に合う画像を生成し画像つきで投稿。失敗すればテキストのみで投稿
8. 記録・学習投稿ログに記録。毎晩、閲覧数の更新→フォロワー推移の記録→「伸びる投稿のルール」の抽出、の順に学習が回る

コード(要点)

禁止表現の検出は、置換ではなく再生成に倒しているのが要点。関西弁の語尾を機械的に置換すると不自然な文章になりやすいため、検出したら理由をフィードバックとして渡し、Geminiに文章ごと作り直させている。

var REJECTION_CHECKS = [
  { pattern: /こんにちは。ブリッジの田中です/, label: '固定挨拶禁止' },
  { pattern: /ほんまに/,   label: 'ほんまに(関西弁)' },
  { pattern: /やった(?!こと|ら|ん)/, label: 'やった(関西弁語尾)' },
  { pattern: /やんか/,     label: 'やんか(関西弁)' },
  { pattern: /#[\w぀-ヿ一-龯]+/, label: 'ハッシュタグ(禁止)' },
  // ...ほか関西弁パターンを列挙
];

function findViolations(text) {
  var found = [];
  REJECTION_CHECKS.forEach(function(check) {
    if (check.pattern.test(text)) found.push(check.label);
  });
  return found;
}

字数オーバーへの対処は「短縮を先に試す」実装にしている。理由は後述のハマった点で説明する。

function threadsShrinkDraft(draft, maxChars) {
  try {
    var target = maxChars - 20;
    if (target < 150) target = 150;
    var prompt = '次のThreads向け投稿文を、意味・口調・文体を変えずに'
      + target + '字以内へ短縮してください。\n'
      + '・最も鋭い一文と結論だけを残し、背景説明・経緯・重複表現を削る\n'
      + '・新しい内容を足さない。関西弁・ハッシュタグ(#)は入れない\n'
      + '--- 投稿文 ---\n' + draft;
    var out = autoGeminiText(prompt, { temperature: 0.2, maxTokens: 900 });
    if (!out) return null;
    out = out.trim();
    if (!out || findViolations(out).length > 0) return null;
    if (out.length > maxChars) return null;
    return out;
  } catch (e) {
    return null;
  }
}

投稿後の学習部分は、投稿ログとフォロワー推移を突き合わせてGeminiに「伸びる投稿のルール」を箇条書きで抽出させ、そのメモを翌日以降の生成プロンプトに差し込むだけの単純な作りにしてある(要点のみ。全文はnoteへ)。

var note = autoGeminiText([
  '学習塾のThreadsアカウントの直近データから',
  '「フォロワーが増える投稿のルール」を抽出してください。',
  '【直近の投稿と成績】', posts.join('\n'),
  '【フォロワー数の推移】', followerLines.join('\n'),
  '出力形式(300字以内・箇条書き): 伸びたテーマ/書き出しの共通点、'
  + '伸びなかった投稿の共通点、明日守るべきルール3つ。'
  + 'データから読み取れないことは書かない。'
].join('\n'), { temperature: 0.3 });

sheet.appendRow([new Date(), note]);  // 「投稿学習メモ」シートへ保存

ハマった点と回避策

一番大きくハマったのが字数オーバーの扱いだった。Threadsの本文上限は500字で、超えると投稿全体がAPIから拒否される(実際にエラーコード100「Param text must be at most 500 characters long」で返ってくる)。最初は「字数指示だけを添えて丸ごと作り直させる」対応にしていたが、これだと6回の再生成すべてが字数オーバーで全滅する日が実際に発生した。Geminiに「短くしてください」とだけ伝えても、同じ構成のまま似た長さの文章を繰り返し出してくることがある。そこで、字数オーバー時はまず今の下書きを「意味・口調は変えず短縮するだけ」の専用プロンプトに通す実装に切り替えた。作り直しではなく圧縮を先に試すことで、全滅する日はほぼなくなった。

もう一つは、夜の回に付けるURL案内の分だけ本文の許容字数を削っておく必要があったこと。末尾に「無料体験は公式LINEから」のような案内を追記すると、本文だけなら収まっていたものが合計で上限を超えることがある。案内文の最大長を見込んで生成段階の目標文字数を下げ、投稿直前にも「本文+案内が500字を超えていたら本文側だけを詰める」安全網を二重に用意した。URL自体を切ってリンク切れにする事故だけは避けたかったため、削るのは常に本文側に固定してある。

もう一つの教訓は、貯めるだけで使わないデータを作らないこと。他アカウントのバズ投稿から型を抽出する仕組みはあったが、しばらくは抽出されるだけで実際の生成プロンプトには一度も読み込まれていなかった。仕組みを作った時点と、それを使う配線をつなぐ時点がずれると「動いているつもりで何も効いていない」状態になりやすい。作ったら必ずどこかの生成処理から呼ばれているかを確認する、という当たり前のことを見落としていた。

導入手順

  1. Threads for Developersでアプリを作成し、Threads APIのアクセストークンとユーザーIDを取得する
  2. GASプロジェクトのスクリプトプロパティに以下を設定する: THREADS_USER_ID / THREADS_ACCESS_TOKEN / GEMINI_API_KEY
  3. 投稿の生成・採点・投稿・学習を行う関数群(本文生成・禁止表現チェック・字数調整・投稿・インサイト取得・学習メモ更新)を1つの.gsファイルにまとめる
  4. 投稿頻度に合わせて時間トリガーを作成する(GASの時刻トリガーは1時間単位で指定。タイムゾーンはappsscript.jsonでAsia/Tokyoに設定しておく)
  5. 投稿ログ用のスプレッドシートを用意し、投稿のたびに日時・テーマ・型・スコア・本文を1行ずつ記録する
  6. 毎晩1回、閲覧数などのインサイト取得→フォロワー数の記録→学習メモの更新、の順で回すトリガーを追加する
  7. まずは手動実行できるテスト関数で生成結果を目視確認してから、本番の時間トリガーに切り替える

よくある質問

Q. 生成AIっぽい文章になってしまいませんか

なる。だからこそ「田中力磨らしさ」を定義したプロファイルと、禁止表現の検出、直近の投稿と被らない書き出しの指示をプロンプトに積み重ねている。それでも一定確率でAI的な言い回しは出るため、採点でも減点対象にし、最終的には人間が投稿ログを時々読み返して違和感がないか確認している。

Q. 毎回同じ内容の繰り返しになりませんか

直近7件の投稿の書き出し1行をプロンプトに渡し「これと被らないこと」と明示している。それでもテーマプール自体が有限なので、テーマは時々見直して入れ替えている。

Q. Threads以外のSNSにも応用できますか

できる。X(旧Twitter)向けの投稿も生成・採点・学習ロジックはこの仕組みと共通で、文字数制約とAPI認証方式(OAuth 1.0a)だけがThreads側と異なる作りにしている。

この仕組みの「全体像」と「コード全文」