Ile osób mieszka w powiecie, do którego planujesz ekspansję? Jaka jest stopa bezrobocia w regionie Twojego klienta? Ile firm zarejestrowano w danym województwie w ostatnim roku? Te pytania padają w każdej analizie rynkowej – i za każdym razem ktoś otwiera stronę GUS, przeklikuje się przez tabele, kopiuje dane do Excela i ręcznie formatuje je pod prezentację.

Tymczasem GUS udostępnia kompletne API – Bank Danych Lokalnych (BDL) – z ponad 40 000 zmiennych statystycznych. Bez klucza, bez rejestracji, bez opłat. Wystarczy jedno zapytanie HTTP, żeby pobrać ludność wszystkich powiatów Polski za ostatnich 10 lat. W tym artykule pokazuję, jak podłączyć to API do Google Sheets – jedną formułą dla prostych przypadków i pełnym skryptem Apps Script dla automatyzacji.

Czym jest API BDL i jak działa

Bank Danych Lokalnych (BDL) to największa baza statystyk publicznych w Polsce. Zawiera dane demograficzne, gospodarcze, społeczne i środowiskowe – od poziomu gmin, przez powiaty i województwa, aż po cały kraj. Wszystko, co publikuje Główny Urząd Statystyczny, trafia do BDL.

API BDL to interfejs REST, który pozwala odpytywać tę bazę programowo. Zamiast klikać po stronie stat.gov.pl i eksportować CSV – wysyłasz zapytanie HTTP GET i dostajesz dane w formacie JSON.

Podstawowy URL API:

https://bdl.stat.gov.pl/api/v1/

Główne endpointy, które będziemy używać:

  • /data/by-variable/{id} – dane dla konkretnej zmiennej (np. ludność)
  • /subjects – lista tematów (demografia, rynek pracy, gospodarka...)
  • /variables – lista zmiennych w ramach tematu
  • /units – jednostki terytorialne (gminy, powiaty, województwa)

Kluczowe parametry zapytania:

  • unit-level – poziom terytorialny: 2 = województwa, 5 = powiaty, 6 = gminy
  • year – rok lub zakres lat (np. 2020&year=2021&year=2022)
  • unit-parent-id – filtrowanie po jednostce nadrzędnej (np. tylko powiaty Mazowsza)
  • page-size – liczba wyników na stronę (max 100)
  • format – domyślnie JSON; CSV niedostępne w BDL API
💡 Wskazówka: API BDL nie wymaga klucza API ani rejestracji – dla typowego użycia anonimowy dostęp w zupełności wystarczy. Opcjonalnie: jeśli Twój projekt robi setki zapytań (np. dane dla wszystkich 2 500 gmin), możesz bezpłatnie pobrać osobisty klucz API przez formularz na api.stat.gov.pl – klucz generowany jest automatycznie i przesyłany e-mailem. Daje on 5× wyższe limity. Używaj go jako nagłówka HTTP: X-ClientId: [twój-klucz].

Jak znaleźć ID zmiennej – krok po kroku

Najtrudniejszy element pracy z API GUS to znalezienie właściwego ID zmiennej. Baza ma ich ponad 40 000, a nazwy bywają kryptyczne ("ludność wg płci i ekonomicznych grup wieku według faktycznego miejsca zamieszkania"). Oto trzy sposoby na znalezienie tego, czego szukasz:

Sposób 1 – przeglądarka BDL (najłatwiejszy):

  1. Wejdź na bdl.stat.gov.pl.
  2. W wyszukiwarce wpisz temat (np. "ludność", "bezrobocie", "wynagrodzenia").
  3. Kliknij wybraną zmienną – w pasku adresu pojawi się URL z ID, np. /dane/podgrupy/3537.
  4. Żeby dostać ID zmiennej (nie podgrupy), kliknij "Wymiary" – zobaczysz listę zmiennych z ich numerami.

Sposób 2 – API subjects (programowy):

// Lista tematów głównych
https://bdl.stat.gov.pl/api/v1/subjects?lang=pl&format=json

// Podtematy w ramach "Ludność" (ID: K3)
https://bdl.stat.gov.pl/api/v1/subjects?parent-id=K3&lang=pl

// Zmienne w podgrupie
https://bdl.stat.gov.pl/api/v1/variables?subject-id=P2425&lang=pl

