[마이 프로젝트] 공공데이터 API 호출 제한: 1,000건씩 나눠 받기

공공데이터 API에서 목록을 1,000건씩 나눠 받는 구조를 나타낸 대표 이미지
한 번에 다 받으려다 막혔습니다.

한 줄 요약: 공공데이터포털 API로 목록 전체를 한 번에 받으려다 응답이 오지 않았고, 1,000건씩 끊어 여러 번 받도록 바꿔서 해결했습니다. 첫 응답에서 전체 개수를 읽어 페이지 수를 계산하는 순서와, 한 페이지가 실패해도 목록이 비지 않게 하는 방법을 정리했습니다.

한 번에 다 달라고 하면 안 된다

지원금 정보를 모아 보여주는 페이지를 만드는 중이었습니다. 수천 건을 한 번에 요청했더니 응답이 오지 않았습니다. 공공데이터 API에는 한 번에 받을 수 있는 개수 상한이 있습니다. 제가 쓴 지원금 목록 API는 perPage로 개수를 정하는데, 여기에 큰 숫자를 넣으면 그냥 실패합니다.

오류 메시지가 친절하지 않아서 처음엔 키가 잘못된 줄 알았습니다. 응답이 아예 오지 않으면 인증 문제부터 의심하게 되는데, 이번에는 요청한 개수가 원인이었습니다. 키를 다시 발급받기 전에 개수를 줄여 한 번 더 불러 보면 원인을 빨리 가를 수 있습니다.

해결은 단순합니다. 1,000건씩 끊어서 여러 번 부르는 것입니다. 문제는 "몇 번 불러야 하는가"를 미리 모른다는 점입니다. 전체 개수가 날마다 달라질 수 있어서, 페이지 수를 코드에 고정해 두면 데이터가 늘어난 날부터 뒷부분이 조용히 잘립니다. 다음 항목에서 이 문제를 푸는 순서를 설명합니다.

상한 값은 API마다 다릅니다. 이 글의 1,000건은 제가 쓴 목록 API 기준이므로, 여러분이 쓰는 API의 활용 안내 페이지에서 개수 상한을 먼저 읽어 보세요.

첫 페이지에서 전체 개수를 받아 나머지 페이지 수를 계산하는 순서
첫 응답이 전체 개수를 알려줍니다.

첫 응답이 전체 개수를 알려준다

대부분의 공공데이터 API는 응답 안에 전체 개수를 같이 담아 줍니다. 그래서 순서가 이렇게 됩니다. 1페이지를 먼저 받아 전체 개수를 읽고, 그걸 1,000으로 나눠 올림해서 전체 페이지 수를 구한 다음, 2페이지부터 끝까지 돕니다.

var first = fetchPage_(1);
var rows  = first.data || first.items || [];
var total = Number(first.totalCount || first.total || rows.length);
var pages = Math.max(1, Math.ceil(total / PER_PAGE));
for (var p = 2; p <= pages; p++) {
  try {
    var next = fetchPage_(p);
    rows = rows.concat(next.data || next.items || []);
  } catch (e) {
    // 한 페이지가 실패해도 나머지는 살린다
  }
}

필드 이름을 data와 items, totalCount와 total 두 가지로 받는 이유가 있습니다. 공공데이터 API마다 응답 형태가 조금씩 다릅니다. 문서에 적힌 것과 실제가 다른 경우도 봤습니다. 그래서 둘 중 있는 쪽을 쓰고, 둘 다 없으면 받은 행 수를 전체 개수로 삼도록 했습니다.

공공데이터포털은 파일 데이터와 오픈 API를 함께 제공하며, 오픈 API는 포털에서 활용신청을 해야 쓸 수 있습니다. 신청 후 받은 서비스키로 요청을 보내는 구조라서, 키 상태와 개수 상한을 구분해서 보는 습관이 필요합니다.

페이지 수는 올림으로 계산한다

