Geonode logo
Geonode Team

Geonode Team

Aktualisiert: 16. Oktober 2026

Veröffentlicht: 16.10.2026

Erkennung von Website-Änderungen mit Python: Überwachung von Dokumentations- und Richtlinienaktualisierungen

Mithilfe der Erkennung von Website-Änderungen können Sie feststellen, ob ein Anbieter eine Klausel in seinen Nutzungsbedingungen umformuliert, einen Endpunkt in seiner Dokumentation als veraltet gekennzeichnet oder die Datenschutzerklärung stillschweigend geändert hat, bevor dies öffentlich bekannt gegeben wird. Am Ende dieses Leitfadens verfügen Sie über ein Python-Skript, das eine Liste von Seiten als Markdown-Snapshots über die Geonode- und Scraper API-Endpunkte erfasst, überflüssige Daten filtert, jede Version in SQLite speichert, einen lesbaren Diff erstellt und nur dann eine Meldung an Slack sendet, wenn sich der Inhalt tatsächlich geändert hat. Es läuft über Cron oder GitHub Actions und übersteht auch unzuverlässige Abrufe. Es handelt sich bewusst nicht um einen Preisüberwacher. Preise sind Zahlen an einer bekannten Stelle; Dokumente sind Prosa, bei der Ort und Art einer Änderung im Voraus unbekannt sind. Die folgenden Designentscheidungen ergeben sich daraus.

Mithilfe der Erkennung von Website-Änderungen können Sie feststellen, ob ein Anbieter eine Klausel in seinen Nutzungsbedingungen umformuliert, einen Endpunkt in seiner Dokumentation als veraltet markiert oder die Datenschutzerklärung stillschweigend geändert hat, bevor dies öffentlich bekannt gegeben wird. Am Ende dieses Leitfadens verfügen Sie über ein Python-Skript, das eine Liste von Seiten als Markdown-Snapshots über die Geonode- und Scraper API-Endpunkte erfasst, überflüssige Daten filtert, jede Version in SQLite speichert, einen lesbaren Diff erstellt und nur dann eine Meldung an Slack sendet, wenn sich der Inhalt tatsächlich geändert hat. Es läuft über Cron oder GitHub Actions und übersteht auch unzuverlässige Abrufe.

Es handelt sich bewusst nicht um einen Preisüberwacher. Preise sind Zahlen an einer bekannten Stelle; Dokumente sind Prosa, bei der Ort und Art einer Änderung im Voraus unbekannt sind. Die folgenden Designentscheidungen ergeben sich daraus.

Was Sie überwachen und warum Markdown

Die Seiten, die es zu beobachten gilt, weisen ein gemeinsames Profil auf: Sie sind öffentlich, textlastig, werden selten aktualisiert und haben bei einer Aktualisierung erhebliche Auswirkungen. API-Referenzseiten, Authentifizierungsanleitungen, Tabellen zu Ratenbeschränkungen, Allgemeine Geschäftsbedingungen, Datenverarbeitungsvereinbarungen, Nutzungsrichtlinien, Änderungsprotokolle und die Historie von Vorfällen auf Statusseiten fallen alle darunter.

Der Vergleich des rohen HTML-Codes dieser Seiten ist mühsam. In Asset-URLs eingebettete Hashes, CSRF-Token, Namen von A/B-Testklassen und eingebettete Analysedaten ändern sich bei jedem Laden. Durch die Umwandlung in Markdown wird fast alles davon verworfen und nur der Text bleibt übrig. Der Endpunkt /v1/extract gibt Markdown direkt zurück, sodass das Skript keinen HTML-Parser benötigt.

Installieren Sie die einzige Abhängigkeit mit pip install requests. Alles andere stammt aus der Standardbibliothek.

Eine Momentaufnahme erstellen

Der Fetcher erwartet Markdown, deaktiviert standardmäßig die JavaScript-Darstellung und aktiviert sie seitenweise für Dokumentationsseiten, die clientseitig gerendert werden. Der Block „wait_config“ wird nur benötigt, wenn eine Seite ihren Inhalt erst nach dem Laden ausfüllt; ist er vorhanden, wird die Darstellung automatisch aktiviert.

import os
import requests

API = "https://scraper.geonode.io"
HEADERS = {"X-Api-Key": os.environ["GEONODE_API_KEY"]}

