사용한 API는 공공데이터포털 농촌진흥청 국립농업과학원_농업기상 관측지점 상세정보 / 기본 관측데이터 조회 두가지
머신러닝 공부하는중이라 데이터 확보가 중요하다보니 자연스레 api로 데이터 취득하는걸 해보게 됨......
파이썬 독학 들어간지 이제야 반년이라 코드 개더러움 주의
더 쉽게 짤 수 있는 부분이 있거나 불필요한 부분이 있는 등 피드백 할게 있으면 가르쳐주면 고맙겠음
일단 api 호출 필수 조건 네가지 중 세가지((시)도 구분 코드 OR 관측지점코드, 관측시작~종료일, API KEY)는 베이스로 두고
1. (시)도 구분 코드를 사용한 경우
1-2. (시)도 구분 코드로 데이터를 한번에 불러올 때,그 안에 불러올 필요가 없는 관측 지점이 존재하는 경우
2. 관측지점코드(1 개소)를 사용한 경우
에 대응할 수 있도록 코딩해봤음
마지막 네 번째 필수 조건인 page no는 그냥 list 형태인 [1, 2] 로 지정해놨는데, 해당 API의 경우 하루치 데이터를 모두 불러오는 데에 반드시 2페이지가 필요해서 굳이 머리아프게 페이지 수까지 유동적으로 변하게는 하지 않았음
그리고 api key는 난 무조건 decoding 키만 사용할 수 있던데 뭐 구글에서 하는 말 들어보니까 encoding 키를 써야 할 수도 있다 해서 일단은 바꿀 수 있게는 해 뒀음
추가로 넣어놓은 기능들이
3. 수시로 HTTP Error(api 서버 에러)를 띄우면서 끊겨대는 바람에 부득이하게 error 발생 시 대처할 수단을 코드에 넣었음
4. 정상적으로 한 지점의 데이터를 받고 나면 이 지점들의 정보를 리스트로 저장해뒀다가 오류떠서 멈춰버리면 해당 정상 다운로드 완료된 목록을 지점 리스트에서 빼버려서 이미 다운받은걸 다시 다운받는 일이 없게 만들었음
5. 데이터가 없어 api의 'item' 부분이 텅 비어있는 경우가 있는데 이때는 아예 데이터가 없는 경우니 에러로 멈출 바에야 그냥 무시하고 계속 다운받는게 맞다고 봐서 데이터가 없으면 그냥 없는대로 돌아가게 해 뒀음. **다만 가끔 가다 보면 'items' 나 'body' 자체가 없는 경우가 있는데 이건 api 서버에 오류가 생겼을 가능성도 없지 않다 생각해서 배제했음
5. 하다가 어떠한 원인으로 다운로드 진행이 막히면 일단 받아온 지점까지의 데이터만을 저장할건지, 또한 지점 리스트에서 이 오류가 발생한 지점을 빼버릴 것인지를 input을 사용해서 그때그때 선택하게 만들어둠
6. 에러 발생한 지점들은 따로 error_spot 변수에 저장해둬서 나중에 오류 해결되면 spot_code 자리에 error_spot 변수로 바꿔치기만 하면 다시 에러가 발생했던 지점만 다운받을 수 있도록 해 놨음
7. error 뜨는것도 바로바로 정보를 확인할 수 있게 정리해놨음.
8. 마지막으로 이왕 데이터 api 통해서 불러오는거 지점명이 value, 지점코드가 key값인 딕셔너리로 저장해뒀다가 나중에 spot code가 어디 지점인지 한글로 보고 싶으면 바로 볼 수 있게 했음
개인적으론 관측지점코드도 list로 여러개 입력해서 한번에 쫙 받을 수 있게 하고 싶었는데 자꾸 에러떠서 포기함
어째서 이 조건이랑 저 조건이랑 받아오는 데이터 모양새가 다른거임
이건 코딩 실력이 늘어나면 그때 해봐야지






