반응형

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

반응형

| 들어가기 전에


GIT 저장소


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


[유용한 정보들] - 국토교통부 공공데이터 부동산 실거래가 API 신청 방법



이전 포스팅


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



| 부동산 매매 데이터 간단하게 받아오기



국토교통부 사이트에 접속해서 로그인을 하신다음 마이페이지의 오픈 API를 클릭합니다. 그러면 전에 신청했던 API의 목록이 나오며 API에서 데이터를 얻기 위한 일반 인증키와 End Point를 다음과 같이 제공합니다.


이 일반 인증키와 End Point를 조합해서 URL을 만든 후 scrapy로 데이터를 간단하게 받아와서 파싱하는 작업을 할 것입니다. 여기서는 아파트매매 실거래 자료를 받아오도록 하겠습니다.




| 프로젝트 구성


프로젝트 구조

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

  • 간단한 예시를 들기 위해 전에 startproject에서 있었던 items.py, middleswares.py, pipelines.py 파일들을 삭제했습니다. 추후 예제 프로젝트에 기능을 추가할 때마다 item, middlesware, pipeline 모듈을 추가하여 프로젝트를 구성할 예정입니다.
  • 웹에서 데이터를 요청하고 받아오는 핵심 모듈을 spider라고 합니다. 공공데이터를 받아오는 spider 코드가 있는 apt_trade_spiders.py 파일을 추가했습니다.


소스 코드


consts.py

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

  • 위에서 받아온 일반 인증키와 End Point를 조합하여 위와 같은 ENDPOINT URL을 구성해야 합니다. 이 URL에 LAWD_CD와 DEAL_YMD, pageNo, numOfRows 파라미터를 추가하여야 데이터를 받아올 수 있습니다.


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)
yield scrapy.Request(url=url)

def parse(self, response):

print(response.body)

  • 부동산 공공데이터를 핵심 로직이 담겨있는 TradeSpider 클래스 코드입니다. scrapy의 spiders.XMLFeedSpider를 상속받아서 크롤러를 만들었습니다. 
  • name 속성은 scrapy에서 spider의 이름을 나타냅니다. 이때 각 name은 spider마다 유일한 값이며 각 spider를 식별합니다. 
  • start_requests 메서드는 scrapy가 크롤링을 시도할 때 제일 먼저 요청되는 로직을 구현하는 곳입니다. 이 최초 요청을 시점으로 spider가 다른 새로운 요청을 생성하여 크롤링을 진행할 수 있습니다.
  • start_requests를 보면 urls 변수에 APT_DETAIL_ENDPOINT 값을 볼 수 있습니다. 이 값은 매매데이터의 URL을 나타낸 것입니다. 또한 params 변수에 각 파라미터들을 추가하여 url에 파라미터를 더해 특정 데이터를 요청할 수 있습니다. 위에서는 LAWD_CD 파라미터에 44133(충청남도 천안시를 나타내는 코드)와 DEAL_YMD에 20060101 날짜를 집어넣어서 충청남도 천안시의 2006년 1월 1일에 거래된 부동산 매매 데이터를 받아 올 수 있습니다. pageNo와 numOfRows는 각각 데이터를 받아올 때 몇 번째 page 데이터를 받아올 것인지와 얼마만큼의 행 데이터를 받아올 것인지를 나타냅니다. numOfRows를 999로 해놓으면 한 번에 많은 데이터를 받아올 수 있습니다.
  • 참고로 각 지역의 지역코드는 아래 링크를 통해 확인할 수 있습니다.
    https://github.com/drtagkim/kor_gg_code/blob/master/region_code5.csv
  • parse 메서드는 요청한 데이터가 들어올 때 그 데이터를 분석하는 로직을 구현하는 메서드입니다. 요청한 데이터가 들어올 시 특정 이벤트가 발생하며 이 이벤트가 발생함에 따라 parse 메서드가 호출됩니다. 이 요청한 데이터는 response 인수에 담겨져 오며 이 response를 중심으로 데이터를 처리해야합니다. 


settings.py

# Automatically created by: scrapy startproject
#
# For more information about the [deploy] section see:
# https://scrapyd.readthedocs.io/en/latest/deploy.html

[settings]
default = invest_crawler.settings

[deploy]
#url = http://localhost:6800/
project = invest_crawler


실행 명령어

