budongsan-api
Version:
국토교통부 아파트 실거래가, 전월세, 단지 정보, 용적률 등 공공데이터 API 래퍼
392 lines (387 loc) • 15.1 kB
JavaScript
// node_modules/tsup/assets/esm_shims.js
import { fileURLToPath } from "url";
import path from "path";
var getFilename = () => fileURLToPath(import.meta.url);
var getDirname = () => path.dirname(getFilename());
var __dirname = /* @__PURE__ */ getDirname();
// src/budongsan-api.ts
import axios from "axios";
var BudongsanAPIClass = class {
/**
* BudongsanAPI 인스턴스를 생성합니다.
* @param serviceKey 공공데이터 포털에서 발급받은 서비스 키
*/
constructor(serviceKey) {
this.serviceKey = serviceKey;
}
/**
* 아파트 단지 기본 정보를 조회합니다.
* @param kaptCode 아파트 단지 코드
* @returns 단지 기본 정보 (object)
* @throws API 호출 실패 시 예외가 발생합니다.
*/
async getApartmentBasicInfo(kaptCode) {
const url = `https://apis.data.go.kr/1613000/AptBasisInfoServiceV4/getAphusBassInfoV4?serviceKey=${this.serviceKey}&kaptCode=${kaptCode}`;
return this.fetchAndExtract(url);
}
/**
* 아파트 단지 상세 정보를 조회합니다.
* @param kaptCode 아파트 단지 코드
* @returns 단지 상세 정보 (object)
* @throws API 호출 실패 시 예외가 발생합니다.
*/
async getApartmentDetailInfo(kaptCode) {
const url = `https://apis.data.go.kr/1613000/AptBasisInfoServiceV4/getAphusDtlInfoV4?serviceKey=${this.serviceKey}&kaptCode=${kaptCode}`;
return this.fetchAndExtract(url);
}
/**
* 시군구 코드에 따른 아파트 단지 목록을 조회합니다.
* @param sigunguCode 시군구 코드
* @param numOfRows 페이지당 결과 수
* @param pageNo 페이지 번호
* @returns 단지 목록 (배열)
* @throws API 호출 실패 시 예외가 발생합니다.
*/
async getApartmentList(sigunguCode, numOfRows = "10000", pageNo = "1") {
const url = `https://apis.data.go.kr/1613000/AptListService3/getSigunguAptList3?serviceKey=${this.serviceKey}&sigunguCode=${sigunguCode}&numOfRows=${numOfRows}&pageNo=${pageNo}`;
return this.fetchAndExtract(url);
}
/**
* 특정 거래 년월의 아파트 실거래가(기본)를 조회합니다.
* @param sigunguCode 시군구 코드 (5자리)
* @param DEAL_YMD 거래 년월 (YYYYMM)
* @param numOfRows 페이지당 결과 수
* @param pageNo 페이지 번호
* @returns 실거래 정보 목록 (배열)
* @throws API 호출 실패 시 예외가 발생합니다.
*/
async getApartmentTradeBasicList(sigunguCode, DEAL_YMD, numOfRows = "10000", pageNo = "1") {
const url = `https://apis.data.go.kr/1613000/RTMSDataSvcAptTrade/getRTMSDataSvcAptTrade?serviceKey=${this.serviceKey}&LAWD_CD=${sigunguCode}&DEAL_YMD=${DEAL_YMD}&numOfRows=${numOfRows}&pageNo=${pageNo}`;
return this.fetchAndExtract(url);
}
/**
* 특정 거래 년월의 아파트 실거래가(상세)를 페이지 단위로 조회합니다.
* @param sigunguCode 시군구 코드 (5자리)
* @param DEAL_YMD 거래 년월 (YYYYMM)
* @param numOfRows 페이지당 결과 수
* @param pageNo 페이지 번호
* @returns 실거래 상세 정보 목록 (배열)
* @throws API 호출 실패 시 예외가 발생합니다.
*/
async getApartmentTradeDetailList(sigunguCode, DEAL_YMD, numOfRows = "10000", pageNo = "1") {
const url = `https://apis.data.go.kr/1613000/RTMSDataSvcAptTradeDev/getRTMSDataSvcAptTradeDev?serviceKey=${this.serviceKey}&pageNo=${pageNo}&numOfRows=${numOfRows}&LAWD_CD=${sigunguCode}&DEAL_YMD=${DEAL_YMD}`;
return this.fetchAndExtract(url);
}
/**
* 특정 거래 년월의 아파트 전월세 정보를 조회합니다.
* @param sigunguCode 시군구 코드 (5자리)
* @param DEAL_YMD 거래 년월 (YYYYMM)
* @param numOfRows 페이지당 결과 수
* @param pageNo 페이지 번호
* @returns 전월세 정보 목록 (배열)
* @throws API 호출 실패 시 예외가 발생합니다.
*/
async getApartmentRentList(sigunguCode, DEAL_YMD, numOfRows = "10000", pageNo = "1") {
const url = `https://apis.data.go.kr/1613000/RTMSDataSvcAptRent/getRTMSDataSvcAptRent?serviceKey=${this.serviceKey}&pageNo=${pageNo}&numOfRows=${numOfRows}&LAWD_CD=${sigunguCode}&DEAL_YMD=${DEAL_YMD}`;
return this.fetchAndExtract(url);
}
/**
* 건축물대장 총괄표제부 정보를 조회합니다.
*
* @param {string} sigunguCode - 시군구 코드 (예: '11710')
* @param {string} bjdongCode - 법정동 코드 (예: '11200')
* @param {string} bun - 번지 (예: '0138')
* @param {string} ji - 지번 (예: '0000')
* @param {string} [numOfRows="10"] - 페이지당 결과 수 (기본값: 10)
* @param {string} [pageNo="1"] - 페이지 번호 (기본값: 1)
* @returns {Promise<any>} 건축물대장 총괄표제부 API 응답 데이터
*/
async getBrRecapTitleList(sigunguCode, bjdongCode, bun, ji, numOfRows = "10", pageNo = "1") {
const baseUrl = "https://apis.data.go.kr/1613000/BldRgstHubService/getBrRecapTitleInfo";
const url = `${baseUrl}?serviceKey=${this.serviceKey}&sigunguCd=${sigunguCode}&bjdongCd=${bjdongCode}&platGbCd=0&bun=${bun}&ji=${ji}&startDate=19800101&endDate=20301231&_type=json&numOfRows=${numOfRows}&pageNo=${pageNo}`;
return this.fetchAndExtract(url);
}
/**
* 공통 fetch 및 응답 처리 로직
*/
async fetchAndExtract(url) {
var _a;
try {
const res = await axios.get(url);
const { header, body } = res.data.response;
if (header.resultCode === "00" || header.resultCode === "000") {
return ((_a = body == null ? void 0 : body.items) == null ? void 0 : _a.item) || (body == null ? void 0 : body.item) || (body == null ? void 0 : body.items);
} else {
throw new Error(`API Error: ${header.resultMsg} (code: ${header.resultCode})`);
}
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(`Network Error: ${error.message}`);
}
throw error;
}
}
};
// src/sigungu-service.ts
import fs from "fs";
import path2 from "path";
import { fileURLToPath as fileURLToPath2 } from "url";
var SigunguServiceClass = class _SigunguServiceClass {
constructor() {
this._dataCache = null;
this._dataPath = this._getLibraryDataPath();
}
_getLibraryDataPath() {
let currentDir;
if (typeof __dirname !== "undefined") {
currentDir = __dirname;
} else if (typeof import.meta.url !== "undefined") {
currentDir = path2.dirname(fileURLToPath2(import.meta.url));
} else {
currentDir = process.cwd();
}
const localPath = path2.join(currentDir, "sigungu.json");
if (fs.existsSync(localPath)) {
return localPath;
}
const parentPath = path2.join(currentDir, "..", "sigungu.json");
if (fs.existsSync(parentPath)) {
return parentPath;
}
const cwd = process.cwd();
const possiblePaths = [
path2.join(cwd, "node_modules", "budongsan-api", "dist", "sigungu.json"),
path2.join(cwd, "node_modules", "budongsan-api", "sigungu.json")
];
for (const possiblePath of possiblePaths) {
if (fs.existsSync(possiblePath)) {
return possiblePath;
}
}
return localPath;
}
static getInstance() {
if (!_SigunguServiceClass._instance) {
_SigunguServiceClass._instance = new _SigunguServiceClass();
}
return _SigunguServiceClass._instance;
}
_loadDataSync() {
if (!this._dataCache) {
try {
const raw = fs.readFileSync(this._dataPath, "utf8");
this._dataCache = JSON.parse(raw);
} catch (error) {
let currentDir;
try {
currentDir = path2.dirname(fileURLToPath2(import.meta.url));
} catch {
currentDir = __dirname;
}
let errorMessage = `sigungu.json \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
`;
errorMessage += `\uC2DC\uB3C4\uD55C \uACBD\uB85C\uB4E4:
`;
const localPath = path2.join(currentDir, "sigungu.json");
errorMessage += `1. \uB85C\uCEEC \uACBD\uB85C: ${localPath} - ${fs.existsSync(localPath) ? "\uC874\uC7AC\uD568" : "\uC874\uC7AC\uD558\uC9C0 \uC54A\uC74C"}
`;
const parentPath = path2.join(currentDir, "..", "sigungu.json");
errorMessage += `2. \uC0C1\uC704 \uACBD\uB85C: ${parentPath} - ${fs.existsSync(parentPath) ? "\uC874\uC7AC\uD568" : "\uC874\uC7AC\uD558\uC9C0 \uC54A\uC74C"}
`;
const cwd = process.cwd();
const nodeModulesPath = path2.join(cwd, "node_modules", "budongsan-api", "sigungu.json");
errorMessage += `3. node_modules \uACBD\uB85C: ${nodeModulesPath} - ${fs.existsSync(nodeModulesPath) ? "\uC874\uC7AC\uD568" : "\uC874\uC7AC\uD558\uC9C0 \uC54A\uC74C"}
`;
errorMessage += `
\uD574\uACB0 \uBC29\uBC95:
`;
errorMessage += `- sigungu.json \uD30C\uC77C\uC774 \uD328\uD0A4\uC9C0 \uB8E8\uD2B8\uC5D0 \uC788\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694
`;
errorMessage += `- package.json\uC758 files \uBC30\uC5F4\uC5D0 "sigungu.json"\uC774 \uD3EC\uD568\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694`;
throw new Error(errorMessage);
}
}
return this._dataCache;
}
getSigunguList() {
const data = this._loadDataSync();
return data.flatMap(
({ sido_name, sido_code, sigungu_array }) => sigungu_array.map(({ sigungu_name, sigungu_code, bjd_array }) => ({
sido_name,
sido_code,
sigungu_name,
sigungu_code,
bjd_array
}))
);
}
getSigunguMap(keyType = "code") {
const list = this.getSigunguList();
const map = /* @__PURE__ */ new Map();
list.forEach(({ sigungu_name, sigungu_code, sido_name, sido_code }) => {
const key = keyType === "name" ? sigungu_name : sigungu_code;
map.set(key, { sigungu_name, sigungu_code, sido_name, sido_code });
});
return map;
}
getBjdList() {
const data = this._loadDataSync();
return data.flatMap(
({ sigungu_array }) => sigungu_array.flatMap(({ bjd_array }) => bjd_array ?? [])
);
}
getBjdMapBySigungu(keyType = "name") {
const list = this.getSigunguList();
const map = /* @__PURE__ */ new Map();
list.forEach(({ sigungu_name, sigungu_code, bjd_array }) => {
const key = keyType === "code" ? sigungu_code : sigungu_name;
map.set(key, bjd_array ?? []);
});
return map;
}
};
var SigunguService = SigunguServiceClass.getInstance();
var sigungu_service_default = SigunguService;
// src/budongsan-util.ts
import axios2 from "axios";
var BudongsanUtil = class {
/**
* 현재 한국 기준 연도와 월을 반환합니다.
* @returns {{ year: string, month: string }}
*/
static getKoreanYearMonth() {
const now = /* @__PURE__ */ new Date();
const utc = now.getTime() + now.getTimezoneOffset() * 6e4;
const koreaTime = new Date(utc + 9 * 60 * 6e4);
return {
year: koreaTime.getFullYear().toString(),
month: (koreaTime.getMonth() + 1).toString()
};
}
/**
* 시작 연월부터 종료 연월까지의 YYYYMM 문자열 배열을 생성합니다.
* @param {number} startYear 시작 연도
* @param {number} startMonth 시작 월
* @param {number} endYear 종료 연도
* @param {number} endMonth 종료 월
* @returns {string[]} YYYYMM 문자열 배열
*/
static generateDealYMDRange(startYear, startMonth, endYear, endMonth) {
const result = [];
let year = startYear;
let month = startMonth;
while (year < endYear || year === endYear && month <= endMonth) {
const ymd = `${year}${month.toString().padStart(2, "0")}`;
result.push(ymd);
month++;
if (month > 12) {
month = 1;
year++;
}
}
return result;
}
/**
* 숫자 문자열을 한글 화폐 단위로 포맷팅합니다. (예: "55,000" → "5억 5000만")
* @param {string|number} amount 원 단위 기준 숫자 또는 문자열
* @returns {string} 한글 화폐 단위 문자열
*/
static formatKoreanCurrency(amount) {
const unitMap = ["\uC6D0", "\uB9CC", "\uC5B5", "\uC870", "\uACBD", "\uD574"];
const isNegative = String(amount).startsWith("-");
const numericStr = String(amount).replace(/[^0-9]/g, "") + "0000";
const chunks = [];
let temp = numericStr;
while (temp.length > 0) {
chunks.unshift(temp.slice(-4));
temp = temp.slice(0, -4);
}
const parts = chunks.map((chunk, index) => {
const num = parseInt(chunk, 10);
if (num === 0)
return null;
return `${num}${unitMap[chunks.length - 1 - index]}`;
}).filter(Boolean);
const result = parts.join(" ");
return isNegative ? `( -${result} )` : result;
}
};
BudongsanUtil.getGoogleMapLatitudeAndlongitude = async (krjuso, googleApikey) => {
const encodedJuso = encodeURI(krjuso);
const latitudeNlongitude = await axios2({
method: "get",
url: `https://maps.googleapis.com/maps/api/geocode/json?address=${encodedJuso}&key=${googleApikey}`
}).then((res) => {
const latitude = res.data.results[0].geometry.location.lat;
const longitude = res.data.results[0].geometry.location.lng;
return {
latitude,
longitude
};
}).catch(() => {
return {
latitude: "",
longitude: ""
};
});
return latitudeNlongitude;
};
BudongsanUtil.getKakaoMapPosition = async (param_juso, kakaoApikey) => {
return await axios2({
method: "get",
url: `https://dapi.kakao.com/v2/local/search/address.json?query=${encodeURIComponent(param_juso)}`,
headers: { "Authorization": `KakaoAK ${kakaoApikey}` }
}).then((res) => {
if (res.data.documents && res.data.documents[0] && res.data.documents[0].address && res.data.documents[0].road_address) {
const y = res.data.documents[0].y;
const x = res.data.documents[0].x;
return {
addressName: res.data.documents[0].address.address_name,
roadAddressName: res.data.documents[0].road_address.address_name,
apartKakaoName: res.data.documents[0].road_address.building_name,
latitude: y,
longitude: x
};
} else {
return {
addressName: "",
roadAddressName: "",
apartKakaoName: "",
latitude: "",
longitude: ""
};
}
}).catch(() => {
return {
addressName: "",
roadAddressName: "",
apartKakaoName: "",
latitude: "",
longitude: ""
};
});
};
BudongsanUtil.getKakaoCategory = async (param_y, param_x, param_category_group_code, kakaoApikey) => {
return await axios2({
method: "get",
url: `https://dapi.kakao.com/v2/local/search/category.json`,
headers: { "Authorization": `KakaoAK ${kakaoApikey}` },
params: {
y: param_y,
x: param_x,
category_group_code: param_category_group_code,
radius: 4e3
}
}).then((res) => {
return res.data.documents;
}).catch(() => {
return [];
});
};
var budongsan_util_default = BudongsanUtil;
export {
BudongsanAPIClass,
budongsan_util_default as BudongsanUtil,
sigungu_service_default as SigunguService,
SigunguServiceClass
};