Excel) OpenAPI를 이용한 공공데이터포털 토지임야정보, 토지특성정보, 개별공시지가, 토지이용계획 조회

프로그램 설명

data.go.kr (공공데이터포털)의 OpenAPI를 활용하여 토지에 대한 표준식별체계로 사용되는 번호인 PNU(Parcel Numbering Unit)로부터 토지임야정보, 토지특성정보, 개별공시지가, 토지이용계획 등의 정보를 가져와 엑셀 시트에 표시하는 역할을 수행합니다.

코드는 XML 데이터를 파싱하여 필요한 정보를 추출하고, 추출한 정보를 엑셀 시트에 기록하는 작업을 수행하며 이를 통해 해당 토지에 대한 정보와 관련된 데이터를 엑셀에서 확인할 수 있습니다.

사용시 유의사항

  1. 이 매크로는 data.go.kr에 회원가입 후 아래 4개 서비스에 대한 Open API 활용신청을 한 후 api key를 엑셀 vba코드내 service key를 등록 후 사용 가능합니다.
  2. open api 신청 예시
    • 활용목적 : 기타(토지정보 조회)
  3. 발급받은 api key로 vba 코드내 "REPLACE YOUR API KEY" 부분을 교체합니다.(아래 사용전 엑셀설정 참고)
  4. 일일트래픽은 10,000회 입니다.(data.go.kr의 제한사항)
  5. 엑셀 시트명 Sheet1, Sheet2의 시트명을 바꾸면 작동하지 않습니다. (다른 시트, 다른 파일에서 매크로 실행시 에러방지 위해 vba코드에서 시트이름 고정했으며 이름변경을 하려면 코드내 이름도 변경을 해야 합니다.)
  6. Sheet1, Sheet2의 기본폼을 변경 하지마세요.(열 삽입 등)
  7. 코드는 엑셀 2010에서 작성되었고, 해당 버전에서 정상적으로 작동합니다. (일반적으로 엑셀의 상위 버전에서도 호환성이 유지될 것입니다.)
  8. 코드를 사용함으로써 발생하는 오류나 문제에 대해서는 사용자 본인이 책임을 집니다. 또한, 업데이트된 주소 정보나 정확성에 대한 책임은 data.go.kr에 있습니다. 사용자는 항상 최신 정보를 확인하고, api를 사용함에 있어서 data.go.kr의 이용약관과 정책을 준수해야 합니다.
  9. 코드를 수정하고 출처를 표시하여 배포하는 것은 자유롭게 가능합니다. 그러나 상업적인 목적으로 이용하는 것은 허용되지 않습니다.

사용전 엑셀설정

엑셀에서 xml데이터를 가져오기 위한 필수 라이브러리인 Microsoft XML을 활성화해야 합니다. 또한 발급받은 api key를 올바르게 교체해야 코드가 정상적으로 작동합니다.

  1. 엑셀을 엽니다.(매크로 포함하여 엽니다)
  2. "개발도구" 메뉴를 선택하고 "Visual Baic" 메뉴를 클릭합니다.
  3. vba에디터에서 "도구" 메뉴를 클릭합니다.
  4. "참조"를 선택합니다.
  5. "Microsoft XML, v6.0" 을 찾아 선택합니다.
  6. "확인" 버튼을 클릭합니다.
  7. vba 에디터로 돌아가서 "REPLACE YOUR API KEY" 부분을 발급받은 api 키로 교체합니다. (아래 코드의 33행)
  8. vba 에디터를 종료합니다.

사용법(Sheet1에서)

  1. A6:A열에 pnu 입력(코드내 최대행수를 2000으로 한정했지만 2000행의 기존 값을 삭제한다는 의미지 최대 2000개의 행으로 한정해 구동된다는 의미는 아닙니다. 최대 입력할 수 있는 행은 사용자 pc의 메모리량과 조회되는 결과에 따릅니다.)
  2. pnu코드는 숫자서식이 아닌 텍스트서식으로 입력합니다 (엑셀은 15자리 이상의 숫자는 15자리 하위에 0으로 채웁니다.)
  3. 다량의 주소입력시 입력범위내(A6:A)에 중간에 빈셀이 있으면 안됩니다. 빈셀의 경우 삭제 후 실행하세요.
  4. A3셀에는 토지이용계획 조회시 특정 키워드 1개를 조회할 경우 사용합니다.(예 농업진흥구역) 공백일 경우는 전체를 조회합니다.
  5. A4셀에 토지특성정보/개별공시지가의 기준년도 입력합니다.(숫자만 입력 예: 2023)
  6. 일괄실행 버튼 또는 실행(개별) 버튼 눌러 실행합니다..

