Search

[R] 오픈 API를 활용한 사업자번호 조회하기

OPEN API를 활용하여 사업자번호의 폐업 여부를 R 코드로 구현해봅니다.

배경

이 사업체가 지금도 운영하고 있는지 어떻게 알지?
최근 한 프로젝트를 진행하면서 전국에 있는 사업체를 대상으로 설문조사를 진행할 일이 있었습니다. 내부 자료를 통해 전국에 있는 사업체의 목록은 확보했으나 목록에 있는 사업체들이 설문조사를 하는 시점에도 사업을 영위 중인지 알 길이 없었습니다.
사업 영위 여부를 사전에 전화나 이메일로 연락을 취해 직접 확인하는 방법도 있지만, 목록 내 모든 사업체를 대상으로 사전 확인을 하는 것은 매우 비효율적인 방법입니다.
폐업한 사업체를 설문조사 대상에서 배제하기 위한 효율적인 사전 확인 작업이 필요했고, 정말 다행히도 행정안전부에서 운영 및 제공하는 공공데이터 포털의 오픈 API 서비스(Click)를 통해 이를 쉽게 수행할 수 있었습니다.
공공데이터 포털의 오픈 API 서비스를 활용하는 글은 매우 많으나 보통 Python 코드가 많고 R 코드로 구현된 것은 잘 없습니다. 또 이를 R로 구현하다고 하여도 API 1회 호출에 최대 100개 사업자등록번호만 조회할 수 있습니다. 따라서 한 번의 코드 실행으로 100개를 초과하는 사업자등록번호 목록을 모두 조회할 수 있는 방법을 상세히 설명한 글은 제가 찾았을 때 없었습니다. 때문에 이번 글을 통해 제 경험을 여러분께 공유하고자 합니다.

API

우선 API라는 단어를 들어본 적이 없거나 API에 대한 개념이 명확하지 않으신 분들을 위해 API에 대한 개념부터 짚고 넘어가고자 합니다. 해당되지 않는 분들께서는 본 챕터를 건너뛰셔도 좋습니다.

API의 정의

데이터를 전달하는 웨이터
우리가 한 레스토랑에 방문했을 때 음식을 먹기 위해서 메뉴판을 보고 웨이터에게 주문을 합니다. 그리고 주문한 음식을 웨이터가 전달 해주면 그것을 먹습니다.
레스토랑에서 음식을 먹기 위해 요리하는 법을 모두 알고 있을 필요는 없습니다. 그저 웨이터에게 먹고 싶은 음식이 무엇인지 추가할 사이드 메뉴나 음료는 없는지 정확하게 전달만 해주면 됩니다.
이처럼 API는 전문지식을 갖추고 있지 않아도 어떤 응용 프로그램으로부터 해당 기능을 사용할 수 있도록 해주는 일종의 연결 통로와 같은 것입니다.
조금 더 데이터 관점의 단어로 바꿔서 정리해보면 다음과 같습니다.
API는 Application Programming Interface의 약자로, 응용 프로그램 간에 정보를 교환할 수 있도록 해주는 도구입니다. 사용자는 API에게 데이터 요청을 하고, API는 시스템에서 필요한 데이터를 가져와 사용자에게 전달합니다.

REST API

