LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Przegląd bloga /Digital Marketing/Tutorials / Automatyczny monitoring cen i stanów magazynowych dla...
Ten artykuł w innych językach:

Automatyczny monitoring cen i stanów magazynowych dla Meta Ads (Python & Skrypty)

Automatyczny monitoring cen i stanów magazynowych dla Meta Ads (Python & Skrypty)

Management Summary

Monitoring cen i zapasów porównuje dane produktów sklepu internetowego z reklamami Meta. Ma wskazywać reklamy wymagające wstrzymania, na przykład przy wyczerpaniu towaru lub nieaktualnej cenie w ofercie. Pomaga to ograniczać zbędne wydatki i promowanie niedostępnych produktów. Przedstawiony program przygotowuje propozycje zmian zamiast bezpośrednio przełączać kampanie. Potrzebne są aktualne dane i jednoznaczny związek produktu z reklamą: brak informacji nie oznacza pustego magazynu.

Spis treści
  1. 1. Najpierw właściwy obiekt
  2. 2. Reguły bez fałszywej pewności
  3. 3. Oddzielna funkcja decyzyjna
  4. 4. Osobne połączenie z API i wykonanie
  5. 5. Kontrola działania monitora
  6. Pytania i odpowiedzi
  7. Źródła

Monitor cen i stanów magazynowych może wykrywać reklamy niedostępnych produktów i przygotowywać zmiany. Potrzebuje aktualnych danych, jednoznacznego mapowania reklam i reguł obsługi istniejących wstrzymań. Przebieg godzinowy nie działa w czasie rzeczywistym ani natychmiastowo; nie gwarantuje poprawy rentowności lub całkowitego wyeliminowania zbędnych wydatków.

1. Najpierw właściwy obiekt

Reklama jednego produktu może odpowiadać jednemu SKU, czyli wewnętrznemu oznaczeniu konkretnego wariantu produktu w sklepie. Zestaw reklam może zawierać wiele reklam i produktów, więc wstrzymanie całego zestawu z powodu jednego SKU zatrzymuje również dostępne oferty. W reklamach katalogowych dostępność należy aktualizować w katalogu, na przykład przez availability; przyjęcie danych, przetwarzanie i rzeczywista emisja wymagają oddzielnych kontroli.

Sprawdzone mapowanie konta reklamowego, identyfikatora reklamy i SKU jest pewniejsze niż fragment nazwy. Nazwy mogą się zmieniać lub powtarzać. Reklamy katalogowe i wieloproduktowe pozostają poza poniższą regułą pojedynczego produktu.

Kontrola danych, propozycje i rzeczywiste zmiany statusu to oddzielne etapy.
Kontrola danych, propozycje i rzeczywiste zmiany statusu to oddzielne etapy.

2. Reguły bez fałszywej pewności

  • Stan: Dostępny zapas sprzedażowy musi uwzględniać rezerwacje i przyjętą obsługę przedsprzedaży. Brakujące lub nieaktualne dane nie oznaczają zera. Wartości ujemne wymagają jawnej reguły.
  • Cena: Porównywalne muszą być wariant, wielkość opakowania, waluta, podatek, dostawa i dostępność. Sama liczba pobrana ze strony nie wystarcza.
  • Próg: Osiem procent to przykładowa decyzja biznesowa, a nie udowodniona uniwersalna granica spadku konwersji. Względem własnej ceny obowiązuje (własna cena − cena konkurencji) / własna cena. Przy 100 € i 92 € wynik to dokładnie 8%; dzielenie przez cenę konkurencji daje około 8,70%.
  • Wznowienie: Poprawny stan nie może znosić ręcznego wstrzymania. Nawet własne wstrzymanie monitora wymaga sprawdzenia, czy nie pojawiła się inna blokada.

3. Oddzielna funkcja decyzyjna

Poniższy przykład Python zwraca wyłącznie propozycje. Nie pobiera feedu, nie odczytuje konta reklamowego i nie zmienia reklam. Wcześniejszy etap przygotowania dostarcza sprawdzone mapowanie jednego produktu, aktualny ustawiony status reklamy i jawny stan blokady ręcznej. manual_hold=False jest świadomie utrzymywaną informacją wejściową, a nie polem API Meta.

Ceny są tekstem z kropką dziesiętną, np. "19.95", stan nieujemną liczbą całkowitą, a znaczniki czasu zawierają strefę. Przykład ogranicza rząd wielkości ceny do wykładników dziesiętnych od −6 do 12 (wartości od 0,000001 do wartości mniejszych niż 10 000 000 000 000) i sprawdza format waluty jako trzech wielkich liter; rzeczywisty kod ISO oraz porównywalność biznesową weryfikuje przygotowanie danych. Maksymalny wiek danych wynoszący godzinę również jest przykładową regułą.

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 oznacza uzasadnioną propozycję wstrzymania. RESUME_REVIEW zleca ocenę możliwego wznowienia, nie jest statusem ACTIVE. REVIEW wskazuje błędne lub niepewne dane, a NO_CHANGE nie powoduje zmiany. Poprawny stan zerowy jest oceniany niezależnie od brakujących cen konkurencji. Przy dodatnim stanie brak porównania cen blokuje propozycję wznowienia.

