SteerAds
Google AdsTutorielOptimisation

AI Słowa kluczowe negatywne: discovery i clustering

Ręczna analiza raportu wyszukiwanych zapytań pomija 60-80% ukrytych wykluczeń. Oto odtwarzalny pipeline AI: embeddingi OpenAI lub sentence-transformers, klasteryzacja DBSCAN, scoring intent + cost wasted, eksport CSV do Google Ads Editor. W bonusie repozytorium Python open-source.

Andrew
AndrewSmart Bidding & Automation Lead
···9 min czytania

W benchmarkach Google Ads, ręczna analiza raportu zapytań wyszukiwania pomija 60-80% ukrytych wykluczeń po 30 minutach przeglądania przez doświadczonego PPC managera. A koszt tego przeoczenia jest mierzalny: 14-28% miesięcznego spend trafia w nieistotne zapytania, których nikt nie zobaczy w top 50 SQR (oficjalna dokumentacja Google Ads o wykluczeniach). To typowo zaniedbana dźwignia, która psuje stosunek CPA / Quality Score dojrzałego konta.

Oto diagnoza: człowiek skanujący SQR wykonuje wizualne dopasowanie wzorców na 200-500 pierwszych wierszach posortowanych według kosztu. Widzi (1) zapytania z długiego ogona, które ważą tylko 3-8 EUR każde, ale łącznie stanowią 18-26% spend, (2) semantyczne warianty dające się pogrupować ('opinie', 'recenzja', 'ocena', 'świadectwo' = ta sama intencja), oraz (3) klastry zapytań poza intencją komercyjną (praca, bezpłatne szkolenia, definicje, tanie produkty low-end), których algorytm Smart Bidding nie identyfikuje, gdy śledzenie konwersji opiera się na stałej wartości.

Ten przewodnik dostarcza odtwarzalny pipeline AI: ekstrakcja GAQL z SQR, embeddingi (OpenAI lub sentence-transformers), klasteryzacja DBSCAN, scoring intent + cost wasted, eksport CSV do Google Ads Editor. Cały kod jest w Pythonie, działa lokalnie, a publiczne repozytorium github.com/steerads/google-ads-negatives-ai zawiera Dockerfile i przykładowy workflow n8n. Fundamentalne zagadnienie typów dopasowania warunkujących ten pipeline opisano w naszym przewodniku po typach dopasowania 2026. Nasz kalkulator marnowanego budżetu szacuje EUR spalone miesięcznie przez broad bez wykluczeń lub nadmierne odbicia z LP.

Problem: 60-80% ukrytych wykluczeń przy ręcznej analizie

Ręczna analiza raportu zapytań wyszukiwania (SQR) jest jednym z najbardziej czasochłonnych zadań PPC managera. Na przeciętnym koncie (5 000-15 000 zapytań / 30 dni) trzeba liczyć 45 minut do 2 godzin na porządny przegląd. A nawet przy tym zainwestowanym czasie wskaźnik wykrywania trafnych wykluczeń pozostaje strukturalnie ograniczony przez zmęczenie poznawcze i sortowanie według malejącego kosztu.

Trzy systematyczne martwe punkty przy ręcznej analizie:

  1. Długi ogon jest niewidoczny. Pierwsze 50 zapytań posortowanych według kosztu skupia 35-50% spend. PPC manager je widzi. Ale pozostałe 4 950 zapytań — każde po 1-8 EUR — skupia 50-65% całkowitego spend i pozostaje niezbadane. To właśnie w tym ogonie ukrywają się powtarzające się nieistotne wzorce.
  2. Wizualne dopasowanie wzorców pomija semantyczne warianty. Człowiek identyfikuje 'opinie' jako kandydata na wykluczenie. Ale 'recenzja', 'ocena', 'świadectwo', 'feedback' mają tę samą intencję i pojawiają się w różnych zapytaniach. Bez embeddingów niemożliwe jest ich pogrupowanie.
  3. Brak scoringu marnowanego kosztu. Zapytanie z 25 EUR kosztu / 0 konwersji przez 30 dni ma większy wpływ niż zapytanie z 4 EUR kosztu / 0 konwersji — nawet jeśli to drugie pojawia się 6 razy częściej. Człowiek sortuje alfabetycznie lub według surowego kosztu, rzadko według cost wasted (cost / konwersje).
Insight — stosunek wykluczeń do spend :

W benchmarkach Google Ads wskaźnik odzyskiwalnego spend dzięki klasteryzacji AI wykluczeń zbiega się do bardzo odtwarzalnego progu: 12-22% miesięcznego spend po 60 dniach aktywnego pipeline. Na budżecie 30 000 EUR/miesiąc to od 3 600 do 6 600 EUR spend odzyskanego każdego miesiąca — nie ku konkurencji, lecz reinwestowanego w konwertujące zapytania. ROI w stosunku do zaoszczędzonego czasu PPC managera przekracza 25x dla większości obsługiwanych kont.

Pipeline AI opisany poniżej zastępuje ręczny przegląd odtwarzalnym workflow. Pierwsze uruchomienie zajmuje 30-45 minut (konfiguracja + uruchomienie + przegląd). Kolejne uruchomienia zajmują 5-15 minut (tylko przegląd próbki). Aby uzyskać bazę audytową, która powinna poprzedzać tę automatyzację, przeczytaj naszą checklistę audytu Google Ads.

