Narzędzie do pobierania, monitorowania i archiwizacji aktów prawnych z
oficjalnego API ELI Sejmu RP (https://api.sejm.gov.pl/eli) — Dziennika
Ustaw i Monitora Polskiego.
Skąd „scraper", skoro to API? Bo narzędzie hurtowo pobiera dane oraz pełne teksty aktów (PDF/HTML) na dysk — w potocznym sensie to scraper. Robi to jednak przez oficjalne, publiczne API JSON, a nie przez parsowanie strony
isap.sejm.gov.pl(chronionej przez WAF). Dzięki temu jest szybkie i stabilne — pobranie metadanych całego rocznika to jedno zapytanie.
- Instalacja
- Konfiguracja
- Użycie
- Pełne teksty aktów
- Automatyczne monitorowanie
- API klienta
- Struktura bazy danych
- Rozwiązywanie problemów
- Licencja
✅ Pobieranie aktów prawnych
- Pobieranie metadanych aktów z Dziennika Ustaw (DU) i Monitora Polskiego (MP)
- Konfigurowalny zakres lat
- Bogate metadane: tytuł, status, daty, typ aktu, ELI, adres publikacyjny
✅ Monitorowanie zmian
- Wykrywanie nowych aktów (po dacie ogłoszenia)
- Wykrywanie aktów, które utraciły moc obowiązującą (pole
inForcez API) - Automatyczne powiadomienia (do konfiguracji)
✅ Pełne teksty aktów
- Pobieranie treści jako PDF i HTML (tryb
fetch-texts) - Pojedynczy artykuł po numerze —
get_article(act, 100) - Pomijanie aktów bez tekstu w danym formacie (flagi
textPDF/textHTML)
✅ Eksport danych
- Eksport do CSV
- Lokalna baza JSON
- Pełne logi operacji
- Python 3.8 lub nowszy
- pip
pip install -r requirements.txtEdytuj plik config.yaml, aby dostosować narzędzie do swoich potrzeb:
# Źródło danych
api_url: "https://api.sejm.gov.pl/eli"
# Wydawcy do pobierania (DU = Dziennik Ustaw, MP = Monitor Polski)
publishers:
- DU
- MP
# Zakres lat
year_range:
start: 2020
end: null # null = do bieżącego roku
# Monitorowanie
monitoring:
enabled: true
interval_hours: 24
# Opcje pobierania
download:
fetch_details: false # true = dociągaj pełne szczegóły każdego aktu (wolniej)python isap_scraper.py --mode scrape-allTo polecenie:
- Pobierze metadane wszystkich aktów zgodnie z konfiguracją
- Zapisze je w bazie
data/acts_database.json - Trwa kilkanaście sekund (jedno zapytanie API na rocznik)
python isap_scraper.py --mode check-new --days 7Sprawdzi akty ogłoszone w ostatnich 7 dniach i znajdzie te, których nie ma jeszcze w bazie.
python isap_scraper.py --mode find-replacedSprawdzi (na podstawie pola inForce i statusu z API), które akty zostały
uchylone lub wygasły, i zaktualizuje ich status w bazie.
python isap_scraper.py --mode export --output akty.csvpython isap_scraper.py --mode statsWyświetli statystyki bazy: podział według wydawcy, statusu i lat.
# PDF (domyślnie z konfiguracji), tylko pierwsze 50 aktów z bazy
python isap_scraper.py --mode fetch-texts --format pdf --limit 50
# PDF i HTML
python isap_scraper.py --mode fetch-texts --format bothPobiera treść aktów z bazy i zapisuje do data/texts/ ({adres}.pdf / {adres}.html).
Akty już pobrane są pomijane (chyba że dodasz --overwrite). Szczegóły poniżej —
patrz Pełne teksty aktów.
python monitor.py --mode oncepython monitor.py --mode continuousMonitor sprawdza nowe akty zgodnie z harmonogramem w konfiguracji (domyślnie co 24h).
Utwórz plik /etc/systemd/system/isap-monitor.service:
[Unit]
Description=ISAP Monitor
After=network.target
[Service]
Type=simple
User=twoj_user
WorkingDirectory=/sciezka/do/ISAP-scraper
ExecStart=/usr/bin/python3 /sciezka/do/ISAP-scraper/monitor.py --mode continuous
Restart=always
RestartSec=60
[Install]
WantedBy=multi-user.targetNastępnie:
sudo systemctl daemon-reload
sudo systemctl enable isap-monitor
sudo systemctl start isap-monitor# Sprawdzaj nowe akty codziennie o 6:00
0 6 * * * cd /sciezka/do/ISAP-scraper && /usr/bin/python3 monitor.py --mode onceISAP-scraper/
├── config.yaml # Konfiguracja
├── requirements.txt # Zależności Python
├── isap_scraper.py # Główny klient API
├── monitor.py # Monitor automatyczny
├── README.md # Ta dokumentacja
├── data/ # Dane (tworzone automatycznie)
│ └── acts_database.json # Baza aktów
├── logs/ # Logi (tworzone automatycznie)
└── examples/ # Przykłady użycia
from isap_scraper import ISAPScraper
# Utwórz klienta
scraper = ISAPScraper('config.yaml')
# Pobierz akty z konkretnego roku (publisher: DU lub MP)
acts_2025 = scraper.scrape_acts_by_year(2025, publisher='DU')
# Sprawdź nowe akty
new_acts = scraper.check_for_new_acts(days_back=7)
# Pobierz szczegóły aktu (po adresie publikacyjnym)
details = scraper.get_act_details('WDU20240001984')
# Znajdź akty, które utraciły moc
replaced = scraper.find_replaced_acts()
# Pełne teksty (patrz sekcja „Pełne teksty aktów")
act = scraper.db['acts']['WDU20240001221']
scraper.download_act_text(act, 'pdf') # zapis PDF do data/texts/
scraper.get_article(act, 100) # art. 100 jako HTML (lub None)
# Eksportuj do CSV
scraper.export_to_csv('output.csv')
# Pobierz statystyki
stats = scraper.get_statistics()
print(f"Łącznie aktów: {stats['total_acts']}")Baza danych to plik JSON o strukturze:
{
"acts": {
"WDU20240001984": {
"address": "WDU20240001984",
"publisher": "DU",
"type": "Rozporządzenie",
"year": 2024,
"pos": 1984,
"title": "Rozporządzenie Rady Ministrów z dnia ...",
"displayAddress": "Dz.U. 2024 poz. 1984",
"ELI": "DU/2024/1984",
"status": "obowiązujący",
"inForce": "IN_FORCE",
"announcementDate": "2024-12-30",
"url": "https://isap.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20240001984",
"scraped_at": "2026-01-20T10:30:00"
}
},
"metadata": {
"last_update": "2026-01-20T10:30:00",
"total_acts": 23485
}
}- DU — Dziennik Ustaw (główne akty prawne)
- MP — Monitor Polski (akty niższego rzędu)
Pełną listę wydawców zwraca endpoint GET /acts (oraz metoda get_publishers()).
Status pochodzi wprost z API (pole status), np.:
obowiązujący— akt obowiązującyuchylony,uznany za uchylony,wygaśnięcie aktu— akt nieobowiązującyakt posiada tekst jednolity,akt objęty tekstem jednolitym— informacje o tekście jednolitym
Dodatkowo pole inForce przyjmuje wartości IN_FORCE / NOT_IN_FORCE / UNKNOWN
i jest najpewniejszym wskaźnikiem mocy obowiązującej.
ELI API udostępnia treść aktów, ale w trzech „smakach" o różnej jakości strukturalnej. Warto rozumieć ich ograniczenia:
| Forma | Endpoint | Dostępność | Uwagi |
|---|---|---|---|
/acts/{pub}/{rok}/{poz}/text.pdf |
niemal zawsze (textPDF=true) |
render dokumentu — nie tekst per-artykuł | |
| Pełny HTML | /acts/{pub}/{rok}/{poz}/text.html |
gdy textHTML=true |
cały akt jako HTML |
| Fragment | .../text.html/{tree} np. art=1 |
gdy textHTML=true |
czysty pojedynczy artykuł, bez parsowania PDF; ścieżkę dla zagnieżdżonych artykułów rozwiązuje get_article |
Czego ELI API nie ma: czystego, strukturalnego endpointu „daj artykuł N jako JSON" dla dowolnego aktu. Dwa istotne przypadki brzegowe:
- Najnowsze teksty jednolite kodeksów (KC, KP) bywają PDF-only (
textHTML=false) — wtedytext.htmlzwraca pusty body, zostaje PDF. - Teksty jednolite są publikowane jako Obwieszczenia — ich drzewo na poziomie
głównym to treść obwieszczenia, a właściwy kodeks jest zagnieżdżony, więc bare
art=Nnie trafia (trzeba pełnej ścieżkitreealbo całego HTML).
Klient obsługuje to wprost:
from isap_scraper import ISAPScraper
c = ISAPScraper()
act = c.db['acts']['WDU20240001221'] # Prawo komunikacji elektronicznej
c.download_act_text(act, 'pdf') # zapis data/texts/WDU20240001221.pdf
c.download_act_text(act, 'html') # tylko gdy textHTML=true; inaczej None
# Pojedynczy artykuł PO NUMERZE — ścieżka rozwiązywana przez strukturę aktu,
# więc działa też dla ustaw z działami/rozdziałami:
html = c.get_article(act, 100) # art. 100 jako HTML (lub None)
# Wariant niskopoziomowy — gdy znasz pełną ścieżkę tree:
html = c.get_act_article(act, 'dzial=II/rozdzial=1/art=100')Adresowanie artykułów: w płaskich aktach artykuły są na poziomie głównym (
art=1), ale w dużych ustawach są zagnieżdżone w działach/rozdziałach (dzial=II/rozdzial=1/art=100).get_article(act, numer)sam odczytuje strukturę z/structi buduje właściwą ścieżkę, więc wystarczy podać numer.
fetch_texts() (tryb CLI fetch-texts) pomija akty bez tekstu w danym formacie
na podstawie flag textPDF/textHTML — bez marnowania zapytań i bez pustych plików.
API bywa chwilowo niedostępne. Klient ponawia próby (rate_limiting.retry_attempts).
W razie potrzeby zmniejsz tempo zapytań w config.yaml:
rate_limiting:
requests_per_second: 2- Sprawdź, czy konfiguracja zawiera odpowiednich wydawców (
DU,MP) - Sprawdź zakres lat (
year_range) - Zwiększ okno wyszukiwania:
python isap_scraper.py --mode check-new --days 30
Wszystkie operacje są logowane do katalogu logs/.
python monitor.py --mode continuousfrom isap_scraper import ISAPScraper
scraper = ISAPScraper()
scraper.scrape_all_acts()
scraper.export_to_csv('baza_prawa.csv')from isap_scraper import ISAPScraper
scraper = ISAPScraper()
new_acts = scraper.check_for_new_acts(days_back=1)
keywords = ['podatkowy', 'VAT', 'podatek']
relevant_acts = [
act for act in new_acts
if any(keyword in act.get('title', '').lower() for keyword in keywords)
]
if relevant_acts:
print(f"Znaleziono {len(relevant_acts)} nowych aktów dotyczących podatków!")- Pobranie metadanych całego rocznika: jedno zapytanie API (ułamek sekundy)
- Pełny zakres 2020–2026 (DU + MP): ~20 tys. aktów w kilkanaście sekund
- Sprawdzenie nowych aktów: kilka sekund
find-replaced: zależnie od liczby aktów (jedno zapytanie szczegółów na akt)
- Domyślnie pobierane są metadane aktów; pełne szczegóły (referencje, organ wydający)
wymagają opcji
download.fetch_details: truelub wywołaniaget_act_details() - Pełne teksty: PDF to render dokumentu, a nie tekst strukturalny per-artykuł;
HTML i fragmenty
art=Nsą dostępne tylko dla części aktów — patrz Pełne teksty aktów
- Pobieranie pełnych tekstów aktów (PDF/HTML) przez API
- Ekstrakcja tekstu z PDF (dla aktów PDF-only, np. teksty jednolite kodeksów)
- Budowa grafu zależności między aktami (referencje)
- Powiadomienia e-mail/Slack
- Web UI do przeglądania bazy
- Docker container
Pełna specyfikacja OpenAPI: https://api.sejm.gov.pl/eli.html
Kod źródłowy: MIT License — szczegóły w pliku LICENSE.
Licencja MIT obejmuje wyłącznie kod tego projektu. Nie dotyczy samych aktów prawnych ani ich metadanych — zgodnie z art. 4 ustawy o prawie autorskim i prawach pokrewnych akty normatywne nie są przedmiotem prawa autorskiego i należą do domeny publicznej.
Narzędzie korzysta z oficjalnego, publicznego API Sejmu RP i służy celom edukacyjnym oraz badawczym. Przestrzegaj regulaminu korzystania z tego API.