💻

3. 공공데이터포털

 
 

3. 공공데이터포털의 OpenAPI 활용

 
 

3.1 공공데이터포털

 
공공데이터포털은 정부에서 지원하는 공공데이터 수집 및 제공 웹사이트입니다. 공공데이터 포털을 통해 다방면의 공공데이터를 활용할 수 있습니다. 이를 통해 제공되는 데이터는 정부 기관이나 지자체, 공공기관 등에서 수집하고 지원하는 데이터이기 때문에 높은 신뢰성을 갖고 있습니다.
지원되는 자료로는 교육, 공공행정, 사회복지, 식품건강, 환경, 교통 등 굉장히 다양한 통계자료가 있으며 누구나 회원가입만 하면 원하는 자료를 검색해 필요로 하는 곳에 이용할 수 있습니다. 뿐만아니라 다음처럼 데이터활용에 들어가면 다른 이용자들은 데이터를 어떻게 활용했나에 대한 인사이트를 얻을 수도 있습니다.
 
notion imagenotion image
이처럼 공공데이터포털은 개인이나 기업 등 누구나 무료로 원하는 데이터를 이용할 수 있고, 다양한 분야의 신뢰할 수 있는 데이터들이 풍부하다는 장점이 있습니다. 이를 이용해 다방면으로 연구하고 분석하고 서비스를 개발하여 다양한 인사이트를 얻어 사회적 가치를 창출하거나 개인의 경제적 가치를 창출할 수 있습니다.
 

3.1.1 공공데이터포털 데이터 검색

공공데이터포털에서 데이터를 검색하는 방법을 알아보도록 하겠습니다.
 
  1. 로그인 및 회원가입 진행
notion imagenotion image
 
  1. 검색창 - ‘원하는 자료’ 검색
notion imagenotion image
 
  1. 검색 결과 - 오픈API 클릭
검색 결과에는 데이터 별로 파일데이터, 오픈 API, 표준데이터셋이 있습니다.
저희는 오픈 API를 클릭하여 진행하도록 하겠습니다.
notion imagenotion image
 
  1. ‘공정거래위원회_가맹정보_업종별 가맹본부 변동현황 제공 서비스’ 클릭
이 책에서는 업종별 가맹본부 변동현황 제공 서비스 데이터를 사용하지만 목적에 따라 필요한 데이터를 클릭하여 사용하시면 됩니다.
데이터 제목 앞에 보면 XML, JSON이 써있는 것을 보실 수 있습니다. 데이터에 따라 XML과 JSON을 둘 다 제공하거나 둘 중 하나만 제공하는 데이터가 있으므로 필요에 따라 확인하시면 됩니다.
notion imagenotion image
 
 

3.1.2 공공데이터포털 데이터 탐색

검색한 데이터를 활용 신청하기 전에 간단히 탐색하는 방법에 대해 알아보도록 하겠습니다.
검색한 데이터를 클릭하면 아래와 같은 화면이 나옵니다.
notion imagenotion image
 
  • 데이터 조회하기
‘데이터 조회하기’ 기능을 통해 미리 데이터를 조회할 수 있습니다.
이 기능의은 모든 데이터의 필수가 아니기 때문에 조회하기 기능이 없는 데이터도 있습니다.
notion imagenotion image
notion imagenotion image
 
  • OpenAPI 정보
OpenAPI 정보란에서는 메타데이터 다운로드, OpenAPI 에러코드 확인, API유형, 데이터포맷, 등록일, 수정일 등 다양한 정보를 확인할 수 있습니다.
notion imagenotion image
 
  • 상세기능 정보
화면을 더 아래로 내리면 해당 데이터의 상세 기능과 사용 가이드를 다운로드 받을 수 있습니다.
notion imagenotion image
notion imagenotion image
 

3.1.3 OpenAPI 활용 신청

OpenAPI와 관련된 데이터들을 이용하기 위해서는 추가적으로 OpenAPI를 신청하는 절차가 필요합니다.
 
  1. 검색창 - ‘원하는 자료’ 검색
notion imagenotion image
 
  1. 검색 결과 - 오픈API 클릭
notion imagenotion image
 
  1. 데이터 활용신청 클릭
notion imagenotion image
 
  1. 활용목적 선택 - 동의 체크 - 활용신청 클릭
notion imagenotion image
  1. 마이페이지 - 데이터 활용 - OpenAPI - 활용신청 현황