def snapshot(session: requests.Session, url: str, render_js: bool = False,
             wait_for: str | None = None) -> tuple[int, str]:
    """Return (origin_http_status, markdown). Raises on API-level errors."""
    body = {"url": url, "formats": ["markdown"], "render_js": render_js,
            "processing_mode": "sync", "timeout": 60000}
    if wait_for:
        body["wait_config"] = {"wait_until": "domcontentloaded", "wait_for": wait_for,
                               "wait_timeout": 2000}
        body.pop("render_js")          # wait_config implies rendering; sending false is rejected
    r = session.post(f"{API}/v1/extract", json=body, headers=HEADERS, timeout=90)
    if r.status_code == 429:
        err = r.json().get("error", r.json())
        raise RuntimeError(f"rate limited ({err.get('code')}): {err.get('message')}")
    r.raise_for_status()
    payload = r.json()
    return payload["metadata"]["http_status"], (payload.get("data") or {}).get("markdown") or ""

Der Status der Quelle ist ebenso wichtig wie der Inhalt. Eine 503-Wartungsseite lässt sich in vollkommen gültiges Markdown konvertieren; ohne die Überprüfung von http_status würden Sie sie als neue Version speichern und eine Warnung ausgeben.

Rauschen entfernen

Zwei Mechanismen, die nacheinander angewendet werden. Ein Regex-Durchlauf entfernt Zeilen, von denen bekannt ist, dass sie sich ständig ändern: Datumsangaben, „Zuletzt aktualisiert“-Banner, Cookie-Hinweise, Copyright-Fußzeilen, „Diese Seite bearbeiten“-Links. Anschließend behält eine Abschnitt-Whitelist nur die Überschriften bei, die für Sie von Interesse sind. Dadurch werden Navigationselemente, Seitenleisten und Marketingblöcke entfernt, die der Markdown-Konverter beibehalten hatte, da es sich um echten Text handelte.

import re

NOISE = [
    re.compile(r"(?im)^.*\b(last (updated|modified|reviewed)|effective date|updated on)\b.*$"),
    re.compile(r"(?im)^.*\b(cookie|cookies)\b.*\b(accept|consent|preferences)\b.*$"),
    re.compile(r"(?im)^.*(©|copyright|all rights reserved).*$"),
    re.compile(r"(?im)^.*\b(edit this page|was this page helpful|on this page)\b.*$"),
    re.compile(r"\b(?:\d{1,2}\s)?(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\.?\s+\d{1,2},?\s+\d{4}\b", re.I),
    re.compile(r"\b\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?\S*)?\b"),
    re.compile(r"!\[[^\]]*\]\([^)]*\)"),                 # images
]

def strip_noise(md: str, keep_sections: list[str] | None = None) -> str:
    for rx in NOISE:
        md = rx.sub("", md)
    if keep_sections:
        kept, keep = [], False
        for line in md.splitlines():
            if line.startswith("#"):
                title = line.lstrip("#").strip().lower()
                keep = any(s.lower() in title for s in keep_sections)
            if keep:
                kept.append(line)
        md = "\n".join(kept)
    return normalize_ws(md)

def normalize_ws(text: str) -> str:
    lines = (re.sub(r"[ \t]+", " ", ln).strip() for ln in text.splitlines())
    return "\n".join(ln for ln in lines if ln)

keep_sections ist eine Liste von Teilzeichenfolgen, die mit Überschriften abgeglichen werden; daher behält ["rate limit", "authentication"] diese H2/H3-Blöcke und alles darunter bis zur nächsten Überschrift bei. Belassen Sie die Einstellung bei None für Richtlinienseiten, auf denen jede Klausel von Bedeutung ist.

Die regulären Ausdrücke für Datumsangaben sind bewusst aggressiv gewählt. Die Zeile „Gültig ab 1. März 2026“ auf einer Richtlinienseite ist genau die Art von Änderung, die Sie sehen möchten. Wenn Ihnen das wichtig ist, entfernen Sie den ersten NOISE-Eintrag und lassen Sie den Diff dies anzeigen.

Hashing versus Diffing

Hashing beantwortet die Frage „Hat sich etwas geändert?“ in konstanter Zeit. Diffing beantwortet die Frage „Was hat sich geändert?“ in einer für Menschen lesbaren Form, die sich in einer Slack-Nachricht darstellen lässt. Die meisten Tools zur Erkennung von Änderungen an Websites beschränken sich auf das Hashing; nutzen Sie beides: Hashing zur Vorabprüfung, Diffing zur Berichterstellung.

