Agentive
自動化ラボ

学生フリーランスが自作した応募自動化パイプラインの全体構造 — automation-v2 解説 第1回

約11分で読めます

フリーランスとして稼働しながら、「応募作業」そのものをAIに任せたいと考えたことはないだろうか。私は慶應理工3年の学生として、クラウドワークス・ランサーズ・ココナラの3プラットフォームで制作案件を受注している。そしてこの3サイトを横断して案件を拾い、自動で応募文の下書きまで作るパイプラインを自前で書いて毎日動かしている。

本記事はそのパイプライン automation-v2 の全体構造と、実装時に下した設計判断を公開するシリーズの第1回だ。

なぜ自作したか

最初は既存のクラウドソーシング支援ツールを探した。だがどれも「案件通知をSlackに飛ばす」程度で、応募文面の生成・履歴管理・信頼度スコアリングまで一気通貫で面倒を見るものはなかった。しかも多くは月額課金で、自分の業務フローに合わせて改変できない。

フリーランスの応募は、実は「似た判断を毎日繰り返す」作業だ。

  • 予算は許容範囲か
  • 案件カテゴリは自分の得意分野か
  • 面談必須になっていないか
  • 過去に辞退されたクライアントではないか
  • 文面はどのテンプレを使うべきか

これらは全てルール化できる判断だ。ルール化できる判断はAIとコードで再現できる。自作すれば自分のフローに最適化でき、コストは API 従量課金のみに抑えられる。

全体構造

パイプラインは以下の順で動く。毎朝 8:00 JST に Windows Task Scheduler が起動点となる。

[Cron 08:00 JST]
      |
      v
[scrapers/] cloudworks.py / lancers.py / coconala.py
      |  各プラットフォームを Playwright でログイン済みセッション経由で巡回
      |  → data/jobs/YYYY-MM-DD/*.json
      v
[confidence.py] 案件スコアリング(カテゴリ一致・予算・スキル・面談有無・長さ)
      |  → 0.0〜1.0 のスコア + ペナルティ
      v
[apply_draft.py] しきい値 0.60 以上の案件にテンプレ適用
      |  → data/drafts/YYYY-MM-DD/*.md
      v
[apply_send.py] しきい値 0.85 以上 かつ --commit フラグ有効時のみ送信
      |  → 実際のプラットフォームに応募(dry-run がデフォルト)
      v
[inbox_watcher.py] 返信を検知
      |
      v
[inbox_reply_drafter.py] 返信下書きを生成
      |  → data/inbox/reply-drafts/YYYY-MM-DD/*.md
      v
[daily_digest.py] 1日のサマリを ntfy.sh で通知

この構造で重要なのは、「応募」という単一タスクを5つの独立した小タスクに分解していることだ。この分解が後述する設計判断のほとんどを決定している。

設計判断1: ドライランを第一級の実行モードにする

apply_send.py--commit フラグがない限り絶対に送信しない。これは後から追加した安全策ではなく、最初から 「送信する方がオプション」という設計 にしてある。

理由は明確で、AIが書いた応募文をそのまま送信するのは信頼できないからだ。テンプレが意図通り埋まっているか、案件固有要件に応答しているか、クライアント名が正しいか — これらは目で確認しないと事故る。

実装上は --commit なしで走らせると dry-run ログだけが出る。Discord/ntfy に通知が飛ぶが、プラットフォームに対する書き込み動作はゼロ。これにより「とりあえず毎朝動かしておく」を安全に実現できる。

学んだこと: 破壊的操作をデフォルトOFFにすると、運用者の精神的負荷が大幅に下がる

設計判断2: スコアリングを2段しきい値にする

信頼度スコアには2つのしきい値がある。

  • draft_threshold: 0.60 — これ以上で応募下書きを生成する
  • auto_send_threshold: 0.85 — これ以上で実際に送信する(—commit 時のみ)

単一しきい値にしなかった理由は、「ドラフトは作っておきたいが送信はしたくない」案件が存在するからだ。0.70 の案件は目視で判断して手動送信したい。0.90 の案件は自動で送ってよい。この2段構成がフリーランスの判断プロセスと一致している。

さらに daily_send_cap = 5 という上限もある。1日の自動送信は最大5件。これはプラットフォーム側のスパム判定を避けるためと、応募が濃度管理できない状態で走るのを防ぐためだ。

設計判断3: クライアント履歴をスコアに反映させる

client_history.py は過去にやりとりしたクライアントを記録し、スコアに加点・減点する。

  • 2回連続で辞退されたクライアント → -0.80
  • 「面談必須」と明示してくるクライアント → -0.30
  • 過去に好反応をもらったクライアント → +0.05

これにより、同じクライアントから新しい案件が出たときに「もう応募しない」判断が自動で入る。ブラックリスト方式ではなくスコア加減算方式にしているのは、「微妙な減点」を表現したいからだ。

実運用で効くのは+0.05の方だ。好反応クライアントは放っておくとリピート受注につながるので、このわずかな加点で優先順位が上がる。

設計判断4: プラットフォームごとにスクレイパーを分ける

3プラットフォームは共通のベースクラスを持つが、実装は別ファイルにしている。最初は共通化して1つのクラスで済ませようとしたが、プラットフォームごとに認証・DOM構造・reCAPTCHA対策が大きく違うため、共通化すると分岐が増えて可読性が下がった。

scrapers/
├── base_scraper.py      ← 共通のスコア・保存ロジック
├── cloudworks.py        ← CW専用: ログインと案件リストDOM
├── lancers.py           ← ランサーズ専用
└── coconala.py          ← ココナラ専用(DOMが最も独特)

この分割により、1プラットフォームが仕様変更しても他に波及しない。保守コストが圧倒的に下がる。

ハマりどころ: ストレージステート管理

Playwright は storage_state.json にログイン済みCookie/LocalStorage を保存する。これが定期的に失効するため、手動で再生成する必要がある。

当初は失効するたびにコマンドを手打ちしていたが、面倒すぎて運用が停滞した。最終的に regen_storage.bat というワンクリックバッチを用意し、regen_storage.bat all で3プラットフォーム分を順次ログインできるようにした。

学んだこと: 「面倒」は運用停止の最大要因。1秒でも省ける場所は省いた方がいい。

同じ構造は「制作代行」以外にも使える

ここまで読んで気づいた人もいるかもしれないが、このパイプラインは本質的には以下の一般化ができる。

情報取得 → スコアリング → 下書き生成 → 確認ゲート → 実行 → 記録

この流れは中小企業のオペレーションのほとんどに当てはまる。

  • 営業リスト巡回 → 優先度スコア → 営業メール下書き → 担当者承認 → 送信 → CRM記録
  • 問い合わせ受信 → カテゴリ分類 → 返信下書き → レビュー → 返信 → 履歴保存
  • 請求書受信 → 仕訳判定 → 登録下書き → 経理確認 → 会計システムへ登録 → ログ

つまり**「応募自動化」のアーキテクチャは、業務自動化の雛形そのもの**だ。私が毎朝使っているこの構造を、事業者向けにカスタマイズして提供する仕事を今後増やしていく予定だ。

次回予告

第2回では confidence.py のスコアリング実装を詳細に解説する。カテゴリマッチ・予算判定・面談検出をどう数値化しているかと、失敗を通じて学んだペナルティ設計を公開する。

パイプラインのどの層から見たいか質問があれば、このサイトの問い合わせ経由で教えてほしい。リクエストの多い層から優先して書く予定だ。

A

Agentive 編集部

AIエージェントを実際に使い倒す個人開発者。サイト制作の自動化を実践しながら、その知見を発信しています。