순서대로 들어가서 신청한 데이터를 이용할 수 있습니다.
notion imagenotion image
📢
OpenAPI란? 오픈API란 누구든지 사용할 수 있도록 공개된 API입니다. 데이터를 표준화하고 프로그래밍해 다른 소프트웨어 개발자나 사용자와 상호작용하고 서비스를 개발할 수 있도록 제공되는 인터페이스입니다. 공공데이터포털에서는 개인 고유의 API 인증키와 함께사용할 수 있습니다.
 
 

3.2 데이터 활용

 
공공데이터포털의 많은 데이터들 중 저희가 활용해 볼 데이터는 공정거래위원회에서 제공하는 ‘가맹정보_업종별 가맹본부 변동현황 제공 서비스’ 데이터입니다. 기업이나 개인이 새로운 사업을 시작하기 위해서는 시장의 트렌드나 현황을 잘 파악하고 분석하는 것이 중요합니다. 그래야만 실패확률은 적어지고 성공적으로 사업을 시작하고 이어나갈 수 있기 때문입니다.
가맹정보_업종별 가맹본부 변동현황 제공 서비스’ 데이터는 년도를 기준으로 가맹본부 증가수, 가맹본부 증가율, 신규등록 가맹본부수, 신규등록 가맹본부율, 등록취소 가맹본부수, 등록취소 가맹본부율, 가맹본부 평균 영업기간 등의 정보를 제공합니다. 이를 통해 최근 몇 년간 어떤 분야가 떠오르고 있고 어떤 분야가 식어가고 있는지 추세를 파악하고 예측할 수 있습니다. 또한 현재의 트렌드는 어떠하고 그 트렌드가 어느정도로 지속될 수 있을 것인지, 어느 분야가 블루오션이고 레드오션인지 등의 다양한 정보도 파악할 수 있습니다. 특히 저희는 이번 쳅터에서 외식사업의 현황을 크롤링하여 정보를 가져오고 분석해 보도록 하겠습니다.
 

3.2.1 URL과 매개변수

공공데이터포털에서 OpenAPI를 사용하여 데이터를 크롤링하기 위해서는 해당 데이터의 URL과 매개변수를 알아야 데이터 요청이 가능합니다.
데이터의 URL과 매개변수를 확인하는 방법에 대해 알아보도록 하겠습니다.
 

URL 확인

데이터의 URL은 공공데이터포털 상세페이지에서 확인할 수 있습니다.
 
  • URL
    • → https:// + Base URL + 해당 GET URL
notion imagenotion image
 

매개변수 확인

API목록 중 데이터를 클릭하면 해당 데이터의 매개변수를 확인할 수 있습니다.
외식별 업종별 데이터의 경우 매개변수는 총 5개(serviceKey, pageNo, numOfRows, resultType, yr)이며 이 중 resultType 매개변수는 선택 매개변수입니다.
notion imagenotion image
📢
매개변수(Parameters) 공공데이터포털 OpenAPI에서 매개변수는 선택 매개변수와 필수 매개변수가 있습니다. - 선택 매개변수 : 입력하지 않아도 API가 정상적으로 실행됨 - 필수 매개변수(* required) : 입력하지 않으면 API가 실행되지 않으며 입력하는 값에 따라 결과 데이터가 달라짐
 

3.2.2 JSON 데이터 크롤링

라이브러리 호출

JSON 데이터를 크롤링하기 위해 필요한 라이브러리를 호출합니다.
import json import requests import pandas as pd

데이터 불러오기

requests를 통해 URL요청을 하여 JSON 데이터를 불러오도록 하겠습니다.
 
  1. 공공데이터포털 OpenAPI - ServiceKey 입력
공공데이터포털 → 마이페이지 → Decoding Key 복사 → 변수 ‘key’에 입력
key = 'DecodingKey 입력'
 
  1. URL, 매개변수 입력 - requests 요청
2021년도를 기준으로 한 페이지에 최대 15개의 데이터 json형태로 불러오도록 하겠습니다.
— serviceKey : 공공데이터포털의 Service Key
— pageNo : 불러온 데이터 중 확인할 페이지 숫자
— numOfRows : 한 페이지당 보여줄 데이터 갯수
— resultType : 불러올 데이터 타입 (xml or json)
— yr : 불러올 데이터의 기준 연도
url = 'https://apis.data.go.kr/1130000/FftcIndutyJnghdqrtrsStatsService/getIndutyJnghdqrtrsOutStats?' params = {'serviceKey' : key, 'pageNo' : '1', 'numOfRows' : '15', 'resultType' : 'json', 'yr' : '2021' } response = requests.get(url, params=params)
 
  1. 요청받은 데이터 확인
