공공데이터포털 API + DuckDB로 부동산 실거래가 수집하기

소개

업무상 대시보드로 데이터를 들여다보는 일은 많은데, 정작 데이터를 내 손으로 직접 긁어와서 쌓아본 적은 없었어요. 늘 누가 만들어준 걸 보기만 했지, 원천 API를 때려서 DB에 넣는 과정은 저한테 블랙박스였거든요. 이번 1주차 주제가 마침 "공공데이터 수집 → DuckDB 저장"이라, 이 참에 그 블랙박스를 한 번 열어보자는 마음으로 시작했습니다.

목표는 단순했어요. 부동산 실거래가랑 KB 통계, 주식 데이터를 공공/오픈 API로 직접 받아와서 로컬 DB에 저장하고, 실제로 쿼리까지 돌려보는 것. 강의 들으면서 한 줄씩 같이 따라 했습니다.

진행 방법

쓴 도구는 이렇게 됩니다.

  • Claude Code (실습 파트너 겸 코드 작성)

  • uv — 파이썬 가상환경/패키지 관리 (업무 워크스페이스랑 안 섞이게 gpters-study23 폴더를 아예 따로 팠어요)

  • 국토교통부 실거래가 오픈API, KB부동산 통계, FinanceDataReader

  • DuckDB (저장·조회) + DuckDB CLI/UI

Claude Code한테 그냥 "해줘" 하면 저도 뭘 한 건지 안 남을 것 같아서, 실행 계획을 먼저 보여달라고 하고 → 확인하고 → 진행하는 식으로 갔어요. 실제로 넣었던 프롬프트는 이거였습니다.

현재 폴더에서 아파트 매매·전월세 데이터 수집 실습을 진행해줘. 먼저 짧은 실행 계획을 보여주고 이어서 진행해줘.

환경:

  • 현재 프로젝트의 uv 가상환경을 사용한다.

  • 필요한 라이브러리를 확인하고 없는 것만 uv로 설치한다.

  • PublicDataReader와의 호환을 위해 pandas는 3 미만을 사용한다.

  • 전역 pip와 관련 없는 패키지 업그레이드는 하지 않는다.

  • API 실패 시 가짜 데이터를 만들지 말고 오류를 그대로 보여줘.

마지막 줄이 개인적으로 제일 중요했어요. AI한테 데이터 수집을 시키면 API가 막혔을 때 그럴듯한 가짜 데이터를 슬쩍 채워넣는 경우가 있다고 해서, 아예 프롬프트에 "실패하면 실패했다고 해라"를 못 박았습니다. 실제로 이 한 줄 덕분에 나중에 키 인증 문제를 바로 잡아낼 수 있었어요.

수집 코드는 이런 식이었습니다. 국토부 실거래가는 월 단위로 페이징하면서 XML을 받아오는 구조예요.

def fetch_month(url, key, ym):
    rows, page = [], 1
    while True:
        params = {"serviceKey": key, "LAWD_CD": SIGUNGU, "DEAL_YMD": ym,
                  "numOfRows": "1000", "pageNo": str(page)}
        r = requests.get(url, params=params, timeout=30)
        # 정상이면 XML(<...)로 시작. 아니면 그냥 에러를 던진다 (가짜 데이터 생성 안 함)
        if r.status_code != 200 or not r.text.lstrip().startswith("<"):
            raise RuntimeError(f"[{ym}] 비정상 응답:\n{r.text[:300]}")
        doc = xmltodict.parse(r.text)
        resp = doc.get("response", {})
        code = resp.get("header", {}).get("resultCode")
        if code not in ("000", "00"):
            raise RuntimeError(f"[{ym}] API 오류 resultCode={code}")
        # ... items 파싱 후 rows 에 누적, totalCount 만큼 페이지 넘기기
        page += 1
    return rows

📸 캡처 넣을 자리 ① — 공공데이터포털에서 '아파트 매매 실거래' 활용신청 승인된 화면

