국방/우주항공 분야의 새 프로젝트를 정리하는 리포트를 만들고 있었다. 뉴스 검색만으로는 사업 이름조차
제대로 못 찾아서, 정부 예산 데이터(열린재정)로 13개 대형 사업의 공식 명칭을 확보했고, 그 공공데이터
조회 스크립트는 3편에서 gov-data-lead-finder라는 별도 저장소로 떼어 냈다.
이제 “어떤 기업이 어떤 부분을 담당하는지”를 채울 차례였다. 그런데 정부 조달 사이트 나라장터(조달청)로
아무리 검색해도 13개 대형 사업이 전부 0건이었다.
나라장터가 아니라 D2B였다 — 국방전자조달시스템 API
원인은 검색어가 아니었다. 국방 무기체계 조달은 나라장터가 아니라 방위사업청 자체 시스템인 D2B(국방전자조달시스템)를 따로 쓴다는 걸 뒤늦게 알았다. data.go.kr에서 D2B 쪽 API 4개(계약정보/입찰공고/ 입찰결과/기관코드)를 새로 승인받아 스크립트로 붙였다.
붙이고 나서 13개 프로젝트를 전부 스캔해보니, 이번에도 대형 사업명 자체로는 대부분 0건이었다 — “425사업”, “누리호”, “차세대발사체” 같은 이름으로는 여전히 안 걸렸다. 대신 “함정”, “위성”, “무인”, “잠수함”처럼 넓은 무기체계 카테고리로 검색하니 데이터가 쏟아졌다. 다만 나오는 건 대형 체계종합 계약이 아니라 정비·소모품·소규모 용역 위주였다 — D2B는 DART가 놓치는 하위 협력업체를 보완하는 용도였지, 원청을 찾는 용도는 아니었다.
KRIT — 계약 이전 단계, 국방 R&D 과제공고 크롤링
D2B와 DART(금융감독원 전자공시)는 이미 나온 조달 공고와 체결된 계약을 보여줄 뿐이라는 공통점을 그제야 알아챘다. 그보다 이른 단계, 그러니까 대학 산학협력단이나 중소 연구기관이 지원하는 R&D 공모 단계는 어디서도 안 보였다. 국방기술진흥연구소(KRIT) 사이트에 그 공모 목록이 있길래 붙여보기로 했다.
처음엔 agent-browser로 화면을 직접 조작했는데, 페이지를 새로고침할 때마다 이전에 잡아둔 요소 참조가 깨져서 매번 다시 스냅샷을 떠야 했다. 안정성이 떨어진다고 판단해서, 순수 HTTP 요청으로 바꾸기로 했다. agent-browser의 네트워크 로그로 실제 검색 버튼을 눌렀을 때 나가는 요청을 관찰해서, POST로 보내는 폼 필드 이름을 그대로 알아냈다.
스크립트를 짜면서 테스트부터 먼저 썼는데, 그 과정에서 정규식 버그를
하나 잡았다. 공고 행을 감지하는 정규식이 onclick 속성값 바로 뒤에
닫는 따옴표가 온다고 가정했는데, 실제 값은 fncCtPrjNotiInfo('아이디');
처럼 세미콜론이 하나 더 끼어 있었다. 테스트 픽스처를 실제 관찰한
마크업 그대로 만들어뒀던 덕분에, 실제 사이트에 붙이기 전에 발견됐다.
사이트 자체의 이상한 동작도 하나 확인했다. “위성”으로 검색하면 전체 22건이라고 표시되는데, 실제로 꺼내올 수 있는 행은 20개뿐이었다. 2페이지를 요청하면 페이지 번호는 정상적으로 “현재 2페이지”라고 찍히는데, 목록 자체는 항상 “등록된 정보가 없습니다”로 비어 있었다. 우리 쪽 파싱 문제가 아니라 사이트가 실제로 그렇게 응답하는 걸 직접 확인했다 — 고칠 수 있는 게 아니라서, 이런 경우가 있다는 걸 문서에 남겨두고 넘어갔다.
새 저장소를 만들려다가, 이미 있는 걸 발견했다
D2B와 KRIT 스크립트를 어디에 둘지 고민하다가, 3편에서 이미 한 번
겪었던 것과 똑같은 문제라는 걸 알아챘다 — 이건 원래 프로젝트(고객사
뉴스 리포트)와 성격이 다른 범용 도구였다. 새 저장소를 만들어야겠다고
생각하고 있는데, 확인해보니 gov-data-lead-finder가 이미 있었다.
3편에서 정확히 이 문제를 위해 만들어둔 저장소였는데, 그 사실 자체를
까먹고 있었다. 새로 만드는 대신 거기에 D2B/KRIT 스크립트를 옮겨
넣었다.
선정결과는 PDF 안에 있었다 — OpenAI API PDF 입력
리포트를 다 만들고 나서 나온 질문 하나가 핵심을 찔렀다 — “여전히 연구소나 학교는 안 나오지?” 확인해보니, KRIT 공고 목록 자체엔 누가 지원했는지·선정됐는지 정보가 아예 없었다. 그냥 공모 공고일 뿐이었다.
근데 일부 공고에 “결과 보기” 링크가 달려 있길래 열어봤더니, 진짜 “우선협상대상자 선정결과” PDF가 나왔다. 하나를 다운받아 확인해보니 실제 선정 기업명이 그대로 적혀 있었다(이번 샘플은 대학이 아니라 LIG넥스원이었다). Claude Code의 Read 툴이 PDF를 별도 라이브러리 없이 그대로 읽어줘서, 확인 자체는 어렵지 않았다. 다만 결과가 걸린 공고 자체가 일부뿐이라 커버리지가 좁고, 있는 것도 하나하나 열어봐야 해서 이건 나중 과제로 남겨뒀다.
이 얘기가 나온 김에 확인한 게 하나 더 있다 — 이 프로젝트가 실제로
쓰는 LLM이 뭔지 다시 봤더니, 설정 파일에 죽어있는 GEMINI_MODEL
값과 실제로 호출되는 OPENAI_MODEL(gpt-5.6-luna)이 따로 있었다.
그리고 이 모델을 부르는 OpenAI API에 PDF를 직접 입력으로 받는
기능이 있다는 것도 공식 문서로 확인했다(이 모델도 쓸 수 있을 것으로 보였다) — 지금 파이프라인은 순수 텍스트만
보내는 구조라 아직 안 쓰고 있지만, 나중에 이런 PDF를 파이프라인
안에서 직접 처리하고 싶을 때 쓸 수 있는 길이 있다는 것만 확인해뒀다.
NTIS — 원래 찾던 게 여기 있었다 (Open API “허용되지 않은 접근입니다”)
KRIT PDF는 커버리지가 좁아서, 더 나은 원천이 있는지 찾아보니
NTIS(국가과학기술지식정보서비스)가 나왔다. 전 부처의 국가 R&D 과제를
전부 모아두는 시스템이고, 매뉴얼을 받아 확인해보니 응답 필드에
ResearchAgency(과제수행기관명)가 그대로 있었다. PerformAgent
필드는 아예 대학/출연연구소/국공립연구소/중소기업/대기업으로
기관 유형까지 분류해준다. 원래 찾던 게 정확히 여기 있었다.
문제는 지금 갖고 있는 키가 개발단계 자동승인 키라서, 매뉴얼 샘플 검색어(“나노”) 말고는 뭘 넣어도 “허용되지 않은 접근입니다”로 막혔다. 처음엔 국방 관련 검색어라서 막히는 건가 싶었는데, “인공지능”처럼 국방과 전혀 무관한 단어로도 똑같이 막히는 걸 확인하고서야 이게 보안 필터가 아니라 그냥 개발단계 키의 제약이라는 걸 알았다. 운영단계 심의승인을 신청해뒀고, 이 글을 쓰는 시점까지는 아직 승인 대기 중이다.
지금까지 엮은 것
국방 프로젝트 하나를 제대로 추적하려고 뉴스, DART, 열린재정, 나라장터, D2B, KRIT까지 여섯 개 시스템을 엮게 됐다. 각각 다른 단계(예산 편성 → 조달 공고 → 계약 체결 → R&D 공모 → 선정결과)를 보여주고, 어느 하나도 전체 그림을 혼자 주지 않았다. NTIS 승인이 떨어지면, 지금까지 못 채웠던 “어느 대학·연구소가 참여하는지”까지 채워질 것 같다. 다음 편은 그때.