Pipeline AI w 5 krokach: raport zapytań → lista wykluczeń

Kompletny pipeline łączy 5 deterministycznych kroków, z których każdy można testować oddzielnie. Celowo prosta architektura, bez ciężkich zależności ML poza scikit-learn i sentence-transformers / openai. Końcowy output to CSV bezpośrednio importowalny do Google Ads Editor.

Pre-processing zapytań wyszukiwania: krok, który zbyt często pomijamy

Przed embeddowaniem czegokolwiek, pre-processing SQR warunkuje 30-50% końcowej jakości klasteryzacji. Cztery transformacje do systematycznego stosowania. Pierwszy krok: normalizacja Unicode i wielkości liter. SQR Google Ads często zawiera fantomowe duplikaty: 'Wycena Ubezpieczenia', 'wycena ubezpieczenia', 'wycena ubezpieczenia' (podwójna spacja), 'wycena-ubezpieczenia' pojawiają się jako 4 różne zapytania, choć reprezentują to samo. Zastosowanie unicodedata.normalize('NFKC', s).strip().lower() plus regex \s+ -> ' ' łączy te duplikaty w jedną linię z zagregowanym kosztem i kliknięciami. Na przeciętnym koncie ta operacja redukuje wolumen zapytań o 8-14% bez utraty informacji.

Drugi krok: inteligentna deduplikacja. Poza prostą normalizacją, deduplikacja powinna obsługiwać morfologiczne warianty (liczba pojedyncza/mnoga, rodzaj, akcenty) w zależności od potrzeb. W klasteryzacji zachowanie surowych wariantów jest zazwyczaj bardziej przydatne (embedding mechanicznie je pogrupuje), ale w końcowym eksporcie wykluczeń, deduplikacja na representative_query klastra zapobiega pięciokrotnemu zaproponowaniu tego samego wykluczenia. Trzeci krok: filtrowanie jednorazowych zapytań o niskim koszcie. Zapytanie, które pojawia się tylko raz w ciągu 30 dni z kosztem 1,20 EUR, nie zasługuje na embedding (to czysty szum). Wstępne filtrowanie według clicks >= 1 AND cost >= 1 zapobiega zanieczyszczeniu klasteryzacji 25-40% nieredukowalnego szumu.

Czwarty krok: wykrywanie PII i wrażliwych terminów. Niektóre zapytania zawierają numery telefonów, e-maile lub imiona i nazwiska użytkowników (wpisane przez pomyłkę w pasek wyszukiwania zamiast URL). Takie zapytania nie mają wartości analitycznej, a ich przetwarzanie przez OpenAI byłoby problematyczne z punktu widzenia RODO. Filtruj wstępnie przez regex i NIGDY nie wysyłaj do zewnętrznego API. W zagregowanych danych Google Ads 2025-2026 ten typ zapytań stanowi 0,3-1,2% SQR — niski wolumen, ale krytyczny dla compliance.

[Google Ads API / CSV export]
    |
    v  Etape 1 — extraction SQR (GAQL ou CSV)
[search_terms.csv : query, clicks, cost, conv]
    |
    v  Etape 2 — embeddings (OpenAI ou ST)
[embeddings.npy : matrice 384/1536 dim]
    |
    v  Etape 3 — clustering DBSCAN ou HDBSCAN
[queries_clustered.csv : query, cluster_id]
    |
    v  Etape 4 — scoring intent + cost wasted
[clusters_scored.csv : cluster, waste_score, intent_score]
    |
    v  Etape 5 — filtrage + match type + export
[negatives_export.csv : pret pour Google Ads Editor]

Oto szkielet Python głównego runnera. Kompletny kod (z obsługą błędów, logami, parametrami CLI) znajduje się w publicznym repozytorium.

# main.py — pipeline complet en 5 etapes
import pandas as pd
import numpy as np
from embeddings import embed_queries
from clustering import cluster_dbscan
from scoring import score_clusters
from export import export_negatives_csv

def main(sqr_path: str, output_path: str, backend: str = "sentence-transformers"):
    # Etape 1 — chargement SQR
    df = pd.read_csv(sqr_path)
    df = df[df["clicks"] >= 1]  # filtrer les queries sans clic
    print(f"Loaded {len(df)} queries")

    # Etape 2 — embeddings
    embeddings = embed_queries(df["search_term"].tolist(), backend=backend)
    print(f"Embeddings shape: {embeddings.shape}")

    # Etape 3 — clustering
    df["cluster_id"] = cluster_dbscan(embeddings, eps=0.15, min_samples=5)
    n_clusters = df["cluster_id"].nunique() - (1 if -1 in df["cluster_id"].values else 0)
    print(f"Found {n_clusters} clusters")

    # Etape 4 — scoring
    clusters_scored = score_clusters(df, whitelist_path="whitelist.txt")

    # Etape 5 — export
    export_negatives_csv(clusters_scored, output_path,
                         min_cluster_size=3, min_waste_score=30)
    print(f"Exported negatives to {output_path}")

