Files
web-page-backend/realestate-lab/docs/superpowers/specs/2026-07-09-naver-listing-worker-design.md

9.1 KiB

네이버 매물 fetch 워커 (naver-fetch) — 설계

상태: 설계 승인됨(2026-07-09) → 구현 계획(writing-plans) 대기 관련: 2026-07-09-매물알림-안전마진-design(매물 파이프라인 본체), infra_distributed_workers 메모리(워커↔NAS 계약·관측)

1. 배경 / 목표

매물 알림 파이프라인(2026-07-09 배포)에서 국토부 실거래(합법 baseline)는 정상이나, 네이버 호가 매물 수집은 NAS(datacenter IP)에서 ReadTimeout/429로 차단되어 자동 매물 유입이 막혀 있다. → 네이버 fetch를 **Windows AI 머신(192.168.45.59, 가정 IP)**의 신규 워커에서 수행해 NAS로 push한다. 기존 render/trade-monitor 워커와 동형 패턴.

성공 기준: 워커가 대상 동의 네이버 호가 매물을 주기적으로 fetch → NAS가 upsert→매칭→(안전마진 판정 포함)→신규 매물 텔레그램 알림. 워커는 /infra에서 관측 가능. 네이버가 일시 차단돼도 실거래·안전마진·청약 무중단.

2. 비목표 (Out of Scope)

  • 네이버 인증 토큰 획득 전략(SPA Bearer JWT 등) — web-ai 워커의 구현·병렬 spike로 확인(BE 계약과 독립).
  • 단지(complex)별 심층 크롤·과거 backfill — MVP는 동(법정동)별 article list만.
  • 안전마진/적정성 판정 로직 — 이미 존재·불변(listing_matcher).
  • market_deals 수집 성능 개선(행별 개별 _conn upsert가 대량시 느림) — 별개 후속(관측됨, §9 참조).

3. 아키텍처 (3-repo 분담)

[web-ai: naver-fetch 워커]  (Windows 192.168.45.59, WSL2 docker, 가정 IP)
  자체 스케줄 루프(2~3h, 낮 시간):
   1. GET  NAS /api/internal/realestate/targets            (X-Internal-Key)
        → {dongs:[{dong, cortar_no}], deal_types, page_limit}
   2. 네이버 new.land.naver.com/api/articles?cortarNo=<10자리>&... 동별 fetch
        (인증 토큰 확보는 워커 책임 — 병렬 spike)
   3. POST NAS /api/internal/realestate/listings-ingest    (X-Internal-Key)
        {fetched_at, batches:[{dong, articles:[<raw 네이버 article>...]}]}
   4. worker:naver-fetch:heartbeat (EX45, kind=fetcher, state=idle/fetching)

[web-backend: NAS realestate-lab]  (BE 담당)
   - GET  /api/internal/realestate/targets     → criteria.dongs + cortar_no 매핑 + criteria.deal_types
   - POST /api/internal/realestate/listings-ingest
        → 각 article _parse_naver_article(기존·검증) + dong 태깅 → upsert_listing
        → run_listing_matching() + notify_new_listings()  (기존 재사용)
        → {received, new, matched}
   - node_monitor.WORKER_REGISTRY += naver-fetch(kind=fetcher)
   - nginx: /api/internal/realestate/ → realestate-lab:8000 (IP 화이트리스트)
   - collect_listings의 _fetch_naver_all deprecated (MOLIT 실거래 cron은 유지)

[web-ui: /infra]  (FE 담당, 소규모)
   - fetcher kind 노드 시각(statusVisual 색·라벨)

설계 원칙: 워커는 무상태 얇은 프록시("네이버 IP차단 우회 fetch"만). 파싱·dedup·매칭·알림·상태는 전부 NAS. 파싱은 NAS의 검증된 _parse_naver_article 한 곳에서만 유지(네이버 포맷 변경 시 BE만 수정).

4. 계약 (BE 소유 — cross-repo lock)

4.1 GET /api/internal/realestate/targets (X-Internal-Key)

워커가 "무엇을 긁을지" pull.

{
  "dongs": [
    {"dong": "신대방동", "cortar_no": "1159010100"},
    {"dong": "봉천동",   "cortar_no": "1162010100"}
  ],
  "deal_types": ["전세", "반전세", "매매"],
  "page_limit": 2
}
  • dongs: listing_criteria.dongs 각각에 대해 NAVER_CORTAR[dong](10자리 법정동코드) 조인. 매핑 없는 동은 제외(+경고 로그).
  • deal_types: listing_criteria.deal_types.
  • page_limit: 동당 fetch 페이지 수 상한(기본 2). 상수/env.
  • 인증 실패(키 불일치/누락) → 401.

4.2 POST /api/internal/realestate/listings-ingest (X-Internal-Key)

워커가 네이버 raw 매물 push.

{
  "fetched_at": "2026-07-09T05:44:23Z",
  "batches": [
    {"dong": "신대방동", "articles": [ { /* 네이버 articleList item raw */ }, ... ]},
    {"dong": "봉천동",   "articles": [ ... ]}
  ]
}
  • 각 batch의 각 article → _parse_naver_article(article)d["dong"] = batch.dongupsert_listing(d). article별 try/except(불량 1건 skip).
  • 전체 upsert 후 run_listing_matching() + notify_new_listings() 호출(신규 매물만 알림, 멱등).
  • 응답: {"received": N, "new": M, "matched": K} (received=처리 article 수, new=신규 upsert 수, matched=이번 호출에서 신규 텔레그램 발송 건수(notify_new_listings().sent) — 누적 통과 매칭 수가 아님).
  • 인증 실패 → 401. batches 비면 no-op {received:0,new:0,matched:0}.
  • ⚠️ MVP는 인라인 처리(매물 수 수백 규모). 대량 시 upsert만 동기+매칭/알림 BackgroundTask로 분리 여지(§9).

