はじめに(なぜ書くか・背景)
こんにちは。エンジニアをしている長井洸太です。
これまでX(Twitter)・Instagram・LinkedIn・Qiita・Zennと、SNS/技術記事投稿をPythonで自動化してきました。動くものはたくさん作ってきましたが、その過程で「最初の設計のまま突き進んでいたら詰んでいた」という場面が何度かありました。
今回はその中から、実際に自分がハマった3つの失敗と、そこからどう設計を直したかをまとめます。「動くコード」の紹介というより、失敗談コラムとして読んでもらえたらと思います。
今回取り上げる3つの失敗(概要)
| # | 失敗 | 何が起きたか |
|---|---|---|
| 1 | 複数媒体を1本のスクリプトに詰め込もうとした | X・Instagram・LinkedInを「同時投稿」する統合スクリプトを作ろうとして、実装の途中で手が止まった |
| 2 | 自動生成物を「上書きしていい前提」で運用していた | 記事公開時に自動生成されるX告知ツイート下書きを、手直し後にそのまま上書き保存しようとして失敗した |
| 3 | 画像生成APIを単一プロバイダ前提で組んでいた | 本番運用中にOpenAI側のクレジットが切れ、フォールバック先のGeminiまで失敗する回があった |
必要なもの(前提となる自動化の構成)
3つとも、以下の既存の自動化スクリプトの上で実際に起きたことです。
-
scripts/x/run_daily.ps1/scripts/instagram/instagram_reel_generator.py/scripts/linkedin/schedule_comment.ps1(媒体別の投稿自動化) -
scripts/zenn_publish.py/scripts/post_to_qiita.py(記事公開時のX告知ツイート自動生成) -
scripts/linkedin/linkedin_all_patterns.py(ニュース収集→文章生成→画像生成のパイプライン)
コード(失敗と対策の実装)
失敗①:1本のスクリプトに複数媒体を詰め込もうとした
もともとは「X・Instagram・LinkedInに同時投稿する統合スクリプト」を作る想定でした。しかし実際にコードにしようとすると、媒体ごとに投稿に適した時間帯も、生成にかかる処理時間も、投稿前に人が確認したい度合いもバラバラで、1本のスクリプトに同時実行として詰め込む理由がそもそもありませんでした。
最終的に落ち着いたのは、媒体ごとに別スクリプト・別トリガーをタスクスケジューラに時間差で登録する構成です。
# setup_scheduler.ps1(抜粋)Xは毎日07:00に「下書き生成のみ」
$TaskName = "LIFE_X_DailyDraft"
$ScriptPath = "c:\.github\LIFE\scripts\x\run_daily.ps1"
$TriggerTime = "07:00"
$trigger = New-ScheduledTaskTrigger -Daily -At $TriggerTime
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument ("-NonInteractive -ExecutionPolicy Bypass -File `"" + $ScriptPath + "`"") `
-WorkingDirectory "c:\.github\LIFE"
Register-ScheduledTask `
-TaskName $TaskName -Trigger $trigger -Action $action `
-RunLevel Limited -Force | Out-Null
:: Instagramは「生成」と「投稿」を別バッチ・別タイミングに分離
:: daily_reel_generate.bat(前日夜に実行)
py -3 -X utf8 scripts/instagram/instagram_reel_generator.py ^
--full-auto --tts --tts-voice ja-JP-KeitaNeural --auto-bgm --duration 60 --no-post ^
>> "Earning/instagram-posts/reels/daily_log.txt" 2>&1
「同時に投げる」のをやめて「別々に、しかし自動で」にしたことで、初めてそれぞれの媒体に合った構成にできました。
失敗②:自動生成物を上書きしていい前提で運用していた
zenn_publish.pyとpost_to_qiita.pyは、記事を公開すると告知ツイートの下書きをClaudeで自動生成して保存します。あるとき、自動生成された文章が記事の実際の論旨(見出しの3層構造など)を拾えていなかったため手直しをしたのですが、その際に元の自動生成ファイルへ直接上書き保存しようとしたところ「編集履歴として元のファイルは残したいので、別ファイルで保存してほしい」という指摘を受けました。
これを受けて、両スクリプトに「同じ下書きに対して複数回実行しても、既存ファイルは上書きせず_v2, _v3…で新規保存する」ガードを追加しました。
# zenn_publish.py の generate_x_announcement() 内(抜粋)
out_path = out_dir / f"{file_id}_zenn_{short}_draft.md"
if out_path.exists():
# 同日中に複数回push(下書きpush→後日の--publish pushなど)しても
# 既存の下書きを上書きしない。_v2, _v3... で新規保存する
n = 2
while (out_dir / f"{file_id}_zenn_{short}_draft_v{n}.md").exists():
n += 1
out_path = out_dir / f"{file_id}_zenn_{short}_draft_v{n}.md"
自動生成物を「その場限りの使い捨てファイル」として扱い、無邪気に上書き運用にしていたのが失敗の原因でした。人が手直しする前提のファイルは、上書きではなく履歴を残す設計にしておくべきでした。
失敗③:画像生成APIを単一プロバイダ前提で組んでいた
linkedin_all_patterns.pyの画像生成は、もともとOpenAI(DALL-E系)を第一候補、Gemini Imagenをフォールバックとして呼ぶ二段構成にしていました。実際にOpenAI側のクレジットが切れた回があり、このフォールバックが役に立ったのですが、同時に「フォールバック先も無条件には成功しない」ことも実際のログで分かりました。
# linkedin_all_patterns.py の generate_image()(抜粋)
if openai_client:
for model_name in ["gpt-image-1", "gpt-image-1-mini"]:
try:
response = openai_client.images.generate(
model=model_name, prompt=prompt[:4000], size="1536x1024", n=1,
)
...
return str(output_path)
except Exception as e:
print(f" [WARN] {model_name} failed -> trying next: {e}")
print(" [WARN] All OpenAI models failed -> fallback to Gemini")
# --- Gemini 画像生成フォールバック ---
try:
response = gemini_client.models.generate_content(
model='gemini-2.5-flash-image', contents=prompt, ...
)
...
except Exception as e:
print(f" [WARN] image generation skipped: {e}")
return None
出力サンプル(実際の結果)
失敗③は実際に本番実行時のログで再現しています。2026年8月31日と9月10日の実行で、OpenAI側は毎回クレジット切れで失敗しました。
[WARN] gpt-image-1 failed -> trying next: Error code: 429 - {'error': {'message': 'You have no credits remaining. Add credits to continue using the API at https://platform.openai.com/settings/organization/billing/.', 'type': 'insufficient_quota', ...}}
[WARN] gpt-image-1-mini failed -> trying next: Error code: 429 - {'error': {'message': 'You have no credits remaining...', 'type': 'insufficient_quota', ...}}
[WARN] All OpenAI models failed -> fallback to Gemini
[OK] Gemini (gemini-2.5-flash-image) generated
9月10日の実行では、3パターン中2パターンはGeminiフォールバックが成功しましたが、1パターンはフォールバック自体も失敗しました。
[WARN] gpt-image-1 failed -> trying next: Error code: 429 - {...'insufficient_quota'...}
[WARN] gpt-image-1-mini failed -> trying next: Error code: 429 - {...'insufficient_quota'...}
[WARN] All OpenAI models failed -> fallback to Gemini
[WARN] image generation skipped: 'NoneType' object has no attribute 'parts'
「フォールバックがあるから安心」ではなく、フォールバック先も含めて失敗しうる前提で、失敗時の最終手段(この場合は画像なしで記事を保存し、人が手動対応する)まで用意しておく必要があると実感しました。
やってみてわかったこと(セールス視点のコラム)
「まとめてやる」と「同時にやる」は違う
3媒体を1本のスクリプトにまとめること自体は間違っていませんでしたが、「まとめて管理する」ことと「同時に実行する」ことを混同していたのが失敗①の原因でした。セールスの日々の顧客対応でも、担当している案件を一つのリストで管理することと、全部を同じタイミングで一斉対応することは別物です。まとめて見えるようにしつつ、実行するタイミングは相手ごとに分ける、という発想は営業活動にも通じると感じました。
「自動化されたもの」ほど、後から人が触る前提で設計する
失敗②で学んだのは、自動生成されたものだからといって「使い捨てていい」わけではないということです。むしろ自動生成は叩き台であり、人が手直しすることの方が多い。だからこそ、上書きより履歴が残る設計の方が、結果的に運用が楽になりました。
外部サービスへの依存は、1段構えでは足りないことがある
失敗③は、二段構え(OpenAI→Gemini)にしていたからこそ「1段では足りなかった」ことに気づけたケースでした。フォールバックを用意した時点で満足せず、そのフォールバックすら失敗した場合にどう運用でカバーするか(今回であれば、画像なしで下書きだけ保存し、必要なら手動で再生成する)まで決めておくことが、実際に本番で自動化を回し続ける上では欠かせないと感じています。
おわりに(SNSフォローの誘導)
3つとも、最初から完璧な設計だったわけではなく、実際に動かしてみて初めて見えてきた失敗でした。自動化は「一度作ったら終わり」ではなく、実運用の中で何度も設計を見直すものだと改めて感じています。
毎日X(Twitter)・Instagram・LinkedInで自動化ツールや開発の話を投稿しています。よろしければ@kotajobs0からフォローしてください。