본문으로 건너뛰기

공공데이터 수집에서 막히는 6가지와 해결법: 공공데이터포털·KOSIS·PDF 통계표 (Colab 노트북 포함)

· 약 8분
Datapopcorn CEO / AI automation educator

공공데이터를 받다가 403 오류, 2만 셀 제한, 칸이 밀린 PDF 표에서 막히셨다면 이 글의 6단계로 풀 수 있습니다. 이번 주 공공데이터로 순위 변화 영상을 30편 넘게 만들면서 실제로 걸린 지점과, 직접 돌려서 확인한 해결법만 담았습니다.

TOPIK 국가별 지원자와 한국 유학생 국적, 두 공공데이터에서 같은 '베트남 첫 1위' 장면이 나왔습니다

이 글은 이런 분께 필요합니다​

  • 공공데이터포털(data.go.kr)·국가통계포털(KOSIS)·기관 PDF에서 여러 해치 데이터를 모아 비교하려는 분
  • API 신청은 했는데 SERVICE_KEY_IS_NOT_REGISTERED_ERROR만 보고 계신 분
  • 기관이 PDF로만 올린 통계표를 엑셀로 옮기다 숫자가 엉뚱한 칸에 들어간 경험이 있는 분

준비물은 공공데이터포털 회원가입과 구글 계정(Colab)입니다. 실습 코드는 노트북 하나에 모두 넣었습니다.

Open In Colab

이 방법으로 만든 결과물 예시: 한국어능력시험(TOPIK) 국가별 지원자 순위 2017~2025 (2025년 베트남 83,884명이 중국 70,267명을 처음 앞섬)

Step 1. 인증키는 다시 인코딩하지 않고 그대로 붙입니다​

공정거래위원회 브랜드별 가맹점 API를 활용신청하면 개발계정은 바로 승인됩니다. 그런데 승인 직후에 호출해도 403이 나는 경우가 많습니다. 원인은 대부분 키를 두 번 인코딩한 것입니다.

마이페이지의 **일반 인증키(Encoding)**는 이미 %2B, %3D처럼 인코딩된 문자열입니다. 이것을 urlencode()나 requests의 params=에 넣으면 %가 다시 %25로 바뀌어 전혀 다른 키가 됩니다.

# (X) 403 SERVICE_KEY_IS_NOT_REGISTERED_ERROR
url = BASE + '?' + urllib.parse.urlencode({'serviceKey': SERVICE_KEY, 'yr': 2025, ...})

# (O) 200 NORMAL SERVICE
url = f'{BASE}?serviceKey={SERVICE_KEY}&' + urllib.parse.urlencode({'yr': 2025, ...})

노트북에서 두 줄을 나란히 돌리면 첫 줄은 403, 둘째 줄은 NORMAL SERVICE 전체 11724 건이 나옵니다. "승인 반영을 기다려야 하나" 하고 몇 시간을 보내기 전에 이것부터 확인하시면 됩니다.

받은 뒤에도 두 가지를 챙깁니다.

  • 연도 의미: 이 API의 yr=2025는 2025년이 아니라 2024년 말 기준 가맹점 수입니다(직전 사업연도 말). 정보공개서의 이디야 2023년 말 2,805곳이 yr=2024 값과 같았습니다.
  • 중복 행·누락: 같은 해에 같은 브랜드가 두 줄로 나오는 경우가 있어 최댓값을 씁니다. 반대로 2022년 말 메가커피는 0으로 들어가 있는 등 빈 값도 있습니다. 앞뒤 해 평균으로 채웠다면 결과물에 반드시 밝힙니다.

노트북 실행 결과(2024년 말 치킨 가맹점): BBQ 2,316곳, bhc 2,228곳, 교촌치킨 1,361곳입니다.

Step 2. 파일데이터의 과거 버전은 '주기성 과거 데이터'에서 받습니다​

공공데이터포털 파일데이터 페이지는 기본으로 최신 파일 하나만 보여 줍니다. 여러 해치를 이어 붙이려면 페이지 아래쪽 주기성 과거 데이터 표를 봐야 합니다.

  1. 데이터 상세 페이지(예: 법무부_체류외국인 국적 및 체류자격별 현황)에서 주기성 과거 데이터(13) 탭을 엽니다.
  2. 연도별 항목을 하나씩 눌러 다운로드합니다. 이 데이터는 2009~2024년 중 13개 연도 파일이 있었습니다.
  3. 받은 파일의 머리글을 먼저 확인합니다. 해마다 열 이름이 바뀝니다(유학(D-2) → 유학D-2 → D2(유학)). 성별 행이 따로 있거나, 세부 비자(D-2-1~D-2-8)만 있고 합계 열이 없는 해도 있어 연도별로 맞춰야 합니다.