if __name__ == "__main__":
    main(
        sqr_path="data/search_terms.csv",
        output_path="output/negatives_export.csv",
        backend="sentence-transformers",  # ou "openai"
    )

Każdy krok generuje utrwalony artefakt (CSV lub .npy). Ułatwia to debugowanie: jeśli klasteryzacja generuje zbyt dużo szumu, można iterować na kroku 3 bez ponownego uruchamiania embeddingów (kosztownych w wywołaniach API). Zob. nasz przewodnik po automatyzacji Google Ads API w Pythonie dla szczegółów konfiguracji OAuth i ekstrakcji GAQL.

Embeddingi: OpenAI text-embedding-3 vs sentence-transformers

Embedding to kluczowy krok. Zwektoryzowane zapytanie pozwala mierzyć semantyczne podobieństwo między 'wycena ubezpieczenia auto' a 'stawka ubezpieczenia samochód' — nawet bez żadnego wspólnego słowa. Bez embeddingów klasteryzacja sprowadza się do dopasowania tokenów i pomija 70% trafnych grupowań.

Dwa istotne backendy w 2026: OpenAI text-embedding-3-small (hostowany, 1536 dim, płatny za użycie) i sentence-transformers all-MiniLM-L6-v2 lub multilingual-e5-base (lokalny, 384 lub 768 dim, bezpłatny). Wybór zależy od wolumenu i wrażliwości danych.

Wariant OpenAI — production-ready, zoptymalizowany batch:

# embeddings.py — backend OpenAI
from openai import OpenAI
import numpy as np
import os

def embed_queries_openai(queries: list[str], batch_size: int = 100) -> np.ndarray:
    client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
    all_embeddings = []

    for i in range(0, len(queries), batch_size):
        batch = queries[i : i + batch_size]
        response = client.embeddings.create(
            model="text-embedding-3-small",
            input=batch,
            encoding_format="float",
        )
        embeddings = [item.embedding for item in response.data]
        all_embeddings.extend(embeddings)

    return np.array(all_embeddings, dtype=np.float32)

Wariant sentence-transformers — lokalny, zero kosztów:

# embeddings.py — backend local sentence-transformers
from sentence_transformers import SentenceTransformer
import numpy as np

def embed_queries_st(queries: list[str], model_name: str = "all-MiniLM-L6-v2") -> np.ndarray:
    model = SentenceTransformer(model_name)
    embeddings = model.encode(
        queries,
        batch_size=64,
        show_progress_bar=True,
        normalize_embeddings=True,  # important pour cosine
    )
    return embeddings.astype(np.float32)


def embed_queries(queries: list[str], backend: str = "sentence-transformers") -> np.ndarray:
    if backend == "openai":
        return embed_queries_openai(queries)
    elif backend == "sentence-transformers":
        return embed_queries_st(queries, model_name="paraphrase-multilingual-MiniLM-L12-v2")
    else:
        raise ValueError(f"Unknown backend: {backend}")

Obserwacja z praktyki: na koncie e-commerce z 8 000 zapytań, delta jakości klastrów między OpenAI a e5-base wynosi około 6-9% precyzji (miara: liczba trafnych wykluczeń / liczba propozycji). W przypadku MiniLM v1 (angielski), delta rośnie do 12-18% — stąd wybór paraphrase-multilingual-MiniLM-L12-v2 dla polskich/wielojęzycznych danych. Dla konta w jednym języku to najlepszy kompromis koszt/jakość. Oficjalna dokumentacja OpenAI embeddings.

text-embedding-3-large vs text-embedding-3-small: kiedy large uzasadnia dodatkowy koszt? Trzy konkretne przypadki, w których text-embedding-3-large (3072 dim, 0,13 USD / 1M tokenów, czyli 6,5x droższy niż small) uzasadnia inwestycję. Przypadek 1: precyzja intencji w wyspecjalizowanych sektorach technicznych (medycyna, prawo, inżynieria), gdzie niuans między 'adwokat podatkowy' a 'doradca podatkowy' zmienia scoring intencji. Large osiąga mierzalnie 4-7% dodatkowej precyzji na kohorcie walidacyjnej. Przypadek 2: mieszana wielojęzyczność (PL + EN + DE na tym samym koncie), gdzie cross-językowa odporność large pozwala uniknąć podziału pipeline na trzy osobne przebiegi. Przypadek 3: bardzo wysoki wolumen (50 000+ zapytań miesięcznie), gdzie marginalny dodatkowy koszt pozostaje poniżej 1 EUR za uruchomienie bez znaczącego wpływu na P&L. Poza tymi trzema przypadkami, text-embedding-3-small pozostaje właściwym domyślnym wyborem dla konta mid-market.

Analiza kosztów API OpenAI na rzeczywistym przypadku. Dla konta przetwarzającego 8 000 zapytań o 4-8 słowach tygodniowo (średnio 32 tokeny na zapytanie, czyli 256 000 tokenów na uruchomienie), koszt uruchomienia w small wynosi 256 000 / 1 000 000 x 0,02 USD = 0,005 USD. Na 52 uruchomienia rocznie, to około 0,27 USD na konto rocznie. W przypadku large koszt wynosi 256 000 / 1 000 000 x 0,13 USD = 0,033 USD na uruchomienie, czyli około 1,72 USD rocznie. W obu przypadkach koszt API OpenAI jest marginalny w porównaniu z zaoszczędzonym czasem PPC managera. Klasyczna pułapka: uruchamianie bez batchowania (1 zapytanie na wywołanie zamiast 100 na batch), co mnoży koszt 8-12x ze względu na powtarzające się stałe opłaty per żądanie. Zawsze batchuj minimum 50-100 zapytań na wywołanie.