content = response.text content
{"resultCode":"00","resultMsg":"NORMAL SERVICE","numOfRows":"15","pageNo":"1","totalCount":15,"items":[{"yr":"2021","indutyMlsfcNm":"기타 외국식","jnghdqrtrsIncCnt":60,"jnghdqrtrsIncRt":71.43,"newRgsJnghdqrtrsCnt":76,"newRgsJnghdqrtrsRt":47.5,"rgsRtrcnJnghdqrtrsCnt":16,"rgsRtrcnJnghdqrtrsRt":10,"jnghdqrtrsAvrgBsnPdVal":"5년 7개월"},{"yr":"2021","indutyMlsfcNm":"기타 외식","jnghdqrtrsIncCnt":407,"jnghdqrtrsIncRt":57.57,"newRgsJnghdqrtrsCnt":533,"newRgsJnghdqrtrsRt":43.62,"rgsRtrcnJnghdqrtrsCnt":108,"rgsRtrcnJnghdqrtrsRt":8.84,"jnghdqrtrsAvrgBsnPdVal":"6년 7개월"},{"yr":"2021","indutyMlsfcNm":"분식","jnghdqrtrsIncCnt":230,"jnghdqrtrsIncRt":65.16,"newRgsJnghdqrtrsCnt":308,"newRgsJnghdqrtrsRt":48.73,"rgsRtrcnJnghdqrtrsCnt":49,"rgsRtrcnJnghdqrtrsRt":7.75,"jnghdqrtrsAvrgBsnPdVal":"6년 1개월"},{"yr":"2021","indutyMlsfcNm":"서양식","jnghdqrtrsIncCnt":127,"jnghdqrtrsIncRt":115.45,"newRgsJnghdqrtrsCnt":150,"newRgsJnghdqrtrsRt":55.76,"rgsRtrcnJnghdqrtrsCnt":32,"rgsRtrcnJnghdqrtrsRt":11.9,"jnghdqrtrsAvrgBsnPdVal":"5년 6개월"},{"yr":"2021","indutyMlsfcNm":"아이스크림/빙수 ","jnghdqrtrsIncCnt":24,"jnghdqrtrsIncRt":80,"newRgsJnghdqrtrsCnt":33,"newRgsJnghdqrtrsRt":54.1,"rgsRtrcnJnghdqrtrsCnt":7,"rgsRtrcnJnghdqrtrsRt":11.48,"jnghdqrtrsAvrgBsnPdVal":"7년 7개월"},{"yr":"2021","indutyMlsfcNm":"음료 (커피 외)","jnghdqrtrsIncCnt":32,"jnghdqrtrsIncRt":53.33,"newRgsJnghdqrtrsCnt":43,"newRgsJnghdqrtrsRt":43,"rgsRtrcnJnghdqrtrsCnt":8,"rgsRtrcnJnghdqrtrsRt":8,"jnghdqrtrsAvrgBsnPdVal":"6년 4개월"},{"yr":"2021","indutyMlsfcNm":"일식","jnghdqrtrsIncCnt":166,"jnghdqrtrsIncRt":99.4,"newRgsJnghdqrtrsCnt":200,"newRgsJnghdqrtrsRt":53.76,"rgsRtrcnJnghdqrtrsCnt":39,"rgsRtrcnJnghdqrtrsRt":10.48,"jnghdqrtrsAvrgBsnPdVal":"5년 6개월"},{"yr":"2021","indutyMlsfcNm":"제과제빵","jnghdqrtrsIncCnt":81,"jnghdqrtrsIncRt":65.32,"newRgsJnghdqrtrsCnt":112,"newRgsJnghdqrtrsRt":49.34,"rgsRtrcnJnghdqrtrsCnt":22,"rgsRtrcnJnghdqrtrsRt":9.69,"jnghdqrtrsAvrgBsnPdVal":"7년 2개월"},{"yr":"2021","indutyMlsfcNm":"주점","jnghdqrtrsIncCnt":103,"jnghdqrtrsIncRt":46.61,"newRgsJnghdqrtrsCnt":154,"newRgsJnghdqrtrsRt":42.78,"rgsRtrcnJnghdqrtrsCnt":36,"rgsRtrcnJnghdqrtrsRt":10,"jnghdqrtrsAvrgBsnPdVal":"6년 11개월"},{"yr":"2021","indutyMlsfcNm":"중식","jnghdqrtrsIncCnt":130,"jnghdqrtrsIncRt":139.78,"newRgsJnghdqrtrsCnt":150,"newRgsJnghdqrtrsRt":62.24,"rgsRtrcnJnghdqrtrsCnt":18,"rgsRtrcnJnghdqrtrsRt":7.47,"jnghdqrtrsAvrgBsnPdVal":"4년 10개월"},{"yr":"2021","indutyMlsfcNm":"치킨","jnghdqrtrsIncCnt":218,"jnghdqrtrsIncRt":56.77,"newRgsJnghdqrtrsCnt":292,"newRgsJnghdqrtrsRt":44.44,"rgsRtrcnJnghdqrtrsCnt":55,"rgsRtrcnJnghdqrtrsRt":8.37,"jnghdqrtrsAvrgBsnPdVal":"7년 4개월"},{"yr":"2021","indutyMlsfcNm":"커피","jnghdqrtrsIncCnt":344,"jnghdqrtrsIncRt":106.5,"newRgsJnghdqrtrsCnt":399,"newRgsJnghdqrtrsRt":55.26,"rgsRtrcnJnghdqrtrsCnt":55,"rgsRtrcnJnghdqrtrsRt":7.62,"jnghdqrtrsAvrgBsnPdVal":"5년 10개월"},{"yr":"2021","indutyMlsfcNm":"패스트푸드","jnghdqrtrsIncCnt":86,"jnghdqrtrsIncRt":122.86,"newRgsJnghdqrtrsCnt":103,"newRgsJnghdqrtrsRt":59.88,"rgsRtrcnJnghdqrtrsCnt":16,"rgsRtrcnJnghdqrtrsRt":9.3,"jnghdqrtrsAvrgBsnPdVal":"7년 1개월"},{"yr":"2021","indutyMlsfcNm":"피자","jnghdqrtrsIncCnt":87,"jnghdqrtrsIncRt":64.44,"newRgsJnghdqrtrsCnt":112,"newRgsJnghdqrtrsRt":46.67,"rgsRtrcnJnghdqrtrsCnt":18,"rgsRtrcnJnghdqrtrsRt":7.5,"jnghdqrtrsAvrgBsnPdVal":"6년 7개월"},{"yr":"2021","indutyMlsfcNm":"한식","jnghdqrtrsIncCnt":815,"jnghdqrtrsIncRt":65.83,"newRgsJnghdqrtrsCnt":1160,"newRgsJnghdqrtrsRt":51.21,"rgsRtrcnJnghdqrtrsCnt":212,"rgsRtrcnJnghdqrtrsRt":9.36,"jnghdqrtrsAvrgBsnPdVal":"5년 7개월"}]}
이렇게 불러온 JSON데이터는 가독성이 떨어져 쉽게 파악하기 어렵습니다.
 
