짜투리

[Oepn API] 공공데이터포탈- 나를 기만하는 SERVICE KEY IS NOT REGISTERED ERROR 해결 수기

소곡이 2024. 7. 6. 22:56
728x90

 

 

 

 

 


 

 저 에러는 공공데이터포탈에서 등록되지 않은 서비스 키를 사용할 때 발생합니다. 하지만 재밌는 사실이 있습니다. 1page당 1000개의 데이터를 요청하고, page가 200까지 있다고 치면, 4, 50, 100, 199... 이런 특정 페이지에서 에러가 생기는 것입니다. 

 

 

 

 이와 같은 상황에서 해결법을 구하다 지친 저는 문의를 남겼지만. 돌아오는 답변은

 

"저희는 되는데용? 한 번 더 해보고 문의 남겨주세용ㅎㅎ"

 

 

 

 뭐.... 아무튼 슬프지만 어떻게든 해내야죠. 그러다 연구실 선배가 해결법을 찾아주셨답니다..후후.... 이 글은 저와 같은 고난을 겪는 분들을 위한 글이자, 구원과도 같은 선배에게 바치는 헌정글입니다. 

 


 

일단 원래 제 코드입니다.

 

import requests
import pandas as pd
from tqdm import tqdm
from bs4 import BeautifulSoup
import os
import xmltodict
import json
# response 반환
def getResponse(url, params):
    try:
        response = requests.get(url, params=params)
    except Exception as e:
        print(e)
        return
    return response
    
# 전체 데이터개수 반환
def getLastPageNum(response):
    soup =BeautifulSoup(response.text, 'xml')
    return int(soup.find("totalCount").text)
    
def createData(response):
    contents = response.json()
    return pd.DataFrame(contents['response']['body']['items'])
    
def getData(url, totalPageNum):
    df_list = []
    
    with tqdm(total=totalPageNum, desc=f"진행 상황") as pbar:
        for i in range(totalPageNum):
            params ={'serviceKey' : decodingKey, 'pageNo' : i+1, 'numOfRows' : '1000', 'type' : 'json'}
            df_list.append(createData(getResponse(url, params=params))
            pbar.update(1)  # 진행 상황 업데이트
            
    return df_list

 

encodingKey = ""
decodingKey = ""

urlDict = {"15025454" : "http://apis.data.go.kr/1543061/animalShelterSrvc/shelterInfo",
           "15025449" : "https://apis.data.go.kr/1741000/TsunamiShelter4/getTsunamiShelter4List",
           "15028196" : "https://apis.data.go.kr/1613000/btiData/getBrdgList",
           "15072622" : "https://apis.data.go.kr/1741000/EarthquakeIndoors3/getEarthquakeIndoors3List"}

 

for url in urlDict1:
    print(f"{url} 데이터 수집 중...")
    params ={'serviceKey' : decodingKey, 'pageNo' : 1, 'numOfRows' : '1000', 'type' : 'xml'}
    
    # 데이터 전체 개수 카운팅
    totalDataLength = getLastPageNum(getResponse(urlDict1[url], params))
    totalPageNum = int(totalDataLength/1000)+1

    df = pd.concat(getData(urlDict[url], totalPageNum), ignore_index=True)
    df.to_csv(f"data/{url}.csv")

 

(전체 카운팅 개수를 셀 때 type을 xml로 한 이유는 json으로 호출했을 때는 totalCount가 나오지 않기 때문임. 근데 데이터 별로 전체 개수 정보를 담은 변수? 명이 totalCount가 아니어서 에러날 때도 있음. 이 때는 xml로먼저 변수명 확인해야 함)

 

 

 여기서 위에서 말한 에러가 나길래, 해당 page만 모아서, params의 serviceKey를 encodingKey로 바꾼뒤에 다시 돌렸지만 단 한 개의 page의 데이터도 반환되지 않았습니다.  

 

 하지만 웹에서 직접 링크 찍고 들어가면 데이터가 나오는 아이러니한 상황.

 

 그래서 어떻게 고쳤냐면요....

 

웹에서 반환된 데이터를 response.json()으로 받는 부분을

 

# response 반환
def getResponse(url, params):
    try:
        response = requests.get(url, params=params)
    except Exception as e:
        print(e)
        return
    return response
    
def createData(response):
    contents = response.json()
    return pd.DataFrame(contents['response']['body']['items'])

 

 

요케! xmltodict.parse() 형태로 바꿨습니다!

 

res = requests.get(URL, params=params, timeout=30)
res_dict = xmltodict.parse(res.text)

 

 

 res.text(문자열)을 xmltodict 모듈을 이용해서 python dictionary로 변환하는거죠. (당연한 말임)

 

 원인을 정확하게 말하기는 어려우나. 뭔가 본문을 json() 형식으로 가져오는 과정에서 문제가 있는 것 같아요. 그래서 본문을 통째로 문자열로 가져온 다음에 xml 형식으로 파싱하고 딕셔너리로 저장하는 방법이 통했던 거라고 생각... 

 

 암튼 코드를 고치면 이런 식입니다. 

 

# get Data
df_list = []
error_list = list()
URL = ""

for i in tqdm(range(totalPageNum), desc="진행 상황"):
    params ={'serviceKey' : decodingKey, 'pageNo' : i+1, 'numOfRows' : '1000', 'type' : 'json'}
    try:
        res = requests.get(URL, params=params, timeout=30)
        res_dict = xmltodict.parse(res.text)
    except Exception as e:
        print(e)
        error_list.append(i+1)
        continue
    
    try:
        df_list.append(pd.DataFrame(res_dict['response']['body']['items']))
    except Exception as e:
        print(i+1)
        continue

 

 


 

 감사합니다!