Klasteryzacja DBSCAN: dlaczego nie K-means

K-means to najpopularniejszy algorytm klasteryzacji, ale jest złym wyborem dla zapytań wyszukiwania Google Ads. Trzy powody techniczne:

  1. K-means wymaga z góry określenia K. Ile klastrów nieistotnych zapytań istnieje na Twoim koncie? Nikt tego nie wie przed analizą. K-means zmusza do zgadywania — złe K daje albo zbyt szerokie klastry (wymieszane intencje), albo zbyt wąskie (nadmierna fragmentacja).
  2. K-means przypisuje wszystkie punkty do klastra. Tymczasem 30-50% zapytań to szum (unikalne zapytania bez odpowiednika). DBSCAN identyfikuje je jako szum (klaster -1) zamiast wymuszać przypisanie do fikcyjnego klastra. Wynik: czystsze klastry.
  3. K-means zakłada sferyczne klastry o jednakowej gęstości. Klastry nieistotnych zapytań mają bardzo zmienną gęstość (bardzo gęsty klaster 'gratis' + rozproszony klaster 'porównanie konkurencji'). DBSCAN radzi sobie z tą wariancją, K-means nie.

DBSCAN (Density-Based Spatial Clustering of Applications with Noise) przyjmuje dwa parametry: eps (promień sąsiedztwa) i min_samples (minimalna liczba punktów do utworzenia klastra). Identyfikuje gęste obszary w przestrzeni embeddingów i grupuje bliskie punkty.

# clustering.py — DBSCAN
from sklearn.cluster import DBSCAN
import numpy as np

def cluster_dbscan(embeddings: np.ndarray, eps: float = 0.15, min_samples: int = 5) -> np.ndarray:
    # Cosine distance pour embeddings normalises
    clustering = DBSCAN(
        eps=eps,
        min_samples=min_samples,
        metric="cosine",
        n_jobs=-1,
    )
    labels = clustering.fit_predict(embeddings)
    return labels


def cluster_hdbscan(embeddings: np.ndarray, min_cluster_size: int = 5) -> np.ndarray:
    import hdbscan
    clustering = hdbscan.HDBSCAN(
        min_cluster_size=min_cluster_size,
        metric="euclidean",  # HDBSCAN ne supporte pas cosine direct
        cluster_selection_method="eom",
    )
    labels = clustering.fit_predict(embeddings)
    return labels

Kalibracja parametru eps: to próg podobieństwa cosinusowego, poniżej którego dwa zapytania są uważane za sąsiednie. Im mniejszy eps, tym węższe klastry. Punkt odniesienia z praktyki: zacznij od eps=0.15. Jeśli ponad 70% zapytań wychodzi jako szum (klaster -1), zwiększ do 0,20. Jeśli klastry są zbyt szerokie (klaster 'usługa' mieszający B2B i B2C), zmniejsz do 0,12.

HDBSCAN jako alternatywa, gdy DBSCAN generuje źle skalibrowane klastry. HDBSCAN automatycznie wykrywa lokalną gęstość i nie potrzebuje eps. Bardziej odporny na heterogeniczne datasety, ale wolniejszy i trudniejszy w debugowaniu.

Porada — wizualizuj klastry przed scoringiem :

Przed przejściem do scoringu zwizualizuj uzyskane klastry za pomocą UMAP w 2D. umap-learn redukuje embeddingi z 384 wymiarów do 2 wymiarów dla czytelnego wykresu punktowego. Od razu widać, czy klasteryzacja jest czysta (wyraźne, oddzielone klastry) czy zaszumiona (nakładające się klastry). Jeśli zaszumiona, dostosuj eps przed kontynuowaniem. 5 minut inwestycji oszczędza 30 minut bezużytecznego scoringu.

Walidacja klastrów: trzy metryki poza wizualnym UMAP

Wizualizacja UMAP jest przydatna, ale subiektywna. Aby ilościowo ocenić jakość klasteryzacji, trzy obiektywne metryki do systematycznego obliczania przed przejściem do scoringu. Metryka 1: silhouette score. Zaimplementowany w sklearn.metrics.silhouette_score, mierzy, jak blisko każdy punkt jest swojego własnego klastra w porównaniu z innymi klastrami. Wynik od -1 (katastrofalny) do +1 (doskonały). Silhouette powyżej 0,3 dla całego datasetu wskazuje na użyteczną klasteryzację; poniżej 0,15 trzeba przetuningować eps. W benchmarkach Google Ads, przeciętne konto e-commerce mid-size osiąga typowo 0,32-0,48 silhouette w sentence-transformers i 0,38-0,55 w OpenAI text-embedding-3-small.

