← 모든 프로젝트

등기 사건 자동 입찰 프로그램

마켓에 올라온 등기 사건을 다른 사무소보다 먼저 잡는 윈도우 앱, 캡차까지 직접 풀어요

기간
2026년 7월 – 2026년 9월
대상
법무사 사무소
역할
단독: 리버스 엔지니어링, 개발, 윈도우 배포, 원격 지원
기술
Python, Chrome DevTools Protocol, Selenium, numpy CNN, tkinter, PyInstaller, GitHub Actions
규모
커밋 234개, 34k줄 (앱 21k, 테스트 11k), 테스트 493개
외주 작업, neoworks

다른 언어: english

어떤 프로그램인가요

법무사 사무소에서 쓰는 윈도우 앱이에요. 법무사들이 부동산 등기 사건을 가져가는 마켓이 있는데, 좋은 사건은 몇 분 안에 사라지고, 대부분 오전 11시 전후의 짧은 시간에 몰려 있어요. 사무소는 5초마다 페이지를 새로고침하는 오래된 매크로를 쓰고 있었어요. 제 일은 그걸 대체하는 거였어요. 더 빠르게 감시하고, 6자리 캡차를 풀고, 사무소가 처리할 수 없는 날짜는 건너뛰고, 날짜당 2건이라는 사이트 제한을 지키면서 수락 단계까지 진행하는 거죠.

기존 매크로는 Delphi 런처가 PyInstaller exe를 실행하는 구조였어요. 저는 그걸 풀어서 파이썬 코드를 디컴파일하고, 엔드포인트와 셀렉터, 캡차 모델 가중치를 복구했어요. 다만 이건 출발점이었지, 정답은 아니었어요.

실제 사이트에서만 드러난 버그 네 개

7월 22일에 고객 계정으로 로그인해서 실제 공고를 대상으로 프로그램을 돌려 봤어요. 서로 다른 결함이 네 개 나왔는데, 그중 하나만 있어도 사건을 하나도 못 잡는 상황이었어요.

  • 엉뚱한 탭. 이전 버전은 마켓 탭 하나만 폴링했어요. 어떤 필터를 걸어도 0건이 나왔는데, 실제 공고는 다른 탭에 있었어요. 지금은 두 탭을 모두 폴링하고 공고 id 기준으로 합쳐요.
  • 로케이터 종류. 셀렉터가 "css" 같은 짧은 이름으로 저장돼 있었는데, Selenium은 "css selector"를 기대해요. css 조회가 전부 예외를 던졌고, 그래서 마지막 수락 과정은 한 번도 실행된 적이 없었어요.
  • 캡차 크기. 코드가 캡차 요소를 스크린샷으로 찍고 있었는데, 그건 250x72 이미지를 찌그러뜨리고 테두리까지 붙인 사본이었어요. 모델은 이걸 0.94 신뢰도로 빈 문자열이라고 읽었고, 함께 들어 있던 테스트 샘플은 계속 통과했어요. 지금은 원본 이미지 바이트를 직접 읽어요.
  • 111초 걸리던 달력 읽기. 일일 한도 확인 코드가 3초 implicit wait 상태에서 달력 칸마다 find_elements를 호출했고, 빈 칸마다 그 3초를 다 기다렸어요. execute_script 한 번으로 바꾸니 2초가 됐어요.

그다음 며칠 동안, 이 사무소가 받을 수 있는 사건은 기존 코드가 알던 버튼과 다른 버튼을 쓴다는 것, 그리고 남아 있던 "캡차 실패"는 사실 일일 한도 때문이었다는 게 드러났어요. 직접 확인해 보니 캡차는 제대로 읽고 있었어요.

캡차를 두 번 풀다

