사례 연구: Beyond Tabs를 위해 브라우저 기반 채용 페이지 크롤러 만들기
Beyond Tabs는 인터넷의 모든 일자리를 색인하려는 사이트가 아닙니다.
대상은 더 좁습니다. 니치 프로그래밍 언어의 일자리와 그 주변 기술 커뮤니티입니다. 그래서 crawling 문제가 흥미로워집니다. 유용한 결과는 “이 회사에 채용 공고가 있다”가 아닙니다. “이 source에는 Beyond Tabs 독자에게 관련 있을 수 있는 일이 포함되어 있을지도 모른다”입니다.
현재 과제는 careers pipeline입니다. 수백 개 회사가 채용 포털을 갖고 있습니다. 익숙한 ATS를 쓰는 곳도 있고, 공유 플랫폼 위에 브랜드 페이지를 얹는 곳도 있습니다. 링크 몇 개만 있는 custom site도 있습니다. 무시해야 할 aggregator도 있습니다. JavaScript가 실행되고, cookie banner가 닫히고, 나이 확인을 통과하고, 국가별 리디렉션이 끝난 뒤에야 실제 구조가 보이는 곳도 있습니다.
Guida는 이런 작업을 위해 만들어졌습니다.
문제는 scraping만이 아니었다
모든 회사가 같은 ATS를 사용한다면 쉽습니다. 플랫폼을 감지하고, API를 호출하고, 결과를 정규화하면 끝입니다.
현실의 웹은 그렇지 않습니다.
어떤 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에서 시작합니다.
- 워크스페이스 저장소에서 미분류 포털을 고릅니다.
- 네 개의 브라우저 탭을 재사용합니다.
- 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은 일회성 스크립트가 아닙니다. 운영 워크플로입니다.
포털은 채용 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 덕분에 사용자는 네 개의 실제 사이트를 동시에 볼 수 있었습니다. MCP history pane은 tool call을 보여 줬습니다. Store pane은 structured findings를 보여 줬습니다. 브라우저 탭은 실제 렌더링된 페이지를 보여 줬습니다. 이 조합은 headless log stream보다 훨씬 유용합니다. 웹이 웹답게 행동할 때 사람이 개입할 수 있기 때문입니다.
또한 Guida는 작업을 다시 시작 가능하게 만들었습니다. Findings가 워크스페이스 저장소로 옮겨지자 batch는 대화 기억이나 손으로 편집한 메모에 의존하지 않고 세션을 넘어 이어질 수 있었습니다.