1. 수집할 데이터

공공 데이터포털 (data.go.kr) 소개

공공데이터포털은 공공기관이 생성 또는 취득하여 관리하고 있는 공공데이터를 한 곳에서 제공하는 통합 창구입니다. 포털에서는 국민이 쉽고편리하게 공공데이터를 이용할 수 있도록 파일데이터, 오픈API, 시각화 등 다양한 방식으로 제공하고 있으며, 누구라도 쉽고 편리한 검색을통해 원하는 공공데이터를 빠르고 정확하게 찾을 수 있습니다.

아파트매매 실거래 상세 자료

image

  • 주소 : https://www.data.go.kr/data/15057511/openapi.do
  • 설명 : 부동산 거래신고에 관한 법률에 따라 신고된 주택의 실거래 자료를 제공
  • 방법 : 지역코드와 기간을 이용하여 해당기간, 해당지역의 아파트 매매 신고자료를 제공하는 아파트 매매 신고 정보 조회

위 주소에 들어가서 로그인 후 아파트 실거래 상세자료 활용신청을 진행한다.
약 10분 후 부터 api 호출할 수 있다.

  • 요청변수(Request Parameter)
항목명(국문)항목명(영문)항목크기항목구분샘플데이터항목설명
서비스키ServiceKey20필수-공공데이터포털에서 받은 인증키
페이지 번호pageNo4옵션1페이지번호
한 페이지 결과 수numOfRows4옵션10한 페이지 결과 수
지역코드LAWD_CD5필수11110지역코드
계약월DEAL_YMD6필수201512계약월
  • 출력결과(Response Element)
항목명(국문)항목명(영문)항목크기항목구분샘플데이터항목설명
결과코드resultCode2필수00결과코드
결과메시지resultMsg50필수OK결과메시지
한 페이지 결과 수numOfRows4필수10한 페이지 결과 수
페이지 번호pageNo4필수1페이지번호
전체 결과 수totalCount4필수3전체 결과 수
거래금액거래금액40필수82,500거래금액
건축년도건축년도4필수2008건축년도
4필수2015
도로명도로명40필수사직로8길도로명
도로명건물본번호코드도로명건물본번호코드5필수00004도로명건물본번호코드
도로명건물부번호코드도로명건물부번호코드5필수00000도로명건물부번호코드
도로명시군구코드도로명시군구코드5필수11110도로명시군구코드
도로명일련번호코드도로명일련번호코드2필수03도로명일련번호코드
도로명지상지하코드도로명지상지하코드1필수0도로명지상지하코드
도로명코드도로명코드7필수4100135도로명코드
법정동법정동40필수사직동법정동
법정동본번코드법정동본번코드4필수0009법정동본번코드
법정동부번코드법정동부번코드4필수0000법정동부번코드
법정동시군구코드법정동시군구코드5필수11110법정동시군구코드
법정동읍면동코드법정동읍면동코드5필수11500법정동읍면동코드
법정동지번코드법정동지번코드1필수1법정동지번코드
아파트아파트40필수광화문풍림스페이스본(9-0)아파트
2필수12
6필수1~10
일련번호일련번호14필수11110-2203일련번호
전용면적전용면적20필수94.51전용면적
지번지번10필수9지번
지역코드지역코드5필수11110지역코드
4필수11

2. 데이터 수집

요청 방법 : Rest API

매매 내역을 API 방식으로 제공하고 있다. google 홈페이지에 접속하고 싶을 때 브라우저에 https://google.com 를 치면 브라우저에 화면이 떠지는 것처럼 받고싶은 데이터를 지정된 양식에 맞춰서 요청하면 데이터가 받아지는 방식이다.

http://openapi.molit.go.kr/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTradeDev?serviceKey={개인서비스키}&LAWD_CD={시군구코드}&DEAL_YMD={년월}&pageNo=1&numOfRows=1000