물려받은 모델은 캡차 하나 기준으로 64% 맞혔어요. 간단한 수정을 세 가지 해 봤는데 전부 오히려 나빠졌어요. 그래서 렌더링한 캡차 15만 장과 직접 라벨링한 실제 캡차 100장으로 고정 길이 6-head CNN을 학습시켰어요. 합성 데이터만으로는 66~68%였고, 실제 데이터를 넣으니 따로 떼어 둔 50장에서 96%가 나왔어요. 순수 numpy로 18ms에 돌아가서 기존 약 480ms보다 훨씬 빠르고, TensorFlow를 빼니 번들이 약 436MB 줄었어요.

8월 4일에 사이트가 JSON API 기반의 새 SPA로 바뀌었어요. 기존 엔드포인트는 전부 404를 돌려줬어요. 새 캡차는 숫자는 같은데 캔버스가 더 커져서(335x100) 모델 정확도가 1%로 떨어졌어요. 해결책은 리사이즈가 아니라 크롭이었어요. 사이트가 사건을 보기 전에 캡차부터 검사했기 때문에, 사무소가 이미 처리를 마친 사건 하나에 대고 부작용 없이 캡차 인식을 테스트할 수 있었어요. 실제 캡차 300장을 그 검사에 돌려서 95%가 나왔어요.

프로그램이 너무 많이 했을 때

어느 날 아침, 프로그램이 사무소 관할에서 한참 떨어진 도시의 사건을 찾아서 아무도 자리에 없는 상태로 승인 코드를 요청했어요. 코드를 입력하지 않았으니 실제로 확정된 건 없었지만, 거기까지 가면 안 되는 거였어요. 시작 버튼을 누른 걸 그 이후에 찾은 모든 사건에 대한 동의로 취급하고 있었던 거예요.

최종적으로는 이렇게 정리했어요. 지역 필터(기본값은 서울과 인근 도시 네 곳)가 사건을 열기 전에 목록 데이터에서 먼저 걸러서, 관할 밖 사건에는 요청을 하나도 보내지 않아요. 프로그램은 캡차를 풀고, 약관에 체크하고, 승인 코드를 요청한 다음 브라우저 창을 사람에게 넘겨요. 사람이 6자리 숫자를 입력하고 보험료 결제 방식을 고르는 거죠. 테스트 하나가 소스를 읽어서, 프로그램이 결제 버튼을 누르는 코드가 생기면 실패하게 해 뒀어요.

브라우저 쪽은 CDP로 제어하는 Chrome으로 옮겼고, 폴링은 페이지 자체의 fetch로 하게 했어요. 따로 API 로그인을 하면 계정에 세션이 하나 더 생겨서 고객이 계속 로그아웃되는 문제가 있었거든요. 나중에 실제 사이트에서 다시 돌려 보니, 테스트 픽스처가 사이트보다 너그러워서 가려져 있던 버그가 네 개 더 나왔어요. 그중 하나는 세션 스토리지의 토큰이 JSON으로 감싸져 있어서 헤더가 Bearer {"token":"..."}가 됐고, 서버는 이걸 만료된 토큰으로 처리했어요.

느린 부분 중에는 제가 고칠 수 없는 것도 있었어요. 11시 몰리는 시간에는 목록부터 승인 코드까지 서버 시간만 약 49초가 걸렸어요. 반대로 제 잘못인 실패도 하나 있었어요. 결제 팝업 때문에 넣어 둔 예전 우회 코드가, 사이트가 결제 스크립트를 불러오는 방식을 바꾼 뒤로는 그 스크립트가 아예 로드되지 않게 막아 버렸어요.

지금 상태

마켓마다 하나씩, 프로그램을 두 개로 나눴어요. 모드 전환 스위치가 잘못된 설정에 있어도 화면상으로는 똑같아 보였기 때문이에요. 입찰 프로그램은 목록을 폴링하는 대신 1분에 한 번 사이트의 알림 개수를 확인하면서 기다려서, 봇처럼 덜 보여요. 자동 업데이트는 새 빌드가 실행되지 않으면 롤백해요. 마지막 릴리스는 v3.9.15예요. 남은 문제는 사무소 PC 한 대에서 Windows Smart App Control이 서명되지 않은 exe를 막는 건데, 이건 코드 서명으로만 해결할 수 있어요.