국내의 공공 데이터 역시 시대의 흐름에 맞게 open API로,
딥러닝을 포함한 여러 데이터 기반 어플리케이션 프로그램 제작에 필요한 데이터들이 open API 를 통해 공개 되어 있다. (https://www.data.go.kr/)
이 사이트에서 자신이 원하는 데이터의 종류를 검색하고, 매우 간단한 회원 가입을 통해 (즉, 공인인증서와 같은 번거롭고 복잡한 절차 없는 가입을 통해)
서비스 키를 요청하면 대부분의 데이터의 경우 자동적이고 즉각적으로 api key가 발급이 된다.
이때 url로 쿼리를 전송하여 공공 데이터를 저장하는데 사용한 언어는 python인데 다른 경우들과 마찬가지로 한글 encoding이 꽤나 귀찮은 문제가 되곤한다.
따라서 이번 포스트에서는 한글을 포함한 공공 데이터 파일을 query를 통해 요청하고 그 response를 한글 encoding으로 저장하여,
궁극적으로 이 데이터를 csv 로 저장하는 방법에 대해 다루도록 하겠다.
Part1 에서는 쿼리를 보내고, 이 쿼리의 데이터를 어레이로 저장하는 법에 대해 다루고,
Part2에서는 그 쿼리의 데이터를 csv로 저장하는 법에 대해 다루겠다.
1. Query sender 메소드:
- 기능: local code( 서울 시내 특정 구에 해당하는 코드), time code(201612와 같이 특정 연월을 입력함.) 을 받아,
특정 구에서 특정 연월에 일어난 아파트 실거래 데이터를 한글로 읽어들여 문자열로 저장하는 함수이다.
- 구현
이 때 url 코드로 쿼리를 전송하는데, 쿼리의 내용을 작성하고 요청을 전송하기 위해 request instance를 만들어야하는데 python3 의 경우 다음과 같은 library import가 필요하다.
import urllib.request
이 때 urllib.request.urlopen(request).read() 를 통해 쿼리를 전송하고 response를 읽어온다.
그리고 이 response는 utf-8 format이 아닐 수 있기 때문에, response body를 "utf-8" 로 읽어들여 스트링으로 저장한다.
def query_sender(localcode, timecode): url = 'http://openapi.molit.go.kr:8081/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTrade?' code_info = 'LAWD_CD='+ localcode + '&DEAL_YMD=' + timecode key = '&serviceKey=발급한 서비스키' request = urllib.request.Request(url+code_info+key) request.get_method = lambda: 'GET' response_body = urllib.request.urlopen(request).read() u = str(response_body, "utf-8") return u
- 사용: 그러면 아래와 같이 쿼리를 보내면, 쿼리에 대한 응답을 문자열로 반환한다.
s = query_sender('11110','201612')
그러나 이는 문자열에 지나지 않으며, 우리가 원하는 정보들을 산발적으로 보관하고 있다.
따라서 이 스트링으로 부터 우리가 원하는 정보를 csv형태로 저장하는 메소드가 추가적으로 더 필요하다.
2. Xml_to_item_list
- 기능: xml_string과 local_name과 time code를 받아, xml string으로 부터 data array를 만든다.
- 구현:
import xml.etree.ElementTree as ET
from xml.etree.ElementTree import parse, Element
먼저 xml string 스트링을 parsing하여, elementTree를 만든다. ( ElementTree는, xml을 효율적으로 관리해 데이터를 tree형태로 보관할수 있도록 하는 python 외부 라이브러리이다.)
(없는 경우 pip3 install ElementTree를 통해 설치하도록 하자.) 이 경우 ET.fromstring(xml_string)는, 그 tree의 root를 반환하기 때문에 우리가 원하는 item list를 반환받기 위해서는
elements = root.findall('body/items/item')
과 같이 해주어야한다. 그리고 이 elements list를 순회하면서, item_list array에 정보들을 append하고, 이를 result에 append하면 된다.
(그리고 중간에 에러가 날 수 있는데, 이 부분은 저장하지 않도록 한다. missing entry가 있는 record는 추후 에러를 일으킬 소지가 있다.)
def xml_to_item_list(xml_string,local_name, timecode):
result = []
root = ET.fromstring(xml_string)
elements = root.findall('body/items/item')
for item in elements:
try:
item_list = []
item_list.append(time_code)
item_list.append(item.find('거래금액').text)
item_list.append(item.find('건축년도').text)
item_list.append(local_name+" "+item.find('법정동').text+" "+item.find('지번').text)
item_list.append(item.find('아파트').text)
item_list.append(item.find('전용면적').text)
result.append(item_list)
except Exception as e:
print("This row will be ignored. ",item_list)
return result
- 사용 : 그리고 다음과 같이 이 함수를 호출하면, 해당 쿼리에 저장된 데이터들이 array에 마치맞게 잘 저장이 된다.
item_list = xml_to_item_list(xml_string,'11110','201612')
이를 pprint로 출력하면
[['201612', ' 87,000', '2000', '11110 청운동 56-45', '청운현대', '129.76'],
['201612', ' 114,500', '2008', '11110 사직동 9', '광화문풍림스페이스본(9-0)', '146.92'],
['201612', ' 100,500', '2008', '11110 사직동 9', '광화문풍림스페이스본(9-0)', '131.44'],
['201612', ' 117,500', '2004', '11110 내수동 72', '경희궁의아침3단지', '150.48'],
['201612', ' 98,000', '2003', '11110 내수동 95', '파크팰리스', '117.26'],
['201612', ' 96,000', '2003', '11110 내수동 95', '파크팰리스', '103.77'],
['201612', ' 118,500', '2004', '11110 내수동 73', '경희궁의아침4단지', '124.17'],
['201612', ' 92,000', '2003', '11110 내수동 95', '파크팰리스', '103.77'],
['201612', ' 125,000', '2004', '11110 내수동 73', '경희궁의아침4단지', '150.77'],
['201612', ' 28,000', '2003', '11110 익선동 55', '현대뜨레비앙', '48.54'],
['201612', ' 27,000', '2003', '11110 익선동 55', '현대뜨레비앙', '48.54'],
['201612', ' 39,000', '1998', '11110 익선동 30-6', '운현신화타워', '84.91'],
['201612', ' 25,000', '2003', '11110 익선동 55', '현대뜨레비앙', '46.64'],
['201612', ' 51,000', '2006', '11110 인의동 48-2', '효성쥬얼리시티', '98.39'],
['201612', ' 63,000', '2006', '11110 인의동 48-2', '효성쥬얼리시티', '106.81'],
['201612', ' 55,000', '2006', '11110 인의동 48-2', '효성쥬얼리시티', '84.7'],
['201612', ' 17,700', '2014', '11110 연건동 195-10', '이화에수풀', '16.98'],
['201612', ' 17,500', '2014', '11110 연건동 195-10', '이화에수풀', '16.98'],
['201612', ' 18,000', '2014', '11110 연건동 195-10', '이화에수풀', '16.98'],
['201612', ' 82,000', '1996', '11110 명륜2가 236', '아남2', '114.36'],
['201612', ' 62,800', '1995', '11110 명륜2가 4', '아남1', '84.9'],
['201612', ' 24,000', '1994', '11110 창신동 640-252', '성용', '59.28'],
['201612', ' 31,000', '1993', '11110 창신동 703', '창신쌍용2', '64.66'],
['201612', ' 37,500', '1993', '11110 창신동 703', '창신쌍용2', '79.87'],
['201612', ' 31,600', '1993', '11110 창신동 703', '창신쌍용2', '54.7'],
['201612', ' 45,100', '1993', '11110 창신동 703', '창신쌍용2', '106.62'],
['201612', ' 42,200', '1999', '11110 창신동 232', '두산', '59.95'],
['201612', ' 33,500', '1992', '11110 창신동 702', '창신쌍용1', '54.7'],
['201612', ' 37,000', '1999', '11110 창신동 232', '두산', '59.95'],
['201612', ' 11,300', '2013', '11110 숭인동 201-11', '종로유케이201', '12.01'],
['201612', ' 14,200', '2013', '11110 숭인동 1421', '버넷하임1', '17.96'],
['201612', ' 45,900', '2008', '11110 숭인동 2-1', '종로센트레빌', '59.92'],
['201612', ' 13,900', '2012', '11110 숭인동 296-19', '삼전솔하임2차', '16.67'],
['201612', ' 12,400', '2013', '11110 숭인동 1392-1', '종로아인스빌', '12.15'],
['201612', ' 56,000', '2009', '11110 숭인동 766', '종로청계힐스테이트', '84.95'],
['201612', ' 12,000', '2014', '11110 숭인동 1421-2', '숭인한양LEEPS', '12.78'],
['201612', ' 45,800', '2008', '11110 숭인동 2-1', '종로센트레빌', '59.92'],
['201612', ' 17,000', '1997', '11110 평창동 595', '갑을', '30.55'],
['201612', ' 31,500', '1996', '11110 평창동 145-5', '일성빌라트', '59.82'],
['201612', ' 72,000', '2008', '11110 평창동 102', '르트랑시망', '140.97'],
['201612', ' 136,000', '2009', '11110 평창동 108', '롯데캐슬 로잔', '191.73'],
['201612', ' 55,990', '2000', '11110 무악동 82', '현대', '84.92'],
['201612', ' 48,000', '2000', '11110 무악동 82', '현대', '60'],
['201612', ' 68,000', '2008', '11110 무악동 60', '인왕산아이파크', '84.86'],
['201612', ' 74,000', '2008', '11110 무악동 60', '인왕산아이파크', '84.86'],
['201612', ' 68,990', '2000', '11110 무악동 82', '현대', '114.9'],
['201612', ' 63,990', '2000', '11110 무악동 82', '현대', '114.9'],
['201612', ' 59,990', '2000', '11110 무악동 82', '현대', '84.92'],
['201612', ' 59,490', '2000', '11110 무악동 82', '현대', '84.92'],
['201612', ' 59,650', '2000', '11110 무악동 82', '현대', '84.92'],
['201612', ' 85,900', '2008', '11110 무악동 60', '인왕산아이파크', '114.93']]
이 뜬다는 것을 알 수 있다.