막히는 경우가 두 가지 있습니다.

  • 파일 없이 기관 URL만 걸린 항목: TOPIK 지원자 현황의 과거 항목은 첨부 파일 대신 topik.go.kr 주소만 남아 있었습니다. 그 주소의 파일 이름 규칙(2017~2021 TOPIK Download.pdf)을 보고, 현재 사이트에서 링크가 사라진 2017~2021년 파일을 찾아 기간을 5년에서 9년으로 늘렸습니다.
  • 비어 있는 해: 체류외국인 파일은 2013년, 2020·2021년이 없었습니다. 이런 해는 Step 3의 KOSIS로 채우거나, 끝까지 없으면 보간하고 표시합니다.

Step 3. KOSIS는 '조회설정'으로 2만 셀 안에 맞춥니다​

KOSIS 통계표는 처음 열면 최신 1년, 대분류만 보여 줍니다. 여러 해를 한 번에 보려고 시점을 전부 고르면 "2만셀이 넘는 자료는 엑셀파일로 제공하고 있습니다" 창이 뜨며 막힙니다.

출입국자및체류외국인통계 > 유학생관련 현황(2010~2025)을 예로 들면 이렇게 풉니다.

  1. 조회설정에서 항목을 필요한 것만 남깁니다. 체류자격 15개 중 합계, 유학(D-2), 일반연수(D-4), 대학부설어학원연수(D-4-1) 4개만 체크했습니다.
  2. 성별은 계만 남기고 남성·여성을 뺍니다. 이것만으로 셀 수가 3분의 1로 줄어듭니다.
  3. 국적은 처음에 아시아주계 같은 대륙 합계만 선택돼 있습니다. 대륙 옆 펼침 버튼을 눌러 하위 국가를 연 뒤 전체를 체크해야 나라별 행이 나옵니다(이 표는 펼치면 227개).
  4. 시점에서 2010~2025를 모두 고르고 조회합니다. 위 설정으로 16개 연도가 한 화면에 나왔습니다.

셀 수는 항목 수 × 분류 수 × 시점 수입니다. 한 번에 안 되면 시점을 반으로 나눠 두 번 받는 것도 방법입니다.

Step 4. PDF 통계표는 좌표(bbox)로 읽습니다​

기관이 PDF로만 올린 표가 가장 까다롭습니다. TOPIK 시행 현황 PDF는 국가별로 회차마다 TOPIK I 지원자·응시자·합격자, TOPIK II 지원자·응시자·합격자 6칸이 이어집니다.

함정: pdftotext -layout 결과를 공백으로 자르면 칸이 밀립니다. 2023년까지는 빈칸에 -가 찍혀 있어 괜찮았지만, 2024년부터는 빈칸이 그냥 공백입니다. 노트북에서 2024년 중국 행을 공백으로 자르면 43칸이어야 할 숫자가 31개만 나오고, 93회 값(9,517)이 첫 회차(92회) 자리로 당겨집니다. 글자 폭으로 열 위치를 맞추는 방법도 시도했지만 연간 합계와 맞지 않았습니다.

해결: pdftotext -bbox-layout으로 단어마다 좌표를 받습니다.

  1. -bbox-layout은 단어마다 xMin·yMin·xMax·yMax를 줍니다. y 중심이 같은 단어끼리 묶으면 한 행이 됩니다.
  2. 머리글 행에서 지원자/응시자/합격자 단어의 x 중심을 열 위치로 저장합니다.
  3. 각 숫자의 x 중심을 가장 가까운 열에 배정합니다(25pt 이내). 빈칸은 숫자가 없으니 자연히 비어 있습니다.
  4. 회차 k의 지원자는 열[k×6] + 열[k×6+3](TOPIK I + II)입니다.

Colab에서는 apt-get update를 먼저 해야 poppler-utils(pdftotext)가 설치됩니다. 노트북 첫 PDF 셀에 넣어 두었습니다.

참고로 topik.go.kr 파일은 스크립트로 바로 받으면 PDF 대신 1.6KB짜리 안내 HTML이 내려옵니다. 브라우저에서 사이트 메인을 연 뒤 같은 브라우저로 파일 주소를 열어 받고, Colab에 업로드합니다.

Step 5. 파싱 결과와 출처 이어붙이기는 같은 값끼리 대조합니다​

숫자를 뽑았다고 끝이 아닙니다. 영상이나 글로 내보내기 전에 아래 대조를 통과해야 씁니다.

