Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EqCYBhvTcdeCTUDX3RhWx9
9.0 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수집 성능 개선(행별 개별_connupsert가 대량시 느림) — 별개 후속(관측됨, §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.dong→upsert_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=passed match 수). - 인증 실패 → 401.
batches비면 no-op{received:0,new:0,matched:0}. - ⚠️ MVP는 인라인 처리(매물 수 수백 규모). 대량 시 upsert만 동기+매칭/알림 BackgroundTask로 분리 여지(§9).
4.3 cortarNo 매핑 (BE)
app/lawd_codes.py에 NAVER_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)
- 워커 heartbeat (§4.4) — web-ai.
- BE 등재:
agent-office/app/node_monitor.py의WORKER_REGISTRY += {"name":"naver-fetch","kind":"fetcher","queue":None}.collect_status()가fetcherkind를 처리(heartbeat 유무→alive/dead, link type=http-pull(ai_trade/trade-monitor와 동일, redis-queue 아님)). 다운/복구 텔레그램 경보는 기존 1분 cron 재사용(_notified패턴). - FE:
/infrastatusVisual에fetcher라벨·색 + http-pull 링크. (web-ui, 소규모)
6. 인증 / 보안
- nginx: 신규 location
/api/internal/realestate/→realestate-lab:8000, IP 화이트리스트(기존 render 워커 화이트리스트에 쓰는 Windows 워커 IP 재사용). (nginx-conf 변경 → 배포) - 앱 레벨: realestate-lab이
X-Internal-Key검증(envREALESTATE_INTERNAL_KEY). 다른 서비스internal_router패턴 준용. GET targets·POST ingest 둘 다 검증. - 워커는
.env로 키 주입(커밋 금지).
7. 에러처리 / 멱등성
- 워커: 네이버 차단/토큰만료 → 사이클 skip, 다음 폴링 재시도. heartbeat는 계속 발신(생사 가시성). errors 카운트 증가.
- NAS ingest: article별 try/except(1건 실패 격리). 멱등 —
listings.article_noUNIQUE 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_CORTAR10자리·대상 6동.- 워커 테스트는 web-ai(별도).
9. 후속 / 알려진 개선점 (비차단)
market_deals/listingsupsert가 행별 개별_conn()커밋 → 대량 시 Celeron에서 느림(관측: 매매 승인 후 collect 수 분). 배치 트랜잭션(단일_connexecutemany)로 개선 여지.- 네이버 인증 토큰 만료·갱신 자동화(워커, 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) 동시 수정.