📸 캡처 넣을 자리 ② — 터미널에서 "매매 10,518행 / 전월세 28,111행" 수집 로그 뜬 화면

📸 캡처 넣을 자리 ③ — DuckDB UI(localhost)에서 raw_apt_trade 테이블 조회한 화면

최종적으로 DuckDB 파일 하나(study23.duckdb) 안에 테이블 6개가 쌓였습니다 — 아파트 매매/전월세 실거래, KB 매매·전세 지수, 전세가율, 주식 시세. 나중에 엑셀로도 봐야 해서 CSV(UTF-8 BOM)로 내보내는 것까지 해뒀어요.

결과와 배운 점

막상 해보니 코드보다 '연결'에서 시간을 다 썼어요. 정리하면 삽질 포인트가 세 개였는데,

  1. API 키 승인 대기. 공공데이터포털은 활용신청을 넣어야 키가 나오는데, 신청→승인 사이에 텀이 있어요. 강의 따라가다가 여기서 한 번 멈췄습니다. (매매용/전월세용 API를 각각 신청해야 하는 것도 처음엔 몰랐어요.)

  2. 라이브러리가 키랑 안 맞았던 것. 처음엔 PublicDataReader라는 편한 라이브러리를 쓰려고 했는데, 얘가 내부적으로 개발용(Dev) 엔드포인트를 호출하더라고요. 그런데 제가 발급받은 건 표준 활용신청 키라 서로 안 맞아서 계속 인증 오류가 났어요. 결국 라이브러리를 걷어내고 국토부 표준 API를 직접 호출하는 걸로 바꿔서 해결했습니다. 편의 라이브러리가 항상 답은 아니구나 싶었어요.

  3. DuckDB를 웹서비스인 줄 알았던 것. 이건 좀 부끄러운데, UI가 브라우저(localhost)로 떠서 저는 무슨 온라인 서비스에 데이터가 올라간 줄 알았어요. 알고 보니 파일 하나짜리 로컬 DB를 브라우저 화면으로 보여주는 거였죠. 내 PC 안에 다 있는 거라 오프라인에서도 그대로 열립니다.

나만의 꿀팁 두 개.

  • 업무 폴더에 섞지 말고 스터디용 uv 프로젝트를 따로 파는 것. pandas 버전 같은 게 업무 환경이랑 충돌하면 골치 아픈데, 폴더를 분리해두니 마음 편하게 이것저것 깔아봤어요.

  • Windows에서 한글이 깨지면 그냥 명령 앞에 PYTHONUTF8=1 붙이면 됩니다. 이거 몰라서 로그가 죄다 ???로 나올 때 좀 헤맸어요.

그리고 여기서 재미가 붙어서, 배운 API 방식 그대로 온비드(KAMCO) 공매 데이터까지 손을 뻗어봤어요. 같은 인증키로 전국 공매 물건을 긁어서 시세랑 교차해보는 것까지 해봤는데, 이건 1주차 범위를 넘는 거라 후기엔 짧게만 남깁니다. "한 번 붙이는 법을 익히니 다른 공공API도 똑같이 되는구나"를 몸으로 느낀 게 제일 큰 수확이었어요.

앞으로는 이걸 매일 자동으로 돌려서 쌓는 쪽으로 붙여볼 생각입니다. (스케줄러를 걸어두긴 했는데 아직 가끔 죽어서, 그 안정화가 다음 숙제네요.)

도움 받은 글 (옵션)

  • 지피터스 스터디 23기 1주차 강의 및 핸드아웃

  • 공공데이터포털(data.go.kr) — 국토교통부 아파트 매매/전월세 실거래 API

  • DuckDB 공식 문서 (설치 및 CLI)

1
2개의 답글
지피터스 뉴스레터

이번 주 AI, 딱 쓸 것만

매주 월요일 오후,
읽고 바로 써먹을 AI 소식만