어떡하집 프로젝트 - 1차 시스템 구축 계획

1차 시스템 목표로 Elastic 스택을 기반으로 데이터를 저장하고 시각화 합니다.

정민호정민호

1. 들어가며

기존에 ELK를 사용하던 경험이 있어 Elastic 스택을 사용하기로 결정하였습니다. Logstash와 Fluentd 사이에서 고민을 하였지만, 아직 배경지식이 얕아 차후에 고민을 더 해보겠습니다.

제가 구상하는 시스템의 모토(?)는 아래와 같습니다. 생각되로 잘 될지는 모르겠지만 노력해 보겠습니다.

  • PC에 종속적이지 않을 것

  • 무중단 운영

  • SW간 상호 독립적일 것

  • 누구나 사용할 수 있을 것

우선 1차 시스템을 목표로 하고 저의 시야가 보

2. 1차 : 시스템 구성도 및 데이터 흐름도

1차 시스템구성도

위의 그림은 1차 시스템 구성도 입니다. 기본적으로 모든 시스템은 도커를 이용하여 컨테이너 기반으로 구성하였고, 이를 관리하기 위해 쿠버네티스와 앤서블을 사용할 계획입니다. 각 시스템을 컨테이너로 구성함으로써 개별 PC의 개별 도커로 운영될 수도 있고, 공용 PC의 공용 도커에서 운영될 수도 있습니다.

아래 그림은 1차 시스템의 데이터흐름도 입니다.

1차 데이터흐름도

공공데이터포털이나 법정동코드, 도로명주소는 RDB 기반의 PostgreSQL 에 저장할 수 있도록 하였습니다. 현재는 주소 데이터를 CSV 파일 기반으로 받아서 수동으로 업로드 하고 있지만 차후 자동화할 계획입니다.

아파트 정보는 파이썬으로 수집하고 Filebeat를 이용해 Kafka로 전달하고, Logstash를 통해 Kafka가 전달받은 데이터를 가져와 Elasticsearch 또는 PostgreSQL에 저장하도록 합니다. Elasticsearch에 저장되어 있는 데이터를 Kibana를 통해 시각화하여 보여줍니다.

3. 데이터 수집 코드

파이썬으로 작성한 데이터 수집코드는 기존 단일 파일로 구현하였던것을 각 기능(역할)별로 파일로 나누어 리팩토링 하였습니다. 파이썬의 코드의 작성규칙은 pep8을 준수하려했지만 아직 완벽하지 않은것 같습니다.

1차 데이터흐름도

어떡하집 프로젝트 - 아파트 정보 수집 계획(아파트 정보 저장 방안 수립-1)

버추얼박스에서 OS를 설치하고 DB를 설치하여 주소 데이터를 저장합니다.

정민호정민호

1. 들어가며

공공데이터 포털에서 받은 데이터를 DB에 저장하는 과정을 테스트하기 위해_(공공데이터 약관에따라 일반→서버구축으로 변경)_ 임시로 가상머신에 CentOS-7 64비트 버전을 최소설치하고 PostgreSQL 11을 설치하고자 합니다.

가상머신 설치 및 SSH 설정, PostgreSQL 11 설치 방법은 구글에 검색하시거나 아래 링크를 참조해주시면 좋을것 같습니다.

2. DB 접속 및 테이블 생성

저는 PostgreSQL DB 접속을 pgAdmin이 아닌 DBeaver를 통해 접속을 하였고, 주소 데이터를 관리할 rowdata 계정을 추가하였습니다. 도로명과 법정동 정보는 HowHome 프로젝트에서 사용할 용어로 표준화 하였습니다.

먼저 테이블 생성에 앞서 데이터 표준사전을 만든 후 테이블을 생성하였습니다. 데이터 표준사전의 기본 구성은 아래와 같고, 저는 별도로 원본 주소정보의 스키마 정보를 저장해 놓았습니다. (의도는 데이터 표준사전이었지만 프로젝트가 안정될 때까지 작업 겸용으로 유지할 계획입니다.)