Metryka 2: procent szumu. Oblicz stosunek (labels == -1).mean(). Dobra klasteryzacja DBSCAN na SQR Google Ads powinna mieć 25-45% szumu (nieprzypisanych zapytań). Poniżej 25%, eps jest zbyt szeroki i klastry są nadmiernie połączone. Powyżej 60%, eps jest zbyt restrykcyjny i większość zapytań jest odrzucana. Ta metryka jest bezpłatna w obliczeniu i służy jako zabezpieczenie przed degeneratywnymi konfiguracjami.

Metryka 3: medianowy rozmiar klastrów. Oblicz np.median([size for _, size in clusters_size]). Użyteczna klasteryzacja daje klastry o medianowym rozmiarze między 4 a 12 zapytaniami. Powyżej 20, klastry są zbyt szerokie i mieszają różne intencje. Poniżej 4, są zbyt wąskie i scoring staje się niestabilny. Jeśli medianowy rozmiar jest poza tym zakresem, dostosuj min_samples (DBSCAN) lub min_cluster_size (HDBSCAN) przed kontynuowaniem pipeline. W uruchomieniach produkcyjnych monitorowanie tych trzech metryk i logowanie ich dryftu pozwala wykrywać ciche regresje pipeline (zmiana API embeddingów, dryfт źródłowych zapytań) zanim wpłyną na proponowane wykluczenia.

Scoring: trafność intencji + cost wasted

Po klasteryzacji zapytań trzeba ocenić każdy klaster, aby zdecydować, które są kandydatami na wykluczenia. Scoring łączy dwa wymiary: (1) cost wasted (ile spend zostało zmarnowane bez konwersji) i (2) intent score (jak bardzo klaster odbiega od docelowej intencji komercyjnej).

Cost wasted jest trywialny do obliczenia: suma kosztu / max(suma konwersji, 0,1) na klaster. Im wyższy stosunek, tym klaster jest bardziej kandydatem do wykluczenia.

Intent score jest bardziej subtelny. Porównujemy średnie podobieństwo cosinusowe klastra do whitelisty docelowych słów kluczowych (Twoje słowa kluczowe marki + kluczowych produktów + silnych intencji zakupowych). Jeśli średnie podobieństwo klastra do whitelisty przekracza 0,65, to prawdopodobnie istotny klaster (NIE wykluczać). Jeśli poniżej 0,30, to klaster oddalony od intencji komercyjnej = silny kandydat na wykluczenie.

# scoring.py — scoring intent + cost wasted
import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

def score_clusters(df: pd.DataFrame, whitelist_path: str) -> pd.DataFrame:
    # Charger whitelist (1 keyword par ligne)
    with open(whitelist_path) as f:
        whitelist_keywords = [line.strip() for line in f if line.strip()]

    # Embedder la whitelist
    from embeddings import embed_queries
    whitelist_emb = embed_queries(whitelist_keywords, backend="sentence-transformers")

    # Aggreger par cluster
    cluster_stats = []

    for cluster_id, group in df.groupby("cluster_id"):
        if cluster_id == -1:
            continue  # ignorer le bruit

        cost = group["cost"].sum()
        conv = group["conversions"].sum()
        clicks = group["clicks"].sum()
        size = len(group)

        # waste_score : cout par conversion (0.1 floor pour eviter division par 0)
        waste_score = cost / max(conv, 0.1)

        # intent_score : similarite cosinus moyenne avec whitelist
        cluster_emb = np.stack(group["embedding"].values)
        sim_matrix = cosine_similarity(cluster_emb, whitelist_emb)
        intent_score = float(sim_matrix.max(axis=1).mean())

        # exemple representatif (query la plus centrale du cluster)
        centroid = cluster_emb.mean(axis=0)
        distances = np.linalg.norm(cluster_emb - centroid, axis=1)
        representative_idx = distances.argmin()
        representative_query = group.iloc[representative_idx]["search_term"]

        cluster_stats.append({
            "cluster_id": cluster_id,
            "size": size,
            "cost": round(cost, 2),
            "conversions": round(conv, 2),
            "clicks": clicks,
            "waste_score": round(waste_score, 2),
            "intent_score": round(intent_score, 3),
            "representative_query": representative_query,
            "negative_candidate": waste_score > 30 and intent_score < 0.30,
        })

    result = pd.DataFrame(cluster_stats)
    return result.sort_values("waste_score", ascending=False)

Interpretacja tabeli wyjściowej: sortuj według malejącego waste_score, następnie filtruj według intent_score < 0.30 ORAZ size >= 3. Ten trio identyfikuje klastry, które (1) marnują spend, (2) są odległe od intencji komercyjnej, i (3) mają wystarczająco zapytań, żeby uzasadnić wykluczenie (nie jednorazowe).

Progi referencyjne:

  • waste_score >= 30 (co najmniej 30 EUR kosztu bez konwersji) — odpowiada progowi istotności na większości kont.
  • intent_score < 0.30 (podobieństwo cosinusowe do whitelisty poniżej 30%) — powyżej, ryzyko fałszywego wykluczenia.
  • size >= 3 (co najmniej 3 zapytania w klastrze) — poniżej 3, to szum lub długi ogon, traktuj indywidualnie.

Te progi to punkty startowe. Dostosuj je na podstawie wyników ręcznego przeglądu próbki po 2-3 iteracjach.

Eksport CSV gotowy do Google Ads Editor