우리는 미디어를 시청할 수 있는 도구들이 다양해지면서 PC 외에도 여러 스크린을 통해 미디어를 시청합니다. 저는 주말 밤이면 아이패드에 토트넘 경기를 틀고, 스마트폰에는 지난 토트넘 경기들의 하이라이트를 틀고, 노트북으로는 코딩을 합니다(그래서 집중이 되겠냐고 몇 번 질문 받았으나, 열심히 노력하면 간혹 집중이 되기는 합니다).
어떤 때는 스마트폰으로 토트넘 경기를 보다가 너무 집중한 나머지 충전을 깜빡하여 배터리가 방전되면, 아이패드를 바로 꺼내 마저 보기도 합니다.
과거에는 대부분의 사용자가 단일 장치, 주로 데스크탑 컴퓨터를 통해 인터넷을 사용했었습니다. 그러나 지금은 같은 서비스(유튜브, 넷플릭스 등)를 사용하더라도 저와 같이 노트북, 스마트폰, 태블릿PC 등 여러 기기를 사용하시는 분들이 많습니다.
저 같은 멀티플레이어가 다른 기기들에서도 동일한 서비스를 경험할 수 있는 이유는 REST API 때문입니다.
REST(Representational State Transfer) API는 특정한 규칙을 가진 컴퓨터 간 ‘대화 방식’이라고 볼 수 있습니다. 이 대화를 하면 인터넷을 통해 컴퓨터끼리 정보를 매우 효율적으로 주고 받을 수 있습니다.
그 특정한 규칙은 아래와 같습니다.
분업: 레스토랑에서 웨이터는 주문만 받고, 주방에서는 음식만 만드는 것처럼 클라이언트⁽1⁾와 서버는 각자의 일을 해야 합니다.
기억X: 웨이터가 한 손님의 주문을 가져다 준 뒤에는 그 주문을 보통 잊어버리는 것처럼 서버도 요청을 처리한 뒤 그 정보를 기억하지 않습니다.
캐싱: 사전에 요리가 된 메뉴(호프집의 팝콘 등)나 요리가 필요 없는 음료와 같은 것들은 웨이터가 주문을 받지 않고도 미리 제공할 수 있습니다. 정보를 미리 저장해둔다면 당연히 더 빨리 제공할 수 있습니다.
일관성: 레스토랑에 방문하는 고객들이 받는 메뉴판이 모두 같은 형태로 되어 있어야 주문 받기 쉽습니다. API도 사용하기 쉬운 일관된 방식을 따라야 합니다.
여러 계층: 웨이터 한 명만이 주방에 음식을 요청하는 것이 아니라 다른 웨이터들도 주방에게 요청을 하는 것과 같이 클라이언트는 여러 계층을 거쳐 데이터를 요청합니다.
(1) 클라이언트는 서버로부터 서비스나 데이터를 요청하는 컴퓨터입니다.
위와 같은 규칙을 지킨다고 가정하면, 이제 실제로 레스토랑에서 음식 주문과 생산, 그리고 전달하는 프로세스에 빗대어 REST API의 작동 프로세스를 살펴보겠습니다.
웨이터가 주문을 받을 때와 같이 컴퓨터도 서버에 요청(Request)을 합니다. 이때 몇 가지 방법을 사용할 수 있는데, 그 방법은 아래와 같습니다.
GET: 손님이 메뉴판에서 음식을 보는 것처럼 정보를 보기만 할 때 사용합니다.
POST: 신메뉴를 메뉴판에 추가하는 것처럼 어떤 정보를 추가할 때 사용합니다.
PUT: 메뉴판에 가격 변경과 같이 기존 항목을 변경하는 것처럼 정보를 업데이트할 때 사용합니다.
DELETE: 메뉴판에서 어떤 메뉴를 제거하는 것처럼 정보를 삭제할 때 사용합니다.
서버는 이러한 요청들을 처리하고 필요한 정보를 요청한 컴퓨터(클라이언트)에 다시 보내줍니다.
비유적인 표현이 오히려 이해하는 데 방해가 된다면, 이렇게 이해하셔도 좋습니다.
REpresentational State Transfer는 직역하면 “표현적 상태 전달”입니다. 이것을 좀 풀어서 설명해보면, “내가 원하는 것을 요청을 통해 명확히 표현한다”라는 것입니다.
위에서 말씀 드린 설명과 작동 원리를 따르면 클라이언트와 서버가 서로 통신할 때 사용하는 주소(URL)를 요청의 내용을 쉽게 파악할 수 있습니다. 예를 들어 웹사이트에서 사진을 보고 싶다면 그 사진의 URL 주소만 봐도 “아, 이 주소는 사진을 요청하는 것이구나”하고 바로 알 수 있게 된다는 것입니다.
여기까지의 설명이 이해가지 않으셔도 괜찮습니다. 다음 챕터에서 데이터 형식에 대해 살펴보고 직접 R 코드를 수행한 뒤 다시 읽어보면 조금 더 이해하실 수 있습니다.

JSON? XML?

데이터 통신과 API를 사용하는 데 있어서 데이터의 형식은 매우 중요합니다.
JSON과 XML은 API를 통해 데이터를 호출 하는 데 가장 많이 사용되는 형식입니다.
특히 이번 글에서 소개할 공공데이터 오픈 API 서비스에서 가장 많이 활용되는 형식이기 때문에 JSON과 XML을 좀 더 쉽게 이해 해보는 시간을 갖도록 하겠습니다.

JSON