4. Osobne połączenie z API i wykonanie

Adapter produkcyjny wymaga odpowiednich uprawnień aplikacji i przypisanych zasobów konta reklamowego. Ogólne zalecenie tworzenia systemowego administratora jest zbędne. Odczyt i zapis wymagają osobnego planowania; ads_management pozwala na zmiany. Token bez stałej daty wygaśnięcia również może stracić ważność. Poświadczenia nie powinny trafiać do kodu, wartości domyślnych ani logów.

Wersja Marketing API musi być wskazana jawnie i aktualizowana przed wycofaniem. Dokumentacja sprawdzona 24 października 2026 r. wskazuje v26.0; stare v20.0 zapisane na stałe nie jest pewną podstawą. Odczyt listy reklam wymaga wszystkich stron wyników. Większe limit nie zastępuje stronicowania. Błąd jednej strony nie może zamieniać częściowych wyników w pozornie pełny sukces.

  1. Bezpośrednio przed zmianą ponownie sprawdzić zatwierdzony identyfikator, konto, aktualność danych produktu, blokady ręczne i status. Plik propozycji może już być nieaktualny.
  2. Zapisywać wyłącznie jawnie wybrany status. Błędy API, niepoprawny JSON, timeouty i limity traktować jako niepowodzenie. Po niepewnym wyniku najpierw odczytać status, dopiero potem rozważyć ponowienie.
  3. Zapisać udaną zmianę i późniejsze porównanie statusu. Samo HTTP 200 nie potwierdza emisji. effective_status może różnić się od ustawionego statusu z powodu blokad nadrzędnych, weryfikacji lub innych problemów.
  4. Własne wstrzymania dokumentować w trwałym dzienniku: czas, poprzedni status, reguła i dane produktu. Wznowienie wymaga dodatkowego zatwierdzenia; równoległe zmiany ręczne nie mogą zostać nadpisane.

Automatyczne wznowienie wymaga ponadto kontroli procesów zapisujących, jawnej odpowiedzialności i strategii współbieżnych zmian. Samo pause_owner nie dowodzi spełnienia tych warunków. Dlatego przykład zwraca jedynie propozycję wznowienia także dla własnego wstrzymania.

5. Kontrola działania monitora

Harmonogram nie zastępuje monitoringu. Każdy przebieg wymaga zapisu początku, końca, odczytanych stron, wieku danych, liczby mapowań, propozycji i błędów. Puste feedy i brakujące mapowania wymagają jawnej oceny. Kontrola zewnętrzna musi wykrywać brak udanych przebiegów; alarmowanie tylko przy zmianie statusu nie wykryje awarii monitora.

Blokada wykonania pozwala na tylko jeden przebieg monitora w danej chwili. Integracja operacyjna wymaga też ograniczonej liczby ponowień z odstępem, bezpiecznej wymiany plików dziennika w celu ograniczenia ich rozmiaru oraz sprawdzonych powiadomień. Te elementy i rzeczywisty dostęp Meta pozostają poza czystą funkcją decyzyjną.

Pytania i odpowiedzi

Dlaczego ręcznie wstrzymana oferta nie wznawia się automatycznie?

Przyczyna może leżeć poza stanem i ceną, na przykład w trwającej kontroli lub decyzji kampanijnej. Dostępny zapas nie unieważnia tego powodu. Zatwierdzenie i dziennik zmian muszą uwzględniać aktualną sytuację.

Czy udany przebieg zapobiega wszystkim zbędnym wydatkom?

Nie. Zmiana zapasu, transmisja feedu, przetwarzanie, wywołanie API i rzeczywista emisja wprowadzają opóźnienia. Monitor może zmniejszyć tę lukę, lecz nie usuwa jej ani nie gwarantuje określonego ROAS. ROAS to przypisany przychód podzielony przez koszt reklamy. Skuteczność i fałszywe alarmy wymagają pomiarów na konkretnym koncie.

Lukas Wójcik

Lukas Wójcik

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

Masz pytanie lub pomysł na projekt internetowy? Napisz krótko, o co chodzi. Formularz korzysta z reCAPTCHA do ochrony przed spamem.

Napisanie komentarza

Doświadczenia z praktyki i pytania o wdrożenie są tu mile widziane.

Adres e-mail nie jest publikowany. Pola obowiązkowe oznaczono gwiazdką.