4.3 cortarNo 매핑 (BE)

app/lawd_codes.pyNAVER_CORTAR: dict[str,str](동명→10자리 법정동코드) 추가. 현재 LAWD_CODES(5자리 시군구)는 MOLIT 전용으로 유지 — 네이버는 10자리 필요(기존 _fetch_naver_all이 5자리를 넘기던 버그를 이 매핑으로 해소). 초기 대상 6동:

10자리(구현 시 행정표준코드로 검증)
신대방동 동작구 1159010100
대방동 동작구 1159010200
상도동 동작구 1159010600
봉천동 관악구 1162010100
대림동 영등포구 1156013600
신길동 영등포구 1156013200

위 코드는 구현 시 행정표준코드관리시스템으로 최종 검증(법정동 통폐합 반영).

4.4 heartbeat (워커→Redis, 관측 계약)

worker:naver-fetch:heartbeat EX45, 값 JSON:

{"name":"naver-fetch","kind":"fetcher","state":"idle|fetching","ts":<epoch>,
 "last_fetch_at":<epoch|null>,"listings_pushed":<int>,"errors":<int>}
  • kind="fetcher"는 신규(기존 render/watcher/trader에 추가). state 의미: idle=대기, fetching=수집중.

5. 관측 (팀 규칙 3단계 — infra_distributed_workers)

  1. 워커 heartbeat (§4.4) — web-ai.
  2. BE 등재: agent-office/app/node_monitor.pyWORKER_REGISTRY += {"name":"naver-fetch","kind":"fetcher","queue":None}. collect_status()fetcher kind를 처리(heartbeat 유무→alive/dead, link type=http-pull(ai_trade/trade-monitor와 동일, redis-queue 아님)). 다운/복구 텔레그램 경보는 기존 1분 cron 재사용(_notified 패턴).
  3. FE: /infra statusVisualfetcher 라벨·색 + http-pull 링크. (web-ui, 소규모)

6. 인증 / 보안

  • nginx: 신규 location /api/internal/realestate/realestate-lab:8000, IP 화이트리스트(기존 render 워커 화이트리스트에 쓰는 Windows 워커 IP 재사용). (nginx-conf 변경 → 배포)
  • 앱 레벨: realestate-lab이 X-Internal-Key 검증(env REALESTATE_INTERNAL_KEY). 다른 서비스 internal_router 패턴 준용. GET targets·POST ingest 둘 다 검증.
  • 워커는 .env로 키 주입(커밋 금지).

7. 에러처리 / 멱등성

  • 워커: 네이버 차단/토큰만료 → 사이클 skip, 다음 폴링 재시도. heartbeat는 계속 발신(생사 가시성). errors 카운트 증가.
  • NAS ingest: article별 try/except(1건 실패 격리). 멱등 — listings.article_no UNIQUE upsert + listing_matches.notified_at → 같은 매물 재push해도 재알림 없음.
  • NAS collect_listings: _fetch_naver_all 미호출로 변경(MOLIT만). naver_ok/naver diag는 collect_log에서 의미 상실 → MOLIT 전용 로그로 정리(컬럼은 nullable 유지, 하위호환).

8. 테스트 (BE)

  • test_internal_targets: criteria seed 후 GET → dongs에 cortar_no 조인, deal_types 반환. 매핑 없는 동 제외.
  • test_internal_ingest: raw 네이버 article batch POST → upsert_listing 호출 + run_listing_matching/notify_new_listings 호출(mock) + {received,new,matched}. 멱등(재POST시 new=0).
  • test_internal_auth: X-Internal-Key 없거나 틀리면 401.
  • test_naver_cortar_mapping: NAVER_CORTAR 10자리·대상 6동.
  • 워커 테스트는 web-ai(별도).

9. 후속 / 알려진 개선점 (비차단)

  • market_deals/listings upsert가 행별 개별 _conn() 커밋 → 대량 시 Celeron에서 느림(관측: 매매 승인 후 collect 수 분). 배치 트랜잭션(단일 _conn executemany)로 개선 여지.
  • 네이버 인증 토큰 만료·갱신 자동화(워커, web-ai).
  • page_limit·수집 주기 튜닝(네이버 rate-limit 관측 후).

10. 의존성 / 분담 요약

repo 작업 세션
web-backend targets/ingest 엔드포인트, X-Internal-Key, NAVER_CORTAR 매핑, node_monitor fetcher 등재, nginx /api/internal/realestate/, _fetch_naver_all deprecate BE(이 세션)
web-ai naver-fetch 워커(폴링+네이버 fetch+토큰+push+heartbeat) + Dockerfile _shared 포함 확인 AI (병렬 spike 포함)
web-ui /infra fetcher 노드 시각 FE (소규모)

계약(§4)은 co-gahusb로 잠그고 3세션 병렬. 계약 변경 시 web-ai+web-backend(+web-ui) 동시 수정.