위 URL에서 3가지 조건을 바꿔가며 검색할 수 있다.

  • {개인서비스키} : 공공데이터포털에 활용신청하면 받을 수 있음.
  • {시군구코드} : https://www.code.go.kr/stdcode/regCodeL.do 에서 확인할 수 있다. 시코드 2자리와 구코드 3자리를 합쳐서 5자리 코드로 입력하면 된다.
    • 서울특별시 종로구 청운동 : 1111010100
    • 서울특별시 : 11
    • 종로구 : 110
    • 청운동 : 10100
  • {년월} : 매매 월 - 201111 (YYYYMM)

이렇게 완성된 url을 브라우저 url 입력하는 곳에 넣어서 확인해볼 수 있다.

image

python으로 10, 11월만 빠르게 수집

1
2
3
4
5
6
import time
import requests
import xml.etree.ElementTree as ET
import pandas as pd

from tqdm import tqdm
1
2
3
4
5
6
7
8
9
10
11
12
def get_items(get):
    root = ET.fromstring(get.content)
    item_list = []
    for child in root.find('body').find('items'):
        elements = child.findall('*')
        data = {}
        for element in elements:
            tag = element.tag.strip()
            text = element.text.strip()
            data[tag] = text
        item_list.append(data)  
    return item_list
1
2
3
4
5
6
7
8
9
serviceKey = "################################공유하면안됨#################################"

url = "http://openapi.molit.go.kr/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTradeDev?serviceKey={}&LAWD_CD={}&DEAL_YMD={}&pageNo=1&numOfRows=1000"

locCode = ["11110", "11140", "11170", "11200", "11215", "11230", "11260", "11290", "11305", "11320", "11350", "11380", "11410", "11440", "11470", "11500", "11530", "11545", "11560", "11590", "11620", "11650", "11680", "11710", "11740"]

locCode_nm = ["종로구", "중구", "용산구", "성동구", "광진구", "동대문구", "중랑구", "성북구", "강북구", "도봉구", "노원구", "은평구", "서대문구", "마포구", "양천구", "강서구", "구로구", "금천구", "영등포구", "동작구", "관악구", "서초구", "강남구", "송파구", "강동구"]

yyyymm = ['202110', '202111']
1
2
3
4
5
6
7
8
9
10
11
12
13
DataFrame = pd.DataFrame([])

for ym in tqdm(yyyymm):

    for code, name in zip(locCode, locCode_nm):

        time.sleep(0.1)
        req = url.format(serviceKey, code, ym)
        get = requests.get(req)
        items_list = get_items(get)
        items = pd.DataFrame(items_list) 
        items["지역구"] = name
        DataFrame = pd.concat([DataFrame, items])
1
DataFrame.shape

(2036, 29)

1
DataFrame.head()
 거래금액거래유형건축년도도로명도로명건물본번호코드도로명건물부번호코드도로명시군구코드도로명일련번호코드도로명지상지하코드도로명코드법정동법정동본번코드법정동부번코드법정동시군구코드법정동읍면동코드법정동지번코드아파트일련번호전용면적중개사소재지지번지역코드해제사유발생일해제여부지역구
0140,000 20072021사직로0010200000111100203100005필운동0254000011110113001신동아블루아광화문의꿈102511110-2164111.97 254111108  종로구
1146,000 20082021사직로8길0000400000111100304100135사직동0009000011110115001광화문풍림스페이스본(101동~105동)101211110-2203108.07 9111101  종로구
2180,000 20032021사직로8길0002000000111100504100135내수동0095000011110118001경희궁파크팰리스102011110-107147.62 95111102  종로구
336,800 20052021율곡로2길0000700000111100104100234수송동0085000011110124001로얄팰리스스위트10111110-20539.67 851111013  종로구
433,000 20032021돈화문로11가길0005900000111100304100050익선동0055000011110133001현대뜨레비앙10711110-10246.64 55111103  종로구

