どうも、おじいです。
本業の傍ら、AI エージェントとか自動売買 bot を個人開発してる。最近、自分の副業探し自体を自動化しようと思って「副業応募 bot」を作って動かしてた。
こいつの仕事はシンプル。
- 週末に『ある求人サイト』とか複数のマーケットを巡回して、良さげな案件をリストアップする
- リストアップした案件に対して、俺のスキルセットに合わせた提案文を AI で自動生成して応募する
これを1ヶ月ほど動かしてみたんだけど、結果は「受注ゼロ」。まあ、そんなに甘くないよな、と。最初は「提案文の質が悪いのかな」とか「そもそもスキルが足りてないのかも」とか、結構まともな反省をしてた。
でも、念のためログをちゃんと見てみたら、えぐい事実が発覚した。
そもそも、提案を1件も送ってなかった。
いや、マジかよと。630件も提案文を「下書き」は作ってたのに、応募ボタンを押すフェーズまで到達したのがゼロ件。これは提案文の質とかそういうレベルの話じゃない。完全にシステムの問題。マジでやばいと思った。
原因:エラー1つで全データを吹き飛ばす脆すぎる設計
なんでこんなことになったのか。原因を深掘りしたら、問題は応募スクリプトじゃなくて、その前段の「案件収集スクリプト」にあった。
こいつは毎週日曜の深夜に動くように設定してたんだけど、どうやら処理の途中でクラッシュしてたらしい。そして、このクラッシュの仕方が最悪だった。
当時のコードは、だいたいこんな感じ。
// Before: A single error crashes the whole script
def scrape_all():
browser = pw.chromium.launch()
results = []
# 複数のサイトをループで巡回
for site in SITES:
# このへんでサイトの仕様変更とかでエラーが出ると…
page = browser.new_page()
try:
# 収集した結果をいったんメモリ上のリストに追加
results.extend(scrape_site(page))
except Exception as e:
log.error(f"Failed on {site}: {e}")
# エラーが出てもループを止めないように try-except は入れてたつもりだった
# ループが全部正常に終わらないと、ここにはたどり着かない
# つまり、途中でクラッシュすると、それまでの成果は全部消える
save_results(results)
このコードの問題点は2つ。
- エラー処理の範囲がデカすぎる: ループの外側でブラウザを起動してるから、
browser.new_page()みたいな処理で何かあると、try-exceptブロックの外で例外が発生して、スクリプト全体が即死する。 - 最後にしか保存しない: 収集した案件データは、全部のサイトを回りきるまでメモリ上の
resultsリストに溜め込んでる。だから、最後の1サイトでコケたとしても、それまでに集めた全データが失われる。
実際、ログを追ったら『某マーケット』の HTML 構造が微妙に変わってて、要素が見つからずにタイムアウトエラーを起こしてた。たったこれだけのことで、毎週日曜の深夜、bot は数時間かけて集めたデータを全部ドブに捨てて、黙って死んでたわけだ。そりゃ応募できるわけがない。
修正:死ぬことを前提にしたエラー処理と中間保存
この手の長時間動くスクリプトは、「絶対にどこかでコケる」という前提で設計しないとダメだった。反省して、こう書き直した。
// After: Robust error handling and intermediate saves
def scrape_all():
all_results = []
# サイトごとに、ブラウザの起動から全部やる
for site in SITES:
try:
# エラーが起きる可能性のある処理を全部 try ブロックに入れる
browser = pw.chromium.launch()
page = browser.new_page()
site_results = scrape_site(page)
all_results.extend(site_results)
# 成功したサイトの結果は、その都度ファイルに書き出す
save_intermediate_results(site, site_results)
except Exception as e:
# ここでエラーをキャッチすれば、次のサイトの処理には進める
log.error(f"Failed on {site}, skipping: {e}")
finally:
# ちゃんとブラウザを閉じる処理も忘れずに
if 'browser' in locals() and browser:
browser.close()
# 最後に全部の結果をマージして保存
save_final_results(all_results)
ポイントは2つ。
-
try-exceptをループの内側に入れる: これで、あるサイトの処理で失敗しても、そのエラーはループ内で完結する。次のサイトの処理には影響が出ない。 - 中間保存: 1サイトのデータが取れたら、その時点ですぐにファイルに保存する。これで、たとえスクリプトが最後の最後でクラッシュしたとしても、それまでに成功した分のデータは手元に残る。
この修正で、bot はようやく毎週安定して案件データを集めてこれるようになった。
学び:本当の課題はコードの先にあった
この一件で学んだことは多い。
まず技術的な話。長時間動くバッチ処理は、とにかく堅牢性が命。エラー処理は「念のため」じゃなくて「必須の設計要素」。そして、状態(この場合は収集データ)はメモリに置かず、こまめに永続化する。基本だけど、サッと作ると忘れがちだ。
次に、問題解決のアプローチ。最初、「受注ゼロ」という結果だけ見て「提案文が悪い」とか「スキルが足りない」とか、表層的な仮説に飛びついちゃったのが良くなかった。「そもそも応募はされているのか?」という、一番基本的なファクトの確認を怠ってた。ログは嘘をつかない。何か問題が起きたら、まずデータを深く掘るのが鉄則。
そして一番大きかったのが、このバグを直したことで見えてきた、さらに本質的な課題だ。
安定して案件を収集できるようになった結果、わかったことがある。
「俺がやりたいレベルの副業案件、市場にほとんど出てねえわ」
結局、技術的な問題を解決したら、その先にある事業戦略(この場合は副業戦略)の課題が浮き彫りになっただけだった。まあ、これも大きな一歩か。
コードのバグを直すのは楽しいけど、そのコードが解決しようとしてる問題そのものがズレてないか?っていう視点も、個人開発では大事なんだなと、改めて痛感した。
さて、次は何を自動化しようかな。
I build and run small Python systems — trading bots, RAG APIs, scheduled automation — and write up whatever breaks along the way.
If a provider-agnostic RAG Q&A API is useful to you, mine is MIT-licensed on GitHub: rag-faq-api. It runs and passes its full test suite **with no API key* (offline stub LLM + hashing embedder), swaps to Claude / Gemini / OpenAI via one env var, and ships a retrieval-quality harness (Hit@k / MRR / Recall@k) with a chunking sweep.*
Top comments (0)