📢
SERVICE KEY IS NOT REGISTERED ERROR 해당 에러는 서비스키가 등록되지 않았다는 에러입니다. 방법1. ServiceKey → EncodingKey로 변경하여 실행 방법2. 기다리기 : OpenAPI는 데이터에 따라 승인을 받기까지 시간이 걸릴 수 있습니다.

데이터 변환

요청 받은 JSON데이터를 Python데이터 구조로 변환 후 데이터프레임으로 출력해보도록 하겠습니다.
 
  1. JSON 형식의 문자열을 Python 객체로 변환
json_load = json.loads(contents) json_load
{'resultCode': '00', 'resultMsg': 'NORMAL SERVICE', 'numOfRows': '15', 'pageNo': '1', 'totalCount': 15, 'items': [{'yr': '2021', 'indutyMlsfcNm': '기타 외국식', 'jnghdqrtrsIncCnt': 60, 'jnghdqrtrsIncRt': 71.43, 'newRgsJnghdqrtrsCnt': 76, 'newRgsJnghdqrtrsRt': 47.5, 'rgsRtrcnJnghdqrtrsCnt': 16, 'rgsRtrcnJnghdqrtrsRt': 10, 'jnghdqrtrsAvrgBsnPdVal': '5년 7개월'}, {'yr': '2021', 'indutyMlsfcNm': '기타 외식', 'jnghdqrtrsIncCnt': 407, 'jnghdqrtrsIncRt': 57.57, 'newRgsJnghdqrtrsCnt': 533, 'newRgsJnghdqrtrsRt': 43.62, 'rgsRtrcnJnghdqrtrsCnt': 108, 'rgsRtrcnJnghdqrtrsRt': 8.84, 'jnghdqrtrsAvrgBsnPdVal': '6년 7개월'}, {'yr': '2021', 'indutyMlsfcNm': '분식', 'jnghdqrtrsIncCnt': 230, 'jnghdqrtrsIncRt': 65.16, 'newRgsJnghdqrtrsCnt': 308, 'newRgsJnghdqrtrsRt': 48.73, 'rgsRtrcnJnghdqrtrsCnt': 49, 'rgsRtrcnJnghdqrtrsRt': 7.75, 'jnghdqrtrsAvrgBsnPdVal': '6년 1개월'}, {'yr': '2021', 'indutyMlsfcNm': '서양식', 'jnghdqrtrsIncCnt': 127, 'jnghdqrtrsIncRt': 115.45, 'newRgsJnghdqrtrsCnt': 150, 'newRgsJnghdqrtrsRt': 55.76, 'rgsRtrcnJnghdqrtrsCnt': 32, 'rgsRtrcnJnghdqrtrsRt': 11.9, 'jnghdqrtrsAvrgBsnPdVal': '5년 6개월'}, {'yr': '2021', 'indutyMlsfcNm': '아이스크림/빙수 ', 'jnghdqrtrsIncCnt': 24, 'jnghdqrtrsIncRt': 80, 'newRgsJnghdqrtrsCnt': 33, 'newRgsJnghdqrtrsRt': 54.1, 'rgsRtrcnJnghdqrtrsCnt': 7, 'rgsRtrcnJnghdqrtrsRt': 11.48, 'jnghdqrtrsAvrgBsnPdVal': '7년 7개월'}, {'yr': '2021', 'indutyMlsfcNm': '음료 (커피 외)', 'jnghdqrtrsIncCnt': 32, 'jnghdqrtrsIncRt': 53.33, 'newRgsJnghdqrtrsCnt': 43, 'newRgsJnghdqrtrsRt': 43, 'rgsRtrcnJnghdqrtrsCnt': 8, 'rgsRtrcnJnghdqrtrsRt': 8, 'jnghdqrtrsAvrgBsnPdVal': '6년 4개월'}, {'yr': '2021', 'indutyMlsfcNm': '일식', 'jnghdqrtrsIncCnt': 166, 'jnghdqrtrsIncRt': 99.4, 'newRgsJnghdqrtrsCnt': 200, 'newRgsJnghdqrtrsRt': 53.76, 'rgsRtrcnJnghdqrtrsCnt': 39, 'rgsRtrcnJnghdqrtrsRt': 10.48, 'jnghdqrtrsAvrgBsnPdVal': '5년 6개월'}, {'yr': '2021', 'indutyMlsfcNm': '제과제빵', 'jnghdqrtrsIncCnt': 81, 'jnghdqrtrsIncRt': 65.32, 'newRgsJnghdqrtrsCnt': 112, 'newRgsJnghdqrtrsRt': 49.34, 'rgsRtrcnJnghdqrtrsCnt': 22, 'rgsRtrcnJnghdqrtrsRt': 9.69, 'jnghdqrtrsAvrgBsnPdVal': '7년 2개월'}, {'yr': '2021', 'indutyMlsfcNm': '주점', 'jnghdqrtrsIncCnt': 103, 'jnghdqrtrsIncRt': 46.61, 'newRgsJnghdqrtrsCnt': 154, 'newRgsJnghdqrtrsRt': 42.78, 'rgsRtrcnJnghdqrtrsCnt': 36, 'rgsRtrcnJnghdqrtrsRt': 10, 'jnghdqrtrsAvrgBsnPdVal': '6년 11개월'}, {'yr': '2021', 'indutyMlsfcNm': '중식', 'jnghdqrtrsIncCnt': 130, 'jnghdqrtrsIncRt': 139.78, 'newRgsJnghdqrtrsCnt': 150, 'newRgsJnghdqrtrsRt': 62.24, 'rgsRtrcnJnghdqrtrsCnt': 18, 'rgsRtrcnJnghdqrtrsRt': 7.47, 'jnghdqrtrsAvrgBsnPdVal': '4년 10개월'}, {'yr': '2021', 'indutyMlsfcNm': '치킨', 'jnghdqrtrsIncCnt': 218, 'jnghdqrtrsIncRt': 56.77, 'newRgsJnghdqrtrsCnt': 292, 'newRgsJnghdqrtrsRt': 44.44, 'rgsRtrcnJnghdqrtrsCnt': 55, 'rgsRtrcnJnghdqrtrsRt': 8.37, 'jnghdqrtrsAvrgBsnPdVal': '7년 4개월'}, {'yr': '2021', 'indutyMlsfcNm': '커피', 'jnghdqrtrsIncCnt': 344, 'jnghdqrtrsIncRt': 106.5, 'newRgsJnghdqrtrsCnt': 399, 'newRgsJnghdqrtrsRt': 55.26, 'rgsRtrcnJnghdqrtrsCnt': 55, 'rgsRtrcnJnghdqrtrsRt': 7.62, 'jnghdqrtrsAvrgBsnPdVal': '5년 10개월'}, {'yr': '2021', 'indutyMlsfcNm': '패스트푸드', 'jnghdqrtrsIncCnt': 86, 'jnghdqrtrsIncRt': 122.86, 'newRgsJnghdqrtrsCnt': 103, 'newRgsJnghdqrtrsRt': 59.88, 'rgsRtrcnJnghdqrtrsCnt': 16, 'rgsRtrcnJnghdqrtrsRt': 9.3, 'jnghdqrtrsAvrgBsnPdVal': '7년 1개월'}, {'yr': '2021', 'indutyMlsfcNm': '피자', 'jnghdqrtrsIncCnt': 87, 'jnghdqrtrsIncRt': 64.44, 'newRgsJnghdqrtrsCnt': 112, 'newRgsJnghdqrtrsRt': 46.67, 'rgsRtrcnJnghdqrtrsCnt': 18, 'rgsRtrcnJnghdqrtrsRt': 7.5, 'jnghdqrtrsAvrgBsnPdVal': '6년 7개월'}, {'yr': '2021', 'indutyMlsfcNm': '한식', 'jnghdqrtrsIncCnt': 815, 'jnghdqrtrsIncRt': 65.83, 'newRgsJnghdqrtrsCnt': 1160, 'newRgsJnghdqrtrsRt': 51.21, 'rgsRtrcnJnghdqrtrsCnt': 212, 'rgsRtrcnJnghdqrtrsRt': 9.36, 'jnghdqrtrsAvrgBsnPdVal': '5년 7개월'}]}
얼핏 보기에는 크게 달라진 부분이 없는 것처럼 보이지만
print(type(json_load))
<class 'dict'>
type을 이용해 변수의 데이터 타입을 출력해보면 딕셔너리 형태로 변환되었다는 것을 알 수 있습니다.
 
  1. 필요한 데이터 추출해 데이터프레임 변환
