서울시 부동산 실거래가 정보 API 크롤링 2 - 크롤링편 (XML)

Page content

개요

  • Open API를 통해서 부동산 실거래가 정보를 pandas 데이터프레임으로 변환하는 코드를 구현한다.

요청인자 확인

Untitled

  • 샘플 URL은 크게 2가지를 제공한다.
    • 서울시 부동산 실거래가 정보
    • 서울시 부동산 실거래가 정보(마곡일성트루엘플래닛)
  • 출력 예제는 다음과 같다.
<?xml version="1.0" encoding="UTF-8"?>
<tbLnOpendataRtmsV>
<list_total_count>2639192</list_total_count>
<RESULT>
<CODE>INFO-000</CODE>
<MESSAGE>정상 처리되었습니다</MESSAGE>
</RESULT>
<row>
<ACC_YEAR>2023</ACC_YEAR>
<SGG_CD>11545</SGG_CD>
<SGG_NM>금천구</SGG_NM>
<BJDONG_CD>10100</BJDONG_CD>
<BJDONG_NM>가산동</BJDONG_NM>
<LAND_GBN>1</LAND_GBN>
<LAND_GBN_NM>대지</LAND_GBN_NM>
<BONBEON>0776</BONBEON>
<BUBEON>0000</BUBEON>
<BLDG_NM>가산대명벨리온</BLDG_NM>
<DEAL_YMD>20230127</DEAL_YMD>
<OBJ_AMT>12300</OBJ_AMT>
<BLDG_AREA>16.28</BLDG_AREA>
<TOT_AREA>25.630000</TOT_AREA>
<FLOOR>8</FLOOR>
<RIGHT_GBN/>
<CNTL_YMD/>
<BUILD_YEAR>2017</BUILD_YEAR>
<HOUSE_TYPE>오피스텔</HOUSE_TYPE>
<REQ_GBN>중개거래</REQ_GBN>
<RDEALER_LAWDNM>서울 금천구</RDEALER_LAWDNM>
</row>
<row>
<ACC_YEAR>2023</ACC_YEAR>
<SGG_CD>11500</SGG_CD>
<SGG_NM>강서구</SGG_NM>
<BJDONG_CD>10500</BJDONG_CD>
<BJDONG_NM>마곡동</BJDONG_NM>
<LAND_GBN>1</LAND_GBN>
<LAND_GBN_NM>대지</LAND_GBN_NM>
<BONBEON>0793</BONBEON>
<BUBEON>0000</BUBEON>
<BLDG_NM>유림트윈파크</BLDG_NM>
<DEAL_YMD>20230127</DEAL_YMD>
<OBJ_AMT>13900</OBJ_AMT>
<BLDG_AREA>19.99</BLDG_AREA>
<TOT_AREA>30.300000</TOT_AREA>
<FLOOR>6</FLOOR>
<RIGHT_GBN/>
<CNTL_YMD/>
<BUILD_YEAR>2015</BUILD_YEAR>
<HOUSE_TYPE>오피스텔</HOUSE_TYPE>
<REQ_GBN>중개거래</REQ_GBN>
<RDEALER_LAWDNM>서울 강서구</RDEALER_LAWDNM>
</row>
<row>
<ACC_YEAR>2023</ACC_YEAR>
<SGG_CD>11500</SGG_CD>
<SGG_NM>강서구</SGG_NM>
<BJDONG_CD>10300</BJDONG_CD>
<BJDONG_NM>화곡동</BJDONG_NM>
<LAND_GBN>1</LAND_GBN>
<LAND_GBN_NM>대지</LAND_GBN_NM>
<BONBEON>1115</BONBEON>
<BUBEON>0034</BUBEON>
<BLDG_NM>casagio</BLDG_NM>
<DEAL_YMD>20230127</DEAL_YMD>
<OBJ_AMT>22100</OBJ_AMT>
<BLDG_AREA>29.98</BLDG_AREA>
<TOT_AREA>21.430000</TOT_AREA>
<FLOOR>2</FLOOR>
<RIGHT_GBN/>
<CNTL_YMD/>
<BUILD_YEAR>2019</BUILD_YEAR>
<HOUSE_TYPE>연립다세대</HOUSE_TYPE>
<REQ_GBN>직거래</REQ_GBN>
<RDEALER_LAWDNM/>
</row>
<row>
<ACC_YEAR>2023</ACC_YEAR>
<SGG_CD>11320</SGG_CD>
<SGG_NM>도봉구</SGG_NM>
<BJDONG_CD>10600</BJDONG_CD>
<BJDONG_NM>방학동</BJDONG_NM>
<LAND_GBN>1</LAND_GBN>
<LAND_GBN_NM>대지</LAND_GBN_NM>
<BONBEON>0632</BONBEON>
<BUBEON>0023</BUBEON>
<BLDG_NM>(632-23)</BLDG_NM>
<DEAL_YMD>20230126</DEAL_YMD>
<OBJ_AMT>12500</OBJ_AMT>
<BLDG_AREA>41.58</BLDG_AREA>
<TOT_AREA>30.760000</TOT_AREA>
<FLOOR>1</FLOOR>
<RIGHT_GBN/>
<CNTL_YMD/>
<BUILD_YEAR>1986</BUILD_YEAR>
<HOUSE_TYPE>연립다세대</HOUSE_TYPE>
<REQ_GBN>직거래</REQ_GBN>
<RDEALER_LAWDNM/>
</row>
<row>
<ACC_YEAR>2023</ACC_YEAR>
<SGG_CD>11410</SGG_CD>
<SGG_NM>서대문구</SGG_NM>
<BJDONG_CD>11200</BJDONG_CD>
<BJDONG_NM>대현동</BJDONG_NM>
<LAND_GBN>1</LAND_GBN>
<LAND_GBN_NM>대지</LAND_GBN_NM>
<BONBEON>0101</BONBEON>
<BUBEON>0007</BUBEON>
<BLDG_NM>혜우</BLDG_NM>
<DEAL_YMD>20230126</DEAL_YMD>
<OBJ_AMT>80000</OBJ_AMT>
<BLDG_AREA>129.27</BLDG_AREA>
<TOT_AREA>0.000000</TOT_AREA>
<FLOOR>10</FLOOR>
<RIGHT_GBN/>
<CNTL_YMD/>
<BUILD_YEAR>1996</BUILD_YEAR>
<HOUSE_TYPE>아파트</HOUSE_TYPE>
<REQ_GBN>직거래</REQ_GBN>
<RDEALER_LAWDNM/>
</row>
</tbLnOpendataRtmsV>

