LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Blog-Übersicht /Digital Marketing/Tutorials / Automatisches Preis- und Bestands-Monitoring für Meta Ads...
Diesen Artikel in anderen Sprachen lesen:

Automatisches Preis- und Bestands-Monitoring für Meta Ads via Python & Scripts

Automatisches Preis- und Bestands-Monitoring für Meta Ads via Python & ScriptsAutomatisches Preis- und Bestands-Monitoring für Meta Ads via Python & Scripts

Management Summary

Eine Bestands- und Preisüberwachung vergleicht Produktdaten eines Onlineshops mit den zugehörigen Meta-Anzeigen. Das soll Werbung erkennen, die eine Pause braucht, etwa bei ausverkaufter Ware oder nicht mehr passenden Preisangaben. So lassen sich unnötige Ausgaben und veraltete Angebote begrenzen. Das vorgestellte Programm erstellt Änderungsvorschläge, statt Kampagnen unmittelbar umzuschalten. Aktuelle Daten und die eindeutige Verbindung zwischen Produkt und Anzeige sind entscheidend: Fehlende Angaben bedeuten keinen leeren Lagerbestand.

Inhalt
  1. 1. Zuerst das richtige Objekt bestimmen
  2. 2. Regeln ohne falsche Gewissheit
  3. 3. Eine isolierte Entscheidungsfunktion
  4. 4. API-Anbindung und tatsächliche Ausführung ergänzen
  5. 5. Den Monitor selbst überwachen
  6. Fragen und Antworten
  7. Quellen

Ein Preis- und Bestandsmonitor kann Anzeigen für nicht verfügbare Produkte erkennen und Änderungen vorbereiten. Dafür braucht der Prozess aktuelle Produktdaten, eine eindeutige Anzeigenzuordnung und klare Regeln für vorhandene Pausen. Ein stündlicher Lauf arbeitet weder in Echtzeit noch ohne Verzögerung; bessere Rendite oder vollständig vermiedene unnötige Ausgaben lassen sich daraus nicht garantieren.

1. Zuerst das richtige Objekt bestimmen

Eine einzelne Produktanzeige kann einer SKU zugeordnet werden, der internen Kennung einer bestimmten Produktvariante im Shop. Eine Anzeigengruppe kann dagegen mehrere Anzeigen und Produkte enthalten. Das Pausieren der gesamten Gruppe wegen einer SKU trifft dann auch verfügbare Angebote. Bei Katalog-Anzeigen gehört die Produktverfügbarkeit in den Katalog, etwa über availability; Datenannahme, Verarbeitung und tatsächliche Auslieferung benötigen eigene Kontrollen.

Eine geprüfte Zuordnung aus Werbekonto, Anzeigen-ID und SKU ist belastbarer als ein Namensfragment. Namen können geändert oder mehrfach verwendet werden. Katalog-Anzeigen und Anzeigen mit mehreren Produkten bleiben außerhalb der unten gezeigten Einzelproduktregel.

Datenprüfung, Änderungsvorschlag und tatsächliche Statusänderung sind getrennte Schritte.
Datenprüfung, Änderungsvorschlag und tatsächliche Statusänderung sind getrennte Schritte.

2. Regeln ohne falsche Gewissheit

  • Bestand: Verfügbarer verkaufbarer Bestand muss Reservierungen und den vorgesehenen Umgang mit Vorbestellungen berücksichtigen. Fehlende oder veraltete Daten bedeuten nicht null Bestand. Negative Werte benötigen eine ausdrücklich definierte Behandlung.
  • Preis: Varianten, Packungsgröße, Währung, Steuer, Versand und Lieferbarkeit müssen vergleichbar sein. Ein automatisch aus einer Webseite ausgelesener Zahlenwert allein genügt nicht.
  • Schwelle: Acht Prozent sind eine Beispielentscheidung, keine belegte allgemeine Grenze für Conversion-Verluste. Bezogen auf den eigenen Preis gilt (eigener Preis − Vergleichspreis) / eigener Preis. Bei 100 € und 92 € ergibt das genau 8 %; mit dem Vergleichspreis als Nenner wären es rund 8,70 %.
  • Reaktivierung: Eine manuelle Pause darf nicht durch einen guten Lagerbestand aufgehoben werden. Auch eine vom Monitor verursachte Pause benötigt einen Nachweis, dass keine andere Sperre hinzugekommen ist.

3. Eine isolierte Entscheidungsfunktion

Das folgende Python-Beispiel gibt ausschließlich Vorschläge zurück. Es lädt keinen Produktfeed, liest kein Werbekonto und verändert keine Anzeige. Die vorgelagerte Datenaufbereitung liefert eine geprüfte Einzelproduktzuordnung, den aktuellen konfigurierten Anzeigenstatus und einen expliziten manuellen Sperrzustand. manual_hold=False ist eine bewusst gepflegte Eingabe, kein Meta-API-Feld.