Sposób 3 – dokumentacja Swagger:

GUS udostępnia interaktywną dokumentację API pod adresem bdl.stat.gov.pl/api/v1 – możesz tam testować zapytania bezpośrednio w przeglądarce.

Najczęściej używane zmienne (te, po które sięgam w co drugim projekcie):

Wskaźnik ID zmiennej Poziom
Ludność ogółem 72305 gmina+
Stopa bezrobocia rejestrowanego 60559 powiat+
Przeciętne wynagrodzenie brutto 64428 województwo
Podmioty gospodarki narodowej (REGON) 10110 gmina+
Powierzchnia w km² 3510 gmina+

Apps Script: pobieranie danych z API GUS

Podstawowa funkcja, która odpytuje API BDL i zwraca dane w postaci tablicy gotowej do wklejenia do arkusza. Skopiuj ją do edytora Apps Script (Rozszerzenia > Apps Script):

/**
 * Pobiera dane z API BDL (GUS) dla wybranej zmiennej.
 * @param {number} idZmiennej – ID zmiennej z BDL (np. 72305 = ludność)
 * @param {number} poziom – poziom terytorialny: 2=woj., 5=powiat, 6=gmina
 * @param {number[]} lata – tablica lat, np. [2022, 2023, 2024]
 * @returns {Array[]} tablica wierszy [nazwa, rok, wartość]
 */
function pobierzDaneGUS(idZmiennej, poziom, lata) {
  var bazaUrl = 'https://bdl.stat.gov.pl/api/v1/data/by-variable/'
                + idZmiennej + '?unit-level=' + poziom
                + '&format=json&page-size=100';

  // Dodaj parametry lat
  for (var i = 0; i < lata.length; i++) {
    bazaUrl += '&year=' + lata[i];
  }

  var wszystkieDane = [];
  var strona = 0;
  var calkowitaStron = 1;

  while (strona < calkowitaStron) {
    var url = bazaUrl + '&page=' + strona;
    var odpowiedz = UrlFetchApp.fetch(url, {muteHttpExceptions: true});

    if (odpowiedz.getResponseCode() !== 200) {
      Logger.log('Błąd API GUS: kod ' + odpowiedz.getResponseCode());
      break;
    }

    var json = JSON.parse(odpowiedz.getContentText());
    calkowitaStron = json.totalPages || 1;

    var wyniki = json.results || [];
    for (var j = 0; j < wyniki.length; j++) {
      var jednostka = wyniki[j];
      var nazwa = jednostka.name;
      var wartosci = jednostka.values || [];

      for (var k = 0; k < wartosci.length; k++) {
        wszystkieDane.push([
          nazwa,
          wartosci[k].year,
          wartosci[k].val !== null ? wartosci[k].val : 'brak'
        ]);
      }
    }

    strona++;
    Utilities.sleep(300); // limit 100 req/min
  }

  return wszystkieDane;
}

Jak to działa:

Funkcja buduje URL zapytania na podstawie ID zmiennej, poziomu terytorialnego i listy lat. API BDL paginuje wyniki (max 100 na stronę), więc skrypt iteruje po stronach w pętli while – automatycznie pobiera wszystkie strony, nie tylko pierwszą. Każdy wynik zawiera nazwę jednostki terytorialnej i tablicę wartości rocznych.

Pauza Utilities.sleep(300) między stronami zapobiega przekroczeniu limitu 100 zapytań na minutę.

Przykład 1: ludność wg powiatów

Najbardziej typowe zapytanie – ile osób mieszka w każdym powiecie Polski. Przydatne przy planowaniu dystrybucji, analizie rynku lub raportach dla klientów B2B operujących regionalnie.

/**
 * Pobiera ludność wg powiatów i zapisuje do arkusza "Ludność".
 */
function pobierzLudnosc() {
  var dane = pobierzDaneGUS(72305, 5, [2022, 2023, 2024]);

  var arkusz = SpreadsheetApp.getActiveSpreadsheet()
                             .getSheetByName('Ludność');
  if (!arkusz) {
    arkusz = SpreadsheetApp.getActiveSpreadsheet()
                           .insertSheet('Ludność');
  }

  // Nagłówki
  arkusz.clearContents();
  arkusz.appendRow(['Powiat', 'Rok', 'Ludność']);

  // Wstaw dane jednym wywołaniem (szybciej niż appendRow w pętli)
  if (dane.length > 0) {
    arkusz.getRange(2, 1, dane.length, 3).setValues(dane);
  }

  Logger.log('Pobrano ' + dane.length + ' wierszy danych o ludności.');
}