df = pd.DataFrame()
먼저 데이터프레임을 df라는 변수에 할당해줍니다.
if json_data['totalCount'] != 0: items = json_data['items'] df = pd.DataFrame(items) df
notion imagenotion image
이 후 딕셔너리로 변환된 데이터에서 필요로하는 'items'라는 키만 불러 데이터프레임으로 변환해주면 위와 같은 형태의 데이터프레임을 출력할 수 있습니다.
 

3.2.3 XML 데이터 크롤링

라이브러리 호출

XML 데이터를 크롤링하기 위해 필요한 라이브러리를 호출합니다.
import requests import bs4 import pandas as pd
 

데이터 불러오기

requests를 통해 URL요청을 하여 XML 데이터를 불러오도록 하겠습니다.
 
  1. 공공데이터포털 OpenAPI - ServiceKey 입력
공공데이터포털 → 마이페이지 → Decoding Key 복사 → 변수 ‘key’에 입력
key = 'DecodingKey 입력'
 
  1. URL, 매개변수 입력 - requests 요청
2022년도를 기준으로 한 페이지에 최대 15개의 데이터 xml형태로 불러오도록 하겠습니다.
— serviceKey : 공공데이터포털의 Service Key
— pageNo : 불러온 데이터 중 확인할 페이지 숫자
— numOfRows : 한 페이지당 보여줄 데이터 갯수
— resultType : 불러올 데이터 타입 (xml or json)
— yr : 불러올 데이터의 기준 연도
url = 'https://apis.data.go.kr/1130000/FftcIndutyJnghdqrtrsStatsService/getIndutyJnghdqrtrsOutStats?' params = {'serviceKey' : key, 'pageNo' : '1', 'numOfRows' : '15', 'resultType' : 'xml', 'yr' : '2022' } response = requests.get(url, params=params)
 
  1. 요청받은 데이터 확인