3. 데이터 정제 작업

도로명 주소 없는 아파트는 거래 대상이 아니다. 보통 신축인 경우가 그렇다.

1
2
3
4
5
DataFrame = DataFrame.loc[(
    ~(DataFrame.도로명.isna()) &
    ~(DataFrame.도로명코드.isna()) &
    ~(DataFrame.도로명건물본번호코드.isna())
)]

데이터 타입 변경

1
2
3
4
5
DataFrame["년"] = DataFrame["년"].astype(int)
DataFrame["월"] = DataFrame["월"].astype(int)
DataFrame["일"] = DataFrame["일"].astype(int)
DataFrame["전용면적"] = DataFrame["전용면적"].astype(float)
DataFrame["아파트가격"] = DataFrame["거래금액"].apply(lambda x: int(x.replace(",", "")))

계약 취소 거래 제거 : 가격 조작

1
DataFrame.해제여부 = DataFrame.해제여부.apply(lambda x: "정상거래" if len(x) == 0 else '취소거래')

지역 코드 규격화

1
2
DataFrame["지역구_법정동"] = DataFrame[["지역코드", "법정동읍면동코드"]].apply(lambda x: "{}{}".format(x[0], x[1]), 1)
DataFrame["지역구_도로명"] = DataFrame[["지역코드", "도로명코드"]].apply(lambda x: "{}{}".format(x[0], x[1]), 1)

법정동 도로명 주소

1
2
3
DataFrame["도로명건물번호"] = DataFrame[["도로명건물본번호코드", "도로명건물부번호코드"]].apply(lambda x: "{}".format(int(x[0]))if x[1] == "00000" else "{}-{}".format(int(x[0]), int(x[1])), 1)
DataFrame["도로명주소"] = DataFrame[["지역구", "도로명", "도로명건물번호"]].apply(lambda x: "{} {} {}".format(x[0], x[1], x[2]), 1)
DataFrame["법정동주소"] = DataFrame[["지역구", "법정동", "지번"]].apply(lambda x: "{} {} {}".format(x[0], x[1], x[2]), 1)

살고(사고) 싶은 지역을 검색

1
2
3
4
DataFrame.loc[(
    (DataFrame.법정동 == "상도동") &
    (DataFrame.지역구 == "동작구")
), ['지역구_도로명', '도로명주소', '아파트', '전용면적', '아파트가격', '년', '월', '일', '해제여부']]
지역구_도로명도로명주소아파트전용면적아파트가격해제여부
115903005080동작구 양녕로 170롯데캐슬비엔68.56885002021102정상거래
115903119005동작구 상도로 346-2힐스테이트 상도 프레스티지120.0041952002021104정상거래
115903119008동작구 장승배기로 26아이파크84.961285002021106정상거래
115903119002동작구 만양로 26건영114.9613500020211011정상거래
115903119002동작구 만양로 6e편한세상상도노빌리티59.782715450020211012정상거래
115904157013동작구 국사봉1길 214성우723570020211015정상거래
115904157571동작구 장승배기로10길 100상도더샵2차84.9914700020211015정상거래
115903119005동작구 상도로 320중앙하이츠빌123.60313400020211016정상거래
115903119005동작구 상도로 407상도동삼호아파트84.529350020211018정상거래
115903119002동작구 만양로 26건영114.9613000020211020정상거래
115904157378동작구 상도로55길 25삼환나우빌(520-0)84.8712350020211020정상거래
115904157508동작구 양녕로23길 9경향렉스빌114.65310500020211023정상거래
115903119002동작구 만양로 6e편한세상상도노빌리티84.970417300020211023정상거래
115903005080동작구 양녕로 170롯데캐슬비엔84.9512200020211025정상거래
115903119005동작구 상도로 107쌍용스윗닷홈59.57810200020211028정상거래
115904157318동작구 상도로15길 131상도휴엔하임14.7139002021115정상거래
115904157386동작구 상도로62길 81-1씨티아모리움14.1881156002021116정상거래