Untitled

파이썬 코드

Untitled

  • 이제 본격적으로 주어진 코드를 활용하여 크롤링 데이터를 수집하도록 한다.

코드

  • 우선 API가 정상적으로 호출이 되는지 확인하는 코드를 작성한다.
  • 이 때, ~/1/5/2022 는 조회 날짜를 의미한다.
    • 즉, 여기 부분을 입력 받아서 처리할 수 있는 간단한 프로그램도 고민할 수 있다.
import lxml
import requests
from bs4 import BeautifulSoup
import pandas as pd

service_key = 'your_service_key'
url = f'http://openapi.seoul.go.kr:8088/{service_key}/xml/tbLnOpendataRtmsV/1/5/2022'

result = requests.get(url)
content = result.content
soup = BeautifulSoup(content, "lxml") # html.parser 
# print(soup)
print(soup.prettify())
<?xml version="1.0" encoding="UTF-8"?>
<html>
 <body>
  <tblnopendatartmsv>
   <list_total_count>
    224085
   </list_total_count>
   <result>
    <code>
     INFO-000
    </code>
    <message>
     정상 처리되었습니다
    </message>
   </result>
   <row>
    <acc_year>
     2022
    </acc_year>
    <sgg_cd>
     11680
    </sgg_cd>
    <sgg_nm>
     강남구
    </sgg_nm>
    <bjdong_cd>
     10300
    </bjdong_cd>
    <bjdong_nm>
     개포동
    </bjdong_nm>
    <land_gbn>
     1
    </land_gbn>
    <land_gbn_nm>
     대지
    </land_gbn_nm>
    <bonbeon>
     1165
    </bonbeon>
    <bubeon>
     0016
    </bubeon>
    <bldg_nm>
     칠성빌라나동
    </bldg_nm>
    <deal_ymd>
     20230109
    </deal_ymd>
    <obj_amt>
     69000
    </obj_amt>
    <bldg_area>
     50.28
    </bldg_area>
    <tot_area>
     41.770000
    </tot_area>
    <floor>
     1
    </floor>
    <right_gbn>
    </right_gbn>
    <cntl_ymd>
    </cntl_ymd>
    <build_year>
     1989
    </build_year>
    <house_type>
     연립다세대
    </house_type>
    <req_gbn>
     중개거래
    </req_gbn>
    <rdealer_lawdnm>
     서울 강남구
    </rdealer_lawdnm>
   </row>
   <row>
    <acc_year>
     2022
    </acc_year>
    <sgg_cd>
     11200
    </sgg_cd>
    <sgg_nm>
     성동구
    </sgg_nm>
    <bjdong_cd>
     12200
    </bjdong_cd>
    <bjdong_nm>
     용답동
    </bjdong_nm>
    <land_gbn>
     1
    </land_gbn>
    <land_gbn_nm>
     대지
    </land_gbn_nm>
    <bonbeon>
     0238
    </bonbeon>
    <bubeon>
     0012
    </bubeon>
    <bldg_nm>
     서희스타힐스리버파크
    </bldg_nm>
    <deal_ymd>
     20230106
    </deal_ymd>
    <obj_amt>
     13500
    </obj_amt>
    <bldg_area>
     20.21
    </bldg_area>
    <tot_area>
     27.280000
    </tot_area>
    <floor>
     4
    </floor>
    <right_gbn>
    </right_gbn>
    <cntl_ymd>
    </cntl_ymd>
    <build_year>
     2016
    </build_year>
    <house_type>
     오피스텔
    </house_type>
    <req_gbn>
     중개거래
    </req_gbn>
    <rdealer_lawdnm>
     서울 광진구
    </rdealer_lawdnm>
   </row>
   <row>
    <acc_year>
     2022
    </acc_year>
    <sgg_cd>
     11740
    </sgg_cd>
    <sgg_nm>
     강동구
    </sgg_nm>
    <bjdong_cd>
     10900
    </bjdong_cd>
    <bjdong_nm>
     천호동
    </bjdong_nm>
    <land_gbn>
     1
    </land_gbn>
    <land_gbn_nm>
     대지
    </land_gbn_nm>
    <bonbeon>
     0167
    </bonbeon>
    <bubeon>
     0153
    </bubeon>
    <bldg_nm>
     에코 타워빌
    </bldg_nm>
    <deal_ymd>
     20230102
    </deal_ymd>
    <obj_amt>
     22500
    </obj_amt>
    <bldg_area>
     29.01
    </bldg_area>
    <tot_area>
     38.620000
    </tot_area>
    <floor>
     2
    </floor>
    <right_gbn>
    </right_gbn>
    <cntl_ymd>
    </cntl_ymd>
    <build_year>
     2017
    </build_year>
    <house_type>
     오피스텔
    </house_type>
    <req_gbn>
     중개거래
    </req_gbn>
    <rdealer_lawdnm>
     서울 강동구
    </rdealer_lawdnm>
   </row>
   <row>
    <acc_year>
     2022
    </acc_year>
    <sgg_cd>
     11320
    </sgg_cd>
    <sgg_nm>
     도봉구
    </sgg_nm>
    <bjdong_cd>
     10800
    </bjdong_cd>
    <bjdong_nm>
     도봉동
    </bjdong_nm>
    <land_gbn>
     1
    </land_gbn>
    <land_gbn_nm>
     대지
    </land_gbn_nm>
    <bonbeon>
     0062
    </bonbeon>
    <bubeon>
     0043
    </bubeon>
    <bldg_nm>
     도봉 투웨니퍼스트 2단지
    </bldg_nm>
    <deal_ymd>
     20221231
    </deal_ymd>
    <obj_amt>
     18400
    </obj_amt>
    <bldg_area>
     29.66
    </bldg_area>
    <tot_area>
     38.510000
    </tot_area>
    <floor>
     2
    </floor>
    <right_gbn>
    </right_gbn>
    <cntl_ymd>
    </cntl_ymd>
    <build_year>
     null
    </build_year>
    <house_type>
     오피스텔
    </house_type>
    <req_gbn>
     중개거래
    </req_gbn>
    <rdealer_lawdnm>
     서울 도봉구
    </rdealer_lawdnm>
   </row>
   <row>
    <acc_year>
     2022
    </acc_year>
    <sgg_cd>
     11170
    </sgg_cd>
    <sgg_nm>
     용산구
    </sgg_nm>
    <bjdong_cd>
     12500
    </bjdong_cd>
    <bjdong_nm>
     한강로2가
    </bjdong_nm>
    <land_gbn>
     1
    </land_gbn>
    <land_gbn_nm>
     대지
    </land_gbn_nm>
    <bonbeon>
     0312
    </bonbeon>
    <bubeon>
     0004
    </bubeon>
    <bldg_nm>
     대우디오빌한강로오피스텔
    </bldg_nm>
    <deal_ymd>
     20221231
    </deal_ymd>
    <obj_amt>
     35000
    </obj_amt>
    <bldg_area>
     33.95
    </bldg_area>
    <tot_area>
     48.610000
    </tot_area>
    <floor>
     7
    </floor>
    <right_gbn>
    </right_gbn>
    <cntl_ymd>
    </cntl_ymd>
    <build_year>
     2004
    </build_year>
    <house_type>
     오피스텔
    </house_type>
    <req_gbn>
     중개거래
    </req_gbn>
    <rdealer_lawdnm>
     서울 동작구, 서울 용산구
    </rdealer_lawdnm>
   </row>
  </tblnopendatartmsv>
 </body>