content = response.text content
notion imagenotion image
📢
SERVICE KEY IS NOT REGISTERED ERROR 해당 에러는 서비스키가 등록되지 않았다는 에러입니다. 방법1. ServiceKey → EncodingKey로 변경하여 실행 방법2. 기다리기 : OpenAPI는 데이터에 따라 승인을 받기까지 시간이 걸릴 수 있습니다.
 

데이터 변환

요청 받은 XML데이터를 데이터프레임으로 변환하도록 하겠습니다.
 
  1. XML 데이터 확인
XML 데이터를 확인해보면 저희에게 필요한 데이터는 <item>태그에 담겨있는 것을 확인할 수 있습니다.
notion imagenotion image
 
  1. 필요한 데이터 추출
BeautifulSoup에서 지원하는 다양한 Parser 중 XML Parser를 사용하여 XML 데이터의 <item>부분만 추출하도록 하겠습니다.
xml_result = bs4.BeautifulSoup(content,'xml') result = xml_result.findAll('item') result
notion imagenotion image
 
  1. column, value 추출
XML 데이터에서 column을 추출한 다음, column에 맞는 value를 넣어주도록 하겠습니다.
 
  • column 추출
column_list = [] # for문을 통해 추출한 column을 담을 빈 리스트 for column in result[0].find_all(): column_list.append(column.name) column_list
notion imagenotion image
 
  • value 추출