전체 개수를 페이지당 개수로 나눌 때 나머지가 남으면 마지막 페이지가 하나 더 필요합니다. 그래서 나눗셈 결과를 Math.ceil로 올림합니다. 내림이나 반올림을 쓰면 마지막 몇백 건이 빠집니다. 아래는 계산 과정을 보여 드리기 위한 가상의 예시입니다. 실제 데이터의 개수가 아닙니다.

가상 전체 개수 (예시입니다)페이지당 개수계산필요한 페이지
3,450건1,000건3,450 ÷ 1,000 = 3.45 → 올림4페이지 (마지막은 450건)
2,000건1,000건2,000 ÷ 1,000 = 2.02페이지
800건1,000건0.8 → 올림1페이지 (첫 응답으로 끝)
0건1,000건0 → 올림하면 0최소 1페이지로 보정

코드의 Math.max(1, ...)가 마지막 줄을 처리합니다. 전체 개수가 0이어도 페이지 수가 0이 되면 반복문 앞뒤가 어긋날 수 있어서, 최소값을 1로 묶어 둔 것입니다. 첫 페이지는 이미 받은 상태이므로 반복문은 2페이지부터 시작합니다. 같은 페이지를 두 번 받지 않게 하는 장치이기도 합니다.

이 표를 보면 페이지 수를 미리 정해 두는 방식이 왜 위험한지도 보입니다. 3,450건이 어느 날 4,100건이 되면 필요한 페이지가 4개에서 5개로 늘어나는데, 고정값은 그 변화를 모릅니다.

한 페이지가 실패해도 멈추지 않게

중요한 부분은 try 안에 넣은 것입니다. 10페이지 중 7페이지 하나가 실패했다고 전부 버리면 안 됩니다. 실패한 페이지만 건너뛰고 나머지는 화면에 보여주는 편이 낫습니다.

처음엔 이 처리가 없어서, 어쩌다 한 번 실패할 때마다 목록 전체가 비었습니다. 원인을 찾는 데 시간이 걸렸습니다. 잘 될 때가 더 많아서 재현이 안 됐기 때문입니다. 가끔만 생기는 오류는 "항상 되는데 가끔 안 되는" 모양이라 코드 문제로 보이지 않고 운이 나쁜 것으로 보입니다.

공공데이터 API 페이징에서 확인한 세 가지를 정리한 설명 이미지
이 셋을 확인하면 대부분 해결됩니다.

Google Apps Script의 UrlFetchApp.fetch를 쓰는 경우라면 알아 둘 설정이 하나 있습니다. 공식 문서에 따르면 muteHttpExceptions의 기본값은 false여서, 실패 상태 코드를 받으면 예외가 발생합니다. 이 값을 true로 두면 예외 대신 응답 객체가 돌아오므로, 상태 코드와 오류 내용을 직접 읽어 페이지별로 처리할 수 있습니다. 예외를 잡든 응답 코드를 읽든, 핵심은 실패 하나가 전체를 무너뜨리지 않게 막는 것입니다.

키가 없을 때의 화면도 만들어 둔다

API 키가 비어 있거나 아직 승인이 안 났을 때 화면이 통째로 비는 것이 제일 곤란합니다. 그래서 미리 넣어 둔 예시 데이터로 화면을 그리도록 해 뒀습니다. 개발 중에 키 없이 화면을 만질 수 있어서 편했습니다.

공공데이터포털의 오픈 API는 활용신청을 거쳐야 쓸 수 있으므로, 신청 직후에는 키가 있어도 아직 호출이 통하지 않는 시간이 있을 수 있습니다. 이때 화면 작업이 멈추면 일정이 밀립니다. 예시 데이터는 키가 없을 때뿐 아니라 승인을 기다리는 동안과 호출이 실패했을 때의 대체 화면으로도 쓸 수 있습니다.