</html>
  • acc_year 부터 rdealer_lawdnm 코드가 계속 반복되는 것을 확인할 수 있다.
  • 총 출력 값이 많기 때문에 일부만 가져오도록 한다.
    • 특별한 의미는 없다.
years            = soup.find_all('acc_year')         # 접수년월
sgg_cds          = soup.find_all('sgg_cd')           # 자치구코드
bldg_nms         = soup.find_all('bldg_nm')          # 건물명
obj_amts         = soup.find_all('obj_amt')          # 물건금액(만원)
house_types      = soup.find_all('house_type')       # 건물용도
rdealer_lawdnms  = soup.find_all('rdealer_lawdnm')   # 신고한 개업공인중개사 시군구명
  • 이제 반복문을 통해 코드를 작성하도록 한다. 코드에서 가장 큰 특징은 zip() 함수를 활용한 것이었다.
  • 그리고 반복문을 순환하면서 각 리스트에 담는다.
  • 시간 테스트도 같이 진행했다.
%%time

year_list           = []
sgg_cd_list         = []
bldg_nm_list        = []
obj_amt_list        = []
house_type_list     = []
rdealer_lawdnm_list = []

for year, sgg_cd, bldg_nm, obj_amt, house_type, rdealer_lawdnm in zip(years, sgg_cds, bldg_nms, obj_amts, house_types, rdealer_lawdnms):
  year_list.append(year.get_text())
  sgg_cd_list.append(sgg_cd.get_text())
  bldg_nm_list.append(bldg_nm.get_text())
  obj_amt_list.append(obj_amt.get_text())
  house_type_list.append(house_type.get_text())
  rdealer_lawdnm_list.append(rdealer_lawdnm.get_text())

df = pd.DataFrame({
    "acc_year": year_list, 
    "sgg_cd": sgg_cd_list, 
    "bldg_nm" : bldg_nm_list,
    "obj_amt": obj_amt_list,
    "house_type" : house_type_list,
    "rdealer_lawdnm": rdealer_lawdnm_list
})

df

Untitled

  • 이번에는 다른 방식으로 코드를 작성한 후, 시간을 측정하도록 한다.
%%time

data = []
for i in range(0,len(years)):
  rows = [years[i].get_text(),
          sgg_cds[i].get_text(), 
          bldg_nms[i].get_text(),
          obj_amts[i].get_text(),
          house_types[i].get_text(),
          rdealer_lawdnms[i].get_text()]
  data.append(rows)

cols = ['acc_year', 'sgg_cd', 'bldg_nm', 'obj_amt', 'house_type', 'rdealer_lawdnm']
df = pd.DataFrame(data, columns = cols)
df

Untitled

  • 두개의 코드 중, 코드를 작성하는 시간은 첫번째 방식이 더 오래 걸렸지만, 처리 속도는 2배 더 빨랐다.
  • 이 둘중에서 어떤 것을 함수화 할 것인지는 독자가 판단해서 정하면 된다.