JSON(JavaScript Object Notation)은 데이터를 전송하고 저장하는 경량의 데이터 교환 형식입니다. 그 구조는 매우 간단한데 이름과 값의 쌍으로 구성된 데이터 객체를 사용하여 정보를 표현합니다. 사람이 읽고 쓰기 쉽고 기계가 데이터를 파싱(Parsing, 해석)하기 쉽다는 장점이 있습니다.
예를 들어 한 사람에 대한 정보를 JSON 형식으로 표현하면 다음과 같습니다.
{ "name": "홍길동", "age": 30, "city": "서울" }
JSON
복사
각 데이터 항목을 식별할 수 있는 Key인 name, age, city와 그에 해당하는 값 홍길동, 30, 서울을 제공하고 있습니다. 이러한 쉬운 구조 덕분에 개발자들은 필요한 데이터를 찾아 활용할 수 있는 코드를 빠르게 작성할 수 있습니다.

XML

XML(eXtensible Markup Language) 역시 JSON과 마찬가지로 데이터를 구조화하여 저장하고 전송하기 위한 하나의 형식입니다.
데이터를 계층적 구조(트리 구조)로 표현할 수 있는데, 그 예시는 아래와 같습니다.
<person> <name>홍길동</name> <age>30</age> <city>서울</city> </person>
XML
복사
XML 언어의 특징은 시작 태그와 종료 태그를 사용해서 데이터의 시작과 끝을 명확하게 나타냅니다. 또 사람을 표현하는 상위 요소 안에 이름, 나이, 도시와 같은 여러 하위 요소를 포함 시키는 계층적인 형식으로 데이터를 구조화할 수 있습니다. 때문에 복잡한 데이터 구조에서도 잘 작동한다는 특징이 있습니다.
JSON과 XML을 비교해보면 다음과 같습니다.
가독성: JSON은 보다 간결하고 읽기 쉽습니다. XML에서는 더 많은 정보를 포함할 수 있지만 그만큼 복잡하고 읽기 어렵다는 단점도 존재합니다.
데이터 크기: JSON은 XML보다 데이터 크기가 작습니다. XML은 시작 태그와 종료 태그도 모두 작성하기 때문에 상대적으로 더 많은 공간을 차지합니다.
파싱 속도: 일반적으로 경량이고 구문이 간단한 JSON이 빠른 편입니다.
유연성: XML은 복잡한 계층 구조와 데이터에 대한 정보(메타 데이터)를 표현할 수 있습니다. 따라서 XML이 대형 프로젝트 같은 복잡한 데이터 구조에 더 적합할 수 있습니다.
API를 통해 불러오는 데이터 형식에 대해 알아보았습니다.
이제 직접 활용할 오픈 API에 대해 알아보겠습니다.

오픈 API

오픈API란 누구나 사용할 수 있도록 공개된 API를 말합니다⁽²⁾.
대한민국의 여러 정부 기관에서 운영하는 공공데이터 플랫폼에서는 대부분 오픈 API를 통해 누구나 쉽게 공공데이터에 접근하고 데이터를 활용할 수 있도록 서비스를 무료로 제공하고 있습니다.
통계청은 국가통계포털(KOSIS) ‘공유서비스’라는 이름으로 오픈 API를 제공하여, 통계청의 방대한 통계 자료에 누구나 접근할 수 있도록 서비스를 제공하고 있습니다(Click).
행정안전부에서는 공공데이터포털을 통해 각 지자체, 국세청, 문화재청 등 다양한 행정기관의 데이터를 조회하고 일부 행정서비스까지 이용할 수 있는 API를 제공하고 있습니다.
API 키를 발급하고 활용하는 상세한 방법은 다음 챕터에서 알아보겠습니다. 더불어 각 사업체의 사업 영위 여부를 알려주는 사업자등록정보 진위확인 및 상태조회 서비스를 활용하는 방법을 알아보겠습니다.

사업자등록번호의 사업 영위 여부 확인

조회하고자 하는 사업자번호가 있는 목록을 사전에 준비해야 합니다!
숫자로 이루어진 10자리 값만 가능합니다. ‘-’ 등의 기호 제거해야 합니다.

① 개발계정 신청

② 설정

③ URL 발급

④ R Programming

패키지

pacman패키지를 통해 사용할 라이브러리를 한 번에 불러오겠습니다.
library(pacman) p_load(openxlsx,dplyr, httr, jsonlite)
R
복사

파일 불러오기

#파일 불러오기 df <- read.xlsx("파일 경로/data.xlsx", sheet = "시트명")
R
복사

API 키

api_key라는 이름의 객체로 인증키의 값을 저장합니다.
api_key <- "API KEY 값을 입력하세요~"
R
복사

HTTP 헤더 설정

