192 lines
7.6 KiB
Python
192 lines
7.6 KiB
Python
import os
|
|
import requests
|
|
import xml.etree.ElementTree as ET
|
|
from datetime import datetime
|
|
from typing import List, Dict, Optional
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv()
|
|
|
|
class PublicDataAPIClient:
|
|
"""국토교통부 공공데이터 API 클라이언트"""
|
|
|
|
def __init__(self):
|
|
"""API 키 및 기본 설정 초기화"""
|
|
self.base_url = "http://apis.data.go.kr/1613000"
|
|
|
|
# 통합 API 키
|
|
self.api_key = os.getenv("PUBLIC_DATA_API_KEY")
|
|
|
|
# API 엔드포인트 매핑
|
|
self.endpoints = {
|
|
# 아파트
|
|
("아파트", "매매"): f"{self.base_url}/RTMSDataSvcAptTrade/getRTMSDataSvcAptTrade",
|
|
("아파트", "전세"): f"{self.base_url}/RTMSDataSvcAptRent/getRTMSDataSvcAptRent",
|
|
("아파트", "월세"): f"{self.base_url}/RTMSDataSvcAptRent/getRTMSDataSvcAptRent",
|
|
# 오피스텔
|
|
("오피스텔", "매매"): f"{self.base_url}/RTMSDataSvcOffiTrade/getRTMSDataSvcOffiTrade",
|
|
("오피스텔", "전세"): f"{self.base_url}/RTMSDataSvcOffiRent/getRTMSDataSvcOffiRent",
|
|
("오피스텔", "월세"): f"{self.base_url}/RTMSDataSvcOffiRent/getRTMSDataSvcOffiRent",
|
|
# 빌라/연립
|
|
("빌라", "매매"): f"{self.base_url}/RTMSDataSvcSHTrade/getRTMSDataSvcSHTrade",
|
|
("빌라", "전세"): f"{self.base_url}/RTMSDataSvcSHRent/getRTMSDataSvcSHRent",
|
|
("빌라", "월세"): f"{self.base_url}/RTMSDataSvcSHRent/getRTMSDataSvcSHRent",
|
|
("연립주택", "매매"): f"{self.base_url}/RTMSDataSvcSHTrade/getRTMSDataSvcSHTrade",
|
|
("연립주택", "전세"): f"{self.base_url}/RTMSDataSvcSHRent/getRTMSDataSvcSHRent",
|
|
# 주택
|
|
("주택", "매매"): f"{self.base_url}/RTMSDataSvcSHTrade/getRTMSDataSvcSHTrade",
|
|
("주택", "전세"): f"{self.base_url}/RTMSDataSvcSHRent/getRTMSDataSvcSHRent",
|
|
("단독주택", "매매"): f"{self.base_url}/RTMSDataSvcSHTrade/getRTMSDataSvcSHTrade",
|
|
("다가구주택", "매매"): f"{self.base_url}/RTMSDataSvcSHTrade/getRTMSDataSvcSHTrade"
|
|
}
|
|
|
|
def get_real_estate_data(
|
|
self,
|
|
property_type: str,
|
|
transaction_type: str,
|
|
region_code: str,
|
|
deal_ymd: Optional[str] = None
|
|
) -> List[Dict]:
|
|
"""
|
|
부동산 실거래가 데이터 조회
|
|
|
|
Args:
|
|
property_type: 매물 형태 (아파트, 오피스텔, 빌라 등)
|
|
transaction_type: 거래 유형 (매매, 전세, 월세)
|
|
region_code: 지역 코드 (5자리)
|
|
deal_ymd: 계약년월 (YYYYMM 형식, 기본값: 현재 월)
|
|
|
|
Returns:
|
|
실거래가 데이터 리스트
|
|
"""
|
|
|
|
# 기본값 설정
|
|
if not deal_ymd:
|
|
deal_ymd = datetime.now().strftime("%Y%m")
|
|
|
|
# 매물 형태 정규화
|
|
property_type = self._normalize_property_type(property_type)
|
|
transaction_type = self._normalize_transaction_type(transaction_type)
|
|
|
|
# 엔드포인트 찾기
|
|
endpoint_url = self.endpoints.get((property_type, transaction_type))
|
|
if not endpoint_url:
|
|
return []
|
|
|
|
if not self.api_key or self.api_key == "your-public-data-api-key-here":
|
|
print(f"Warning: PUBLIC_DATA_API_KEY not configured")
|
|
return []
|
|
|
|
# API 호출
|
|
params = {
|
|
"serviceKey": self.api_key,
|
|
"LAWD_CD": region_code,
|
|
"DEAL_YMD": deal_ymd,
|
|
"pageNo": 1,
|
|
"numOfRows": 100
|
|
}
|
|
|
|
try:
|
|
response = requests.get(endpoint_url, params=params, timeout=10)
|
|
response.raise_for_status()
|
|
|
|
# XML 파싱
|
|
return self._parse_xml_response(response.text, property_type, transaction_type)
|
|
|
|
except Exception as e:
|
|
print(f"Error fetching data: {e}")
|
|
return []
|
|
|
|
def _normalize_property_type(self, property_type: str) -> str:
|
|
"""매물 형태 정규화"""
|
|
if not property_type:
|
|
return "아파트"
|
|
|
|
property_type = property_type.strip().lower()
|
|
|
|
# 매핑 테이블
|
|
mappings = {
|
|
"아파트": ["아파트", "apt", "apartment"],
|
|
"오피스텔": ["오피스텔", "officetel", "오피스"],
|
|
"빌라": ["빌라", "villa", "빌딩"],
|
|
"연립주택": ["연립", "연립주택"],
|
|
"주택": ["주택", "house"],
|
|
"단독주택": ["단독", "단독주택"],
|
|
"다가구주택": ["다가구", "다가구주택"]
|
|
}
|
|
|
|
for normalized, variants in mappings.items():
|
|
for variant in variants:
|
|
if variant in property_type:
|
|
return normalized
|
|
|
|
return "아파트" # 기본값
|
|
|
|
def _normalize_transaction_type(self, transaction_type: str) -> str:
|
|
"""거래 유형 정규화"""
|
|
if not transaction_type:
|
|
return "매매"
|
|
|
|
transaction_type = transaction_type.strip().lower()
|
|
|
|
if "매매" in transaction_type or "매도" in transaction_type:
|
|
return "매매"
|
|
elif "전세" in transaction_type:
|
|
return "전세"
|
|
elif "월세" in transaction_type or "렌트" in transaction_type:
|
|
return "월세"
|
|
|
|
return "매매" # 기본값
|
|
|
|
def _parse_xml_response(self, xml_text: str, property_type: str, transaction_type: str) -> List[Dict]:
|
|
"""XML 응답 파싱"""
|
|
try:
|
|
root = ET.fromstring(xml_text)
|
|
|
|
# 응답 코드 확인
|
|
result_code = root.find(".//resultCode")
|
|
|
|
items = root.findall(".//item")
|
|
results = []
|
|
|
|
for item in items:
|
|
data = {}
|
|
|
|
# 공통 필드
|
|
data["거래유형"] = transaction_type
|
|
data["매물형태"] = property_type
|
|
data["년"] = self._get_xml_value(item, "dealYear")
|
|
data["월"] = self._get_xml_value(item, "dealMonth")
|
|
data["일"] = self._get_xml_value(item, "dealDay")
|
|
data["법정동"] = self._get_xml_value(item, "umdNm")
|
|
data["지번"] = self._get_xml_value(item, "jibun")
|
|
data["전용면적"] = self._get_xml_value(item, "excluUseAr")
|
|
data["층"] = self._get_xml_value(item, "floor")
|
|
|
|
# 아파트/오피스텔 특화 필드
|
|
if property_type in ["아파트", "오피스텔"]:
|
|
data["아파트"] = self._get_xml_value(item, "aptNm")
|
|
data["건축년도"] = self._get_xml_value(item, "buildYear")
|
|
|
|
# 전월세 특화 필드
|
|
if transaction_type in ["전세", "월세"]:
|
|
data["보증금액"] = self._get_xml_value(item, "deposit")
|
|
data["월세금액"] = self._get_xml_value(item, "monthlyRent")
|
|
|
|
if transaction_type in ["매매"]:
|
|
data["거래금액"] = self._get_xml_value(item, "dealAmount")
|
|
|
|
results.append(data)
|
|
|
|
return results
|
|
|
|
except Exception as e:
|
|
print(f"Error parsing XML: {e}")
|
|
return []
|
|
|
|
def _get_xml_value(self, element, tag: str) -> Optional[str]:
|
|
"""XML 요소에서 값 추출"""
|
|
found = element.find(tag)
|
|
if found is not None and found.text:
|
|
return found.text.strip()
|
|
return None |