Geldwerte kommen als Text mit Dezimalpunkt, etwa "19.95", Bestand als nicht negative ganze Zahl und Zeitpunkte mit Zeitzone. Das Beispiel begrenzt Preisgrößen auf einen Dezimalexponenten von −6 bis 12 (Werte ab 0,000001 und kleiner als 10.000.000.000.000) und erwartet dreistellige Großbuchstaben als Währungsformat; die tatsächliche ISO-Währung und fachliche Vergleichbarkeit prüft die Datenaufbereitung. Die maximale Datenalterung von einer Stunde ist ebenfalls eine Beispielregel.

Python
from datetime import datetime, timezone
from decimal import Decimal, InvalidOperation

MAX_AGE_SECONDS = 3600  # Example policy, not a Meta requirement.
PRICE_GAP = Decimal("0.08")


def fresh(value, now):
    try:
        stamp = datetime.fromisoformat(value.replace("Z", "+00:00"))
        if stamp.tzinfo is None:
            return False
        return 0 <= (now - stamp).total_seconds() <= MAX_AGE_SECONDS
    except (AttributeError, TypeError, ValueError):
        return False


def positive_money(value):
    if not isinstance(value, str) or len(value) > 40:
        raise ValueError("Money must be a decimal string")
    amount = Decimal(value)
    if not amount.is_finite() or amount <= 0 or not -6 <= amount.adjusted() <= 12:
        raise ValueError("Invalid price")
    return amount


def propose(item, ad, now):
    """Pure decision function: no API calls and no status writes."""
    if now.tzinfo is None:
        raise ValueError("now must include a timezone")
    if (ad.get("kind") != "single_product"
            or ad.get("sku") != item.get("sku")
            or not isinstance(item.get("sku"), str) or not item["sku"]
            or not isinstance(ad.get("id"), str) or not ad["id"].isascii()
            or not ad["id"].isdigit()):
        return "REVIEW", "mapping"
    if ad.get("manual_hold") is not False:
        return "REVIEW", "manual_or_unknown_hold"
    status = ad.get("status")
    if status not in {"ACTIVE", "PAUSED"}:
        return "REVIEW", "unsupported_status"
    stock = item.get("stock")
    if type(stock) is not int or stock < 0:
        return "REVIEW", "invalid_stock"
    if not fresh(item.get("stock_at"), now):
        return "REVIEW", "stale_stock"
    reason = None
    if stock == 0:
        reason = "out_of_stock"
    else:
        if (item.get("comparable") is not True
                or item.get("currency") != item.get("competitor_currency")
                or not isinstance(item.get("currency"), str)
                or len(item["currency"]) != 3
                or not item["currency"].isascii()
                or not item["currency"].isalpha()
                or item["currency"] != item["currency"].upper()
                or not fresh(item.get("price_at"), now)
                or not fresh(item.get("competitor_at"), now)):
            return "REVIEW", "incomparable_or_stale_prices"
        try:
            own = positive_money(item.get("price"))
            other = positive_money(item.get("competitor_price"))
        except (ValueError, InvalidOperation):
            return "REVIEW", "invalid_price"
        # Competitor discount relative to the shop's own price.
        if (own - other) / own > PRICE_GAP:
            reason = "price_gap"
    if reason:
        return ("PAUSE_REVIEW" if status == "ACTIVE" else "NO_CHANGE"), reason
    if status == "PAUSED":
        if ad.get("pause_owner") == "inventory_monitor":
            return "RESUME_REVIEW", "conditions_recovered"
        return "NO_CHANGE", "pause_not_owned"
    return "NO_CHANGE", "conditions_ok"

PAUSE_REVIEW bedeutet einen begründeten Pausenvorschlag. RESUME_REVIEW ist ein Prüfauftrag zur möglichen Wiederaufnahme, kein Status ACTIVE. REVIEW kennzeichnet unbrauchbare oder unklare Eingaben; NO_CHANGE löst keine Änderung aus. Das Beispiel betrachtet gültigen Nullbestand unabhängig von fehlenden Vergleichspreisen. Bei positivem Bestand verhindert ein fehlender Preisvergleich eine Freigabeempfehlung.

4. API-Anbindung und tatsächliche Ausführung ergänzen

Für einen produktiven Adapter benötigt die Anwendung passende App-Berechtigungen und zugewiesene Werbekonto-Ressourcen. Eine pauschale Admin-Systemnutzer-Empfehlung ist unnötig. Lesezugriff und Schreibzugriff sind getrennt zu planen; ads_management ermöglicht Änderungen. Auch ein Token ohne festen Ablauftermin kann ungültig werden. Zugangsdaten gehören nicht in Quellcode, Beispielwerte oder Logs.