import difflib
import hashlib

def digest(text: str) -> str:
    return hashlib.sha256(text.encode("utf-8")).hexdigest()

def unified(old: str, new: str, url: str, context: int = 2, max_lines: int = 120) -> str:
    diff = difflib.unified_diff(old.splitlines(), new.splitlines(),
                                fromfile=f"{url} (previous)", tofile=f"{url} (current)",
                                lineterm="", n=context)
    lines = list(diff)
    if len(lines) > max_lines:
        lines = lines[:max_lines] + [f"... {len(lines) - max_lines} more lines"]
    return "\n".join(lines)

difflib.unified_diff ist in der Standardbibliothek enthalten und unter docs.python.org dokumentiert. Das Argument n legt die Anzahl der Kontextzeilen fest; zwei reichen für Fließtext aus. Begrenzen Sie die Ausgabe, damit eine vollständige Neufassung der Seite keine Warnmeldung mit 5.000 Zeilen erzeugt.

Speichern von Snapshots

SQLite speichert den vollständigen Verlauf, nicht nur die letzte Version. Dadurch ist es möglich, einen Vergleich mit den letzten beiden Snapshots (nächster Abschnitt) durchzuführen und auch Monate später die Frage zu beantworten: „Wann ist diese Klausel zum ersten Mal aufgetaucht?“

import sqlite3
import time

SCHEMA = """
CREATE TABLE IF NOT EXISTS snapshots (
  id          INTEGER PRIMARY KEY,
  url         TEXT NOT NULL,
  taken_at    REAL NOT NULL,
  http_status INTEGER,
  hash        TEXT,
  content     TEXT
);
CREATE INDEX IF NOT EXISTS idx_url_time ON snapshots(url, taken_at DESC);
"""

def open_db(path: str = "changes.sqlite") -> sqlite3.Connection:
    db = sqlite3.connect(path)
    db.executescript(SCHEMA)
    return db

def last_snapshots(db: sqlite3.Connection, url: str, n: int = 2) -> list[tuple[str, str]]:
    """Most recent first: [(hash, content), ...] for successful (200) fetches only."""
    return db.execute("SELECT hash, content FROM snapshots WHERE url=? AND http_status=200 "
                      "ORDER BY taken_at DESC LIMIT ?", (url, n)).fetchall()

def save(db: sqlite3.Connection, url: str, status: int, h: str | None, content: str | None) -> None:
    db.execute("INSERT INTO snapshots (url, taken_at, http_status, hash, content) VALUES (?,?,?,?,?)",
               (url, time.time(), status, h, content))
    db.commit()

Richtlinenseiten sind klein, daher ist das Speichern des vollständigen Inhalts pro Durchlauf kostengünstig. Wenn Sie stündlich Hunderte langer Dokumente überwachen, speichern Sie den Inhalt nur dann, wenn sich der Hash geändert hat, und behalten Sie ansonsten die Hash-Zeile bei.

Umgang mit unzuverlässigen Abrufen

Unzuverlässige Abrufe sind die Hauptursache für Fehlalarme bei der Erkennung von Änderungen an Websites; daher gibt es drei Abwehrmaßnahmen: Wiederholen Sie den Abruf mit einer Wartezeit, bevor Sie aufgeben. Behandeln Sie alles außer einem 200-Statuscode als „keine Daten“, speichern Sie den Status für den Datensatz und führen Sie keinen Vergleich durch. Vergleichen Sie den neuen Hash außerdem mit den letzten beiden korrekten Snapshots, nicht nur mit dem letzten: Wenn die Seite zwischen A, B und A wechselte, war das B höchstwahrscheinlich eine vorübergehende Variante (eine Fehlerseite, die dennoch den Status 200 zurückgab, oder ein Cache, der eine veraltete Version auslieferte) und rechtfertigt keine Warnmeldung.

def fetch_with_retry(session: requests.Session, url: str, attempts: int = 3, **kw) -> tuple[int, str]:
    delay = 5
    for i in range(attempts):
        try:
            status, md = snapshot(session, url, **kw)
            if status < 500 and md.strip():
                return status, md
        except (requests.RequestException, RuntimeError) as exc:
            if i == attempts - 1:
                raise
        time.sleep(delay)
        delay *= 3
    return status, md

