[파이썬 크롤링/부동산 데이터] scrapy를 이용한 부동산 공공 데이터 파싱 및 추출하기

2021. 3. 28. 02:46 Python/파이썬 웹 크롤링

| 들어가기 전에

 

GIT 저장소

 

지금 포스팅은 국토교통부에서 제공하는 부동산 공공데이터 API를 사용합니다. 아래 포스팅을 보시고 먼저 부동산 공공데이터 API를 신청해주시길 바래요!

 

[기타 정보/ETC] - 국토교통부 공공데이터 부동산 실거래가 API 신청 방법

 

 

이전 포스팅

[Python/파이썬 웹 크롤링] - [파이썬 크롤링/부동산 데이터] 스크래피(scrapy) startproject로 초기 프로젝트 구성하기

[Python/파이썬 웹 크롤링] - [파이썬 크롤링/부동산 데이터] scrapy를 이용한 부동산 공공 데이터 간단하게 받아오기

 

 

국토교통부 부동산 매매데이터 분석 및 추출 

 

 

국토교통부에서 제공하는 공공 데이터 포맷은 다음과 같이 xml 포맷으로 전송됩니다.  

 

 

 

이번 포스팅에서는 위 xml 데이터에서 거래금액, 건축년도, 년 등의 데이터를 추출하는 방법을 다룰 것입니다. 

 

 

| Selector와 xpath를 이용해서 매매데이터 쉽게 추출하기

 

프로젝트 구조

|   scrapy.cfg
|
\---invest_crawler
    |   consts.py
    |   settings.py
    |   __init__.py
    |
    +---items
    |   |   apt_trade.py
    |   |   __init__.py
    +---spiders
    |   |   apt_trade_spiders.py
    |   |   __init__.py
  • 간단한 items 파이썬 패키지를 추가했습니다. item은 scrapy에서 데이터를 담아두는 바구니 역할을 합니다. 프로그래밍 세계에서 쓰는 고급진 말로 DTO(Data Transfer Object)라고 합니다.

 

소스 코드

 

consts.py

# 샘플 더미 데이터 입니다. 어떻게 세팅하는 지 보여드리기 위해 넣은 데이터이기 때문에 그대로 사용하시면 에러가 납니다.

# 샘플 더미 데이터 입니다. 어떻게 세팅하는 지 보여드리기 위해 넣은 데이터이기 때문에 그대로 사용하시면 에러가 납니다.
APT_DETAIL_ENDPOINT = "http://openapi.molit.go.kr:8081/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTrade?serviceKey=asdsdfsdfWiZGAJkCsr3wM0YkDO%2BssYpNXZ%2FEWZfuIW5k%2FcHFtD5k1zcCVasdfEtBQID5rIcjXsg%3D%3D&"

 

apt_trade.py

import scrapy


class AptTradeScrapy(scrapy.Item):

    apt_name = scrapy.Field()
    address_1 = scrapy.Field()
    address_2 = scrapy.Field()
    address_3 = scrapy.Field()
    address_4 = scrapy.Field()
    address = scrapy.Field()
    age = scrapy.Field()
    level = scrapy.Field()
    available_space = scrapy.Field()
    trade_date = scrapy.Field()
    trade_amount = scrapy.Field()
  • 매매 데이터를 담는 item인 인스턴스의 설계도인 AptTradeScrapy 클래스를 작성했습니다. scrapy.Item을 상속받았은 것에 주목하세요! 또한 scrapy.Field를 통해 파이썬의 각 속성이 scrapy의 필드라는 것을 명시해야 합니다.

 

 

apt_trade_spiders.py

import datetime as dt
from urllib.parse import urlencode

import scrapy
from scrapy import Selector

import invest_crawler.consts as CONST


class TradeSpider(scrapy.spiders.XMLFeedSpider):
    name = 'trade'

    def start_requests(self):
        page_num = 1
        date = dt.datetime(2006, 1, 1)
        urls = [
            CONST.APT_DETAIL_ENDPOINT
        ]
        params = {
            "pageNo": str(page_num),
            "numOfRows": "999",
            "LAWD_CD": "44133",
            "DEAL_YMD": date.strftime("%Y%m"),
        }
        for url in urls:
            url += urlencode(params)
            print(url)
            yield scrapy.Request(url=url)

    def parse(self, response):
        selector = Selector(response, type='xml')
        items = selector.xpath('//%s' % self.itertag)  # self.intertag는 기본적으로 item으로 되어 있음

        for item in items:
            print(item)

 

  • parse 메서드에 scrapy에서 데이터를 추출하는 데 쓰이는 객체인 Selector가 코드에 들어있는 것을 볼 수 있습니다. 이 Selector는 xml, html 같은 HTTP 응답 포맷에 따라 데이터를 추출하는 데 유용하게 사용할 수 있습니다.
  • xpath란 xml 포맷에서 각 데이터가 위치한 경로를 효과적으로 나타내기 위한 방식입니다. 이 xpath를 이용하면 여러 개로 나뉘어진 xml 태그들을 쉽게 추출할 수 있는 것은 물론 다소 까다로운 조건으로 추출해야하는 xml 태그들도 손쉽게 뽑아낼 수 있습니다. 
    여기서 xpath는 위 코드에서 selector.xpath('//item')으로 해석되어 집니다. 이 의미는 xml 포맷 데이터에서 모든 item 태그들을 선택하라라는 의미와 같습니다. 
  • for .. in 구문을 이용하여 xpath를 통해 추출된 item들을 순회하는 코드입니다. 위 코드를 아래 실행 명령어를 통해 실행하게 되면 다음과 같은 결과값이 콘솔에 나올 것 입니다. 

 