data_list = [] value_list = [] for i in result: for j in i.find_all(): data_list.append(j.text) value_list.append(data_list) data_list = [] value_list
notion imagenotion image
 
  1. 데이터프레임 변환
위에서 추출한 column_list와 value_list를 사용하여 데이터프레임을 만들도록 하겠습니다.
df = pd.DataFrame(value_list, columns = column_list) df
notion imagenotion image
 
 

3.3.4 데이터 전처리 및 저장

데이터 전처리

크롤링한 데이터를 목적에 맞는 전처리를 통해 분석에 용이한 데이터로 만들겠습니다.
 
  • column명 변경
현재 한 눈에 파악하기 어려운 column명을 한 눈에 알기 쉬운 명칭으로 변경합니다.
df.columns = ['기준년도', '업종_중분류명', '가맹본부_증가수', '가맹본부_증가율', '신규등록_가맹본부수', '신규등록_가맹본부율', '등록취소_가맹본부수', '등록취소_가맹본부율', '가맹본부_평균영업기간'] df
notion imagenotion image
 
 
  • 불필요한 column 삭제
새로운 사업 시작을 목적으로 하는 저희는 신규 등록과 등록 취소 가맹본부 데이터가 가장 필요하기 때문에 그 외의 ‘가맹본부_증가수’, ‘가맹본부_증가율’, ‘가맹본부_평균영업기간’ column을 삭제합니다.
df = df.drop('가맹본부_증가수', axis=1) df = df.drop('가맹본부_증가율', axis=1) df = df.drop('가맹본부_평균영업기간', axis=1) df
notion imagenotion image
📢
column 삭제 column 삭제는 데이터 수집의 목적에 따라 필요성이 다르며 같은 데이터라고 하더라도 삭제의 기준이 다를 수 있습니다.
 
 
  • 데이터 형변환
현재 데이터 타입을 확인해보면 모두 object로 되어있습니다.
df.dtypes
notion imagenotion image
 
‘기준년도’, ‘신규등록_가맹본부수’, ‘등록취소_가맹본부수’는 정수형(int)으로,
‘신규등록_가맹본부율’, ‘등록취소_가맹본부율’은 실수형(float)으로 변환하도록 하겠습니다.
df['기준년도'] = df['기준년도'].astype(int) df['신규등록_가맹본부수'] = df['신규등록_가맹본부수'].astype(int) df['신규등록_가맹본부율'] = df['신규등록_가맹본부율'].astype(float) df['등록취소_가맹본부수'] = df['등록취소_가맹본부수'].astype(int) df['등록취소_가맹본부율'] = df['등록취소_가맹본부율'].astype(float)
df.dtypes
notion imagenotion image
 
 
  • 정렬
필요에 따라 정렬을 통해 데이터를 다양하게 확인할 수 있습니다.
 
— ‘등록취소_가맹본부수’를 기준으로 오름차순 정렬
df.sort_values(by='등록취소_가맹본부수', ascending = True)
notion imagenotion image
 
— ‘신규등록_가맹본부율’을 기준으로 내림차순 정렬
df.sort_values(by='신규등록_가맹본부율', ascending = False)
notion imagenotion image
 