jupyter notebook으로 만듬
코드 텍스트로 작성해둔거
#라이브러리 호출 및 파라미터 정리
import pandas as pd
import requests, xmltodict, json
api_encoding = '---'
api_decoding = '---'
spot_url = 'http://apis.data.go.kr/---'
data_url = 'http://apis.data.go.kr/---'
page_size = '100'
page_no = [1, 2]
obsr_spot_code = ''
df = pd.DataFrame()
date_list, spot_record, error_spot = [],[],[]
spot_dict = {}
#함수 정의
def spot_data(param, del_spot_code, div): #지점 정보 받아오기
global api_key
params = {'serviceKey' : api_key, 'Page_Size': '20', 'Page_No': '1'}
params.update(param)
response = requests.get(spot_url, params=params)
dict_response = xmltodict.parse(response.content)
json_string = json.dumps(dict_response['response']['body']['items'], ensure_ascii=False)
jsonObj = json.loads(json_string)
if div == 'do_se': # 도 구분 코드를 사용하여 불러온 api 구조와 지점 code를 사용하여 불러온 api의 행렬이 달라서 추가함.
df_spot = pd.DataFrame(jsonObj['item'])
else:
df_spot = pd.DataFrame(jsonObj).transpose()
spot_code = df_spot['Obsr_Spot_Code'].values.tolist()
spot_name = df_spot['Obsr_Spot_Nm'].values.tolist()
no = 0
for i in spot_code: #spot_code 와 spot_name dictionary <-- 에러 발생 지점 확인을 위해 spot_code를 키값으로 딕셔너리화
spot_dict.update({i : spot_name[no]})
no = no + 1
if div == 'do_se' and del_spot_code != ['']: #없앨 spot code를 입력받았다면 해당 spot code들을 제거함
spot_code = list(set(spot_code).difference(set(del_spot_code)))
print(f'spot_code: {spot_code}')
return spot_code, spot_dict
def spot_list(do_se_code, obsr_spot_code, del_spot_code): #지점code 지점정보api에서 받아와서 list화
spot_code, spot_name, spot_dict = [], [], {} #초기화
if obsr_spot_code =='':
param = {'Do_Se_Code' : do_se_code}
spot_code, spot_dict = spot_data(param, del_spot_code, 'do_se')
else:
param = {'Obsr_Spot_Code' : obsr_spot_code}
spot_code, spot_dict = spot_data(param, del_spot_code, 'obsr')
return spot_code, spot_dict
def from_to(date_from, date_to): #다운받을 일자 list화
date_list = [] #초기화
for i in pd.date_range(date_from, date_to).format(formatter=lambda x: x.strftime("%Y-%m-%d")):
date_list.append(i)
print(f'date_list: {date_list}')
return date_list
def yes_or_no(question): #에러뜰 시 질문
while "(y/n) 중 하나를 입력":
reply = str(input(question+' (y/n): ')).lower().strip()
if reply[0] == 'y':
return True
if reply[0] == 'n':
return False
def csv_download(spot, date): #csv download
global date_list
df.to_csv(f"f:/Result/result_{spot}_{date_list[0]} ~ {date}.csv",index=False,header=True, encoding="utf-8-sig")
print(f'result_{spot}_{date_list[0]} ~ {date}.csv 출력 완료')
def api_load(spot_code, date_list, page_no, spot_dict): #데이터 다운로드
global df, api_key, spot_record
for spot in spot_code:
for date in date_list:
for page in page_no:
print(f'spot: {spot}, date: {date}, Page_no: {page}')
headers = {'Content-Type': 'application/xml; charset=utf-8-sig'} #HTTP Error 방지용?
params = {'serviceKey' : api_decoding, 'Page_Size': '100', 'Page_No': page, 'date' : date, 'Obsr_Spot_Code' : spot} #API requests
res = requests.get(data_url, headers = headers, params=params)
dict_res = xmltodict.parse(res.content)
try: #KeyError 발생 여부 확인
json_string = json.dumps(dict_res['response']['body']['items'], ensure_ascii=False)
except KeyError: #service error 발생 시 KeyError가 출력됨. spot_code에서 작업된 date는 제거하고 셀 강제종료
error_spot.append(spot)
print(f'!!!KeyError!!!\n Error Information: {dict_res}')
answer = yes_or_no("해당 관측 지점의 관측 데이터를 현재 시점까지만 저장하시겠습니까? y: yes, n: no") # y: 저장. n: 코드 작동 중지.
answer_val = yes_or_no("해당 관측 지점의 지점 code를 list에서 제거하시겠습니까? y:yes, n: no") # y: 리스트에서 제거, n: 리스트에서 유지
if answer == True:
csv_download(spot, date)
if answer_val == True:
spot_record.append(spot) #<-- 정상 작업된 Spot은 변수에 저장
if spot_record != []:
spot_code = list(set(spot_code).difference(set(spot_record)))
print(f'에러 발생 정보: date - {date}, Page_no - {page}, Obsr_Spot_Code - {spot}, Obsr_spot_Nm - {spot_dict[spot]}, 남은 spot: {spot_code}')
return spot_code, error_spot #변경된 spot_code와 에러가 저장된 error_spot 반환
else:
jsonObj = json.loads(json_string)
try: #데이터 없어도 계속 돌아가도록 하기 위해 try 사용
if page == 1:
df1 = pd.DataFrame(jsonObj['item'])
elif page == 2:
df2 = pd.DataFrame(jsonObj['item'])
combined = pd.concat([df1, df2])
df = pd.concat([df, combined])
else:
print('ERROR: pageno list check')
except:
print(f'{date} 데이터 없음.')
if date == date_list[-1] or answer == True:
csv_download(spot, date)
spot_record.append(spot) #<-- 정상 작업된 Spot은 변수에 저장
else:
print(f' 저장 안됨. date_list 정상적으로 생성되었는지 확인: {date}, {spot}, {spot_dict[spot]}')
break
return spot_code, error_spot
# '' 안에 다운받을 데이터셋의 정보 입력 (API 기술명세서 참고) <-- [do_se_code, obsr_spot_code 둘 중 하나는 반드시 입력], [date_from, date_to, api_key는 빠짐없이 입력]
#############################################################################################
do_se_code = '8' # (시)도 구분 코드 #ex) 8 (--- 코드)
obsr_spot_code = '' # 관측지점코드 #ex) --- (--- 코드), 코드 하나만 입력할 수 있음
del_spot_code = ['---', '---'] # (도 구분 코드로 다운받을 경우) 다운받을 필요가 없는 관측지점 코드를 list 형태로 입력
date_from = '2022-08-15' # 관측시작일 ex) 0000-00-00 (부터) <-- 시, 분, 초, 밀리초 입력 불가
date_to = '2022-08-20' # 관측종료일 ex) 0000-00-00 (까지) <-- 시, 분, 초, 밀리초 입력 불가
api_key = api_decoding # api_decoding OR api_incoding 선택
#############################################################################################
## llst 형태: ['---', '---', '---', ...]
spot_code, spot_dict = spot_list(do_se_code, obsr_spot_code, del_spot_code)
date_list = from_to(date_from, date_to)
#데이터 다운로드
spot_code, error_spot = api_load(spot_code, date_list, page_no, spot_dict)