From b471d2c455b160728f11a02966611fe1c2b8a227 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 10 Jul 2026 13:50:35 +0900 Subject: [PATCH] =?UTF-8?q?perf(realestate):=20market=5Fdeals=20=EB=B0=B0?= =?UTF-8?q?=EC=B9=98=20upsert(=EB=8B=A8=EC=9D=BC=20conn)=EB=A1=9C=20collec?= =?UTF-8?q?t=20=EC=86=8D=EB=8F=84=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- realestate-lab/app/db.py | 22 ++++++++++++++++++++++ realestate-lab/app/listing_collector.py | 10 +++++----- realestate-lab/tests/test_listing_db.py | 16 ++++++++++++++++ 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/realestate-lab/app/db.py b/realestate-lab/app/db.py index 0629210..74766ed 100644 --- a/realestate-lab/app/db.py +++ b/realestate-lab/app/db.py @@ -997,6 +997,28 @@ def upsert_market_deal(data: Dict[str, Any]) -> None: VALUES ({','.join(':'+c for c in cols)})""", d) +def bulk_upsert_market_deals(rows: List[Dict[str, Any]]) -> int: + """market_deals 다건 INSERT OR IGNORE (단일 connection). area=None은 skip(무용/bloat 방지). + 저장시도 건수 반환. 기존 upsert_market_deal과 동일 dedup(ux_market_deals_dedup 인덱스).""" + cols = ("house_type", "dong_code", "dong", "complex_name", "area", "deal_type", + "deposit", "monthly_rent", "sale_amount", "deal_ym", "floor", "source") + payload = [] + for data in rows: + if data.get("area") is None: + continue + d = {c: data.get(c) for c in cols} + d["source"] = data.get("source") or "molit" + payload.append(tuple(d[c] for c in cols)) + if not payload: + return 0 + with _conn() as conn: + conn.executemany( + f"INSERT OR IGNORE INTO market_deals ({','.join(cols)}) VALUES ({','.join(['?'] * len(cols))})", + payload, + ) + return len(payload) + + def get_market_deals_for(dong_code, complex_name, area, deal_type, months=6) -> List[Dict[str, Any]]: with _conn() as conn: def _query(cn): diff --git a/realestate-lab/app/listing_collector.py b/realestate-lab/app/listing_collector.py index 7f3f964..d9f474e 100644 --- a/realestate-lab/app/listing_collector.py +++ b/realestate-lab/app/listing_collector.py @@ -7,8 +7,8 @@ import requests from datetime import date from .lawd_codes import LAWD_CODES -from .db import (get_listing_criteria, upsert_listing, upsert_market_deal, - save_listing_collect_log) +from .db import (get_listing_criteria, upsert_listing, + bulk_upsert_market_deals, save_listing_collect_log) logger = logging.getLogger("realestate-lab") @@ -108,7 +108,7 @@ def _molit_call(op: str, lawd: str, ym: str): def _fetch_molit_all(): """대상 동 시군구코드 × 유형 × 최근 2개월 전월세·매매 실거래 수집→market_deals. Returns (saved_count, diag). diag = 'calls=N saved=M [err=<첫에러>]' 관측용.""" - saved = 0 + all_deals = [] calls = 0 first_err = None lawds = set(LAWD_CODES.values()) @@ -128,11 +128,11 @@ def _fetch_molit_all(): d = parser(raw, htype) d["dong_code"] = lawd d["dong"] = (raw.get("umdNm") or "").strip() or None - upsert_market_deal(d) - saved += 1 + all_deals.append(d) except Exception as e: logger.warning("MOLIT 파싱 실패: %s", e) time.sleep(0.3) + saved = bulk_upsert_market_deals(all_deals) diag = f"calls={calls} saved={saved}" + (f" err={first_err}" if first_err else "") return saved, diag diff --git a/realestate-lab/tests/test_listing_db.py b/realestate-lab/tests/test_listing_db.py index ff39f88..1fed16c 100644 --- a/realestate-lab/tests/test_listing_db.py +++ b/realestate-lab/tests/test_listing_db.py @@ -73,6 +73,22 @@ def test_upsert_market_deal_area_null_skipped(): assert cnt == 0 +def test_bulk_upsert_market_deals(monkeypatch): + from app import db + rows = [ + {"house_type":"아파트","dong_code":"11590","dong":"신대방동","complex_name":"OO","area":42.0, + "deal_type":"전세","deposit":43000,"sale_amount":None,"deal_ym":"202606","floor":"5"}, + {"house_type":"아파트","dong_code":"11590","dong":"신대방동","complex_name":"OO","area":42.0, + "deal_type":"전세","deposit":43000,"sale_amount":None,"deal_ym":"202606","floor":"5"}, # dup + {"house_type":"아파트","dong_code":"11590","dong":None,"complex_name":None,"area":None, + "deal_type":"매매","sale_amount":90000,"deal_ym":"202606","floor":"3"}, # area None → skip + ] + n = db.bulk_upsert_market_deals(rows) + assert n == 2 # area=None 1건 skip + deals = db.get_market_deals_for("11590","OO",42.0,"전세",months=120) + assert len(deals) == 1 # dup dedup → 1건 + + def test_get_listing_matches_json_parsed(): """get_listing_matches()도 get_unnotified_listing_matches()와 동일하게 regulation_flags/reasons를 list로 파싱해 반환해야 한다."""