이 외에도 2021년의 데이터와 2022의 데이터를 함께 비교하거나, 필요한 부분만을 가져오는 등 필요에 따라 여러가지 방법으로 데이터를 분석할 수 있습니다.
📢
정렬한 데이터가 df에 적용되지 않았습니다. 정렬한 상태로 df에 적용하고 싶다면 아래처럼 앞에 ‘df = ‘을 추가하여 변수 df에 변경사항을 입력해야합니다.
df = df.sort_values(by='신규등록_가맹본부율', ascending = False)
 

데이터 저장

완성한 데이터프레임은 CSV파일로 저장하도록 하겠습니다.
df.to_csv('외식별_가맹정보.csv', index=False)
 
 

3.3 MySQL

JupyterNotebook을 MySQL과 연동하여 DB와 Table을 만든 뒤 위에서 만든 데이터를 입력하고 SQL구문을 사용해서 원하는 데이터를 조회해보도록 하겠습니다.
 

3.3.1. MySQL 연동

 
  1. 라이브러리호출
import pymysql
 
  1. MySQL 연결 및 DB 생성
자신의 MySQL 호스트명, 포트번호, 유저명, 비밀번호를 입력합니다.
DB 생성문에서는 ‘test_db’를 생성하는 SQL구문을 입력하여 실행합니다.
# MySQL 연결 conn = pymysql.connect(host = '호스트명', port = 포트번호, user = '유저명', password = 'password', charset='utf8') # DB 생성 with conn: with conn.cursor() as cur: cur.execute('CREATE DATABASE test_db') conn.commit()
 
  1. 생성한 DB 연결
위의 DB생성문에서 DB이름을 ‘test_db’로 하지 않았다면 아래 코드의 변수 db에서 입력되는 값을 자신이 생성한 DB명으로 수정 후 실행해야합니다.
conn = pymysql.connect(host = '호스트명', port = 포트번호, user = '유저명', password = 'password', db = 'test_db', charset='utf8')
 
  1. Table 생성
저희는 table명을 ‘category’로 설정하도록 하겠습니다.
sql = '''create table category ( category_name VARCHAR(50) primary key, year int, new_store int, new_store_per float, cancel_store int, cancel_store_per float);''' with conn.cursor() as cur: cur.execute(sql) conn.commit()
 
  1. 생성된 Table 확인
sql = 'SHOW TABLES' with conn.cursor() as cur: cur.execute(sql) for t in cur: print(t)
 
  1. 데이터 저장
위에서 생성한 category table에 INSERT INTO문을 통해 데이터를 입력하고 저장합니다.
with conn.cursor() as cursor: for _, row in df.iterrows(): sql = ''' INSERT INTO category (category_name, year, new_store, new_store_per, cancel_store, cancel_store_per) VALUES (%s, %s, %s, %s, %s, %s) ''' values = ( row['업종_중분류명'], row['기준년도'], row['신규등록_가맹본부수'], row['신규등록_가맹본부율'], row['등록취소_가맹본부수'], row['등록취소_가맹본부율'] ) cursor.execute(sql, values) # 커밋 conn.commit()
 
  1. DB 연결 종료
conn.close()
 
 
 

3.3.2. MySQL 데이터 조회

 
  1. DB 연결
conn = pymysql.connect(host = '호스트명', port = 포트번호, user = '유저명', password = 'password', db = 'DB명' charset='utf8')
 
  1. 조건문 실행
SQL구문을 작성하여 query변수에 넣고 코드를 실행합니다.
 
— new_store 갯수 30개 이상
— new_store_per 기준 오름차순
# query query = '''SELECT * FROM category WHERE new_store >= 30 ORDER BY new_store_per ;''' # query 실행 with conn.cursor() as cursor: cursor.execute(query) result = cursor.fetchall() # 결과 출력 print(f'조건에 일치하는 카테고리: 총 {len(result)}개') result_df = pd.DataFrame(result, columns=['category_name', 'year', 'new_store', 'new_store_per', 'cancel_store', 'cancel_store_per']) result_df
notion imagenotion image
 
— cancel_store_per 10% 이상
— cancel_store 상위 5개
# query query = '''SELECT * FROM category WHERE cancel_store_per >= 10 ORDER BY cancel_store DESC LIMIT 5 ;''' # query 실행 with conn.cursor() as cursor: cursor.execute(query) result = cursor.fetchall() # 결과 출력 print(f'조건에 일치하는 카테고리: 총 {len(result)}개') result_df = pd.DataFrame(result, columns=['category_name', 'year', 'new_store', 'new_store_per', 'cancel_store', 'cancel_store_per']) result_df
notion imagenotion image
 
  1. DB 연결 종료
conn.close()