제주 농지 데이터 수집기 — 문서 없는 공공 사이트를 n8n으로 내 데이터셋으로 옮기는 법
이 글과 연결된 글 보기
드래그로 이동 · 휠/두 손가락으로 확대 · 방향키로 글 선택 · 0으로 맞춤. 실선: 본문 링크, 점선: 시리즈·주제의 대표 연결.표시된 글 7편 목록
법정동 코드 열 자리 하나를 넣고 실행하면, 그 동네의 밭·논·과수원 필지마다 토지대장·농지정보·개별공시지가·지도 경계가 한 행에 붙은 CSV가 구글 드라이브에 생깁니다. 데이터는 한국농어촌공사 도메인의 JSON 주소에서 오는데, 이 주소는 공식 API 문서가 없습니다. 그래서 이 워크플로우를 따라 만들면 "문서가 있는 API를 부르는 법"이 아니라 "내가 궁금한 사이트를 데이터셋으로 바꾸는 법"이 손에 남습니다.
이 워크플로우 하나로 보여줄 수 있는 것
이 시리즈의 수집 편들은 데이터가 오는 통로가 조금씩 다릅니다. 대통령 일정 편은 HTML 페이지를 긁었고, 호텔 API 편은 문서와 키가 있는 정식 API를 불렀습니다. 이 편은 그 중간입니다. 문서는 없지만 JSON을 돌려주는 주소가 있고, 그 주소만 알면 정식 API처럼 쓸 수 있습니다. 만들고 나면 네 가지를 말할 수 있습니다.
- 문서 없는 엔드포인트 다루기 — 사이트가 화면을 그리려고 부르는 JSON 주소를 찾아 HTTP Request로 그대로 부르는 경험
- 키 하나로 여러 API 합치기 — 필지고유번호(PNU) 하나로 API 네 개를 병렬로 부르고, Merge 노드의 SQL Query 모드로 LEFT JOIN 하는 경험
- 도메인 지식이 들어간 필터 — 주소 끝 글자의 지목(전·답·과)으로 농지만 남기고, 농지 DB에 없는 필지를 걸러내는 조건 설계
- 파일 산출물 만들기 — 결과를 CSV로 변환해 드라이브 폴더에 넣는, 다른 도구(엑셀·태블로·판다스)로 이어지는 출력
두 번째가 이 편의 핵심입니다. 데이터 일을 하다 보면 "A 목록을 가져와서 항목마다 B·C·D를 붙여라"는 요구가 계속 나오는데, n8n에서 이걸 코드 없이 SQL 한 줄로 푸는 방법을 알면 응용 범위가 확 넓어집니다.
배경 — 스티키 노트에 남은 것과 남지 않은 것
원본 캔버스 맨 앞 스티키 노트에는 데이터팝콘 로고와 "제주 농지 데이터 수집 워크플로우 (2024.09)"라는 제목만 있습니다. 왜 제주 농지인지, 이 데이터로 무엇을 하려 했는지는 적혀 있지 않습니다. 대신 노드 구성이 목적을 말해줍니다. 법정동 하나를 정하고, 그 동네 필지 목록에서 밭·논·과수원만 고른 뒤, 필지마다 토지대장·농지정보·공시지가·지도 경계를 붙여 CSV 한 장으로 만듭니다. "한 동네의 농지를 한 표로 보고 싶다"는 요구입니다.
데이터 출처는 espacek.ekr.or.kr, 한국농어촌공사 도메인입니다. 이 글을 쓰면서 확인해 보니 사이트 루트는 에러 페이지로 넘어가고, 검색으로도 이 주소의 공개 API 문서는 찾지 못했습니다(2026년 8월 기준). 그런데 워크플로우에 적힌 eOpenAPI/get.json/… 주소들은 키 없이 JSON을 그대로 돌려줍니다. 어떤 화면이 이 주소를 쓰는지는 알 수 없지만, 어딘가의 지도 화면이 필지 정보를 그리려고 부르는 뒷문 주소일 가능성이 큽니다. 이런 주소는 예고 없이 바뀌거나 막힐 수 있어서 한계 절에서 다시 다룹니다.
설치와 기본 노드 설명은 n8n으로 반복업무 자동화 포트폴리오 만들기에 있으니, 여기서는 워크플로우 자체만 봅니다.
구조 — Trigger / Task / Target
- Trigger — 30분 간격 스케줄. 다만 이 데이터는 매일 바뀌지 않아서, 실제로는 법정동 코드를 바꿔 가며 수동으로 한 번씩 돌리는 용도에 가깝습니다
- Task — 법정동 코드로 필지 목록을 받고 → 농지만 남기고 → 필지마다 API 네 개를 부르고 → PNU로 조인
- Target —
법정동코드_주소.csv파일을 구글 드라이브 폴더에 저장
만들기
0. JSON을 돌려주는 주소 찾기
원본 스티키 노트에는 주소 다섯 개만 적혀 있고 어디서 찾았는지는 없습니다. 대신 이런 주소를 찾는 방법은 사이트가 달라도 같습니다. 브라우저 개발자 도구의 Network 탭을 열고 Fetch/XHR로 좁힌 뒤, 화면에서 검색이나 지도 이동을 한 번 합니다. 그때 새로 뜨는 요청 중 응답 미리보기가 JSON인 것을 찾고, 우클릭 → Copy as cURL로 복사합니다.
n8n의 HTTP Request 노드에는 Import cURL 버튼이 있어서 복사한 것을 붙여 넣으면 URL·헤더·파라미터가 그대로 채워집니다. 여기서 한 번 실행해 브라우저에서 본 것과 같은 JSON이 오면, 그 주소는 이 워크플로우처럼 쓸 수 있습니다. 헤더에 쿠키나 토큰이 딸려 있다면 그건 로그인이 필요한 요청이라는 뜻이니, 그 사이트는 이 방법의 대상이 아닙니다.
1. Set — 법정동 코드 한 칸
Edit Fields(Set) 노드에 CD라는 문자열 필드 하나를 만들고 열 자리 법정동 코드를 넣습니다. 원본에는 5011012200, 제주시 노형동이 들어 있습니다. 이 값이 다음 노드의 URL과 마지막 파일명에 다시 쓰이기 때문에, 다른 동네를 수집하고 싶으면 이 칸만 바꾸면 됩니다. 법정동 코드는 행정안전부 법정동 코드 목록에서 찾을 수 있습니다.
2. HTTP Request + Split Out — 필지 목록 받기
첫 HTTP Request의 URL은 다음과 같습니다.
https://espacek.ekr.or.kr/eOpenAPI/get.json/CadastralMap_getList?BJD_CD={{ $json.CD }}&SAN=2&BOBN=&BUBN=
응답은 total_count와 result_list 배열이고, 배열 원소마다 address("노형동 산5-1임"처럼 지번 뒤에 지목 한 글자가 붙은 주소)와 pnu가 들어 있습니다. Split Out 노드에서 Field To Split Out을 result_list로 두면 필지 하나가 아이템 하나가 됩니다. 이 뒤의 HTTP Request 응답도 전부 같은 result_list 구조라 Split Out이 네 번 더 나옵니다.
pnu는 열아홉 자리 필지고유번호입니다. 캔버스의 스티키 노트에 만든 사람이 직접 풀어 적어 둔 메모가 있어서 그대로 옮깁니다.
5011012200200170013 = "5011012200" + "2" + "0017" + "0013"
법정동 코드 열 자리, 산 여부 한 자리(1은 일반, 2는 산), 본번 네 자리, 부번 네 자리입니다. 이 구조를 알면 뒤에서 PNU를 조인 키로 쓰는 게 자연스럽게 이해됩니다. 첫 URL의 SAN=2도 같은 자리를 뜻합니다.
3. Filter — 주소 끝 글자로 농지만 남기기
Filter 노드에 조건 세 개를 OR로 겁니다. 왼쪽 값은 셋 다 {{ $json.address.slice(-1) }}, 연산자는 String → is equal to, 오른쪽 값은 각각 전, 답, 과입니다. 스티키 노트의 제목은 "전답과 만 필터링"입니다.
주소 마지막 글자가 지목이라서 가능한 필터입니다. 전은 밭, 답은 논, 과는 과수원이고, 임야(임)·목장용지(목)·도로(도)·묘지(묘) 같은 나머지는 여기서 떨어집니다. 이렇게 한 글자로 걸러도 되는지는 데이터의 규칙을 알아야 판단할 수 있고, 그 판단이 이 워크플로우에서 도메인 지식이 들어간 첫 지점입니다.
4. Limit — 최대 1,000필지
Limit 노드의 Max Items는 1000입니다. 다음 단계에서 필지마다 요청이 네 번씩 나가니, 필지 1,000개면 요청 4,000번입니다. 남의 서버를 부르는 워크플로우에 상한을 두는 건 예의이자 안전장치입니다.
5. HTTP Request 네 개 — 같은 PNU를 병렬로
Limit 노드의 출력을 HTTP Request 노드 네 개에 동시에 연결합니다. 스티키 노트에 적힌 주소 네 개를 그대로 옮기면 다음과 같습니다.
토지대장 https://espacek.ekr.or.kr/eOpenAPI/get.json/CadastralInfo_getList (Query: PNU)
농지정보 https://espacek.ekr.or.kr/eOpenAPI/get.json/Farmland_getListFarmland?PNU={{ $json.pnu }}
공시지가 https://espacek.ekr.or.kr/eOpenAPI/get.json/CadastralInfo_getListPrice?PNU={{ $json.pnu }}
지도WKT https://espacek.ekr.or.kr/eOpenAPI/get.json/CadastralMap_getWktOfPNU?PNU={{ $json.pnu }}
원본은 파라미터를 붙이는 방식을 두 가지로 섞어 썼습니다. 셋은 URL 뒤에 ?PNU={{ $json.pnu }}를 직접 이어 붙였고, 토지대장 노드만 Send Query Parameters를 켜고 이름 PNU, 값 {{ $json.pnu }}로 넣었습니다. 결과는 같지만 파라미터가 여러 개이거나 값에 한글·공백이 들어갈 때는 Query Parameters 쪽이 인코딩을 n8n이 처리해 줘서 안전합니다. 처음 만든다면 넷 다 Query Parameters로 통일하는 걸 권합니다.
이 글을 쓰며 같은 주소를 직접 불러 보니(2026년 8월 기준) 응답 필드는 대략 이렇습니다. 토지대장에는 jimok_nm(지목명)·parea(면적)·own_gbn_nm(소유 구분)·lb_full_name(시도·시군구·동 전체 주소), 농지정보에는 fml_se_code(농업진흥지역 구분)·mst_cult_crp_code(주 재배작물)·fact_jimk_code·rlnd_jimk_code, 공시지가에는 pnilp(㎡당 공시지가)·base_year·pann_ymd(공시일), 지도에는 wkt(MULTIPOLYGON 경계)가 들어 있습니다. 네 응답 모두 pnu를 포함하고, 이것이 다음 단계의 조인 키입니다.
6. Merge — SQL Query 모드로 네 응답을 한 행으로
여기가 이 워크플로우에서 가장 배울 게 많은 노드입니다. Merge 노드의 Mode를 SQL Query로, Number of Inputs를 4로 두면 입력이 input1부터 input4까지 테이블 이름으로 잡히고, 그 위에서 SQL을 쓸 수 있습니다. 원본 쿼리는 다음과 같습니다.
SELECT
*
FROM input1 AS a1
LEFT JOIN input2 AS a2
ON a1.pnu = a2.pnu
LEFT JOIN input3 AS a3
ON a1.pnu = a3.pnu
LEFT JOIN input4 AS a4
ON a1.pnu = a4.pnu
원본의 연결 순서는 input1이 토지대장, input2가 농지정보, input3이 공시지가, input4가 지도 WKT입니다. 토지대장을 기준 테이블로 두고 나머지를 LEFT JOIN 했기 때문에, 어느 API가 그 필지를 모르더라도 행이 사라지지 않고 빈 칸으로 남습니다. Merge 노드의 Combine 모드로 두 입력씩 세 번 합칠 수도 있지만, 입력이 네 개일 때는 SQL 한 번이 훨씬 읽기 쉽습니다.
7. Filter — 농지 DB에 없는 필지 걸러내기
스티키 노트에 "카테고리 없는 토지 필터링"이라고 적힌 Filter 노드입니다. 조건은 {{ $json.fact_jimk_code }}가 비어 있지 않거나(String → is not empty) {{ $json.rlnd_jimk_code }}가 비어 있지 않거나, 둘을 OR로 묶었습니다. 두 필드는 농지정보 API에서 오는 값이라, 앞 단계의 LEFT JOIN에서 농지정보가 비어 있던 필지가 여기서 빠집니다. 주소 끝 글자로는 밭이지만 농지 DB에는 없는 필지가 있을 수 있어서, 6번에서 살려 둔 행을 7번에서 조건을 보고 정리하는 구조입니다.
8. Convert to File → Google Drive — CSV로 저장
Convert to File 노드는 기본 동작인 CSV 변환을 쓰고, File Name에 다음 표현식을 넣습니다.
{{ $('샘플 농경지 코드').item.json.CD }}_{{ $('Filter').item.json.lb_full_name.replaceAll(' ', '_') }}.csv
1번에서 넣은 법정동 코드와 토지대장의 전체 주소를 이어 붙여 5011012200_제주특별자치도_제주시_노형동.csv 같은 이름이 됩니다. 마지막 Google Drive 노드는 Operation을 Upload로 두고 드라이브 폴더를 고릅니다. 원본의 File Name 칸에는 빈 표현식만 남아 있는데, 따라 만들 때는 {{ $binary.data.fileName }}을 넣어 Convert to File에서 정한 이름이 그대로 쓰이게 해 두는 편이 확실합니다.
원본 캔버스에 남아 있는 흔적
이 워크플로우는 n8n 인스턴스에 비활성 상태로 남아 있고 실행 이력이 없어서, 운영 결과 대신 캔버스에 남은 흔적 세 가지를 옮깁니다.
첫째, 첫 URL의 SAN=2입니다. 이 글을 쓰며 같은 주소를 불러 보니(2026년 8월 기준) 노형동의 산 번지 필지는 75개이고 그중 지목이 전·답·과인 것은 1개뿐이었습니다. SAN=1로 바꾸면 8,836개가 옵니다. Limit을 1,000으로 둔 걸 보면 만든 사람도 많은 필지를 염두에 뒀던 것 같은데, 남아 있는 값은 산 번지 쪽입니다. 따라 만들 때는 SAN을 1로 두고 시작하고, 산 번지까지 원하면 두 번 돌리는 게 맞습니다.
둘째, 30분 간격 Schedule Trigger입니다. 토지대장과 공시지가는 하루 단위로도 거의 바뀌지 않는 데이터라, 30분마다 같은 동네를 다시 받는 건 서버에도 드라이브 폴더에도 좋을 게 없습니다. 이 워크플로우는 스케줄보다 "코드 바꾸고 한 번 실행"에 맞는 구조이고, 스티키 노트의 제목이 워크플로우가 아니라 "수집"인 것도 그런 쓰임을 말해 줍니다.
셋째, 스티키 노트 자체가 문서입니다. PNU 자릿수 분해, 주소 다섯 개, 각 구간의 목적("조회할 리스트 불러오기", "전답과 만 필터링", "카테고리 없는 토지 필터링", "머지")이 노드 옆에 적혀 있어서 1년 뒤에 열어도 읽힙니다. 문서 없는 주소를 쓰는 워크플로우일수록 이런 메모가 유일한 문서가 됩니다.
한계
- 비공식 주소 — 공개 문서가 없는 엔드포인트라 언제든 바뀌거나 막힐 수 있습니다. 같은 데이터의 공식 통로가 있는지 공공데이터포털(data.go.kr)에서 먼저 찾아보고, 없을 때만 이 방법을 쓰세요. 쓸 때도 Limit과 HTTP Request 옵션의 Batching(Items per Batch·Batch Interval)으로 요청 간격을 둡니다.
- 새 항목 알림 없음 — 매 실행이 전체 재수집이고, 전에 받은 것과 비교하는 단계가 없습니다. 같은 이름의 CSV가 드라이브에 계속 쌓입니다. 증분 수집이 필요하면 결과를 Google Sheets에 Append or Update로 넣고
pnu를 매칭 컬럼으로 잡는 편이 낫습니다. - CSV가 시트는 아닙니다 — 결과가 파일이라 바로 열어 보기엔 좋지만 이어서 가공하려면 한 번 더 열어야 합니다. Convert to File과 Google Drive 두 노드를 Google Sheets(Append) 하나로 바꾸면 시트 버전이 됩니다.
- 좌표계 —
wkt의 좌표는 위경도가 아니라 미터 단위 평면 좌표입니다. 지도에 얹으려면 좌표계 변환이 필요하고, 그건 이 워크플로우 밖의 일입니다.
포트폴리오·면접 포인트
이 워크플로우를 포트폴리오에 넣을 때는 결과 CSV보다 과정이 중요합니다. Network 탭에서 JSON 주소를 찾은 화면, Merge 노드의 SQL, 그리고 완성된 CSV 세 장이면 "궁금한 사이트를 데이터셋으로 바꿀 수 있는 사람"이라는 메시지가 전달됩니다. 같은 뼈대로 법정동 코드만 바꾸면 다른 동네가 되고, 주소만 바꾸면 다른 사이트가 됩니다. 여기서 만든 CSV를 다음 편의 대시보드나 별도의 분석 노트북으로 이어 가면 수집부터 시각화까지 한 줄로 묶인 포트폴리오가 됩니다.
면접에서는 "왜 Merge를 SQL로 했나요?"와 "왜 LEFT JOIN 뒤에 또 Filter를 뒀나요?"가 나올 수 있습니다. 첫 질문의 답은 입력이 네 개라 두 개씩 세 번 합치는 것보다 SQL 한 번이 읽기 쉽고 조인 키가 한눈에 보이기 때문입니다. 두 번째 답은 조인에서 행을 잃지 않게 먼저 살려 둔 뒤, 어떤 필드가 비었는지 보고 걸러야 "왜 빠졌는지"를 설명할 수 있기 때문입니다. 두 답 모두 노드 사용법이 아니라 데이터 흐름을 설계한 사람의 답입니다.
다음 액션
수집한 데이터를 쌓아 두기만 하면 포트폴리오에서 "그래서 뭘 봤는데요?"라는 질문을 받습니다. 다음 편은 공공데이터를 수집해 전처리하고 대시보드까지 연결하는 워크플로우입니다. Tableau를 쓰는 원본이라 유료 도구가 끼지만, 무료 대안까지 같이 다룹니다. 이 편의 CSV가 그대로 그 편의 입력이 될 수 있습니다.