scrapy crawl trade
  • crawl이란 명령어를 통해 scrapy spider를 실행하여 데이터를 받아옵니다. crawl 명령어 다음에는 spider의 이름을 적어 어떤 spider를 실행할 것인지를 나타냅니다. ( 이 명령어는 scrapy 프로젝트 안에서 꼭 실행해야 합니다! 안 그러면 정상적으로 spider가 실행되지 않습니다.)


결과 화면

2020-03-14 17:34:15 [scrapy.utils.log] INFO: Scrapy 1.8.0 started (bot: invest_crawler)
2020-03-14 17:34:15 [scrapy.utils.log] INFO: Versions: lxml 4.4.2.0, libxml2 2.9.5, cssselect 1.1.0, parsel 1.5.2, w3lib 1.21.0, Twisted 19.10.0, Python 3.8.1 (tags/v3.8.1:1b293b6, Dec 18 2019, 23:11:46) [MSC v.1916 64 bit (AMD64)], pyOpenSSL 19.1.0 (OpenSSL 1.1.1d 10 Sep 2019), cryptography 2.8,
Platform Windows-10-10.0.18362-SP0
2020-03-14 17:34:15 [scrapy.crawler] INFO: Overridden settings: {'BOT_NAME': 'invest_crawler', 'NEWSPIDER_MODULE': 'invest_crawler.spiders', 'ROBOTSTXT_OBEY': True, 'SPIDER_MODULES': ['invest_crawler.spiders']}
2020-03-14 17:34:15 [scrapy.extensions.telnet] INFO: Telnet Password: 4f49a8ba194b9dbf
2020-03-14 17:34:15 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
'scrapy.extensions.telnet.TelnetConsole',
'scrapy.extensions.logstats.LogStats']
2020-03-14 17:34:16 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
'scrapy.downloadermiddlewares.retry.RetryMiddleware',
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
'scrapy.downloadermiddlewares.stats.DownloaderStats']
2020-03-14 17:34:16 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
'scrapy.spidermiddlewares.referer.RefererMiddleware',
'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
'scrapy.spidermiddlewares.depth.DepthMiddleware']
2020-03-14 17:34:16 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2020-03-14 17:34:16 [scrapy.core.engine] INFO: Spider opened
2020-03-14 17:34:16 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2020-03-14 17:34:16 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
2020-03-14 17:34:16 [scrapy.core.engine] DEBUG: Crawled (404) <GET http://openapi.molit.go.kr:8081/robots.txt> (referer: None)
2020-03-14 17:34:16 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://openapi.molit.go.kr:8081/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTrade?serviceKey=aWiZGAJkCsr3wM0YkDO%2BssYpNXZ%2Fawefawdpoj5k1zcCVRBTfThOHm57USjgxOgfvaBEtBQID5rIcjXsg%3D%3D&pageNo=1&numOfRo
ws=999&LAWD_CD=44133&DEAL_YMD=200601> (referer: None)
b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?><response><header><resultCode>00</resultCode><resultMsg>NORMAL SERVICE.</resultMsg></header><body><items><item><\xea\xb1\xb0\xeb\x9e\x98\xea\xb8\x88\xec\x95\xa1> 8,950</\xea\xb1\xb0\xeb\x9e\x98\xea\xb8\x88\xec\x95\xa1><\xea\xb1\xb4\xec\xb
6\x95\xeb\x85\x84\xeb\x8f\x84>1993</\xea\xb1\xb4\xec\xb6\x95\xeb\x85\x84\xeb\x8f\x84><\xeb\x85\x84>2006</\xeb\x85\x84><\xeb\xb2\x95\xec\xa0\x95\xeb\x8f\x99> \xec\x84\xb1\xec\xa0\x95\xeb\x8f\x99</\xeb\xb2\x95\xec\xa0\x95\xeb\x8f\x99><\xec\x95\x84\xed\x8c\x8c\xed\x8a\xb8>\xed\x99\x9c\xeb\xa6\xbc1\xec
\xb0\xa8</\xec\x95\x84\xed\x8c\x8c\xed\x8a\xb8><\xec\x9b\x94>1</\xec\x9b\x94><\xec\x9d\xbc>7</\xec\x9d\xbc><\xec\xa0\x84\xec\x9a\xa9\xeb\xa9\xb4\xec\xa0\x81>84.15</\xec\xa0\x84\xec\x9a\xa9\xeb\xa9\xb4\xec\xa0\x81><\xec\xa7\x80\xeb\xb2\x88>141-3</\xec\xa7\x80\xeb\xb2\x88><\xec\xa7\x80\xec\x97\xad\xe
c\xbd\x94\xeb\x93\x9c>44133</\xec\xa7\x80\xec\x97\xad\xec\xbd\x94\xeb\x93\x9c><\xec\xb8\xb5>4</\xec\xb8\xb5></item><item><\xea\xb1\xb0\xeb\x9e\x98\xea\xb8\x88\xec\x95\xa1> 9,100</\xea\xb1\xb0\xeb\x9e\x98\xea\xb8\x88\xec\x95\xa1><\xea\xb1\xb4\xec\xb6\x95\xeb\x85\x84\xeb\x8f\x84>1991</\xea\xb1\xb
4\xec\xb6\x95\xeb\x85\x84\xeb\x8f\x84><\xeb\x85\x84>2006</\xeb\x85\x84><\xeb\xb2\x95\xec\xa0\x95\xeb\x8f\x99> \xec\x84\xb1\xec\xa0\x95\xeb\x8f\x99</\xeb\xb2\x95\xec\xa0\x95\xeb\x8f\x99><\xec\x95\x84\xed\x8c\x8c\xed\x8a\xb8>\xec\xa3\xbc\xea\xb3\xb56-3</\xec\x95\x84\xed\x8c\x8c\xed\x8a\xb8><\xec\x9b\
x94>1</\xec\x9b\x94><\xec\x9d\xbc>12</\xec\x9d\xbc><\xec\xa0\x84\xec\x9a\xa9\xeb\xa9\xb4\xec\xa0\x81>46.99</\xec\xa0\x84\xec\x9a\xa9\xeb\xa9\xb4\xec\xa0\x81><\xec\xa7\x80\xeb\xb2\x88>789</\xec\xa7\x80\xeb\xb2\x88><\xec\xa7\x80\xec\x97\xad\xec\xbd\x94\xeb\x93\x9c>44133</\xec\xa7\x80\xec\x97\xad\xec\
xbd\x94\xeb\x93\x9c><\xec\xb8\xb5>4</\xec\xb8\xb5></item><item><\xea\xb1\xb0\xeb\x9e\x98\xea\xb8\x88\xec\x95\xa1>