def check(db: sqlite3.Connection, session: requests.Session, url: str,
          keep_sections: list[str] | None = None, **kw) -> str | None:
    """Returns a unified diff if a real change was detected, else None."""
    status, raw = fetch_with_retry(session, url, **kw)
    if status != 200:
        save(db, url, status, None, None)
        return None
    clean = strip_noise(raw, keep_sections)
    h = digest(clean)
    previous = last_snapshots(db, url, n=2)
    save(db, url, status, h, clean)
    if not previous:
        return None                                   # first run, nothing to compare
    if any(h == old_hash for old_hash, _ in previous):
        return None                                   # unchanged, or a revert of a blip
    return unified(previous[0][1], clean, url)

Die „Zweiter-Snapshot“-Regel hat einen Nachteil: Eine echte Änderung, die innerhalb eines Durchlaufs rückgängig gemacht wird, wird nicht gemeldet. Für die Dokumentation und die Überwachung von Richtlinien ist dies fast immer der richtige Kompromiss.

Benachrichtigung

Eingehende Webhooks in Slack akzeptieren einen JSON-Body mit einem Feld „text“; fügen Sie den Diff in einen Code-Block ein, damit er monospaced bleibt. Der E-Mail-Versand über „smtplib“ ist ein paar Zeilen länger und nützlich, wenn Slack nicht verfügbar ist.

import json
import smtplib
from email.message import EmailMessage

SLACK_WEBHOOK = os.environ.get("SLACK_WEBHOOK_URL")

def notify_slack(url: str, diff: str) -> None:
    if not SLACK_WEBHOOK:
        return
    fence = "`" * 3                          # literal ``` would close the Markdown block
    text = f"*Change detected:* {url}\n{fence}\n{diff[:3500]}\n{fence}"
    requests.post(SLACK_WEBHOOK, data=json.dumps({"text": text}),
                  headers={"Content-Type": "application/json"}, timeout=15).raise_for_status()

def notify_email(url: str, diff: str, to: str) -> None:
    msg = EmailMessage()
    msg["Subject"] = f"Change detected: {url}"
    msg["From"] = os.environ["SMTP_FROM"]
    msg["To"] = to
    msg.set_content(diff)
    with smtplib.SMTP(os.environ["SMTP_HOST"], int(os.environ.get("SMTP_PORT", "587"))) as s:
        s.starttls()
        s.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
        s.send_message(msg)

Slack kürzt lange Nachrichten, daher gilt für den Diff eine Obergrenze von 3.500 Zeichen. Bei langen Diffs sollten Sie den ersten Bildschirm posten und den vollständigen Text an anderer Stelle anhängen oder in der Nachricht auf eine gespeicherte Snapshot-ID verweisen.

Die Beobachtungsliste und der Runner

Die Konfiguration wird in einer Liste gespeichert, sodass Sie sie im Repo aufbewahren und Änderungen daran wie am Code überprüfen können. Jeder Eintrag enthält die URL, die Angabe, ob eine Darstellung erforderlich ist, einen optionalen Selektor, auf den gewartet werden soll, sowie eine optionale Zulassungsliste für Abschnitte.

WATCHLIST = [
    {"url": "https://example.com/legal/terms"},
    {"url": "https://example.com/legal/privacy"},
    {"url": "https://example.com/docs/api/authentication",
     "keep_sections": ["authentication", "rate limit"]},
    {"url": "https://example.com/changelog", "render_js": True, "wait_for": "main article"},
]

def main() -> None:
    db = open_db()
    session = requests.Session()
    for item in WATCHLIST:
        url = item["url"]
        try:
            diff = check(db, session, url, keep_sections=item.get("keep_sections"),
                         render_js=item.get("render_js", False), wait_for=item.get("wait_for"))
        except Exception as exc:
            print(f"[skip] {url}: {exc}")
            continue
        if diff:
            print(f"[changed] {url}")
            notify_slack(url, diff)
        else:
            print(f"[ok] {url}")

if __name__ == "__main__":
    main()

Beim ersten Durchlauf wird die Datenbank initialisiert, ohne dass etwas gemeldet wird. Ab dem zweiten Durchlauf erzeugt jede tatsächliche Änderung eine Slack-Nachricht mit dem Diff.

Erkennung von Änderungen an der Website

Zwei Optionen, für die keine Infrastruktur erforderlich ist. Ein Cron-Eintrag auf einem beliebigen Rechner, auf dem Python installiert ist und die Umgebungsvariablen vorhanden sind:

# daily at 06:10, log to a file
10 6 * * * cd /opt/change-monitor && /usr/bin/python3 monitor.py >> monitor.log 2>&1

