Files
gesetzblatt/download_offenegesetze.py
Michael Hermann 2bb70ae0ed Initiales gesetzblatt-Repo: Parser-Zweig ab 2023 von recht.bund.de.
Historie 1949–2022 bleibt in lawgit, bis der GPU-Lauf endet; Schnitt ohne Lücke.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 11:56:05 +02:00

176 lines
5.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Historische BGBl-PDFs 19492022 von OffeneGesetze.de.
Der Bestand liegt bereits lokal in lawgit (`bgbl/bgbl1/1949``2022`, ~5500 PDFs).
Dieses Skript dupliziert ihn nicht. Es schreibt ein Quellen-Manifest und kann
einzelne Jahrgänge nachziehen, falls sie hier wirklich gebraucht werden.
python3 download_offenegesetze.py --hinweis
python3 download_offenegesetze.py --year 1949 # nur wenn nötig
python3 download_offenegesetze.py --from-year 1949 --to-year 1950
"""
from __future__ import annotations
import argparse
import time
from pathlib import Path
from urllib.parse import urlparse
from bgbl_util import (
BGBl_DIR,
LAWGIT_BGBL,
LAWGIT_ROOT,
META_DIR,
SESSION,
download_file,
fundstelle,
issue_id,
save_json,
)
API_URL = "https://api.offenegesetze.de/v1/veroeffentlichung/"
MEDIA_BASE = "https://media.offenegesetze.de"
HISTORIE_BIS = 2022
HISTORIE_VON = 1949
def fetch_api(params: dict) -> list[dict]:
results = []
url = API_URL
params = {**params, "limit": 100}
while url:
r = SESSION.get(url, params=params if url == API_URL else None, timeout=60)
r.raise_for_status()
data = r.json()
results.extend(data.get("results") or [])
url = data.get("next")
params = None
time.sleep(0.15)
return results
def schreibe_hinweis() -> Path:
"""Kein PDF-Duplikat: nur dokumentieren, wo die Historie schon liegt."""
pdfs = 0
jahre: list[str] = []
quelle = LAWGIT_BGBL / "bgbl1"
if quelle.is_dir():
jahre = sorted(p.name for p in quelle.iterdir() if p.is_dir() and p.name.isdigit())
pdfs = sum(1 for p in quelle.rglob("*.pdf"))
manifest = {
"quelle": "offenegesetze.de",
"api": API_URL,
"jahre": f"{HISTORIE_VON}{HISTORIE_BIS}",
"schnitt": "2022 endet hier; ab 2023 nur noch recht.bund.de (download_rechtbund.py)",
"lawgit_bestand": {
"pfad": str(quelle) if quelle.exists() else None,
"jahre": f"{jahre[0]}{jahre[-1]}" if jahre else None,
"pdfs": pdfs,
"hinweis": (
"Nach Archivierung von lawgit diese PDFs hierher übernehmen "
"(kopieren oder verschieben), nicht ein zweites Mal von OffeneGesetze laden."
),
},
"nicht_duplizieren": True,
}
dest = META_DIR / "offenegesetze-quelle.json"
save_json(dest, manifest)
print("Historie 19492022: OffeneGesetze.de")
print(f" Schnitt: 2022 letzte OffeneGesetze-Jahr, 2023+ nur recht.bund.de")
if pdfs:
print(f" Bestand in lawgit: {pdfs} PDFs ({jahre[0]}{jahre[-1]})")
print(f" Pfad: {quelle}")
print(" Keine Kopie angelegt — nach Archivierung von lawgit hierher ziehen.")
else:
print(f" lawgit-Bestand nicht gefunden unter {LAWGIT_ROOT}")
print(" Einzelne Jahre: python3 download_offenegesetze.py --year 1949")
print(f" Manifest: {dest}")
return dest
def download_year(kind: str, year: int) -> list[dict]:
if year > HISTORIE_BIS:
print(f"{year} gehört zu recht.bund.de — download_rechtbund.py nutzen")
return []
if year < HISTORIE_VON:
print(f"{year}: OffeneGesetze beginnt {HISTORIE_VON}")
return []
print(f"Lade Metadaten {kind} {year}")
items = fetch_api({"year": year, "kind": kind})
normiert = []
for raw in items:
number = raw.get("number")
page = raw.get("page")
normiert.append(
{
"id": raw.get("id") or issue_id(kind, year, number or 0),
"kind": kind,
"year": year,
"number": number,
"page": page,
"date": (raw.get("date") or "")[:10] or None,
"title": raw.get("title"),
"titel": raw.get("title"),
"document_url": raw.get("document_url"),
"url": raw.get("url"),
"quelle": "offenegesetze",
"fundstelle": fundstelle(kind, year, number or 0, page),
}
)
save_json(META_DIR / f"{kind}-{year}.json", normiert)
print(f" {len(normiert)} Veröffentlichungen")
archive_url = f"{MEDIA_BASE}/{kind}/{year}.tar.bz2"
archive_path = BGBl_DIR / "archives" / f"{kind}-{year}.tar.bz2"
print(f"Lade Archiv {archive_url}")
download_file(archive_url, archive_path)
if archive_path.exists() and archive_path.stat().st_size > 0:
print(f" Archiv liegt unter {archive_path} (nicht automatisch entpackt)")
else:
print(" Kein Jahresarchiv, lade Einzel-PDFs …")
seen = set()
for item in items:
doc = (item.get("document_url") or "").split("#")[0]
if not doc or doc in seen:
continue
seen.add(doc)
name = Path(urlparse(doc).path).name
dest = BGBl_DIR / kind / str(year) / name
print(f" PDF {name}")
download_file(doc, dest)
time.sleep(0.2)
return normiert
def main() -> None:
parser = argparse.ArgumentParser(description="BGBl 19492022 von OffeneGesetze.de")
parser.add_argument("--hinweis", action="store_true", help="Nur Quellen-Manifest, keine PDFs")
parser.add_argument("--year", type=int, action="append")
parser.add_argument("--from-year", type=int)
parser.add_argument("--to-year", type=int)
parser.add_argument("--kind", choices=["bgbl1", "bgbl2"], default="bgbl1")
args = parser.parse_args()
META_DIR.mkdir(parents=True, exist_ok=True)
years = list(args.year or [])
if args.from_year:
years.extend(range(args.from_year, (args.to_year or HISTORIE_BIS) + 1))
years = sorted(set(y for y in years if HISTORIE_VON <= y <= HISTORIE_BIS))
if args.hinweis or not years:
schreibe_hinweis()
if not years:
return
for year in years:
download_year(args.kind, year)
if __name__ == "__main__":
main()