Końcowy output musi być bezpośrednio importowalny do Google Ads Editor — inaczej tracisz 30 minut na ręczne formatowanie. Oczekiwany format: CSV UTF-8, określone kolumny, jasny typ dopasowania, explicytna akcja ("Add" dla dodania).

# export.py — generation CSV Google Ads Editor
import pandas as pd

def export_negatives_csv(
    clusters_scored: pd.DataFrame,
    output_path: str,
    min_cluster_size: int = 3,
    min_waste_score: float = 30,
    max_intent_score: float = 0.30,
) -> None:
    # Filtrer les clusters candidats
    candidates = clusters_scored[
        (clusters_scored["size"] >= min_cluster_size)
        & (clusters_scored["waste_score"] >= min_waste_score)
        & (clusters_scored["intent_score"] < max_intent_score)
    ]

    rows = []
    for _, cluster in candidates.iterrows():
        # Determine match type selon la dispersion du cluster
        # (a parametrer plus finement en prod : ici simplification)
        match_type = "Phrase match" if cluster["size"] <= 8 else "Broad match"

        rows.append({
            "Action": "Add",
            "Campaign": "ALL",  # ou cluster["campaign"] si disponible
            "Ad group": "",  # vide = niveau campagne
            "Keyword": cluster["representative_query"],
            "Match type": match_type,
            "Status": "Enabled",
            "Comment": f"AI cluster #{cluster['cluster_id']} | "
                       f"size={cluster['size']} | "
                       f"waste={cluster['waste_score']} EUR | "
                       f"intent={cluster['intent_score']}",
        })

    out_df = pd.DataFrame(rows)
    out_df.to_csv(output_path, index=False, encoding="utf-8")
    print(f"Wrote {len(out_df)} negative candidates to {output_path}")

Procedura importu do Google Ads Editor: Google Ads Editor > File > Import > From CSV. Narzędzie wykrywa kolumny, weryfikuje format typu dopasowania i proponuje wizualny diff przed publikacją. Przejrzyj diff (próbka 30-50 linii), zatwierdź, następnie Post Changes. Oficjalna dokumentacja Google Ads Editor dostępna pod adresem support.google.com/google-ads/answer/2475106.

Typ dopasowania — heurystyka z praktyki:

  • Phrase match dla bardzo jednorodnych klastrów (rozmiar 3-8 zapytań, krótkie i podobne wyrażenia). Precyzyjne dopasowanie, niskie ryzyko przypadkowego zablokowania istotnego zapytania.
  • Broad match dla szerokich konceptów (rozmiar powyżej 15, zróżnicowane zapytania wokół tej samej intencji). Szerszy zakres, ale monitoruj po publikacji, aby wykryć nadmierne blokowania.
  • Exact match rzadko odpowiedni dla wykluczeń AI — przetwarzamy raczej koncepty niż dokładne zapytania.
Ostrzeżenie — obowiązkowy przegląd przed uploadem :

NIGDY nie uploaduj CSV bezpośrednio bez ręcznego przeglądu przy pierwszym uruchomieniu. Próbkuj 30-50 losowych linii, sprawdź, czy żaden termin marki / kluczowego produktu / docelowego słowa kluczowego nie pojawia się, zweryfikuj typy dopasowania. Jeśli ponad 10% próbki to fałszywe pozytywy, dostosuj progi (min_waste_score wyżej, max_intent_score niżej) i ponownie uruchom. Gdy pipeline jest zwalidowany po 2-3 iteracjach (wskaźnik błędów poniżej 5%), można go automatyzować w trybie auto.

Typowy ROI: ile spend odzyskujemy

Praktyczne pytanie: ile przynosi ten pipeline AI w porównaniu z ręczną analizą? Odpowiedź w liczbach z praktyki, mierzonych na kontach z benchmarków Google Ads.

Interpretacja: pipeline AI nie jest magiczny pod względem precyzji (uważny człowiek radzi sobie równie dobrze). Jego przewaga leży w recall — identyfikuje 75-88% trafnych wykluczeń vs 20-35% dla człowieka w 30 minut. A czas PPC managera spada z 30 min do 5-15 min na uruchomienie po wstępnej konfiguracji. Prawdziwy zysk jest skumulowany: przez 12 miesięcy to 4-6 godzin zaoszczędzonego czasu PPC managera ORAZ 12-22% lepiej wykorzystanego spend.

Liczba referencyjna: na koncie 30 000 EUR/miesiąc, odzyskanie 15% spend = 4 500 EUR/miesiąc przekierowanych do konwertujących zapytań. Przy tym samym CPA, to 4 500 / CPA dodatkowych konwersji w dolnym lejku każdego miesiąca. Przez 12 miesięcy, typowo między 230 a 380 dodatkowych konwersji bez zwiększania budżetu. ROI vs. inwestycja początkowa (45 min konfiguracji + klucz API embeddingów) przekracza 50x.