다만 예시 데이터로 그린 화면은 진짜 데이터가 아니라는 점을 개발자 자신이 잊지 않아야 합니다. 키를 연결한 뒤에는 예시 값이 화면에 남아 있지 않은지 한 번 더 봐 주세요.

실무에서는 예시 데이터를 별도 변수로 분리해 두면 편합니다. 키가 비어 있을 때만 그 변수를 쓰고, 키가 있으면 호출 결과를 쓰도록 분기 하나만 두면 됩니다. 분기가 한 곳에 모여 있으면 나중에 예시 데이터를 지울 때도 한 군데만 고치면 됩니다. 화면 디자인을 먼저 다듬을 수 있다는 점도 장점입니다.

호출 횟수 한도는 미리 계산해 둔다

1,000건씩 나누면 호출 횟수가 늘어납니다. 호출 횟수는 곧 한도와 시간의 문제로 이어집니다. 앞에서 말한 Google Apps Script를 호출 환경으로 쓴다면 공식 할당량 문서의 숫자를 알아 둘 만합니다.

항목일반 소비자 계정 (gmail.com)Google Workspace 계정
URL Fetch 호출하루 20,000회하루 100,000회
스크립트 실행 시간실행당 6분실행당 6분
URL Fetch 응답 크기호출당 50MB호출당 50MB

예를 들어 한 번 불러올 때 4페이지가 필요하다면 하루 한도에 비해 호출이 아주 적어 보이지만, 방문자가 페이지를 열 때마다 전체를 다시 받으면 이야기가 달라집니다. 호출 횟수는 방문자 수와 페이지 수의 곱으로 늘기 때문입니다. 여기서 아래의 "남은 것" 문제가 다시 나옵니다.

또 하나 기억할 숫자가 있습니다. fetch의 timeoutSeconds 기본값은 360초이고, 한 번의 스크립트 실행은 6분을 넘기지 못합니다. 페이지를 하나씩 순서대로 받으면 페이지 수가 늘수록 실행 시간이 늘어나므로, 수천 건 단위에서도 전체 시간을 어림해 두는 편이 안전합니다. 여러 요청을 한꺼번에 보내는 fetchAll도 있지만, 공공데이터 API 쪽 호출 제한이 있는지 먼저 살핀 뒤에 쓰세요.

남은 것과 증상별 점검표

지금은 방문자가 페이지를 열 때마다 전체를 다시 받습니다. 수천 건이면 느립니다. 다음 글에서 다룰 중간 서버를 두면서 결과를 저장해 두는 방식으로 바꿨고, 그때 이 문제가 같이 해결됐습니다. 그리고 실패한 페이지를 다시 시도하지는 않습니다. 지금은 그냥 건너뜁니다. 빠진 항목이 눈에 띌 정도가 되면 재시도를 넣을 생각입니다.

아래 표는 이 글에서 다룬 증상을 한곳에 모은 점검표입니다. 같은 증상을 만나면 위에서부터 차례로 확인해 보세요.

증상가능한 원인해결 방법
큰 개수를 넣으면 응답이 오지 않는다한 번에 받을 수 있는 개수 상한 초과1,000건씩 끊어 여러 번 부릅니다.
키는 맞는데 오류가 난다인증이 아니라 요청 개수가 원인일 수 있음개수를 줄여 다시 불러 원인을 가릅니다.
가끔 목록 전체가 빈다한 페이지 실패가 전체를 중단시킴페이지별로 예외를 처리해 실패한 곳만 건너뜁니다.
뒷부분 데이터가 잘린다페이지 수를 고정했거나 내림으로 계산함첫 응답의 전체 개수에서 올림으로 계산합니다.
키가 없거나 승인 전에 화면이 빈다대체 화면이 없음예시 데이터로 화면을 그립니다.
페이지를 열 때마다 느리다방문마다 전체를 다시 받음중간 서버에 결과를 저장해 둡니다.

확인 자료

함께 읽기