ケーススタディ: Beyond Tabs のためにブラウザーを使う採用ページクローラーを作る
Beyond Tabs は、インターネット上のすべての求人を索引化しようとしているわけではありません。
対象はもっと狭く、ニッチなプログラミング言語の仕事と、その周辺の技術コミュニティです。だからこそ crawling の問題は面白くなります。有用な結果は「この会社に求人がある」ではありません。「この source には Beyond Tabs の読者に関係する仕事が含まれているかもしれない」です。
現在の課題は careers pipeline です。数百の企業が採用ポータルを持っています。見慣れた ATS を使うものもあれば、共有プラットフォーム上にブランド付きページを載せるものもあります。少数のリンクだけを持つ custom site もあります。無視すべき aggregator もあります。JavaScript が実行され、cookie banner が閉じられ、年齢確認を通り、国別リダイレクトが終わってから初めて本当の構造が見えるものもあります。
Guida はこの種の作業のために作られました。
問題は scraping だけではなかった
すべての会社が同じ ATS を使っているなら簡単です。プラットフォームを検出し、API を呼び、結果を正規化して終わりです。
現実の Web はそうではありません。
ある triage run では、採用ポータルが既知 ATS、会社専用 workflow、一般的な未知サイト、無視すべき aggregator に分かれました。最も扱いにくいのは「一般的な未知サイト」です。小さな静的採用ページから、ブランド URL の裏に隠れたグローバル企業向け recruiting platform まで混ざっています。
URL だけでは足りません。実際の triage では次のような違いがありました。
- 消費者ブランドの自社採用 URL に見えたが、年齢確認後に SuccessFactors/jobs2web board が表示された。
- 産業技術企業は branded careers domain の下で Oracle HCM career site を表示していた。
- 研究組織は洗練された会社ページを表示したが、求人リンクは Lever へ向かった。
- 市場調査企業は BambooHR jobs widget を埋め込んでいた。
- エネルギー取引企業は自社ページに求人を出していたが、詳細リンクは HR-ON へ向かった。
- 大企業は数千件の結果を持つ custom careers search と製品固有の詳細ページを公開していた。
これらの違いは、どんな crawler を書くべきかを決めます。共有プラットフォームなら再利用可能な基盤にするべきです。自社 custom site なら会社固有 worker が必要です。Aggregator は無視すべきです。source data の問題なら、コードを書く前にデータを直すべきです。
WebView2 が workflow を変えた
近道は、HTTP request と URL heuristic だけで分類することです。
速くはなります。しかし後で時間を失う程度には間違えます。
Guida は WebView2 を自動化面として使うため、エージェントとユーザーが同じブラウザーの現実を見ます。ページは通常のブラウザーエンジンでレンダリングされます。JavaScript が動き、cookie banner が出て、リダイレクトが発生します。ユーザーは必要に応じてドメインを許可できます。エージェントは raw HTML から推測するのではなく、live tab のレンダリング済み DOM、表示リンク、script、iframe、最終 URL を調べられます。
このケーススタディでの作業 loop は次の形になりました。
- crawler gap report から始める。
- ワークスペースストアから未分類ポータルを選ぶ。
- 4 つのブラウザータブを再利用する。
- quad layout と Full HD viewport を適用する。
- 各タブを実際の careers page へ移動する。
- レンダリング済み content、links、scripts、iframes、platform hints を調べる。
- portal key をキーに structured finding を保存する。
- 次の batch へ進む。
目的は完全自律ではありません。監督付き自動化です。ユーザーはどのページが開いているか、どこで navigation が止まったか、エージェントが正しいものを分類しているかを見られます。
Store が作業記憶になった
最初の triage notes はテキストファイルでした。長くは続きませんでした。
数百のポータルと複数セッションにまたがると、メモは問い合わせ可能な状態である必要があります。Guida のワークスペースストアが、その作業記憶になりました。
crawler report は安定した portal identifier をキーに collection へ import されました。ブラウザーに基づく findings は同じ identifier と一貫した schema で別 collection に保存されました。これにより、次の問いに答えやすくなりました。
- まだ triage していないポータルはどれか。
- 共有プラットフォームはどれか。
- custom crawler が必要なのはどれか。
- live browser evidence から来た finding はどれか。
- その判断時にエージェントは何を見たのか。
会話の記憶に頼るのではなく、データから再開できるようになりました。
台帳が必要になる理由
Career crawling は 1 回きりのスクリプトではありません。運用ワークフローです。
ポータルは求人 URL を enqueue します。fetch worker はページを訪問します。parse worker は listing を抽出し、index します。完了する項目もあれば、既存の出力データがあるため skip されるものもあります。一時的に失敗するもの、dead letter になるもの、lease が切れて復旧が必要になるものもあります。
そのため Workflow Ledger が重要になります。運用可視性のない crawler pipeline は、ユーザーに推測を強います。台帳は Guida に、実行、項目、状態、段階、イベント、再試行、失敗、出力データを説明する場所を与えます。
この圧力が、専用の Workflow Ledger Console につながりました。Beyond Tabs では、crawler が終わったのか、詰まったのか、再試行中なのか、静かに壊れたのかをユーザーが推測するべきではありません。アプリがそれを直接示すべきです。
Guida がうまく支えたこと
Guida は作業を確認可能にしました。
Quad layout により、ユーザーは 4 つの実サイトを同時に見られました。MCP history pane は tool call を示しました。Store pane は structured findings を示しました。ブラウザータブは実際のレンダリング済みページを示しました。この組み合わせは、headless log stream よりずっと有用です。Web が Web らしく振る舞ったときに、人間が介入できるからです。
また、Guida は作業を再開可能にしました。Findings がワークスペースストアに移ると、batch は会話記憶や手編集のメモに頼らず、セッションをまたいで続けられました。