재개정이력

표준사전 변경시 어떤 변동사항이 있는지를 기록

원본주소정보

도로명과 법정동 원본 스키마 정보와 표준용어 매핑

단어분리

용어를 구성하는 최소 단어 단위로 분리

표준단어

단어의 의미와 약어(최대3자리) 정보 관리

표준도메인

데이터 도메인 분류와 데이터 타입 및 길이 정보 관리

표준용어

용어정보와 용어가 속해있는 표준 도메인 정보 관리

표준코드

DB에서 사용될 공통코드 등 각종 코드 정보 관리

행정표준용어

표준단어 작성시 참조

표준 단어의 약어는 대략 아래의 규칙을 기반으로 작성하였습니다.

- 약어는 자음 3자리로하며, 연속된 자음은 사용하지 않음
( Password -> PSW )

- 첫번째 철자는 자음/모음 상관없이 반드시 사용
( Apple -> APL )

- 전체 단어 철자가 3자 이하인 단어는 그대로 사용
( ID, ETC )

- 영문명 내의 전치사(for, of, about, to by, as), 관사(a, an), 정관사(the), 접속사(and, or,that)는 영문약어를 생성할 때 대상에서 제외
( In And Out -> INO

- 중복 시 다음 자음 사용, 자음 모두 중복 시 처음 나오는 모음을 사용
( Password -> PAS(중복) -> PSR(중복) -> PSD(중복) -> PSS(중복) -> PWR(중복) -> PWD(중복) -> PRD(중복) ->
PAS(중복) -> PAW(중복) -> PAR(중복) -> PAD(중복) -> PSO(중복) -> PWO(중복) -> POR(중복) -> POD )

- 2단어로 된 복합어 사용시 앞의 단어에서 2자리 뒤의 단어에서 1자리로 약어 생성
( Left Right -> LFR, Keyboard Mouse -> KBM, The Rock -> RCK, A apple -> APL)

- 3단어로 된 복합어 사용시 각 단어의 첫글자로 약어 생성
( Left Middle Right -> LMR, Keyboard Mouse Monitor -> KMM, The Left Right -> LFR )

- 4단어 이상으로 된 복합어 사용시 앞의 단어에서 2자리 마지막 단어에서 1자리로 약어 생성
( Left Right Keyboard Mouse -> LFM, Left Keyboard Mouse Monitor -> LFS, The Rock a Apple -> RCA )

완성된 표준 사전 및 테이블 생성 스크립트는 아래 링크를 통해 다운로드 받으실 수 있습니다.

표준사전및스크립트
표준사전
스크립트

테이블 생성 스크립트를 실행하면 아래 그림과 같이 테이블이 생성 됩니다.

테이블

3. DB 데이터 Import

PostgreSQL에 주소정보 데이터(*.csv)를 Import 하는 방법은 DBeaver와 psql의 copy 명령어를 이용하는 2가지 방법이 있습니다. 저는 두가지 방법 중 다소 불편하고 느린 방법이긴 하지만 DBeaver를 통해 데이터를 넣고자 합니다.

psql의 copy를 이용해보니 MS949 인코딩을 지원하지 않고, UTF-8로 변환하니 한글 데이터의 Byte가 2 → 3으로 변경되다보니 문제가 발생하였습니다. (참고 : psql -h localhost -U postgres postgres -c "COPY rowdata_addr.tbl_rna_dtl_adr FROM '/mnt/public_folder/202002/202002_상세주소DB_전체분/adrdc/adrdc_all.csv'(DELIMITER '|', FORMAT CSV, ENCODING 'ms949');")

먼저 DBeaver에 데이터를 Import 하기전 지역별로 분할되어 있는 파일을 하나로 합칩니다. 주소DB를 예로들면 코드, 부가정보, 주소, 지번 별로 데이터가 나뉘는것을 확인 할 수 있습니다.

따라서 각 분류별로 폴더를 생성하고 파일을 이동하고 명령프롬트프(CMD)를 본 폴더위치로 이동하여 'type *.txt > ../부가정보_all.txt' 를 실행하면, 상위폴더에 텍스트파일들이 합쳐진 '부가정보_all.txt' 이 생성된것을 학인할 수 있습니다.

파일합치기

스키마 내 테이블 목록에서 오른쪽 클릭하면 여러 항목들이 표출되는데 이 중 '데이터 가져오기’를 선택합니다.

데이터가져오기1

'CSV에서 가져오기’를 선택하고 '다음’을 선택합니다. 먼저 'Source_name’란을 선택하면 파일을 선택할 수 있는 파일브라우저가 표출되는데 주소DB의 '개선_도로명코드_전체분.txt’를 선택하겠습니다. (txt 파일이 보이지 않으면 우측하단의 *.csv를 * 또는 *.txt로 변경하시면 보입니다.) 그 다음 인코딩과 컬럼 구분자, 헤더구분을 가이드에 따라 각각 'ms949', '|', 'none’으로 변경하여 진행합니다.

데이터가져오기2

Column mapping 에서 Columns가 매핑되지 않을 땐 skip을 선택하여 진행합니다.

Settings 에서 테이블을 비우고 데이터를 추가하길 원하면 'Truncate target table(s) before load' 를 선택합니다. Commit after insert of 는 데이터를 커밋하는 주기를 설정하는데 저는 50,000 건 마다 Insert 할 수 있도록 설정 하였습니다. (지나치게 짧거나 큰 주기로 Insert 을 하게 되면 속도저하의 원인이 될 수 있습니다.)

데이터가져오기3
데이터가져오기4

어떡하집 프로젝트 - 아파트 정보 수집 계획(아파트 정보 수집 방안 수립-2)

파이썬3을 이용해 공공데이터포털의 이용해 아파트 정보를 수집합니다.

정민호정민호

1. 들어가며

공공데이터 포털을 파이썬을 이용해 데이터를 수집해 보려고 합니다. 파이썬을 사용하면서 사용할 툴은 아래와 같습니다.

아나콘다에는 파이썬을 비롯한 유용한 라이브러리가 포함되어 있습니다. 가볍게 파이썬만 설치하는것 보다 아나콘다를 설치하는게 편하지 않을까 생각합니다.

파이썬 설치 및 아나콘다 설치는 아래 URL을 참고하여 설치하였습니다.

2. 공공데이터포털

공공데이터포털에서 활용신청한 데이터는 '마이페이지 → 오픈API → 개발계정' 으로 접근하면 아래의 그림처럼 목록을 보실 수 있습니다.

공공데이터포털

2.1. 공동주택 단지 목록제공 서비스

먼저 공동주택 단지 목록 제공 서비스에서 데이터를 가져와 보겠습니다. 공동주택 단지 목록 제공 서비스에 접속하시면 아래의 그림처럼 보실 수 있습니다.

단지목록-1

하단의 상세기능정보에서 도로명 아파트 목록의 '실행’버튼을 클릭하면 팝업창이 뜨게 됩니다. 팝업창에서 인증키를 입력하고 미리보기를 클릭합니다.

만약 인증키를 신청하지 않으셨다면, 페이지 상단에서 키발급을 받으시면 됩니다.

단지목록-2

미리보기를 클릭하면 아래의 그림처럼 페이지가 뜨게 됩니다. 여기서 필요한 정보는 상단의 URL 정보가 필요하며, 상단의 URL 정보를 가져오면 아래와 같은 정보를 얻을 수 있습니다.

단지목록-3

GET 방식을 사용하므로 가져온 URL은 아래와 같이 분류 할 수 있을 것 같습니다. 이 정보들을 이용해 파이썬 코드를 작성해 보겠습니다.

파이참을 실행하고 프로젝트를 생성 후 API를 테스트할 *.py 파일을 생성합니다.

파이참-4
파이참-5

API를 이용해 데이터를 가져오고 json으로 변환하기 위해 requesets, xmltodict, json 라이브러리를 사용합니다. 파이참에서 라이브러리 설치 및 코드스타일 설정은 아래의 URL을 참조하였습니다.

import requests, xmltodict, json

# 테스팅 관련 변수
testYn      = True
#testYn      = False

# 공공데이터 포털 API 호출관련 기본 변수
serviceKey  = "1UUVwM6YiEySmXbqM%2FLN2f3ZP%2F90D7WxGIsw8ROq1eSkW36ZJuS4YMXKAX12UF2SQtXoZ700EadKIRu2fUp7Ew%3D%3D"
pageNo      = "1"   # 페이지번호
numOfRows   = "100"  # 페이지내목록수

# 아파트목록 관련 변수
roadCode    = "263802006002"    # 도로명코드
bjdCode     = "2638010100"      # 법정동코드

# 에러관련 변수
ERR_STATUS_CODE  = "ERR_STATUS_CODE"
ERR_RESULT_CODE  = "ERR_RESULT_CODE"



"""
'아파트(도로명) 목록 ' API 호출을 위한 URL 생성
https://www.data.go.kr/dataset/3039714/openapi.do

## NN -> 필수(notNull)
serviceKey NN 서비스키
roadCode   MM 도로명코드(12자리(시군구번호+도로명번호))
pageNo        페이지 번호
numOfRows     한 페이지 결과 수
"""
def getStrFromAptListInfoRoadUrl( testYn, serviceKey, roadCode, pageNo, numOfRows ) :
    url = "http://apis.data.go.kr/1611000/AptListService/getRoadnameAptList?serviceKey={}&roadCode={}&pageNo={}&numOfRows={}".format(serviceKey, roadCode, pageNo, numOfRows)
    return url

"""
'아파트(법정동) 목록 ' API 호출을 위한 URL 생성
https://www.data.go.kr/dataset/3039714/openapi.do

## NN -> 필수(notNull)
serviceKey NN 서비스키
roadCode   MM 도로명코드(12자리(시군구번호+도로명번호))
pageNo        페이지 번호
numOfRows     한 페이지 결과 수
"""
def getStrFromAptListInfoBjdUrl( testYn, serviceKey, bjdCode, pageNo, numOfRows ) :
    url = "http://apis.data.go.kr/1611000/AptListService/getLegaldongAptList?serviceKey={}&bjdCode={}&pageNo={}&numOfRows={}".format(serviceKey, bjdCode, pageNo, numOfRows)
    return url

"""
URL을 통해 API를 호출하여 결과값 JSON으로 생성
"""
def getJsonFromUrlContent( url ) :
    response = requests.get(url)
    if( testYn == True ) :
        print("response")
        print(response)
        print("status code :", response.status_code)

    # Err 체크
    if( response.status_code != 200 ) :
        return ERR_STATUS_CODE

    response = response.content
    if (testYn == True):
        print("response")
        print(response)

    dict = xmltodict.parse(response)
    if( testYn == True ) :
        print("dict")
        print(dict)
        print("resultCode :", json.dumps(dict['response']['header']['resultCode']))

    resultCode = json.dumps(dict['response']['header']['resultCode'], ensure_ascii=False)
    print(resultCode)
    # Err 체크
    if( json.dumps(dict['response']['header']['resultCode'], ensure_ascii=False) != "\"00\"" ) :
    #if( resultCode != "\"00\"" ) :
        return ERR_RESULT_CODE

    jsonString = json.dumps(dict['response']['body'], ensure_ascii=False)
    jsonObj = json.loads(jsonString)
    if( testYn == True ) :
        print("jsonObj")
        print(jsonObj)

    return jsonObj


def checkJsonObj( jsonObj ) :
    ERR_STATUS_CODE = "ERR_STATUS_CODE"
    ERR_RESULT_CODE = "ERR_RESULT_CODE"

    if( jsonObj == ERR_STATUS_CODE ) :
        return False
    elif( jsonObj == ERR_RESULT_CODE ) :
        return False
    else :
        return True

url = getStrFromAptListInfoBjdUrl( testYn, serviceKey, bjdCode, pageNo, numOfRows )            # 아파트목록(법정동)
jsonObj = getJsonFromUrlContent(url)

if ( checkJsonObj(jsonObj) ) :
    for item in jsonObj['items']['item'] :
        print(item)
else :
    print("ERR")

위 코드를 실행시키면 아래와 같이 정상적으로 데이터를 받아오는걸 확인 할 수 있습니다.

파이참-5

2.2. 공동주택 기본 정보제공 서비스

공동주택 기본 정보제공 서비스에 접속하시면 아래의 그림처럼 보실 수 있습니다.

기본정보-1

하단의 상세기능정보에서 공동주택 기본 정보조회의 '실행’버튼을 클릭하면 팝업창이 뜨게 됩니다. 팝업창에서 인증키를 입력하고 미리보기를 클릭합니다.

만약 인증키를 신청하지 않으셨다면, 페이지 상단에서 키발급을 받으시면 됩니다.

기본정보-2

미리보기를 클릭하면 아래의 그림처럼 페이지가 뜨게 됩니다. 여기서 필요한 정보는 상단의 URL 정보가 필요하며, 상단의 URL 정보를 가져오면 아래와 같은 정보를 얻을 수 있습니다.

기본정보-3

GET 방식을 사용하므로 가져온 URL은 아래와 같이 분류 할 수 있을 것 같습니다. 이 정보들을 이용해 파이썬 코드를 작성해 보겠습니다.

기존에 작성했던 코드에 아래 함수를 추가 작성하였습니다.

# 아파트 기본정보 관련 변수
kaptCode    = "A10027875"

"""
'아파트 기본정보 ' API 호출을 위한 URL 생성
https://www.data.go.kr/dataset/3039714/openapi.do

## NN -> 필수(notNull)
serviceKey NN 서비스키
kaptCode   MM 단지코드
"""
def getStrFromAptInfoUrl( testYn, serviceKey, kaptCode ) :
    url = "http://apis.data.go.kr/1611000/AptBasisInfoService/getAphusBassInfo?serviceKey={}&kaptCode={}".format(serviceKey, kaptCode)
    return url

url = getStrFromAptInfoUrl( testYn, serviceKey, kaptCode )
jsonObj = getJsonFromUrlContent(url)

print(jsonObj['item'])

위 코드를 실행시키면 아래와 같이 정상적으로 데이터를 받아오는걸 확인 할 수 있습니다.

기본정보-4

2.3. 아파트 실거래 상세 자료

아파트 실거래 상세 자료에 접속하시면 아래의 그림처럼 보실 수 있습니다.

아파트실거래-1

하단의 상세기능정보에서 공동주택 기본 정보조회의 '실행’버튼을 클릭하면 팝업창이 뜨게 됩니다. 팝업창에서 인증키를 입력하고 미리보기를 클릭합니다.

만약 인증키를 신청하지 않으셨다면, 페이지 상단에서 키발급을 받으시면 됩니다.

아파트실거래-2

미리보기를 클릭하면 아래의 그림처럼 페이지가 뜨게 됩니다. 여기서 필요한 정보는 상단의 URL 정보가 필요하며, 상단의 URL 정보를 가져오면 아래와 같은 정보를 얻을 수 있습니다.

아파트실거래-3

GET 방식을 사용하므로 가져온 URL은 아래와 같이 분류 할 수 있을 것 같습니다. 이 정보들을 이용해 파이썬 코드를 작성해 보겠습니다.

기존에 작성했던 코드에 아래 함수를 추가 작성하였습니다.

# 아파트 실거래 관련 변수
LAWD_CD     = "11110"   # 지역코드
DEAL_YMD    = "201512"  # 계약년월

"""
'아파트매매 실거래 상세 자료' API 호출을 위한 URL 생성
https://www.data.go.kr/dataset/3050988/openapi.do

## NN -> 필수(notNull)
serviceKey NN 서비스키
pageNo        페이지 번호
numOfRows     한 페이지 결과 수
LAWD_CD    NN 지역코드
DEAL_YMD   NN 계약월
"""
def getStrFromAptTradeInfoUrl( testYn, serviceKey, pageNo, numOfRows, LAWD_CD, DEAL_YMD ) :
    url = "http://openapi.molit.go.kr/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTradeDev?serviceKey={}&pageNo={}&numOfRows={}&LAWD_CD={}&DEAL_YMD={}".format(serviceKey, pageNo, numOfRows, LAWD_CD, DEAL_YMD)
    return url

url = getStrFromAptTradeInfoUrl( testYn, serviceKey, pageNo, numOfRows, LAWD_CD, DEAL_YMD )    # 아파트실거래정보
jsonObj = getJsonFromUrlContent(url)

if ( checkJsonObj(jsonObj) ) :
    for item in jsonObj['items']['item'] :
        print(item)
else :
    print("ERR")

위 코드를 실행시키면 아래와 같이 정상적으로 데이터를 받아오는걸 확인 할 수 있습니다.

아파트실거래-4

3. 공공데이터포털 전체 코드

공공데이터를 가져오는 코드는 각 API 별로 함수화하여 코드를 작성하였습니다. 공공데이터 API를 테스트하기 위해 함수화 했지만 아직 코드에 나쁜냄새가 존재하는것 같습니다.

추후 이 코드를 리팩토링해보겠습니다.

아래는 작성한 전체 코드 입니다.

import requests, xmltodict, json
import pandas as pd

# 테스팅 관련 변수
testYn      = True
#testYn      = False

# 공공데이터 포털 API 호출관련 기본 변수
serviceKey  = "1UUVwM6YiEySmXbqM%2FLN2f3ZP%2F90D7WxGIsw8ROq1eSkW36ZJuS4YMXKAX12UF2SQtXoZ700EadKIRu2fUp7Ew%3D%3D"
pageNo      = "1"   # 페이지번호
numOfRows   = "100"  # 페이지내목록수

# 아파트목록 관련 변수
roadCode    = "263802006002"    # 도로명코드
bjdCode     = "2638010100"      # 법정동코드

# 아파트 기본정보 관련 변수
kaptCode    = "A10027875"

# 아파트 실거래 관련 변수
LAWD_CD     = "11110"   # 지역코드
DEAL_YMD    = "201512"  # 계약년월

# 에러관련 변수
ERR_STATUS_CODE  = "ERR_STATUS_CODE"
ERR_RESULT_CODE  = "ERR_RESULT_CODE"



"""
'아파트(도로명) 목록 ' API 호출을 위한 URL 생성
https://www.data.go.kr/dataset/3039714/openapi.do

## NN -> 필수(notNull)
serviceKey NN 서비스키
roadCode   MM 도로명코드(12자리(시군구번호+도로명번호))
pageNo        페이지 번호
numOfRows     한 페이지 결과 수
"""
def getStrFromAptListInfoRoadUrl( testYn, serviceKey, roadCode, pageNo, numOfRows ) :
    url = "http://apis.data.go.kr/1611000/AptListService/getRoadnameAptList?serviceKey={}&roadCode={}&pageNo={}&numOfRows={}".format(serviceKey, roadCode, pageNo, numOfRows)
    return url

"""
'아파트(법정동) 목록 ' API 호출을 위한 URL 생성
https://www.data.go.kr/dataset/3039714/openapi.do

## NN -> 필수(notNull)
serviceKey NN 서비스키
roadCode   MM 도로명코드(12자리(시군구번호+도로명번호))
pageNo        페이지 번호
numOfRows     한 페이지 결과 수
"""
def getStrFromAptListInfoBjdUrl( testYn, serviceKey, bjdCode, pageNo, numOfRows ) :
    url = "http://apis.data.go.kr/1611000/AptListService/getLegaldongAptList?serviceKey={}&bjdCode={}&pageNo={}&numOfRows={}".format(serviceKey, bjdCode, pageNo, numOfRows)
    return url



"""
'아파트 기본정보 ' API 호출을 위한 URL 생성
https://www.data.go.kr/dataset/3039714/openapi.do

## NN -> 필수(notNull)
serviceKey NN 서비스키
kaptCode   MM 단지코드
"""
def getStrFromAptInfoUrl( testYn, serviceKey, kaptCode ) :
    url = "http://apis.data.go.kr/1611000/AptBasisInfoService/getAphusBassInfo?serviceKey={}&kaptCode={}".format(serviceKey, kaptCode)
    return url



"""
'아파트매매 실거래 상세 자료' API 호출을 위한 URL 생성
https://www.data.go.kr/dataset/3050988/openapi.do

## NN -> 필수(notNull)
serviceKey NN 서비스키
pageNo        페이지 번호
numOfRows     한 페이지 결과 수
LAWD_CD    NN 지역코드
DEAL_YMD   NN 계약월
"""
def getStrFromAptTradeInfoUrl( testYn, serviceKey, pageNo, numOfRows, LAWD_CD, DEAL_YMD ) :
    url = "http://openapi.molit.go.kr/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTradeDev?serviceKey={}&pageNo={}&numOfRows={}&LAWD_CD={}&DEAL_YMD={}".format(serviceKey, pageNo, numOfRows, LAWD_CD, DEAL_YMD)
    return url



"""
URL을 통해 API를 호출하여 결과값 JSON으로 생성
"""
def getJsonFromUrlContent( url ) :
    response = requests.get(url)
    if( testYn == True ) :
        print("response")
        print(response)
        print("status code :", response.status_code)

    # Err 체크
    if( response.status_code != 200 ) :
        return ERR_STATUS_CODE

    response = response.content
    if (testYn == True):
        print("response")
        print(response)

    dict = xmltodict.parse(response)
    if( testYn == True ) :
        print("dict")
        print(dict)
        print("resultCode :", json.dumps(dict['response']['header']['resultCode']))

    resultCode = json.dumps(dict['response']['header']['resultCode'], ensure_ascii=False)
    print(resultCode)
    # Err 체크
    if( json.dumps(dict['response']['header']['resultCode'], ensure_ascii=False) != "\"00\"" ) :
    #if( resultCode != "\"00\"" ) :
        return ERR_RESULT_CODE

    jsonString = json.dumps(dict['response']['body'], ensure_ascii=False)
    jsonObj = json.loads(jsonString)
    if( testYn == True ) :
        print("jsonObj")
        print(jsonObj)

    return jsonObj


def checkJsonObj( jsonObj ) :
    ERR_STATUS_CODE = "ERR_STATUS_CODE"
    ERR_RESULT_CODE = "ERR_RESULT_CODE"

    if( jsonObj == ERR_STATUS_CODE ) :
        return False
    elif( jsonObj == ERR_RESULT_CODE ) :
        return False
    else :
        return True

"""
JSON 데이터를 CSV 파일로 변환
"""
def createCsvFileFromJson( jsonObj ) :
    df = pd.DataFrame(jsonObj)
    #df.to_csv('aptTest.csv', header=True, index=True, encoding='UTF-8')
    df.to_csv('aptTest.csv', header=True, index=True, encoding='ms949')


#url = getStrFromAptListInfoRoadUrl( testYn, serviceKey, roadCode, pageNo, numOfRows )          # 아파트목록(도로명)
#url = getStrFromAptListInfoBjdUrl( testYn, serviceKey, bjdCode, pageNo, numOfRows )            # 아파트목록(법정동)
#url = getStrFromAptInfoUrl( testYn, serviceKey, kaptCode )
url = getStrFromAptTradeInfoUrl( testYn, serviceKey, pageNo, numOfRows, LAWD_CD, DEAL_YMD )    # 아파트실거래정보
jsonObj = getJsonFromUrlContent(url)

#print(jsonObj['item'])

if ( checkJsonObj(jsonObj) ) :
    createCsvFileFromJson( jsonObj['items']['item'] )    ## JSON to CSV File
    for item in jsonObj['items']['item'] :
        print(item)
else :
    print("ERR")