실행 명령어

scrapy crawl trade

 

결과 화면

[... 생략 ...]
<Selector xpath='//item' data='<item><거래금액>     8,950</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>     9,100</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>    14,000</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>    13,000</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>    10,000</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>     9,050</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>     9,200</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>     9,500</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>     9,350</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>    10,000</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>     9,300</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>    13,100</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>     7,800</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>     8,500</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>    21,600</거래금액><건축년도>20...'>
<Selector xpath='//item' data='<item><거래금액>     9,500</거래금액><건축년도>19...'>
<Selector xpath='//item' data='<item><거래금액>    20,250</거래금액><건축년도>20...'>
<Selector xpath='//item' data='<item><거래금액>     9,000</거래금액><건축년도>19...'>
[... 생략 ...]

 

| Scrapy의 item 객체에 데이터 집어넣기 

 

위에서 한 대로 Selector 객체 형태로만 데이터를 처리하려고 하면 코딩하는 데 시간도 많이 걸릴 뿐더러 코드도 난잡해질 가능성이 커집니다! 이 때 위에서 설명한 Item 객체에 데이터를 넣어 처리하게 되면 scrapy를 통해 아주 손쉽게 데이터를 처리할 수 있습니다.

 

apt_trade_spiders.py

import datetime as dt
from urllib.parse import urlencode

import scrapy
from scrapy import Selector

import invest_crawler.consts as CONST
from invest_crawler.items.apt_trade import AptTradeScrapy


class TradeSpider(scrapy.spiders.XMLFeedSpider):
    name = 'trade'

    def start_requests(self):
        page_num = 1
        date = dt.datetime(2006, 1, 1)
        urls = [
            CONST.APT_DETAIL_ENDPOINT
        ]
        params = {
            "pageNo": str(page_num),
            "numOfRows": "999",
            "LAWD_CD": "44133",
            "DEAL_YMD": date.strftime("%Y%m"),
        }
        for url in urls:
            url += urlencode(params)
            print(url)
            yield scrapy.Request(url=url)

    def parse(self, response):
        selector = Selector(response, type='xml')
        items = selector.xpath('//%s' % self.itertag)  # self.intertag는 기본적으로 item으로 되어 있음

        for item in items:
            apt_trade = self.parse_item(item)
            print(apt_trade)

    def parse_item(self, item):
        state = "천안시"
        district = "서북구"

        try:
            apt_trade_data = AptTradeScrapy(
                apt_name=item.xpath("./아파트/text()").get(),
                address_1=state,
                address_2=district,
                address_3=item.xpath("./법정동/text()").get().strip(),
                address_4=item.xpath("./지번/text()").get(),
                address=state + " " + district + " " + item.xpath("./법정동/text()").get().strip() + " " +
                        item.xpath("./지번/text()").get(),
                age=item.xpath("./건축년도/text()").get(),
                level=item.xpath("./층/text()").get(),
                available_space=item.xpath("./전용면적/text()").get(),
                trade_date=item.xpath("./년/text()").get() + "/" +
                           item.xpath("./월/text()").get() + "/" +
                           item.xpath("./일/text()").get(),
                trade_amount=item.xpath("./거래금액/text()").get().strip().replace(',', ''),
            )
        except Exception as e:
            print(e)
            self.logger.error(item)
            self.logger.error(item.xpath("./아파트/text()").get())

        return apt_trade_data
  • item에 있는 데이터를 추출하는 parse_item 메서드를 추가하였습니다.
  • item에서 xpath를 사용하여 xml에 있는 데이터를 추출하는 작업을 하는 코드를 작성했습니다. text()는 xml 태그 안에 있는 텍스트로 된 데이터를 의미합니다. 
  • 크롤링에서 예기치 못한 공백 문자가 생겨서 같은 데이터라도 다르게 판단되거나 제대로 데이터가 처리되지 않는 경우가 종종 발생합니다. 따라서 strip 메서드를 써서 앞 뒤 공백 문자를 제거하는 작업을 하는 것이 좋습니다.

 

결과 화면

[... 생략...]
{'address': '천안시 서북구 쌍용동 889',
 'address_1': '천안시',
 'address_2': '서북구',
 'address_3': '쌍용동',
 'address_4': '889',
 'age': '1994',
 'apt_name': '대우타워',
 'available_space': '59.76',
 'level': '17',
 'trade_amount': '8450',
 'trade_date': '2006/1/4'}
{'address': '천안시 서북구 쌍용동 1920',
 'address_1': '천안시',
 'address_2': '서북구',
 'address_3': '쌍용동',
 'address_4': '1920',
 'age': '1999',
 'apt_name': '쌍용마을 뜨란채,주공아파트',
 'available_space': '84.61',
 'level': '14',
 'trade_amount': '14500',
 'trade_date': '2006/1/5'}
[... 생략...]

다음 포스팅에는 이 xml 데이터를 csv 파일 형태 혹은 excel에다가 저장하는 법을 다루도록 하겠습니다.



출처: https://engkimbs.tistory.com/963?category=807933 [새로비]