diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a60b85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/Containerfile b/Containerfile index 2c7da9d..c229052 100644 --- a/Containerfile +++ b/Containerfile @@ -9,6 +9,6 @@ RUN pip install --no-cache-dir -r requirements.txt COPY poller.py sources.toml ./ USER ingest -# Ohne Argumente: Dauerbetrieb im 15-Minuten-Takt, Senke aus $DATABASE_URL. +# Ohne Argumente: Dauerbetrieb im 2-Stunden-Takt, Senke aus $DATABASE_URL. # -u, damit die Logs unmittelbar im journal landen und nicht im Puffer haengen. ENTRYPOINT ["python", "-u", "/app/poller.py"] diff --git a/NOTES.md b/NOTES.md index 49a0259..e6e593b 100644 --- a/NOTES.md +++ b/NOTES.md @@ -101,8 +101,8 @@ ueber die gesamte Historie, ohne dass ein Feed erneut abgerufen wird. ## Verhalten -- **15-Minuten-Takt**, an den Slotgrenzen ausgerichtet (+30 s Versatz), - identisch zu GDELTs Paketgrenzen — damit bleiben die Slots vergleichbar. +- **2-Stunden-Takt**, an den Slotgrenzen ausgerichtet (+30 s Versatz), um + unnoetigen Traffic bei den Quellen zu vermeiden. - **Bedingter GET** ueber ETag/Last-Modified, im Register gespeichert. Im Test antworteten 4 von 12 Quellen im zweiten Durchlauf mit 304. - **Dedup** ueber `unique (quelle, item_key)`; `item_key` ist guid, ersatzweise diff --git a/poller.py b/poller.py index 17d3ee1..f23bc95 100755 --- a/poller.py +++ b/poller.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """Ingest-Poller fuer den deutschen Ereignisdatensatz. -Liest die Quellen aus sources.toml, holt alle 15 Minuten die Feeds und legt +Liest die Quellen aus sources.toml, holt alle 2 Stunden die Feeds und legt jedes neue Item roh ab. Bewusst ohne Kodierung: was hier landet, soll sich beliebig oft neu kodieren lassen. @@ -10,7 +10,7 @@ Zwei Senken: --sink jsonl:/pfad/dir Schatten-Ingest als gzip-JSONL pro Slot Aufrufe: - poller.py --sink "$DATABASE_URL" # Dauerbetrieb, 15-Minuten-Takt + poller.py --sink "$DATABASE_URL" # Dauerbetrieb, 2-Stunden-Takt poller.py --sink "$DATABASE_URL" --once # ein Durchlauf poller.py --check-robots # Lizenzstatus nachpruefen """ @@ -40,7 +40,7 @@ USER_AGENT = os.environ.get( "INGEST_USER_AGENT", "wurzelwerk-ingest/0.1 (+https://stinkwurzpresse.de/wurzelwerk; kontakt@stinkwurzpresse.de)", ) -TAKT = 15 * 60 # Slotlaenge in Sekunden, wie bei GDELT +TAKT = 2 * 60 * 60 # Slotlaenge in Sekunden VERSATZ = 30 # Sekunden nach der Slotgrenze, nicht exakt darauf NS = { "a": "http://www.w3.org/2005/Atom", @@ -66,9 +66,10 @@ def _jetzt(): def slot_von(ts): - """Auf 15 Minuten abrunden, wie GDELTs Paketgrenzen.""" - return ts.replace(second=0, microsecond=0, - minute=(ts.minute // 15) * 15) + """Auf TAKT-Sekunden seit Epoch abrunden (bei 2 h also volle gerade Stunden).""" + sekunden = int(ts.timestamp()) + return dt.datetime.fromtimestamp( + (sekunden // TAKT) * TAKT, tz=dt.timezone.utc) def norm_titel(t): diff --git a/schema.sql b/schema.sql index e883db0..a0382dc 100644 --- a/schema.sql +++ b/schema.sql @@ -48,7 +48,7 @@ create table if not exists raw_items ( autor text, pubdate timestamptz, -- Angabe der Quelle, kann fehlen/luegen abgerufen_am timestamptz not null default now(), - slot timestamptz not null, -- auf 15 min abgerundet, GDELT-kompatibel + slot timestamptz not null, -- auf den Poll-Takt abgerundet sprache text not null default 'de', roh jsonb not null, -- alle geparsten Feldwerte, unveraendert @@ -113,7 +113,8 @@ create table if not exists poll_laeufe ( create index if not exists poll_laeufe_slot_idx on poll_laeufe (slot desc); create index if not exists poll_laeufe_quelle_idx on poll_laeufe (quelle, begonnen_am desc); --- Welche aktive Quelle hat seit ueber sechs Stunden nichts Neues geliefert? +-- Welche aktive Quelle hat seit ueber 24 Stunden (12 verpasste Slots bei +-- 2-Stunden-Takt) nichts Neues geliefert? create or replace view quellen_stillstand as select q.id, q.name, max(r.abgerufen_am) as letztes_item, now() - max(r.abgerufen_am) as stille @@ -122,5 +123,5 @@ left join raw_items r on r.quelle = q.id where q.aktiv group by q.id, q.name having max(r.abgerufen_am) is null - or now() - max(r.abgerufen_am) > interval '6 hours' + or now() - max(r.abgerufen_am) > interval '24 hours' order by stille desc nulls first; diff --git a/wurzelwerk-ingest.container b/wurzelwerk-ingest.container index 155825b..8ca9832 100644 --- a/wurzelwerk-ingest.container +++ b/wurzelwerk-ingest.container @@ -2,7 +2,7 @@ # Ablegen unter ~/.config/containers/systemd/wurzelwerk-ingest.container, # danach: systemctl --user daemon-reload && systemctl --user start wurzelwerk-ingest # -# Der Poller laeuft dauerhaft und taktet sich selbst auf die 15-Minuten-Grenzen. +# Der Poller laeuft dauerhaft und taktet sich selbst auf die 2-Stunden-Grenzen. # Deshalb kein systemd-Timer: ein Timer wuerde den bedingten GET-Zustand # (ETag/Last-Modified) bei jedem Start neu aufbauen muessen.