Poll-Takt von 15 Minuten auf 2 Stunden senken
Reduziert den Traffic gegen die Quellen. slot_von() rundet jetzt generisch auf TAKT-Grenzen statt fest auf 15-Minuten-Slots, die Stillstands-Schwelle in quellen_stillstand steigt von 6h auf 24h, damit der Watchdog nicht schon nach drei verpassten Slots anschlaegt. GDELT-Vergleich aus den Kommentaren entfernt, da die Slots nicht mehr mit dessen 15-Minuten-Paketen zusammenfallen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013vHZCwcCJnz4LKNgvT4bxv
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c43d8496bf
commit
8a1ab8a146
@@ -0,0 +1,2 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
+1
-1
@@ -9,6 +9,6 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
COPY poller.py sources.toml ./
|
COPY poller.py sources.toml ./
|
||||||
USER ingest
|
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.
|
# -u, damit die Logs unmittelbar im journal landen und nicht im Puffer haengen.
|
||||||
ENTRYPOINT ["python", "-u", "/app/poller.py"]
|
ENTRYPOINT ["python", "-u", "/app/poller.py"]
|
||||||
|
|||||||
@@ -101,8 +101,8 @@ ueber die gesamte Historie, ohne dass ein Feed erneut abgerufen wird.
|
|||||||
|
|
||||||
## Verhalten
|
## Verhalten
|
||||||
|
|
||||||
- **15-Minuten-Takt**, an den Slotgrenzen ausgerichtet (+30 s Versatz),
|
- **2-Stunden-Takt**, an den Slotgrenzen ausgerichtet (+30 s Versatz), um
|
||||||
identisch zu GDELTs Paketgrenzen — damit bleiben die Slots vergleichbar.
|
unnoetigen Traffic bei den Quellen zu vermeiden.
|
||||||
- **Bedingter GET** ueber ETag/Last-Modified, im Register gespeichert. Im
|
- **Bedingter GET** ueber ETag/Last-Modified, im Register gespeichert. Im
|
||||||
Test antworteten 4 von 12 Quellen im zweiten Durchlauf mit 304.
|
Test antworteten 4 von 12 Quellen im zweiten Durchlauf mit 304.
|
||||||
- **Dedup** ueber `unique (quelle, item_key)`; `item_key` ist guid, ersatzweise
|
- **Dedup** ueber `unique (quelle, item_key)`; `item_key` ist guid, ersatzweise
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Ingest-Poller fuer den deutschen Ereignisdatensatz.
|
"""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
|
jedes neue Item roh ab. Bewusst ohne Kodierung: was hier landet, soll sich
|
||||||
beliebig oft neu kodieren lassen.
|
beliebig oft neu kodieren lassen.
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ Zwei Senken:
|
|||||||
--sink jsonl:/pfad/dir Schatten-Ingest als gzip-JSONL pro Slot
|
--sink jsonl:/pfad/dir Schatten-Ingest als gzip-JSONL pro Slot
|
||||||
|
|
||||||
Aufrufe:
|
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 --sink "$DATABASE_URL" --once # ein Durchlauf
|
||||||
poller.py --check-robots # Lizenzstatus nachpruefen
|
poller.py --check-robots # Lizenzstatus nachpruefen
|
||||||
"""
|
"""
|
||||||
@@ -40,7 +40,7 @@ USER_AGENT = os.environ.get(
|
|||||||
"INGEST_USER_AGENT",
|
"INGEST_USER_AGENT",
|
||||||
"wurzelwerk-ingest/0.1 (+https://stinkwurzpresse.de/wurzelwerk; kontakt@stinkwurzpresse.de)",
|
"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
|
VERSATZ = 30 # Sekunden nach der Slotgrenze, nicht exakt darauf
|
||||||
NS = {
|
NS = {
|
||||||
"a": "http://www.w3.org/2005/Atom",
|
"a": "http://www.w3.org/2005/Atom",
|
||||||
@@ -66,9 +66,10 @@ def _jetzt():
|
|||||||
|
|
||||||
|
|
||||||
def slot_von(ts):
|
def slot_von(ts):
|
||||||
"""Auf 15 Minuten abrunden, wie GDELTs Paketgrenzen."""
|
"""Auf TAKT-Sekunden seit Epoch abrunden (bei 2 h also volle gerade Stunden)."""
|
||||||
return ts.replace(second=0, microsecond=0,
|
sekunden = int(ts.timestamp())
|
||||||
minute=(ts.minute // 15) * 15)
|
return dt.datetime.fromtimestamp(
|
||||||
|
(sekunden // TAKT) * TAKT, tz=dt.timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
def norm_titel(t):
|
def norm_titel(t):
|
||||||
|
|||||||
+4
-3
@@ -48,7 +48,7 @@ create table if not exists raw_items (
|
|||||||
autor text,
|
autor text,
|
||||||
pubdate timestamptz, -- Angabe der Quelle, kann fehlen/luegen
|
pubdate timestamptz, -- Angabe der Quelle, kann fehlen/luegen
|
||||||
abgerufen_am timestamptz not null default now(),
|
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',
|
sprache text not null default 'de',
|
||||||
roh jsonb not null, -- alle geparsten Feldwerte, unveraendert
|
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_slot_idx on poll_laeufe (slot desc);
|
||||||
create index if not exists poll_laeufe_quelle_idx on poll_laeufe (quelle, begonnen_am 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
|
create or replace view quellen_stillstand as
|
||||||
select q.id, q.name, max(r.abgerufen_am) as letztes_item,
|
select q.id, q.name, max(r.abgerufen_am) as letztes_item,
|
||||||
now() - max(r.abgerufen_am) as stille
|
now() - max(r.abgerufen_am) as stille
|
||||||
@@ -122,5 +123,5 @@ left join raw_items r on r.quelle = q.id
|
|||||||
where q.aktiv
|
where q.aktiv
|
||||||
group by q.id, q.name
|
group by q.id, q.name
|
||||||
having max(r.abgerufen_am) is null
|
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;
|
order by stille desc nulls first;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Ablegen unter ~/.config/containers/systemd/wurzelwerk-ingest.container,
|
# Ablegen unter ~/.config/containers/systemd/wurzelwerk-ingest.container,
|
||||||
# danach: systemctl --user daemon-reload && systemctl --user start wurzelwerk-ingest
|
# 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
|
# Deshalb kein systemd-Timer: ein Timer wuerde den bedingten GET-Zustand
|
||||||
# (ETag/Last-Modified) bei jedem Start neu aufbauen muessen.
|
# (ETag/Last-Modified) bei jedem Start neu aufbauen muessen.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user