....

[생략]

...


\xeb\x85\x84><\xeb\xb2\x95\xec\xa0\x95\xeb\x8f\x99> \xec\x84\xb1\xec\xa0\x95\xeb\x8f\x99</\xeb\xb2\x95\xec\xa0\x95\xeb\x8f\x99><\xec\x95\x84\xed\x8c\x8c\xed\x8a\xb8>\xeb\x8c\x80\xec\x9a\xb0\xeb\xaa\xa9\xed\x99\x942</\xec\x95\x84\xed\x8c\x8c\xed\x8a\xb8><\xec\x9b\x94>1</\xec\x9b\x94><\xec\x9d\xbc>16
\x94\xeb\x93\x9c><\xec\xb8\xb5>7</\xec\xb8\xb5></item><item><\xea\xb1\xb0\xeb\x9e\x98\xea\xb8\x88\xec\x95\xa1> 4,500</\xea\xb1\xb0\xeb\x9e\x98\xea\xb8\x88\xec\x95\xa1><\xea\xb1\xb4\xec\xb6\x95\xeb\x85\x8
4\xeb\x8f\x84>1996</\xea\xb1\xb4\xec\xb6\x95\xeb\x85\x84\xeb\x8f\x84><\xeb\x85\x84>2006</\xeb\x85\x84><\xeb\xb2\x95\xec\xa0\x95\xeb\x8f\x99>\xec\x9e\x85\xec\x9e\xa5\xeb\xa9\xb4 \xed\x95\x98\xec\x9e\xa5\xeb\xa6\xac</\xeb\xb2\x95\xec\xa0\x95\xeb\x8f\x99><\xec\x95\x84\xed\x8c\x8c\xed\x8a\xb8>\xec\x97\
xb0\xed\x95\xa9\xec\xb4\x88\xec\x9b\x90</\xec\x95\x84\xed\x8c\x8c\xed\x8a\xb8><\xec\x9b\x94>1</\xec\x9b\x94><\xec\x9d\xbc>26</\xec\x9d\xbc><\xec\xa0\x84\xec\x9a\xa9\xeb\xa9\xb4\xec\xa0\x81>53.82</\xec\xa0\x84\xec\x9a\xa9\xeb\xa9\xb4\xec\xa0\x81><\xec\xa7\x80\xeb\xb2\x88>84</\xec\xa7\x80\xeb\xb2\x88
><\xec\xa7\x80\xec\x97\xad\xec\xbd\x94\xeb\x93\x9c>44133</\xec\xa7\x80\xec\x97\xad\xec\xbd\x94\xeb\x93\x9c><\xec\xb8\xb5>3</\xec\xb8\xb5></item><item><\xea\xb1\xb0\xeb\x9e\x98\xea\xb8\x88\xec\x95\xa1> 2,910</\xea\xb1\xb0\xeb\x9e\x98\xea\xb8\x88\xec\x95\xa1><\xea\xb1\xb4\xec\xb6\x95\xeb\x85\x84\
xeb\x8f\x84>1992</\xea\xb1\xb4\xec\xb6\x95\xeb\x85\x84\xeb\x8f\x84><\xeb\x85\x84>2006</\xeb\x85\x84><\xeb\xb2\x95\xec\xa0\x95\xeb\x8f\x99>\xec\x9e\x85\xec\x9e\xa5\xeb\xa9\xb4 \xec\x8b\xa0\xeb\x8d\x95\xeb\xa6\xac</\xeb\xb2\x95\xec\xa0\x95\xeb\x8f\x99><\xec\x95\x84\xed\x8c\x8c\xed\x8a\xb8>\xec\x97\xb
0\xed\x95\xa9</\xec\x95\x84\xed\x8c\x8c\xed\x8a\xb8><\xec\x9b\x94>1</\xec\x9b\x94><\xec\x9d\xbc>16</\xec\x9d\xbc><\xec\xa0\x84\xec\x9a\xa9\xeb\xa9\xb4\xec\xa0\x81>59.52</\xec\xa0\x84\xec\x9a\xa9\xeb\xa9\xb4\xec\xa0\x81><\xec\xa7\x80\xeb\xb2\x88>200-21</\xec\xa7\x80\xeb\xb2\x88><\xec\xa7\x80\xec\x97
\xad\xec\xbd\x94\xeb\x93\x9c>44133</\xec\xa7\x80\xec\x97\xad\xec\xbd\x94\xeb\x93\x9c><\xec\xb8\xb5>1</\xec\xb8\xb5></item></items><numOfRows>999</numOfRows><pageNo>1</pageNo><totalCount>161</totalCount></body></response>'
2020-03-14 17:34:16 [scrapy.core.engine] INFO: Closing spider (finished)
2020-03-14 17:34:16 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 706,
'downloader/request_count': 2,
'downloader/request_method_count/GET': 2,
'downloader/response_bytes': 48422,
'downloader/response_count': 2,
'downloader/response_status_count/200': 1,
'downloader/response_status_count/404': 1,
'elapsed_time_seconds': 0.307194,
'finish_reason': 'finished',
'finish_time': datetime.datetime(2020, 3, 14, 8, 34, 16, 622155),
'log_count/DEBUG': 2,
'log_count/INFO': 10,
'response_received_count': 2,
'robotstxt/request_count': 1,
'robotstxt/response_count': 1,
'robotstxt/response_status_count/404': 1,
'scheduler/dequeued': 1,
'scheduler/dequeued/memory': 1,
'scheduler/enqueued': 1,
'scheduler/enqueued/memory': 1,
'start_time': datetime.datetime(2020, 3, 14, 8, 34, 16, 314961)}
2020-03-14 17:34:16 [scrapy.core.engine] INFO: Spider closed (finished)

위는 스크래피를 그대로 실행했을 때의 결과 화면입니다. 위에서 보듯이 국토교통부에서 제공하는 공공데이터를 xml형식으로 그대로 받아오는 것을 알 수 있습니다. 주목할 부분은 아래와 같이 공공데이터 API에 데이터를 요청하는 부분입니다.


2020-03-14 17:34:16 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://openapi.molit.go.kr:8081/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTrade?serviceKey=aWiZGAJkCsr3wM0YkDO%2BssYpNXZ%2FEWZfuIW5k%2FcHFtD5k1zcCVRBTfThOHmawefasdfwBEtBQID5rIcjXsg%3D%3D&pageNo=1&numOfRo
ws=999&LAWD_CD=44133&DEAL_YMD=200601>


이 요청부분을 그대로 복사하여 브라우저의 검색창에 복사하면 아래와 같은 요청 데이터가 웹 브라우저 상에 나타납니다.




다음 시간에는 이 xml 데이터에서 유용한 정보를 어떻게 추출해서 가공하는 지 알아보겠습니다.

반응형

이 글을 공유하기

댓글

Designed by JB FACTORY