httr 패키지를 사용하여 HTTP 요청을 보낼 때 필요한 헤더를 설정합니다.
이 헤더를 통해 클라이언트가 서버에 JSON 형식의 데이터를 수신하고 발신하겠다는 것을 알립니다.
headers <- add_headers( Accept = "application/json", `Content-Type` = "application/json" )
R
복사

데이터 분할과 API 요청 함수

사업자등록정보 진위확인 및 상태조회 서비스는 1번 요청에 100개가 최대입니다.
데이터셋의 business_num 필드를 100개 단위로 분할합니다.
100개 단위로 나눠서 데이터셋의 개수에 따라 n번 서버에 요청하게 됩니다.
business_num_chunks <- split(df$business_num, ceiling(seq_along(df$business_num)/100))
R
복사
함수를 생성합니다.
business_num 청크(최대 100개)에 대해 API를 호출하여 데이터를 가져옵니다.
가져온 데이터는 JSON에서 R 데이터프레임으로 변환되며, 오류 처리를 통해 데이터의 유무를 확인합니다.
get_api_data <- function(business_num_chunk) { data <- toJSON(list(b_no = business_num_chunk)) #JSON 형태로 데이터 변환 #POST 요청을 통해 API에 데이터 요청 response <- POST( url = sprintf("https://api.odcloud.kr/api/nts-businessman/v1/status?serviceKey=%s", api_key), body = data, encode = "json", #JSON 형식으로 인코딩 config = headers #요청에 필요한 HTTP 헤더 설정 ) #응답으로 받은 JSON 데이터를 R 환경에 맞게 구조 변환 content <- fromJSON(content(response, "text"), flatten = TRUE) if (!is.null(content$data)) { # 응답에 데이터가 있을 경우, 데이터 프레임 생성 및 반환 return(data.frame(content$data, stringsAsFactors = FALSE)) } else { # 데이터가 없을 경우, business_num만 포함한 데이터 프레임 생성 및 반환 return(data.frame(business_num = business_num_chunk, stringsAsFactors = FALSE)) } }
R
복사

함수 적용 및 병합

분할된 데이터 각각에 대해 get_api_data 함수를 적용하고, 결과를 하나의 데이터프레임으로 병합합니다.
results <- bind_rows(lapply(business_num_chunks, get_api_data), .id = "group")
R
복사

결과물

사업자등록번호 옆으로 ‘계속사업자’ 또는 ‘폐업자’ 결과가 나옵니다.
b_stt 변수에 값이 없는 경우는 사업자등록번호가 국세청에 등록되지 않은 경우로 잘못 기입된 사업자등록번호입니다.
생성되는 변수와 값들을 정리하면 다음과 같습니다.
필드명
설명
상세 정보
b_no
사업자등록번호
-
b_stt
납세자상태(명칭)
01: 계속사업자 02: 휴업자 03: 폐업자
b_stt_cd
납세자상태(코드)
01: 계속사업자 02: 휴업자 03: 폐업자
tax_type
과세유형메세지(명칭)
01: 부가가치세 일반과세자 02: 부가가치세 간이과세자 03: 부가가치세 과세특례자 04: 부가가치세 면세사업자 05: 수익사업을 영위하지 않는 비영리법인이거나 고유번호가 부여된 단체, 국가기관 등 06: 고유번호가 부여된 단체 07: 부가가치세 간이과세자(세금계산서 발급사업자) 등록되지 않았거나 삭제된 경우: "국세청에 등록되지 않은 사업자등록번호입니다"
tax_type_cd
과세유형메세지(코드)
01, 02, 03, 04, 05, 06, 07, 국세청에 등록되지 않은 사업자등록번호입니다
end_dt
폐업일
날짜 포맷: YYYYMMDD
utcc_yn
단위과세전환폐업여부
Y: 예 N: 아니오
tax_type_change_dt
최근과세유형전환일자
날짜 포맷: YYYYMMDD
invoice_apply_dt
세금계산서적용일자
날짜 포맷: YYYYMMDD
rbf_tax_type
직전과세유형메세지(명칭)
이전의 과세유형명칭으로, 변경 전 과세유형 정보
rbf_tax_type_cd
직전과세유형메세지(코드)
이전의 과세유형 코드로, 변경 전 과세유형 코드

출처

(1) 클라이언트는 서버로부터 서비스나 데이터를 요청하는 컴퓨터입니다(Click).
(2) 공공데이터포털(Click)
Main Page | Category |  Tags | About Me | Contact