Po uruchomieniu arkusz "Ludność" wypełni się danymi – ok. 380 powiatów × 3 lata = ~1140 wierszy. Zwróć uwagę na użycie setValues() zamiast appendRow() w pętli – jest wielokrotnie szybsze, bo wykonuje jeden zapis do arkusza zamiast tysiąca osobnych.

Przykład 2: stopa bezrobocia wg województw

Stopa bezrobocia rejestrowanego to jeden z najczęściej cytowanych wskaźników makroekonomicznych. GUS publikuje ją co miesiąc z opóźnieniem ok. 6 tygodni.

/**
 * Pobiera stopę bezrobocia wg województw (ostatnie 5 lat).
 */
function pobierzBezrobocie() {
  var lata = [];
  var aktualnyRok = new Date().getFullYear();
  for (var i = 4; i >= 0; i--) {
    lata.push(aktualnyRok - i);
  }

  var dane = pobierzDaneGUS(60559, 2, lata);

  var arkusz = SpreadsheetApp.getActiveSpreadsheet()
                             .getSheetByName('Bezrobocie');
  if (!arkusz) {
    arkusz = SpreadsheetApp.getActiveSpreadsheet()
                           .insertSheet('Bezrobocie');
  }

  arkusz.clearContents();
  arkusz.appendRow(['Województwo', 'Rok', 'Stopa bezrobocia (%)']);

  if (dane.length > 0) {
    arkusz.getRange(2, 1, dane.length, 3).setValues(dane);
  }

  Logger.log('Pobrano dane o bezrobociu: ' + dane.length + ' wierszy.');
}

Skrypt automatycznie oblicza zakres lat (ostatnie 5) – nie musisz go aktualizować co roku. Dla 16 województw × 5 lat dostaniesz 80 wierszy, które idealnie nadają się do wykresu liniowego w Data Studio pokazującego trendy regionalne.

Pobieranie wielu zmiennych naraz

W praktyce rzadko potrzebujesz tylko jednego wskaźnika. Typowy dashboard rynkowy łączy ludność, bezrobocie, liczbę firm i wynagrodzenia. Poniższa funkcja orchestruje pobieranie wielu zmiennych i składa je w jeden arkusz:

/**
 * Pobiera kilka wskaźników GUS i zapisuje każdy w osobnej zakładce.
 */
function pobierzWszystkieWskazniki() {
  var wskazniki = [
    {id: 72305,  nazwa: 'Ludność',      poziom: 2},
    {id: 60559,  nazwa: 'Bezrobocie',   poziom: 2},
    {id: 64428,  nazwa: 'Wynagrodzenia', poziom: 2},
    {id: 10110,  nazwa: 'Firmy REGON',  poziom: 2}
  ];

  var lata = [2021, 2022, 2023, 2024];
  var ss = SpreadsheetApp.getActiveSpreadsheet();

  for (var i = 0; i < wskazniki.length; i++) {
    var w = wskazniki[i];
    Logger.log('Pobieram: ' + w.nazwa + '...');

    var dane = pobierzDaneGUS(w.id, w.poziom, lata);

    var arkusz = ss.getSheetByName(w.nazwa);
    if (!arkusz) {
      arkusz = ss.insertSheet(w.nazwa);
    }

    arkusz.clearContents();
    arkusz.appendRow(['Jednostka', 'Rok', w.nazwa]);

    if (dane.length > 0) {
      arkusz.getRange(2, 1, dane.length, 3).setValues(dane);
    }

    Utilities.sleep(1000); // pauza między wskaźnikami
  }

  Logger.log('Gotowe – pobrano wszystkie wskaźniki.');
}

Każdy wskaźnik ląduje w osobnej zakładce. W Data Studio możesz podłączyć każdą zakładkę jako osobne źródło danych i użyć blendingu, żeby połączyć je po nazwie jednostki terytorialnej i roku.