PDF 안에서 대조: 회차별로 뽑은 값을 더해 PDF에 인쇄된 연간 합계 열과 비교합니다. 노트북 결과는 불일치 0입니다. 글자 폭으로 열을 맞췄던 방식은 같은 대조에서 9년치 주요 국가 값 52곳이 어긋났습니다.

파일끼리 대조: 2017~2021년 파일과 2021~2025년 파일에 모두 실린 2021년 값을 비교합니다. 중국 76,657, 일본 40,957, 베트남 21,192, 몽골 11,622로 정확히 같았습니다.

출처를 이어 붙일 때: 한국 유학생 국적 분석은 세 출처를 이었습니다(2009~2019 공공데이터포털 연도 파일, 2020~2025 KOSIS, 2026년 1~8월 공공데이터포털 월별 파일). 연도 파일과 KOSIS는 겹치는 2022~2024년 값이 정확히 같았습니다. 그런데 월별 파일의 12월 값과 KOSIS를 대조하자 베트남은 같은데 중국만 조금씩 달랐습니다(2025년 KOSIS 72,869 / 월별 파일 72,719). 월별 파일은 한국계 중국인(150명)을 별도 행으로 세고 있었고, 합치자 값이 맞았습니다. 분류 차이를 맞추지 않고 이으면 한 해 사이에 가짜 증감이 생깁니다.

이렇게 맞춘 결과, 학위과정(D-2) 유학생은 2026년 7월 말 베트남 68,792명, 중국 68,728명으로 64명 차이로 처음 순위가 바뀌었고, 8월 말에는 중국 83,666명, 베트남 76,570명으로 다시 뒤집혔습니다. 원자료가 없는 2013·2018년은 앞뒤 해 평균으로 채우고 화면 하단과 게시글에 적었습니다. 완성된 영상은 한국 유학생 국적 순위 2009~2026에서 볼 수 있습니다.

Step 6. 호출 한도와 요청 간격을 지킵니다​

  • UN Comtrade 무료 미리보기(계정 없음)는 한 번에 500건, 기간·품목 하나씩이라 연도마다 호출이 필요합니다. 연달아 부르자 Out of call volume quota로 1시간 가까이 막혔습니다. 연도당 1번, 3초 간격으로 부른 31번은 모두 200이었습니다. 많이 받아야 하면 무료 회원가입 후 키를 받는 편이 낫습니다(하루 500번, 호출당 10만 건).
  • 위키백과 API는 0.7초 간격으로 연속 요청하자 too many requests가 나왔습니다. 3초 간격으로 바꾸고 나서는 막히지 않았습니다. 받은 응답은 파일로 저장해 두고, 다시 부를 때는 저장본부터 씁니다.
  • 공공데이터포털 API는 1,000건 페이지를 12번 넘겨도 문제가 없었지만, 노트북에는 페이지 사이 0.5초 간격을 넣었습니다.

한도에 막혔을 때 실패한 요청을 바로 다시 보내면 한도가 더 늦게 풀립니다. 실패한 연도만 기록해 두고 나중에 이어 받습니다.

자주 만나는 오류와 해결​

증상원인해결
SERVICE_KEY_IS_NOT_REGISTERED_ERROR (403)Encoding 키를 한 번 더 인코딩키를 URL 문자열에 그대로 붙이기 (Step 1)
가맹점 수가 한 해씩 어긋남API yr는 직전 연도 말 기준yr - 1년 말로 표기
특정 브랜드가 한 해만 0원자료 누락앞뒤 해 평균 보간 후 명시
최신 파일만 받아짐과거 버전은 별도 탭'주기성 과거 데이터'에서 연도별 다운로드 (Step 2)
"2만셀이 넘는 자료는 엑셀파일로…"항목×분류×시점 초과성별 '계'만, 필요한 항목만, 시점 분할 (Step 3)
KOSIS에 대륙 합계만 나옴하위 레벨 미선택대륙 펼친 뒤 하위 국가 체크
PDF 숫자가 앞 칸으로 밀림빈칸이 공백인 표를 공백으로 분리-bbox-layout 좌표 파싱 (Step 4)
기관 PDF가 HTML로 받아짐사이트 세션 필요브라우저에서 받아 업로드
이어 붙인 해에 갑자기 증감출처마다 분류 기준 다름겹치는 해 값 대조, 분류 통일 (Step 5)
Out of call volume quota무료 호출 한도 소진3초 간격, 실패분만 나중에 재시도 (Step 6)

공공데이터 API를 정해진 간격으로 계속 쌓는 자동화가 필요하다면 공공데이터 API를 10분마다 구글시트에 쌓기를 함께 보시면 됩니다. 이렇게 모은 데이터를 순위 변화 영상으로 만드는 과정은 공공데이터로 bar-chart-race 쇼츠 만들기에 정리했습니다.