Kluczowy wskaźnik: recall podzielony przez zainwestowany czas. Dla ręcznej analizy 2h wskaźnik wynosi około 0,25 (50% recall / 2h). Dla pipeline AI wskaźnik wynosi około 6 (80% recall / 0,2h po konfiguracji). To 24x więcej efektywności w metryce, która naprawdę ma znaczenie — ile marnowanego spend identyfikujemy na godzinę pracy PPC managera. Ten wskaźnik poprawia się jeszcze przy kolejnych uruchomieniach: po 5. iteracji ręczny przegląd próbki skraca się do 15-25 wykluczeń (vs 60-80 przy pierwszym uruchomieniu), bo powtarzające się wzorce są absorbowane przez poprzednie klastry.

Praktyczna miara na 60 dni: konta, które aktywują pipeline w trybie cotygodniowym, osiągają stan stabilny, w którym większość nowych wykluczeń pochodzi z nowych wzorców (rosnąca konkurencja, sezonowe zapytania, semantyczna ewolucja odbiorców). Pipeline wykrywa te nowości w mniej niż 7 dni od pojawienia się, vs 30-60 dni przy ręcznym monitoringu — to okno 23-53 zaoszczędzonych dni marnowanego spend na każdy nowy wzorzec. To prawdziwa przewaga automatyzacji opartej na embeddingach: szybkość reakcji, a nie tylko jednorazowy recall.

Nasz silnik auto-optymalizacji zawiera ten pipeline w trybie zarządzanym: automatyczna ekstrakcja GAQL, hostowane embeddingi, klasteryzacja DBSCAN, scoring + filtrowanie, i cotygodniowe propozycje zatwierdzalne jednym kliknięciem. Połączenie OAuth w 2 minuty, pierwsza analiza w 5 minut. Publiczne repozytorium github.com/steerads/google-ads-negatives-ai dostępne dla tych, którzy wolą self-hostować, z Dockerfile i przykładowym workflow n8n. Komplementarność między tymi trzema podejściami — pipeline AI, natywne skrypty, n8n self-hosted — opisana w naszej kolekcji promptów ChatGPT Google Ads.

Częste błędy do unikania przy uprzemysłowieniu

Pięć błędów powraca regularnie przy wdrażaniu pipeline AI wykluczeń Google Ads do produkcji. Każdy generuje albo fałszywe wykluczenie (źle ukierunkowane wykluczenia blokujące istotny ruch), albo fałszywe pominięcie (przeoczone wykluczenia). Diagnoza i bezpośrednia korekta.

1. Uruchamianie pipeline bez whitelisty marki i docelowych produktów. Diagnoza: pipeline proponuje wykluczenia zawierające Twoje terminy marki lub nazwy kluczowych produktów, ponieważ są semantycznie słabo odróżnione od nieistotnych zapytań. Korekta: utrzymuj whitelist.txt z co najmniej wariantami marki (słowa proste + złożone), 10-20 nazwami docelowych produktów, i mocnymi terminami komercyjnymi. Ta whitelist służy do scoringu intencji (intent_score) i blokuje eksport każdego klastra, którego representative_query częściowo pasuje do whitelisty.

2. Mylenie exact match i phrase match w eksporcie wykluczeń. Diagnoza: eksportowanie klastra jako exact match na representative_query blokuje tylko dokładne zapytanie (10-15% klastra), pozostawiając 85-90% marnowanego spend. Korekta: używaj phrase match jako domyślnego dla spójnych klastrów, a broad match dla szerokich konceptów. Testuj przed publikacją w Keyword Plannerze dla proponowanego wykluczenia: jeśli szacowany wolumen przekracza 5x wolumen klastra źródłowego, typ dopasowania jest zbyt szeroki.

3. Zbyt częste uruchamianie pipeline generujące szum. Diagnoza: pipeline uruchamiany codziennie generuje dziesiątki propozycji dziennie, z których 60-70% to przeformułowania poprzednich propozycji. Zmęczenie walidacją popycha zespół do zatwierdzania wszystkiego bez przeglądu. Korekta: uruchamiaj cotygodniowo (poniedziałek rano), nie codziennie. 7-dniowe okno stabilizuje wzorce i eliminuje przedwczesne decyzje na jednorazowych zapytaniach. Dla kont o bardzo wysokiej częstości zmian (silna sezonowość), przejdź na dwutygodniowy maksymalnie, nigdy codzienny.

4. Ignorowanie małych klastrów o wysokim waste. Diagnoza: filtr min_cluster_size: 3 systematycznie wyklucza klastry 1-2 zapytań, które mogą nieść wysoki waste (np. jedno zapytanie za 80 EUR bez konwersji). Te izolowane wzorce prześlizgują się pod radarem pipeline. Korekta: dodaj drugi przebieg proponujący indywidualne zapytania powyżej waste_score = 60 niezależnie od przynależności do klastra. Te indywidualne propozycje są bardziej ryzykowne do auto-walidacji, więc przeglądaj je ręcznie przed eksportem.

5. Brak pomiaru wydajności po zastosowaniu. Diagnoza: wykluczenia są uploadowane, pipeline działa na autopilocie, ale nikt nie sprawdza, czy CPA i ROAS faktycznie się poprawiają. Korekta: śledź wskaźnik zdrowia pipeline: delta_cpa_30j_post_negatifs (zmiana CPA w 30 dniach po opublikowaniu N wykluczeń). Jeśli zmiana jest zerowa lub negatywna, proponowane wykluczenia były marginalne. Dostosuj progi min_waste_score i max_intent_score, aby targetować klastry o wyraźniejszym wpływie. W benchmarkach Google Ads, mierzalna poprawa pojawia się typowo w 3-5 pierwszych iteracjach, a następnie się stabilizuje.