⚠️ Uwaga: Pobieranie danych na poziomie gmin (poziom: 6) generuje ok. 2 500 jednostek × liczbę lat = tysiące wierszy. Przy 4 wskaźnikach i 4 latach to ok. 40 000 wierszy – wciąż w limicie Sheets, ale zapytania do API potrwają kilka minut ze względu na paginację. Dla dużych zbiorów rozważ ładowanie danych do BigQuery zamiast Sheets.

Podłączenie do Data Studio – dashboard demograficzny

Gdy dane GUS są już w arkuszu, podłączenie do Data Studio to kwestia 5 minut:

  1. W Data Studio kliknij "Dodaj dane" → "Arkusze Google" → wybierz arkusz z danymi GUS.
  2. Sprawdź typy pól: "Jednostka" = wymiar tekstowy, "Rok" = wymiar daty (lub liczbowy, jeśli chcesz go użyć jako filtr), wartość wskaźnika = metryka liczbowa.
  3. Utwórz stronę dashboardu z filtrem na województwo i zakresem lat.

Typowy układ dashboardu demograficznego:

  • Górna belka: 4 karty KPI – ludność, stopa bezrobocia, średnie wynagrodzenie, liczba firm. Każda z porównaniem rok do roku (delta %).
  • Lewa kolumna: wykres liniowy – trend wybranego wskaźnika w czasie (4–5 lat).
  • Prawa kolumna: tabela rankingowa – województwa posortowane wg wybranego wskaźnika.
  • Dół: wykres słupkowy porównawczy – wszystkie województwa na jednym wykresie.

Dodaj filtr interaktywny na województwo – użytkownik klika region i wszystkie wykresy filtrują się automatycznie. To jeden z tych dashboardów, które wyglądają imponująco, a zajmują 30 minut pracy gdy dane są już w arkuszu.

💡 Wskazówka: Jeśli łączysz dane z wielu zakładek (ludność + bezrobocie + firmy), użyj blendingu po dwóch kluczach: Jednostka AND Rok. Bez klucza roku blending zwróci zduplikowane wiersze, bo ta sama jednostka pojawia się wielokrotnie (raz na rok).

Limity API i dobre praktyki

API BDL jest stabilne i dobrze udokumentowane, ale ma swoje ograniczenia:

1. Rate limit. Przy typowym użyciu (kilka wskaźników, poziom województw) nie zbliżysz się do żadnego limitu. Problem pojawia się przy pobieraniu danych na poziomie gmin – 2 500 jednostek / 100 na stronę = 25 zapytań na jeden wskaźnik. Pauzy Utilities.sleep(300) między stronami rozwiązują sprawę. Dla bardzo dużych projektów warto zarejestrować się bezpłatnie na api.stat.gov.pl i używać klucza API (nagłówek X-ClientId) – daje on 5× wyższe limity zapytań.

2. Paginacja – max 100 wyników na stronę. Skrypt musi iterować po stronach. Nasza funkcja pobierzDaneGUS() robi to automatycznie dzięki polu totalPages z odpowiedzi JSON.

3. Opóźnienie publikacji. GUS nie publikuje danych w czasie rzeczywistym. Demografia – raz w roku (Q1–Q2 za rok poprzedni). Bezrobocie – co miesiąc, z opóźnieniem 6 tygodni. PKB – co kwartał. Ustawiaj triggery z odpowiednią częstotliwością – nie ma sensu odpytywać API codziennie, jeśli dane zmieniają się raz w roku.

4. Zmienne mogą znikać lub zmieniać ID. GUS reorganizuje bazę BDL – zmienne bywają przenoszone do innych podgrup lub łączone z innymi. Dodaj obsługę błędów (sprawdzenie kodu HTTP 404) i logowanie, żeby wiedzieć, kiedy API przestanie zwracać dane dla danego ID.

5. Nazwy jednostek terytorialnych zmieniają się. Gminy się łączą, powiaty zmieniają granice. Jeśli porównujesz dane historyczne (np. ludność 2015 vs 2024), pamiętaj że niektóre jednostki mogą już nie istnieć w aktualnej siatce podziału terytorialnego.

Najlepsza praktyka: pobieraj dane do Sheets raz na kwartał (trigger kwartalny), a dashboard Data Studio podłączony do tego arkusza odświeża się automatycznie. W ten sposób masz zawsze aktualne statystyki bez ręcznej pracy i bez nadmiernego obciążania API.