결과출력(Sheet1)

  1. 결과 출력전 B6:AF2000까지의 기존 셀의 내용은 실행시 삭제됩니다.
  2. 결과는 순차로 모든 pnu값으로 조회 후 한번에 출력됩니다.(한개의 pnu당 4번의 조회로 실행시간이 다소 걸리 수 있습니다.)
  3. 에러코드가 "0"일 경우라도 실행완료 후 빈셀은 서버에서 조회가 안되는 값입니다.(데이터가 없는 경우 등으로 pnu 확인)
  4. 토지이용계획 키워드 검색시 특정문자 포함시 해당 단어 삭제 조치 후 조회됩니다. 특정문자는 sql 명령어 (SELECT", "INSERT", "DELETE", "UPDATE", "CREATE", "DROP", "EXEC", "UNION", "FETCH", "DECLARE", "TRUNCATE") 및 특수문자( <,>,%,=) 입니다.
  5. 토지이용계획에 대한 데이터기준일자는 엑셀에 표시하지 않았습니다.(각 용도지역마다 기준년도가 다른 경우가 있어 각 용도지역마다 기준년도를 모두 표기하기에는 너무 많고 데이터 표시하는 방법에 대한 고민과 지저분해서 기준년도 기입하지 않았습니다.)

결과출력(Sheet2)

  1. Sheet1에서 일괄실행시에만 집계결과 출력합니다. (조회를 개별적으로 한 경우 모든 조회를 완료해도 집계는 되지 않음)
  2. 결과 출력전 기존 셀의 내용은 삭제됩니다.
  3. 집계 결과 표시항목 : (법정동/지목/소유구분/소유(공유)인수/용도지역1/용도지역2/지형높이)별 면적 집계

에러코드

  • 0 정상
  • 1 어플리케이션 에러
  • 4 HTTP 에러
  • 12 해당 오픈 API 서비스가 없거나 폐기됨
  • 20 서비스 접근거부
  • 22 서비스 요청제한횟수 초과에러
  • 30 등록되지 않은 서비스키
  • 31 활용기간 만료
  • 32 등록되지 않은 IP
  • 99 기타에러

테스트사항

  • 처리속도 관련해서 실제는 위의 영상처럼 빠르게 조회되지 않습니다. 일괄조회시 기본적으로 PNU 1개당 4번의 조회를 수행합니다. 개략 PNU 100개 정도 조회시 약 40~60초 정도 소요되는것 같습니다.(위 영상은 영상제작 전 사전에 미리 조회 후 다시 조회한 것으로 캐시로 인해 빠르게 조회되는 것으로 보입니다.)
  • A열에 입력한 PNU는 약1300개 까지 테스트 하였으나 최대 입력할 수 있는 행수는 사용자 pc의 메모리량과 조회되는 결과의 양에 따릅니다.
  • 해당필지를 "토지이음"에서 직접 조회해보면 data.go.kr의 정보 업데이트가 토지이음에 비해 늦습니다. (예를들어 개별공시지가 2023년도 토지이음에서 조회 가능하지만 data.go.kr에서는 아직 안됩니다. - 2023년 6월 13일 현재) 이점을 감안해 사용하시기 바랍니다.
  • 실행시 에러코드 4를 반환할 경우가 있는데 이것을 서버에서 자료 업데이트시 나타나는 결과로 보입니다(추측). 일정 시간 경과 후 재실행 하면 됩니다.
  • 이상현상 발생시 댓글달아주세요.

다운로드

코드

Option Explicit

Private argsNum As Long

Private Sub GetDataFromAPI(ByRef argsNum As Long)
    
    On Error GoTo ErrorHandler
    
    ' 시트 설정
    Const targetSheetName As String = "Sheet1"
    Dim targetSheet As Worksheet
    Set targetSheet = Worksheets(targetSheetName)
    
    Const aggrSheetName As String = "Sheet2"
    Dim aggrSheet As Worksheet
    On Error Resume Next
    Set aggrSheet = Worksheets(aggrSheetName)
    On Error GoTo 0
    
    ' aggrSheet가 존재하는지 확인하고 없으면 서브를 종료
    If aggrSheet Is Nothing Then
        MsgBox aggrSheetName & "가 존재하지 않습니다.", vbExclamation
        Exit Sub
    End If
    
    ' 활성시트가 targetSheet인지 확인하고, 그렇지 않다면 서브를 종료
    If ActiveSheet.Name <> targetSheetName Then
        MsgBox "이 코드는 " & targetSheetName & " 시트에서만 실행할 수 있습니다.", vbExclamation
        Exit Sub
    End If
        
    ' 서비스키 설정
    Const serviceKey As String = "REPLACE YOUR API KEY"
    
    Const MAX_ROWS As Long = 2000   ' 처리할 최대 행 수
    Const START_ROW As Long = 6     ' 데이터 출력 시작 행
    
    ' 토지이용계획정보 조회시 특정 키워드 검색 셀위치(A3)
    Dim searchPrposAreaDstrcValue As String
    searchPrposAreaDstrcValue = Range("A3").Value
    
    ' 토지특성정보/개별공시지가 기준년도 설정 셀위치(A4)
    Dim stdrYearValue As Variant
    stdrYearValue = Range("A4").Value
    If Not IsNumeric(stdrYearValue) Or Len(stdrYearValue) <> 4 Then
        MsgBox "토지특성정보/개별공시지가 조회할 기준년도(숫자 4자리)를 입력하세요."
        Exit Sub
    End If
        
    ' pnu가 있는 A열의 범위 설정 (A6부터 시작)
    Dim pnuRange As Range
    Set pnuRange = targetSheet.Range("A" & START_ROW & ":A" & Cells.item(Rows.Count, "A").End(xlUp).Row)
    If Not CheckValues(pnuRange) Then
        MsgBox "입력된 범위에 빈셀이 있거나 입력한 pnu값이 형식(숫자 19자리)에 맞지 않습니다."
        Exit Sub
    End If
    
    ' 필요한 변수 및 객체 선언
    Dim startTime As Double
    Dim endTime As Double
    Dim executionTime As Double
    
    Dim pnuRangeValue As Variant
    
    Dim rowOffset As Long
    Dim pnuSingleValue As String
    Dim encodedKeyword As String
    Dim URL(1 To 4) As String
    
    Dim httpRequest As Object
    Dim xmlResponse(1 To 4) As String
    Dim xDoc(1 To 4) As MSXML2.DOMDocument60
    Dim errNode(1 To 4) As MSXML2.IXMLDOMNode
    Dim errCode(1 To 4) As String
    Dim i As Long
    Dim j As Long
    
    Dim totalCount(1 To 4) As Long
    Dim xNode(1 To 4) As MSXML2.IXMLDOMNode
    Dim xNodes4 As MSXML2.IXMLDOMNodeList
    
    Dim mnnmSlno As String
    Dim mainmnnmSlno As String
    Dim submnnmSlno As String
    Dim parts As Variant
    Dim regstrSeCode As String
    
    Dim prposAreaDstrcCodeNm(1 To 3) As String
    Dim cnflcAt As Long
    Dim prposAreaDstrcCodeNmValue As String
    
    Dim dataArr() As Variant
    Dim outputRange As Range
    
    Dim lastRow As Long
    Dim rowNumber As Long
    Dim counter As Long
    
    ' 시작 시간 기록
    startTime = Timer
    
    ' 셀 값을 지우고 셀 형식 변경
    If argsNum = 1 Then
        targetSheet.Range("C" & START_ROW & ":N" & START_ROW + MAX_ROWS).ClearContents
    ElseIf argsNum = 2 Then
        targetSheet.Range("O" & START_ROW & ":W" & START_ROW + MAX_ROWS).ClearContents
    ElseIf argsNum = 3 Then
        targetSheet.Range("X" & START_ROW & ":AB" & START_ROW + MAX_ROWS).ClearContents
    ElseIf argsNum = 4 Then
        targetSheet.Range("AC" & START_ROW & ":AF" & START_ROW + MAX_ROWS).ClearContents
    ElseIf argsNum = 5 Then
        targetSheet.Range("B" & START_ROW & ":AF" & START_ROW + MAX_ROWS).ClearContents
    End If
    targetSheet.Range("A" & START_ROW & ":A" & START_ROW + MAX_ROWS).NumberFormat = "@"
    targetSheet.Range("B" & START_ROW & ":AF" & START_ROW + MAX_ROWS).NumberFormat = "General"
    targetSheet.Range("E" & START_ROW & ":E" & START_ROW + MAX_ROWS).NumberFormat = "@"
    targetSheet.Range("I" & START_ROW & ":I" & START_ROW + MAX_ROWS).NumberFormat = "#,###.0"
    targetSheet.Range("N" & START_ROW & ":N" & START_ROW + MAX_ROWS).NumberFormat = "yyyy-mm-dd"
    targetSheet.Range("W" & START_ROW & ":W" & START_ROW + MAX_ROWS).NumberFormat = "yyyy-mm-dd"
    targetSheet.Range("Y" & START_ROW & ":Y" & START_ROW + MAX_ROWS).NumberFormat = "#,###"
    targetSheet.Range("AA" & START_ROW & ":AB" & START_ROW + MAX_ROWS).NumberFormat = "yyyy-mm-dd"
    
    ' pnuRangeValue가 단일 셀인지 확인
    pnuRangeValue = GetRangeValuesToArray(pnuRange)
    
    ' pnuRangeValue의 행 수를 기준으로 dataArr 배열의 크기를 조정
    ReDim dataArr(1 To UBound(pnuRangeValue, 1), 1 To 30)
    
    ' XMLHTTP 요청을 생성
    Set httpRequest = CreateObject("MSXML2.XMLHTTP")
    
    ' pnuRangeValue의 각 항목에 대해 반복
    For rowOffset = 1 To UBound(pnuRangeValue, 1)
        pnuSingleValue = pnuRangeValue(rowOffset, 1)
        
        ' 토지이용계획정보 조회 특정 키워드값에 sql키워드 및 특수문자 포함시 제거 후 utf-8로 인코딩
        If argsNum = 4 Or argsNum = 5 Then
            If searchPrposAreaDstrcValue <> vbNullString Then
                searchPrposAreaDstrcValue = rmSQLKeywordsAndSpecialChars(searchPrposAreaDstrcValue)
                encodedKeyword = encodeURL(searchPrposAreaDstrcValue, True)
            End If
        End If
        
        ' URLs 빌드
        ' URL1 : 토지임야정보 조회(https://www.data.go.kr/data/15057917/openapi.do)
        ' URL2 : 토지특성정보 조회(https://www.data.go.kr/data/15057558/openapi.do)
        ' URL3 : 개별공사지가 조회(https://www.data.go.kr/data/15059127/openapi.do)
        ' URL4 : 토지이용계획 조회(https://www.data.go.kr/data/15056930/openapi.do)
        URL(1) = "https://apis.data.go.kr/1611000/nsdi/eios/LadfrlService/ladfrlList.xml?&format=xml&serviceKey=" & serviceKey & "&pnu=" & pnuSingleValue
        URL(2) = "https://apis.data.go.kr/1611000/nsdi/LandCharacteristicsService/attr/getLandCharacteristics?&format=xml&serviceKey=" & serviceKey & "&pnu=" & pnuSingleValue & "&stdrYear=" & stdrYearValue
        URL(3) = "https://apis.data.go.kr/1611000/nsdi/IndvdLandPriceService/attr/getIndvdLandPriceAttr?&format=xml&serviceKey=" & serviceKey & "&pnu=" & pnuSingleValue & "&stdrYear=" & stdrYearValue
        URL(4) = "https://apis.data.go.kr/1611000/nsdi/LandUseService/attr/getLandUseAttr?&numOfRows=200&format=xml&serviceKey=" & serviceKey & "&pnu=" & pnuSingleValue & "&prposAreaDstrcCodeNm=" & encodedKeyword
        
        If argsNum = 5 Then
            For i = 1 To 4
                ' GET 요청을 보내고 XML 응답을 받기
                xmlResponse(i) = SendRequest(httpRequest, URL(i))
                
                ' XML 응답을 XML 문서로 로드
                Set xDoc(i) = New MSXML2.DOMDocument60
                xDoc(i).LoadXML xmlResponse(i)
                
                ' 오류를 위한 XML 응답 파싱
                Set errNode(i) = xDoc(i).SelectSingleNode("/OpenAPI_ServiceResponse/cmmMsgHeader/returnReasonCode")
                If Not errNode(i) Is Nothing Then
                    errCode(i) = errNode(i).Text
                End If
            Next i
        Else
            xmlResponse(argsNum) = SendRequest(httpRequest, URL(argsNum))
            
            Set xDoc(argsNum) = New MSXML2.DOMDocument60
            xDoc(argsNum).LoadXML xmlResponse(argsNum)
            
            Set errNode(argsNum) = xDoc(argsNum).SelectSingleNode("/OpenAPI_ServiceResponse/cmmMsgHeader/returnReasonCode")
            If Not errNode(argsNum) Is Nothing Then
                errCode(argsNum) = errNode(argsNum).Text
            End If
        End If
        
        If argsNum = 1 Or argsNum = 5 Then
            ' URL(1)에 에러가 있으면 에러코드를 dataArr에 저장
            If errCode(1) <> vbNullString Then
                dataArr(rowOffset, 1) = errCode(1)
                
            Else
                ' URL(1)에 에러코드 없으면 에러코드로 0을 dataArr에 저장
                dataArr(rowOffset, 1) = 0
                
                ' URL(1)의 XML 응답 데이터 갯수 확인
                totalCount(1) = xDoc(1).SelectSingleNode("/fields/totalCount").Text
                If totalCount(1) = 1 Then
                    ' URL(1)의 XML 응답 파싱
                    Set xNode(1) = xDoc(1).SelectSingleNode("/fields/ladfrlVOList")
                    mnnmSlno = xNode(1).SelectSingleNode("mnnmSlno").Text
                    
                    ' 혹시 있을지 모를 모든 공백을 제거
                    mnnmSlno = Replace(mnnmSlno, " ", vbNullString)
                    
                    ' 지번을 본번,부번으로 분할
                    If InStr(mnnmSlno, "-") > 0 Then
                        ' mnnmSlno를 mainmnnmSlno와 submnnmSlno로 분할
                        parts = Split(mnnmSlno, "-")
                        
                        ' "-" 좌우의 값을 저장
                        mainmnnmSlno = parts(0)
                        submnnmSlno = parts(1)
                    Else
                        ' "-"이 발견되지 않으면 mnnmSlno 전체를 mainmnnmSlno에 할당
                        mainmnnmSlno = mnnmSlno
                    End If
                    
                    ' 지번에서 부번이 없는경우 "-0"을 제거
                    If Right$(mnnmSlno, 2) = "-0" Then
                        mnnmSlno = Left$(mnnmSlno, Len(mnnmSlno) - 2)
                    End If
                    
                    ' regstrSeCode의 값을 확인 후 값이 "2"이면 지번과 본번 앞에"산"덧붙이기
                    regstrSeCode = xNode(1).SelectSingleNode("regstrSeCode").Text
                    If regstrSeCode = "2" Then
                        mnnmSlno = "산" & mnnmSlno
                        mainmnnmSlno = "산" & mainmnnmSlno
                    End If
                    
                    ' dataArr에 저장
                    dataArr(rowOffset, 2) = xNode(1).SelectSingleNode("ldCodeNm").Text
                    dataArr(rowOffset, 3) = mnnmSlno
                    dataArr(rowOffset, 4) = mainmnnmSlno
                    dataArr(rowOffset, 5) = submnnmSlno
                    dataArr(rowOffset, 6) = xNode(1).SelectSingleNode("lndcgrCodeNm").Text
                    dataArr(rowOffset, 7) = xNode(1).SelectSingleNode("lndpclAr").Text
                    dataArr(rowOffset, 8) = xNode(1).SelectSingleNode("regstrSeCodeNm").Text
                    dataArr(rowOffset, 9) = xNode(1).SelectSingleNode("posesnSeCodeNm").Text
                    dataArr(rowOffset, 10) = xNode(1).SelectSingleNode("cnrsPsnCo").Text
                    dataArr(rowOffset, 11) = "'" & xNode(1).SelectSingleNode("ladFrtlScNm").Text
                    dataArr(rowOffset, 12) = xNode(1).SelectSingleNode("lastUpdtDt").Text
                End If
            End If
        End If
            
        If argsNum = 2 Or argsNum = 5 Then
            ' URL(2)에 에러가 있으면 에러코드를 dataArr에 저장
            If errCode(2) <> vbNullString Then
                dataArr(rowOffset, 13) = errCode(2)
            Else
                ' URL(2)에 에러코드 없으면 에러코드로 0을 dataArr에 저장
                dataArr(rowOffset, 13) = 0
                ' URL(2)의 XML 응답 데이터 갯수 확인
                totalCount(2) = xDoc(2).SelectSingleNode("/response/totalCount").Text
                
                If totalCount(2) = 1 Then
                    ' URL(2)의 XML 응답 파싱
                    Set xNode(2) = xDoc(2).SelectSingleNode("/response/fields/field")
                    
                    ' dataArr에 저장
                    dataArr(rowOffset, 14) = xNode(2).SelectSingleNode("prposArea1Nm").Text
                    dataArr(rowOffset, 15) = xNode(2).SelectSingleNode("prposArea2Nm").Text
                    dataArr(rowOffset, 16) = xNode(2).SelectSingleNode("ladUseSittnNm").Text
                    dataArr(rowOffset, 17) = xNode(2).SelectSingleNode("tpgrphHgCodeNm").Text
                    dataArr(rowOffset, 18) = xNode(2).SelectSingleNode("tpgrphFrmCodeNm").Text
                    dataArr(rowOffset, 19) = xNode(2).SelectSingleNode("roadSideCodeNm").Text
                    dataArr(rowOffset, 20) = xNode(2).SelectSingleNode("stdrYear").Text
                    dataArr(rowOffset, 21) = xNode(2).SelectSingleNode("lastUpdtDt").Text
                End If
            End If
        End If
        
        If argsNum = 3 Or argsNum = 5 Then
            ' URL(3)에 에러가 있으면 에러코드를 dataArr에 저장
            If errCode(3) <> vbNullString Then
                dataArr(rowOffset, 22) = errCode(3)
            Else
                ' URL(3)에 에러코드 없으면 에러코드로 0을 dataArr에 저장
                dataArr(rowOffset, 22) = 0
                ' URL(3)의 XML 응답 데이터 갯수 확인
                totalCount(3) = xDoc(3).SelectSingleNode("/response/totalCount").Text
                
                If totalCount(3) = 1 Then
                    ' URL(3)의 XML 응답 파싱
                    Set xNode(3) = xDoc(3).SelectSingleNode("/response/fields/field")
                    
                    ' dataArr에 저장
                    dataArr(rowOffset, 23) = xNode(3).SelectSingleNode("pblntfPclnd").Text
                    dataArr(rowOffset, 24) = xNode(3).SelectSingleNode("stdrYear").Text
                    dataArr(rowOffset, 25) = xNode(3).SelectSingleNode("pblntfDe").Text
                    dataArr(rowOffset, 26) = xNode(3).SelectSingleNode("lastUpdtDt").Text
                End If
            End If
        End If
        
        If argsNum = 4 Or argsNum = 5 Then
        ' URL(4)에 에러가 있으면 에러코드를 dataArr에 저장
            If errCode(4) <> vbNullString Then
                dataArr(rowOffset, 27) = errCode(4)
            Else
                ' URL(4)에 에러코드 없으면 에러코드로 0을 dataArr에 저장
                dataArr(rowOffset, 27) = 0
                ' URL(4)의 XML 응답 데이터 갯수 확인
                totalCount(4) = xDoc(4).SelectSingleNode("/response/totalCount").Text
                
                If totalCount(4) > 0 Then
                    ' URL(4)의 XML 응답 파싱
                    Set xNodes4 = xDoc(4).SelectNodes("/response/fields/field")
                    
                    ' xNodes4 컬렉션에서 노드를 반복하면서 cnflcAt 값(범위 1~3)을 기준으로 prposAreaDstrcCodeNm 변수에 값을 추가
                    For Each xNode(4) In xNodes4
                        cnflcAt = xNode(4).SelectSingleNode("cnflcAt").Text
                        prposAreaDstrcCodeNmValue = xNode(4).SelectSingleNode("prposAreaDstrcCodeNm").Text
                        prposAreaDstrcCodeNm(cnflcAt) = prposAreaDstrcCodeNm(cnflcAt) & prposAreaDstrcCodeNmValue & ", "
                    Next
                    
                    For i = 1 To 3
                        If Len(prposAreaDstrcCodeNm(i)) > 0 Then
                            ' 마지막 쉼표와 공백 제거
                            prposAreaDstrcCodeNm(i) = RemoveDuplicates(prposAreaDstrcCodeNm(i))
                            prposAreaDstrcCodeNm(i) = Left$(prposAreaDstrcCodeNm(i), Len(prposAreaDstrcCodeNm(i)) - 2)
                            
                            ' cnflcAtValue 값에 따라 dataArr의 열 27+j에 저장
                            dataArr(rowOffset, 27 + i) = prposAreaDstrcCodeNm(i)
                            
                            ' 다음 반복을 위해 prposAreaDstrcCodeNm 변수 초기화
                            prposAreaDstrcCodeNm(i) = vbNullString
                        End If
                    Next i
                End If
            End If
        End If
    Next rowOffset
    
    ' 워크시트에 dataArr 데이터 쓰기
    If argsNum = 1 Then
        Set outputRange = targetSheet.Range("C" & START_ROW).Resize(rowOffset - 1, 12)
        
        ' dataArr 배열의 1번째 열부터 12번째 열까지만 해당 범위에 출력
        For i = 1 To outputRange.Rows.Count
            For j = 1 To 12
                outputRange.Cells(i, j).Value = dataArr(i, j)
            Next j
        Next i
            
    ElseIf argsNum = 2 Then
        Set outputRange = targetSheet.Range("O" & START_ROW).Resize(rowOffset - 1, 9)
        
        ' dataArr 배열의 13번째 열부터 21번째 열까지만 해당 범위에 출력
        For i = 1 To outputRange.Rows.Count
            For j = 13 To 21
                outputRange.Cells(i, j - 12).Value = dataArr(i, j)
            Next j
        Next i
    
    ElseIf argsNum = 3 Then
        Set outputRange = targetSheet.Range("X" & START_ROW).Resize(rowOffset - 1, 5)
        
        ' dataArr 배열의 22번째 열부터 26번째 열까지만 해당 범위에 출력
        For i = 1 To outputRange.Rows.Count
            For j = 22 To 26
                outputRange.Cells(i, j - 21).Value = dataArr(i, j)
            Next j
        Next i
    
    ElseIf argsNum = 4 Then
        Set outputRange = targetSheet.Range("AC" & START_ROW).Resize(rowOffset - 1, 4)
        
        ' dataArr 배열의 27번째 열부터 30번째 열까지만 해당 범위에 출력
        For i = 1 To outputRange.Rows.Count
            For j = 27 To 30
                outputRange.Cells(i, j - 26).Value = dataArr(i, j)
            Next j
        Next i
        
    ElseIf argsNum = 5 Then
        targetSheet.Range("C" & START_ROW).Resize(rowOffset - 1, 30).Value = dataArr
        
    End If
    
    ' B열에 행번호 매기기
    lastRow = targetSheet.Cells.item(targetSheet.Rows.Count, "A").End(xlUp).Row
    
    counter = 1
    For rowNumber = START_ROW To lastRow
        If Not IsEmpty(targetSheet.Cells.item(rowNumber, "A")) Then
            targetSheet.Cells.item(rowNumber, "B").Value = counter
            counter = counter + 1
        End If
    Next rowNumber
    
    
    ' 데이터 조회완료 후 집계하기(aggrSheet에 출력, 일괄실행시에만 실행)
    ' --------------------------------------------------------------------------
    If argsNum = 5 Then
        ' 집계 대상 및 항목 설정
        Dim targetColumn As Long
        Dim itemColumn() As String
        Dim itemTitle() As String
        
        targetColumn = 7                                    'dataArr 집계대상에 해당하는 열번호(면적)
        itemColumn = Split("2,6,9,10,14,15,16,17", ",")     'dataArr 집계항목에 해당하는 열번호(법정동,지목, 소유구분, 소유(공유)인수, 용도지역1, 용도지역2, 토지이용상황, 지형높이)
        itemTitle = Split("법정동,지목,소유구분,소유(공유)인수,용도지역1,용도지역2,토지이용상황,지형높이", ",")
        
        Dim item As Long
        Dim aggregateTable() As Variant
        rowOffset = 2           ' 출력할 위치(행)
        
        ' aggrSheet 셀 기존값을 지우기
        aggrSheet.Range("A" & rowOffset & ":C" & MAX_ROWS).ClearContents
        
        If UBound(itemColumn) = UBound(itemTitle) Then
            For item = LBound(itemColumn) To UBound(itemColumn)
                aggregateTable = aggregate(dataArr, targetColumn, CLng(itemColumn(item)))
                
                ' 집계항목 타이틀 및 집계한 배열 데이터 출력범위 설정(aggrSheet 시트)
                Set outputRange = aggrSheet.Range("A" & rowOffset)
                outputRange.Value = itemTitle(item)
                outputRange.Offset(1, 0).Resize(UBound(aggregateTable, 1), UBound(aggregateTable, 2)).Value = aggregateTable
                
                rowOffset = rowOffset + UBound(aggregateTable, 1) + 3   ' 다음 결과를 출력할 행 간격 조정(2행 아래)
                
            Next item
        Else
            MsgBox "집계항목의 열과 타이틀의 갯수가 일치하지 않습니다. 코드를 확인하세요."
        
        End If
    End If
    
    ' --------------------------------------------------------------------------
    
    ' 종료 시간 기록
    endTime = Timer
    executionTime = endTime - startTime
    
    ' 완료 메세지 표시
    MsgBox "데이터 조회가 완료되었습니다." & vbCrLf & "총 실행시간 : " & executionTime & "초"
    
    ' 헤더 행 포맷 설정 및 열 자동 맞춤
    targetSheet.Range("A" & START_ROW - 1 & ":AF" & START_ROW - 1).HorizontalAlignment = xlCenter
    targetSheet.Columns.item("A:AF").AutoFit
    aggrSheet.Columns.item("A:C").AutoFit
    
Exit Sub
    
ErrorHandler:
    MsgBox "오류 발생: " & Err.Description
    
    ' 정리 작업
    Set httpRequest = Nothing
    
    Exit Sub
    
End Sub

Private Function aggregate(ByRef dataArray() As Variant, ByRef targetColumn As Long, ByRef itemColumn As Long) As Variant()
    Dim itemCounts As Object    ' 아이템별 개수를 저장
    Dim itemSums As Object      ' 아이템별 합계를 저장
    Dim result() As Variant     ' 결과를 저장할 배열
    Dim i As Long
    
    Set itemCounts = CreateObject("Scripting.Dictionary")
    Set itemSums = CreateObject("Scripting.Dictionary")
    
    Dim item As Variant
    For i = LBound(dataArray, 1) To UBound(dataArray, 1)
        item = dataArray(i, itemColumn)
        
        ' 아이템별 개수와 합계 갱신
        If Not itemCounts.Exists(item) Then
            itemCounts(item) = 1
            itemSums(item) = CDbl(dataArray(i, targetColumn))
        Else
            itemCounts(item) = itemCounts(item) + 1
            itemSums(item) = itemSums(item) + CDbl(dataArray(i, targetColumn))
        End If
    Next i
    
    Dim itemCount As Long
    Dim itemSum As Double
    Dim index As Long
    
    ReDim result(1 To itemCounts.Count, 1 To 3)
    index = 1
    
    ' 결과 배열에 아이템, 합계, 개수를 저장
    For Each item In itemCounts.Keys
        itemCount = itemCounts(item)
        itemSum = itemSums(item)
        
        result(index, 1) = item
        result(index, 2) = itemSum
        result(index, 3) = itemCount
        
        index = index + 1
    Next item
    
    aggregate = result
End Function

Private Function CheckValues(ByRef rng As Range) As Boolean
    Dim cell As Range
    
    For Each cell In rng
        ' 빈 셀인지 확인
        If IsEmpty(cell) Then
            CheckValues = False
            Exit Function
        End If
        
        ' 입력된 값이 19자리의 숫자 문자열인지 확인
        If Not IsNumeric(cell.Value) Or Len(cell.Value) <> 19 Then
            CheckValues = False
            Exit Function
        End If
    Next cell
    CheckValues = True
End Function

Private Function GetRangeValuesToArray(ByRef rng As Range) As Variant
    ' 범위를 배열로 변환
    Dim values As Variant
    If IsArray(rng.Value) Then
        ' 범위가 다차원 배열인 경우
        values = rng.Value
    Else
        ' 범위가 단일 셀인 경우
        ReDim values(1 To 1, 1 To 1)
        values(1, 1) = rng.Value
    End If
    GetRangeValuesToArray = values
End Function

Private Function rmSQLKeywordsAndSpecialChars(ByRef inputString As String) As String
    ' sql 키워드 및 특수문자 목록
    Dim sqlAndSpecialChars As Variant
    sqlAndSpecialChars = Array("SELECT", "INSERT", "DELETE", "UPDATE", "CREATE", "DROP", "EXEC", "UNION", "FETCH", "DECLARE", "TRUNCATE", "<", ">", "=", "%")

    ' 입력 문자열의 복사본 생성
    Dim resultString As String
    resultString = inputString
    
    ' sql 키워드 및 특수문자 제거
    Dim i As Long
    For i = LBound(sqlAndSpecialChars) To UBound(sqlAndSpecialChars)
        resultString = Replace(resultString, sqlAndSpecialChars(i), vbNullString, Compare:=vbTextCompare)
    Next i
    
    ' 연속된 여러개의 공백 제거
    Do While InStr(resultString, "  ") > 0
        resultString = Replace(resultString, "  ", " ")
    Loop
    
    ' 선행 및 후행 공백제거
    resultString = Trim$(resultString)
    
    ' 결과 반환
    rmSQLKeywordsAndSpecialChars = resultString
End Function

Private Function encodeURL(ByRef varText As Variant, Optional ByRef blnEncode As Boolean = True) As String
    '**********************************************************************
    ' Function: encodeURL
    ' Description: This function converts a string containing Korean/English
    '              characters and special symbols into a web URL standard address.
    '
    ' Arguments:
    '   - varText: The string to be converted into a standard URL address.
    '   - blnEncode: (Optional) If set to TRUE, the result is returned. If set
    '                to FALSE, the function does nothing.
    '
    ' Returns:
    '   - If blnEncode is TRUE, the function returns the URL-encoded string.
    '   - If blnEncode is FALSE, the function returns an empty string.
    '
    ' Dependencies:
    '   - Requires the "htmlfile" object from Microsoft Internet Controls.
    '     Make sure the "Microsoft HTML Object Library" reference is added
    '     to your VBA project.
    '
    '**********************************************************************
    Static objHtmlfile As Object
    
    ' htmlfile 개체가 존재하지 않는 경우에만 생성
    If objHtmlfile Is Nothing Then
        Set objHtmlfile = CreateObject("htmlfile")
        With objHtmlfile.parentWindow
            .execScript "function encode(s) {return encodeURIComponent(s)}", "jscript"
        End With
    End If
    
    ' blnEncode가 TRUE인 경우, varText를 JavaScript 함수를 사용하여 인코딩
    If blnEncode Then
        encodeURL = objHtmlfile.parentWindow.encode(varText)
    End If
End Function

Private Function SendRequest(ByRef httpRequest As Object, ByRef URL As String) As String
    httpRequest.Open "GET", URL, False
    httpRequest.send
    SendRequest = httpRequest.responseText
    
End Function

Private Function RemoveDuplicates(ByRef inputString As String) As String
    Dim result As String
    Dim values() As String
    Dim i As Long
    
    values = Split(inputString, ", ")
    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")
    
    For i = LBound(values) To UBound(values)
        dict(Trim$(values(i))) = 1
    Next i
    
    result = Join(dict.Keys, ", ")
    RemoveDuplicates = result
End Function

Public Sub button1_click()
    argsNum = 1
    GetDataFromAPI argsNum
End Sub

Public Sub button2_click()
    argsNum = 2
    GetDataFromAPI argsNum
End Sub

Public Sub button3_click()
    argsNum = 3
    GetDataFromAPI argsNum
End Sub

Public Sub button4_click()
    argsNum = 4
    GetDataFromAPI argsNum
End Sub

Public Sub button5_click()
    argsNum = 5
    GetDataFromAPI argsNum
End Sub

4 Comments

  1. joyeman

    안녕하세요 필요한 정보였는데 올려주셔서 감사합니다!
    근데 올려주신 파일을 다운로드하고 열어서 연결할 프로그램 설정하려고 하니 계속 거기서 멈추는데 어떻게 해야할까요?

    • qquack

      안녕하세요.
      거기가 어디일까요.ㅜㅜ
      보다 구체적인 상황과 증상을 말씀해주셔야...

  2. 안파이썬

    안녕하세요. 이제 파이썬 프로그램 배운지 2주된 초보입니다. 파이썬 프로그램에 조금 흥미가 생겨서 주소가 들어있는 엑셀파일에서 토지이음정보를 가져오고 싶은데 기본정보를 토지이음 홈페이지('http://www.eum.go.kr/web/am/amMain.jsp')에서 파이썬 프로그램으로 가져오면 혹시 무슨 문제가 있는건가요? 프로그램을 사용할 경우에는 공공포털을 사용해야만 하나요? 너무 기본적인 질문이라 미안합니다.

    • qquack

      안녕하세요.
      토지이음, 공공데이터포털은 모두 국가에서 운영하는 곳으로
      토지이음은 최신 토지관련 정보를 웹상으로 쉽게 확인할 수 있는 환경이지만 개방된 api는 제공하고 있지 않기에
      데이터 획득하기 위해 웹페이지를 파싱해야 하므로 활용이 어렵습니다.
      반면 공공데이터 포털은 다양한 공공데이터를 다운로드 혹은 개방된 api로 제공하기 때문에 json이나 xml을 파싱해서 사용자가 쉽게 활용할 수 있는 것이지요. 그러나 토지이음 만큼 토지정보가 최신은 아닌 단점이 있습니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다