[파이썬 크롤링/부동산 데이터] scrapy를 이용하여 부동산 공공 데이터 DB에 저장하기

2021. 3. 28. 03:00 Python/파이썬 웹 크롤링

| 들어가기 전에

 

GIT 저장소

 

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

 

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

 

 

이전 포스팅

 

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

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

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

[Python/파이썬 웹 크롤링] - [파이썬 크롤링/부동산 데이터] scrapy를 이용한 부동산 공공 데이터 저장하기(csv/excel)

[Python/파이썬 웹 크롤링] - [파이썬 크롤링/부동산 데이터] scrapy를 이용하여 재귀적으로 부동산 공공 데이터 연속적으로 가져오기 및 excel 저장

 

포스팅에 있는 내용을 따라하기 위해서는 sqlalchemy 패키지를 설치해야합니다. sqlalchemy는 쉽게 설명하자면 python으로 DB에 접근해서 데이터를 저장, 삭제, 갱신 등 데이터를 조작할 수 있게 도와주는 이브러리입니다.  자세히 설명하자면 포스팅의 내용이 길어질 수 있기 때문에 이렇게 짧게 설명하도록 하겠습니다.

pip install sqlalchemy

 

또한 이번에는 DataFrame에 저장된 데이터를 SQLite라는 DB에 저장할 것입니다. 이 DB의 데이터를 보려면 해당 DB에 접속하는 툴이 필요합니다. 여기서는 DB Browser for SQLite를 써서 DB에 접속해 볼 것입니다. 

 

해당 툴 다운로드는 여기를 방문해서 다운로드 받으시면 됩니다.

 

 

 재귀적으로 이용해서 공공데이터를 연속적으로 가져오기

 

 

프로젝트 구조

|   scrapy.cfg
\---invest_crawler
    |   consts.py
    |   settings.py
    |   __init__.py
    |
    +---items
    |   |   apt_trade.py
    |   |   __init__.py
    |
    +---spiders
    |   |   apt_trade_spiders.py
    |   |   __init__.py

 

소스 코드

 

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()

    def to_dict(self):
        return {
            '아파트': self['apt_name'],
            '시/도': self['address_1'],
            '군/구': self['address_2'],
            '동/읍/면': self['address_3'],
            '번지': self['address_4'],
            '전체주소': self['address'],
            '연식': self['age'],
            '층': self['level'],
            '면적': self['available_space'],
            '거래일자': self['trade_date'],
            '매매가격': self['trade_amount']
        }

 

apt_trade_spiders.py

import datetime as dt
from urllib.parse import urlencode

import pandas as pd
import scrapy
from dateutil.relativedelta import relativedelta
from openpyxl import Workbook
from scrapy import Selector
from sqlalchemy import create_engine

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):
        self.engine = create_engine('sqlite:///CRAWLER.DB', echo=False)
        date = dt.datetime(2006, 1, 1)
        Workbook().save('APT_TRADE.xlsx')
        yield from self.get_realestate_trade_data(date)

    def get_realestate_trade_data(self, date):
        page_num = 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)
            yield scrapy.Request(url=url, callback=self.parse, cb_kwargs=dict(page_num=page_num, date=date))

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

        if not items:
            return

        """
            To save to Excel Sheet
        """
        # To remove 'Sheet' worksheet created automatically
        # if date.strftime("%Y%m") == "200604":
        #     wb = load_workbook('APT_TRADE.xlsx')
        #     wb.remove(wb['Sheet'])
        #     wb.save('APT_TRADE.xlsx')
        #     return

        # apt_trades = [self.parse_item(item) for item in items]
        # apt_dataframe = pd.DataFrame.from_records([apt_trade.to_dict() for apt_trade in apt_trades])
        #
        # writer = pd.ExcelWriter('APT_TRADE.xlsx', engine='openpyxl', mode='a')
        # apt_dataframe.to_excel(writer, sheet_name='천안-' + date.strftime("%Y%m"), index=False)
        # writer.save()

        apt_trades = [self.parse_item(item) for item in items]
        apt_dataframe = pd.DataFrame.from_records([apt_trade.to_dict() for apt_trade in apt_trades])
        apt_dataframe.to_sql('APT_TRADE', con=self.engine, if_exists='append')

        date += relativedelta(months=1)
        yield from self.get_realestate_trade_data(date)

    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

 

  • start_requests 메서드에 self.engine = create_engine('sqlite:///CRAWLER.DB', echo=False)라는 코드 한 줄이 추가되었습니다. create_engine은 SQLAlchemy에서 mysql, oracle, sqlite 같은 DB의 접속과 메타데이터 생성 등을 수행하는 역할을 하는 객체를 반환합니다. 
    참고로 echo=False의 의미는 SQLAlchemy가 관련 engine을 생성하면서 나오는 로그를 콘솔창에 출력하지 않게 만드는 설정 옵션입니다.
self.engine = create_engine('sqlite:///CRAWLER.DB', echo=False)
  • parse 메서드에서 아래와 같이 딱 한 줄의 파이썬 코드가 추가되었습니다.  이 한 줄로 천안의 2006~2020 년도의 데이터를 sqlite DB에 저장할 수 있습니다. 'APT_TRADE' 은 데이터가 저장될 DB의 테이블의 이름을 나타냅니다. con은 pandas의 DataFrame이 저장될 DB의 엔진을 의미합니다. if_exists는 만약 DB에 같은 테이블이 있을 시 그 테이블에 데이터를 이어 붙이겠다는 뜻입니다. 
    apt_dataframe.to_sql('APT_TRADE', con=self.engine, if_exists='append')
apt_dataframe.to_sql('APT_TRADE', con=self.engine, if_exists='append')

 

 DB Browser로 데이터 보기

 

먼저 DB Browser를 실행합니다. 실행하게되면 다음과 같은 화면을 볼 수 있을 겁니다. 여기서 데이터베이스 열기를 클릭합니다.

 

 

그리고 위 코드를 실행하고 나온 sqlite db file을 클릭합니다.

 

 

아래와 같이 데이터보기를 클릭하게 되면 2006~2020 천안의 공공데이터가 들어있는 것을 볼 수 있습니다.

 

 

이렇게 DB를 이용하여 데이터를 저장하게 되면 Excel를 통해서 저장할 내용을 체계적으로 저장하는 것이 가능합니다. 또한 데이터를 저장할 때의 속도도 Excel보다 훨씬 빠르죠 ( DB : 2~3분 vs Excel : 50~60분)

 

지금까지 scrapy를 이용하여 부동산 공공데이터를 크롤링하고 저장하는 내용을 다뤘습니다. 다음 포스팅에서는 Jupyter Notebook으로 이 데이터들을 어떻게 분석해볼 수 있을 지 다뤄보도록 하겠습니다.



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