Die Marketing-API-Version muss ausdrücklich festgelegt und vor Ablauf aktualisiert werden. Die am 24. Oktober 2026 geprüfte Referenz nennt v26.0; ein altes fest eingebautes v20.0 ist keine belastbare Grundlage. Beim Laden einer Anzeigenliste müssen alle Ergebnisseiten verarbeitet werden. Ein größeres limit ersetzt das Abrufen weiterer Ergebnisseiten nicht. Scheitert eine Seite, darf das Teilergebnis nicht als vollständiger erfolgreicher Lauf gelten.

  1. Vor einer Änderung freigegebene Anzeigen-ID, Kontozuordnung, frische Produktdaten, manuelle Sperren und aktuellen Status erneut prüfen. Eine Vorschlagsdatei kann inzwischen veraltet sein.
  2. Nur den ausdrücklich ausgewählten Status schreiben. API-Fehler, ungültige JSON-Antworten, Zeitüberschreitungen und Rate-Limits als Fehler behandeln. Nach unklarem Ausgang zuerst den Status lesen, bevor eine Wiederholung erfolgt.
  3. Erfolgreiche Änderung und anschließenden Statusabgleich dokumentieren. HTTP 200 allein ist kein Auslieferungsnachweis. effective_status kann wegen übergeordneter Pausen, Prüfung oder anderer Probleme vom gesetzten Status abweichen.
  4. Eigene Pausen mit Zeitpunkt, vorherigem Status, Regel und Produktdatennachweis in einem dauerhaften Journal festhalten. Wiederaufnahme nur nach zusätzlicher Freigabe; konkurrierende manuelle Änderungen dürfen nicht überschrieben werden.

Automatische Reaktivierung benötigt darüber hinaus einen geregelten Schreibzugriff, eine ausdrückliche Zuständigkeit und eine Strategie für konkurrierende Änderungen. Ein bloßes Feld pause_owner beweist diese Voraussetzungen nicht. Das Beispiel liefert deshalb auch bei einer eigenen Pause lediglich einen Wiederaufnahmevorschlag.

5. Den Monitor selbst überwachen

Ein Zeitplan ersetzt keine Betriebsüberwachung. Zu jedem Lauf gehören Start, Ende, gelesene Seiten, Datenalter, Zahl geprüfter Zuordnungen, Vorschläge und Fehler. Leere Feeds und fehlende Zuordnungen benötigen eine ausdrücklich festgelegte Bewertung. Ein externer Prüfer muss ausbleibende erfolgreiche Läufe erkennen; ein Alarm nur bei Statuswechseln übersieht einen abgestürzten Monitor.

Eine Ausführungssperre lässt nur einen Monitorlauf gleichzeitig zu. Zur Betriebsintegration gehören außerdem eine begrenzte Zahl von Wiederholungen mit Wartezeit, ein sicherer Wechsel der Protokolldateien zur Größenbegrenzung und geprüfte Benachrichtigungen. Diese Komponenten und ein tatsächlicher Meta-Zugriff sind nicht Bestandteil der reinen Entscheidungsfunktion.

Fragen und Antworten

Warum wird ein manuell pausiertes Angebot nicht automatisch aktiviert?

Der Pausengrund kann außerhalb von Bestand und Preis liegen, etwa in einer laufenden Prüfung oder Kampagnenentscheidung. Ein weiterhin vorhandener Bestand widerlegt diesen Grund nicht. Freigabe und Änderungsjournal müssen den aktuellen Zustand berücksichtigen.

Verhindert ein erfolgreicher Lauf alle unnötigen Ausgaben?

Nein. Zwischen Bestandsänderung, Feedübertragung, Verarbeitung, API-Aufruf und tatsächlicher Auslieferung liegen Verzögerungen. Ein Monitor kann diese Lücke verkleinern, aber weder auf null setzen noch einen bestimmten ROAS garantieren. Der ROAS setzt den zugerechneten Umsatz ins Verhältnis zu den Werbekosten. Wirkung und Fehlalarme benötigen Messungen am konkreten Konto.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Kontakt

Eine Frage oder eine Idee für ein Webprojekt? Schreib mir kurz, worum es geht. Das Formular nutzt reCAPTCHA und formconfirmed zum Schutz vor Spam.

Kommentar schreiben

Erfahrungen aus der Praxis und Rückfragen zur Umsetzung sind hier willkommen.

Die E-Mail-Adresse wird nicht veröffentlicht. Pflichtfelder sind mit einem Stern versehen.