문제점

현행법상 부동산 거래 후 30일 이내에 신고를 해야한다.
즉 10월 30일 거래여도 11월 20일에 신고를 하면 10월 마감으로 돌린 데이터를 다시 업데이트 해야한다.
또한 허위 거래같은 경우 해제여부 가 정상->취소로 바뀌므로 과거 건을 가끔식 업데이트 해야한다.

4. full code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
yyyymm = ['202101', '202102', '202103', '202104', '202105', '202106', '202107', '202108', '202109', '202110', '202111', '202112']

for ym in tqdm(yyyymm):
    
    DataFrame = pd.DataFrame([])

    for code, name in zip(locCode, locCode_nm):

        time.sleep(0.1)
        req = url.format(serviceKey, code, ym)
        get = requests.get(req)
        items_list = get_items(get)
        items = pd.DataFrame(items_list) 
        items["지역구"] = name
        DataFrame = pd.concat([DataFrame, items])

    ################################################################################     
    DataFrame.to_csv("../data/seoul_apt_trade_{}_raw.csv".format(ym), index=None)

    DataFrame = DataFrame.loc[(
        ~(DataFrame.도로명.isna()) &
        ~(DataFrame.도로명코드.isna()) &
        ~(DataFrame.도로명건물본번호코드.isna())
    )]

    DataFrame["년"] = DataFrame["년"].astype(int)
    DataFrame["월"] = DataFrame["월"].astype(int)
    DataFrame["일"] = DataFrame["일"].astype(int)
    DataFrame["전용면적"] = DataFrame["전용면적"].astype(float)
    DataFrame["아파트가격"] = DataFrame["거래금액"].apply(lambda x: int(x.replace(",", "")))

    DataFrame.해제여부 = DataFrame.해제여부.apply(lambda x: "취소거래" if len(x) == 0 else '정상거래')

    DataFrame["지역구_법정동"] = DataFrame[["지역코드", "법정동읍면동코드"]].apply(lambda x: "{}{}".format(x[0], x[1]), 1)
    DataFrame["지역구_도로명"] = DataFrame[["지역코드", "도로명코드"]].apply(lambda x: "{}{}".format(x[0], x[1]), 1)

    DataFrame["도로명건물번호"] = DataFrame[["도로명건물본번호코드", "도로명건물부번호코드"]].apply(lambda x: "{}".format(int(x[0]))if x[1] == "00000" else "{}-{}".format(int(x[0]), int(x[1])), 1)
    DataFrame["도로명주소"] = DataFrame[["지역구", "도로명", "도로명건물번호"]].apply(lambda x: "{} {} {}".format(x[0], x[1], x[2]), 1)
    DataFrame["법정동주소"] = DataFrame[["지역구", "법정동", "지번"]].apply(lambda x: "{} {} {}".format(x[0], x[1], x[2]), 1)

    ################################################################################
    DataFrame.to_csv("../data/seoul_apt_trade_{}.csv".format(ym), index=None)

2021년 서울시 지역구 아파트 매매가 분포

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import plotly.graph_objects as go
from plotly.colors import n_colors

trace = DataFrame.groupby("지역구").agg({"아파트가격" : "mean"}).sort_values("아파트가격", ascending=True).index.to_list()
colors = n_colors('rgb(10, 10, 200)', 'rgb(200, 10, 10)', len(trace), colortype='rgb')

fig = go.Figure()
for sigungu, color in zip(trace, colors):
    fig.add_trace(go.Violin(x = DataFrame.loc[((DataFrame.지역구 == sigungu)), "아파트가격"], line_color = color, name = sigungu))

fig.update_traces(orientation='h', side='positive', width=3, points=False)
fig.update_layout(xaxis_showgrid=True, xaxis_zeroline=True, legend_traceorder="reversed", height=700, width=800)

fig.show()