Dla kont, które chcą uprzemysłowić bez samodzielnego uruchamiania pipeline, nasz moduł Auto-optymalizacji uruchamia pipeline embeddingów + klasteryzacji każdego tygodnia na Twoim koncie, proponuje zwalidowane kandydatury wykluczeń przez UI i stosuje je po przeglądzie. Połączenie OAuth w 2 minuty, pierwsza analiza w 5 minut. Publiczne repozytorium github.com/steerads/google-ads-negatives-ai dostępne dla tych, którzy wolą self-hostować, z Dockerfile i przykładowym workflow n8n.

Źródła

Oficjalne źródła wykorzystane w tym przewodniku:

FAQ

Czy trzeba koniecznie używać OpenAI, czy można pozostać w 100% open-source?

Możesz pozostać w 100% open-source. sentence-transformers (model all-MiniLM-L6-v2 lub multilingual-e5-base) działa lokalnie na CPU, bez klucza API i kosztów, z wystarczającą jakością do klasteryzacji zapytań. Mierzalna różnica względem OpenAI text-embedding-3-small: -8 do -14% precyzji na szczegółowych klastrach (np. rozróżnienie 'wycena' handlowe vs 'wycena' prawne), ale +zero euro kosztów API. Dla kont obsługujących mniej niż 5 000 zapytań/miesiąc sentence-transformers jest wystarczające. Powyżej tej granicy, lub przy przetwarzaniu wielojęzycznym z akcentami, text-embedding-3-small ma lepszy stosunek jakości do wysiłku. Pipeline obsługuje oba backendy przez zmienną środowiskową.

Jaka jest minimalna liczba zapytań, aby klasteryzacja była wiarygodna?

Praktyczny próg: minimum 500 zapytań w oknie analizy (zazwyczaj 30 dni). Poniżej tej liczby DBSCAN nie ma wystarczającej gęstości do identyfikacji stabilnych klastrów i większość zapytań wychodzi jako szum. Sweet spot: 2 000 do 10 000 zapytań na 30 dni. Powyżej 10 000 podziel według kampanii, aby uniknąć mieszania zbyt różnych intencji (B2B + B2C w tym samym klastrowaniu = szum). Jeśli konto ma mniej niż 500 zapytań/30 dni, rozszerz okno do 60 lub 90 dni.

DBSCAN czy HDBSCAN: który wybrać w praktyce?

HDBSCAN jest generalnie lepszy dla zapytań Google Ads: radzi sobie z klastrami o zmiennej gęstości, co odpowiada rzeczywistości (gęsty klaster 'gratis' + rozproszony klaster 'konkurencja'). DBSCAN wymaga ustalenia jednego parametru eps dla całego datasetu, co wymusza kompromisy. Jednak DBSCAN pozostaje prostszy w konfiguracji i szybszy na małych wolumenach (poniżej 5 000 zapytań). Rekomendacja: zacznij od DBSCAN z eps około 0,15 i min_samples równym 5, a następnie przejdź do HDBSCAN, jeśli klastry wydają się źle skalibrowane. Pipeline obsługuje oba algorytmy przez zmienną konfiguracyjną.

Jak ocenić jakość zaproponowanych wykluczeń przed ich uploadem?

Trzy obowiązkowe sprawdzenia przed każdym uploadem przez Google Ads Editor. (1) Ręczna próbka: przejrzyj 30-50 losowych wykluczeń, celuj w wskaźnik walidacji powyżej 90%. (2) Whitelist: sprawdź, czy żaden termin marki, kluczowego produktu lub docelowego słowa kluczowego nie pojawia się na liście (skrypt musi mieć whitelist jako wejście). (3) Typ dopasowania: sprawdź, czy eksportowany typ dopasowania odpowiada intencji (phrase match dla dokładnych wyrażeń, broad match dla konceptów). Jeśli poniżej 90% walidacji w próbce, dostosuj progi scoringu (cost_min, cluster_size_min) zamiast uploadować. Iteruj 2-3 razy przed przejściem do trybu auto.

Czy można zautomatyzować ten pipeline w cotygodniowym uruchomieniu na n8n lub Cloud Run?

Tak, i to jest cel końcowy w celu uprzemysłowienia. Pipeline Python działa w mniej niż 3 minuty na 5 000 zapytań (lokalny CPU) lub w mniej niż 60 sekund przez OpenAI batch embeddings + DBSCAN GPU. Możesz zaplanować go jako cron na Cloud Run, Lambda lub w workflow n8n. Zalecany wzorzec: cotygodniowe uruchomienie (poniedziałek rano), output w udostępnionym Google Sheet, ręczna walidacja próbki, upload przez Google Ads Editor (lub bezpośrednio przez Google Ads API jeśli zaufanie do pipeline jest wysokie). Repo github.com/steerads/google-ads-negatives-ai zawiera Dockerfile i przykładowy workflow n8n.

Ready to optimize your campaigns?

Start a free audit in 2 minutes and discover the ROI potential of your accounts.

Start my free audit

Free audit — no credit card required

Keep reading