Oder ein GitHub-Actions-Workflow, was praktisch ist, da die SQLite-Datei als Zustandsspeicher zurück ins Repo committet werden kann und die Geheimnisse in den Repository-Einstellungen gespeichert sind:

name: change-monitor
on:
  schedule:
    - cron: "10 6 * * *"
  workflow_dispatch:
jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install requests
      - run: python monitor.py
        env:
          GEONODE_API_KEY: ${{ secrets.GEONODE_API_KEY }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      - run: |
          git config user.name "change-monitor"
          git config user.email "bot@users.noreply.github.com"
          git add changes.sqlite && git commit -m "snapshot $(date -u +%F)" || true
          git push

Stündliche Zeitpläne sind für Changelogs ausreichend; bei rechtlichen Seiten ist eine tägliche Überprüfung bereits mehr, als die meisten Teams benötigen. Die folgende Tabelle bietet einen sinnvollen Ausgangspunkt.

SeitentypEmpfohlene Häufigkeitrender_jskeep_sections
Nutzungsbedingungen, Datenschutz, AUPtäglichneinkeine (gesamte Seite)
API-Referenzalle 6 Stundenhängt von der Dokumentations-Engine abja, die von Ihnen verwendeten Endpunkte
Änderungsprotokoll / Versionshinweisestündlichoftkeine
Verlauf der Vorfälle auf der Statusseitestündlichin der Regelkeine

Ein Hinweis zu Nutzungsbedingungen und Bots

Das einmal tägliche Lesen einer öffentlichen Dokumentations- oder Richtlinienseite gilt als normale Nutzung des Internets und entspricht dem, was ein Browser tut. Überprüfen Sie dennoch die Nutzungsbedingungen und robots.txt der Website, bevor Sie sie zur Beobachtungsliste hinzufügen, halten Sie die Häufigkeit angemessen und überwachen Sie niemals Seiten, für die Sie keine Anmeldeberechtigung haben. Wenn eine Website einen RSS-Feed oder eine Changelog-API veröffentlicht, nutzen Sie diese vorzugsweise: Das ist für beide Seiten kostengünstiger und funktioniert auch dann noch, wenn sich das Seitenlayout ändert.

Häufige Fehler

Vergleich von rohem HTML

Bei jedem Build einer modernen Dokumentations-Website werden Asset-Hashes und Klassennamen umgeschrieben. Die Diffs werden unlesbar, und bei jedem Deploy wird eine Warnmeldung ausgelöst. Konvertieren Sie den Text daher zunächst in Markdown, wie es bei snapshot() der Fall ist.

Warnung beim ersten Nicht-200-Status

Ein einzelner 503-Fehler oder eine Bot-Challenge-Seite, die einen 200-Status mit der Aufforderung „Bitte bestätigen Sie, dass Sie ein Mensch sind“ zurückgibt, sollte nicht als gespeicherte Version oder Warnung erfasst werden. Überprüfen Sie http_status, verlangen Sie nicht leeren Inhalt und vergleichen Sie mit den letzten beiden korrekten Snapshots.

Zu wenig oder zu viel entfernt

Wenn Ihr Diff nur ein Datum anzeigt, fügen Sie es zu NOISE hinzu. Falls eine Änderung an einer Klausel übersehen wurde, weil keep_sections die Überschrift entfernt hat, unter der sie stand, erweitern Sie die Zulassungsliste. Lassen Sie den Monitor eine Woche lang laufen und passen Sie die beiden Listen anhand der erzeugten Diffs an, bevor Sie ihm vertrauen.

Die Watchlist von der Realität abweichen lassen

Eine URL, die auf einen neuen Pfad weiterleitet, oder eine Dokumentationsseite, die unter ein versionsverwaltetes Präfix verschoben wurde, führt stillschweigend zu einem 404-Fehler oder einer anderen Seite. Protokollieren Sie Nicht-200-Status deutlich sichtbar und überprüfen Sie von Zeit zu Zeit die „skip“-Zeilen in der Ausgabe.

Nächster Schritt

Alles oben Genannte besteht aus der Python-Standardbibliothek plus einem HTTP-Aufruf pro Seite – mehr braucht die Erkennung von Änderungen an Dokumenten auf einer Website nicht. Der Teil, der sich nur schwer selbst hosten lässt, ist der Abruf selbst: eine saubere Markdown-Konvertierung, die mit JavaScript gerenderte Dokumentenseiten verarbeitet, den echten http_status-Wert der Quelle zurückgibt und nicht blockiert wird, wenn man jeden Morgen vierzig Anbieterseiten überprüft. Geonode und Scraper API bieten genau diesen Aufruf, zusammen mit render_js und wait_config für clientseitig gerenderte Seiten sowie einem kostenlosen Kontingent von 1,5K Anfragen pro Monat, das eine tägliche Beobachtungsliste problemlos abdeckt. Informationen zu den Tarifen finden Sie auf der Scraper API-Seite und zu entsprechenden Anwendungsmustern im Anwendungsfall „Web-Scraping“.

FAQ

Was ist die Erkennung von Website-Änderungen und wann ist sie nützlich?

Bei der Erkennung von Website-Änderungen wird eine Seite nach einem festgelegten Zeitplan abgerufen, mit dem vorherigen Snapshot verglichen und eine Benachrichtigung ausgelöst, wenn sich wesentliche Inhalte unterscheiden. Sie ist besonders nützlich für Seiten, auf die Sie angewiesen sind, die Sie aber nicht kontrollieren: API-Dokumentationen, Nutzungsbedingungen, Datenschutzrichtlinien, Preisbedingungen und Änderungsprotokolle von Anbietern, bei denen eine stillschweigende Änderung eine Integration oder eine Compliance-Annahme beeinträchtigen kann.

Warum Markdown statt rohem HTML vergleichen?

HTML enthält Layout, Skripte, Tracking-Parameter und Build-Hashes, die sich ändern, ohne dass sich der Text ändert. Durch die vorherige Konvertierung in Markdown wird die Seite auf ihren Inhalt reduziert, sodass ein Diff den bearbeiteten Satz anzeigt und nicht hundert Zeilen mit sich ändernden Attributen. Geonode Scraper API gibt Markdown direkt zurück, was die Pipeline kurz hält.

Wie vermeide ich Fehlalarme durch Datumsangaben, Cookie-Banner und Navigation?

Entfernen Sie bekannte Störfaktoren mit einer kleinen Auswahl regulärer Ausdrücke (Datumsangaben, „Zuletzt aktualisiert“-Zeilen, Cookie-Hinweise, Fußzeilen) und behalten Sie anschließend mithilfe einer Zulassungsliste für Überschriften nur die Abschnitte bei, die für Sie relevant sind. Normalisieren Sie Leerzeichen vor dem Hashen. Vergleichen Sie schließlich mit den letzten beiden Snapshots, damit eine vorübergehende Fehlerseite nicht von selbst einen Alarm auslöst.

Soll ich die Seite hashen oder einen Diff erstellen?

Beides. Ein Hash ist eine schnelle Gleichheitsprüfung, die entscheidet, ob überhaupt etwas unternommen werden soll. Ein „Unified Diff“ aus der diflib-Bibliothek ist das, was ein Mensch in der Benachrichtigung liest. Erstelle zuerst einen Hash, führe einen Diff nur durch, wenn sich der Hash unterscheidet, und sende den Diff zusammen mit der Benachrichtigung, damit der Leser die Änderung beurteilen kann, ohne die Seite öffnen zu müssen.

Wie oft sollte die Erkennung von Website-Änderungen laufen?

Passe die Häufigkeit an die Bedeutung und die Änderungshäufigkeit der Seite an. Rechtliche Hinweise und Richtlinien ändern sich selten öfter als ein paar Mal im Jahr, daher reicht eine tägliche Überprüfung völlig aus. Aktive API-Dokumentationen und Changelogs rechtfertigen stündliche Überprüfungen. Führen Sie die Überprüfung über Cron oder einen GitHub-Actions-Zeitplan aus und wählen Sie das Intervall so lang, dass ein fehlgeschlagener Abruf erneut versucht wird, bevor er gemeldet wird.

Was ist bei Compliance und Limits zu beachten?

Die Überwachung öffentlicher Dokumentations- und Richtlinienseiten in moderater Häufigkeit ist eine normale Nutzung des Internets. Lesen Sie jedoch die Nutzungsbedingungen und die robots.txt der Website, halten Sie sich daran und überwachen Sie keine Seiten hinter einer Authentifizierung, für die Sie keine Berechtigung haben. Auf der API-Seite umfasst die kostenlose Stufe „Scraper API“ 1.500 Anfragen pro Monat; eine tägliche Überprüfung von 40 Seiten passt in diesen Rahmen.