Skript 1 · nur lesend

Der Stale-Copy-Wächter für dein Konto.

Findet abgelaufene Jahreszahlen, Aktionen außerhalb ihrer Saison und vergangene Fristen in den Titeln und Beschreibungen aktiver Anzeigen. Dieses Skript verändert nichts in deinem Konto: Es liest, es protokolliert, es schreibt nicht.

Der Defekt dahinter

Warum das im Konto nicht auffällt

Eine Anzeige mit „Sommer-Aktion bis 31.08.“ läuft im November genauso weiter wie im Juli. Google prüft keine Kalender, und die Anzeige ist nicht abgelehnt — sie ist nur falsch. Das Konto sieht dabei kerngesund aus.

Was das Skript findet

Abgelaufene Jahreszahlen
„Preise 2025“ im Jahr 2026. Belege wie „seit 2008“ oder „gegründet 2014“ bleiben unangetastet, ebenso Mengen wie „2000+ Kunden“ — sonst markiert die Prüfung genau die Zeilen, die stimmen.
Aktionen außerhalb ihrer Saison
Ein Saisonwort zählt erst, wenn im selben Text ein Aktionswort steht. „Wintergarten“ ist ein Produkt, „Winter-Sale“ im Juni ist ein Fund. Die Monatstabelle steht im CONFIG-Block und zieht den Vorlauf mit ein.
Vergangene Fristen
„bis 31.08.“, „nur noch bis zum 15. Juli“, „gültig bis 30.09.2026“ — auf den Tag genau gegen heute geprüft. Fehlt die Jahreszahl, gilt das laufende Jahr; liegt das Datum dann mehr als ein halbes Jahr zurück, ist der kommende Termin gemeint.

Der komplette Code

Alles, was du einfügst — nichts dahinter

434 Zeilen, eine Datei, keine Abhängigkeiten. Die Einstellungen stehen im CONFIG-Block ganz oben; der E-Mail-Versand ist ab Werk aus. Getestet gegen Google Ads Scripts, Stand 08/2026.

stale-copy-waechter.js

Version 1.0.0 · Stand 2026-08-30 · 434 Zeilen

Als Datei
/**
 * STALE-COPY-WAECHTER — Google Ads Script
 * Version 1.0.0 · Stand 2026-08-30
 * https://visnakovs.de/google-ads-scripts/stale-copy-waechter/
 *
 * Findet Anzeigentexte, die die Zeit ueberholt hat: abgelaufene Jahreszahlen,
 * Saison- und Feiertagsaktionen ausserhalb ihrer Saison und Fristen, die
 * vorbei sind. Geprueft werden die Titel und Beschreibungen aller aktiven
 * Responsive Suchanzeigen in aktiven Anzeigengruppen aktiver Kampagnen.
 *
 * ⛔ GARANTIE — Dieses Skript verändert nichts in deinem Konto: Es liest, es
 * protokolliert, es schreibt nicht. Es kommt kein einziger Aufruf darin vor,
 * der eine Anzeige, ein Keyword, ein Gebot, ein Budget oder ein Label
 * anfassen koennte. Die einzigen Ausgaben sind das Protokoll und - wenn du
 * es unten einschaltest - eine E-Mail an dich selbst.
 *
 * Getestet gegen Google Ads Scripts, Stand 08/2026.
 *
 * Umlaute stehen in diesem Code bewusst als ae/oe/ue: der Script-Editor
 * zeigt kopierte Dateien je nach Browser in wechselnder Kodierung an, und
 * ein kaputtes Sonderzeichen in einem Suchmuster faellt still aus. In den
 * Suchtabellen unten stehen die echten Umlaute - dort werden sie gebraucht.
 */

// ---------------------------------------------------------------------------
// CONFIG — alles, was du anfassen solltest, steht hier oben.
// ---------------------------------------------------------------------------
var CONFIG = {
  // E-Mail-Zusammenfassung. Bewusst AUS: ein Skript, das beim ersten Lauf
  // ungefragt Post verschickt, ist ein schlechter Gast. Erst pruefen, was im
  // Protokoll steht, dann einschalten.
  EMAIL_SENDEN: false,
  EMPFAENGER: 'dein.name@beispiel.de',
  BETREFF: 'Google Ads: veraltete Anzeigentexte',

  // Erst ab so vielen Funden ueberhaupt eine Mail. 0 = auch die Entwarnung
  // schicken (sinnvoll, wenn du das Ausbleiben der Mail nicht als Ergebnis
  // lesen willst - ein stiller Lauf sieht aus wie ein gruener Lauf).
  EMAIL_AB_FUNDEN: 1,

  // Die drei Pruefungen lassen sich einzeln abschalten.
  PRUEFE_JAHRESZAHLEN: true,
  PRUEFE_SAISON: true,
  PRUEFE_FRISTEN: true,

  // Leer = heute. 'JJJJ-MM-TT' setzt ein anderes Datum, um eine Regel zu
  // testen, ohne auf den Monat zu warten (z. B. '2026-12-01' fuer Weihnachten).
  STICHTAG: '',

  // Sicherheitsnetz gegen das 30-Minuten-Limit in sehr grossen Konten.
  MAX_ANZEIGEN: 20000,

  // Wie viele Funde maximal ins Protokoll geschrieben werden. Google kuerzt
  // das Protokoll bei 100 KB - danach faellt der Rest ohne Hinweis weg.
  MAX_PROTOKOLL_ZEILEN: 200
};

// ---------------------------------------------------------------------------
// Saisonwoerter: Wort -> Monate, in denen es in Ordnung ist.
// Die Monatszahlen ziehen den Vorlauf mit ein (eine Weihnachtsaktion darf im
// November laufen). Umgekehrt gelesen ist das deine Monat-zu-Saison-Tabelle:
//   Jan  Winter, Silvester, Neujahr, Valentinstag
//   Feb  Winter, Valentinstag, Fruehling, Ostern, Frauentag
//   Mrz  Fruehling, Ostern, Frauentag
//   Apr  Fruehling, Ostern, Muttertag, Vatertag
//   Mai  Fruehling, Sommer, Muttertag, Vatertag
//   Jun  Sommer
//   Jul  Sommer
//   Aug  Sommer, Herbst
//   Sep  Herbst, Oktoberfest, Halloween
//   Okt  Herbst, Oktoberfest, Halloween, Black Friday, Cyber Monday, Singles Day
//   Nov  Herbst, Winter, Black Friday, Cyber Monday, Singles Day, Weihnachten, Advent
//   Dez  Winter, Weihnachten, Advent, Silvester, Neujahr
// ---------------------------------------------------------------------------
var SAISONWOERTER = {
  'frühling': [2, 3, 4, 5],
  'fruehling': [2, 3, 4, 5],
  'frühjahr': [2, 3, 4, 5],
  'spring': [2, 3, 4, 5],
  'sommer': [5, 6, 7, 8],
  'summer': [5, 6, 7, 8],
  'herbst': [8, 9, 10, 11],
  'autumn': [8, 9, 10, 11],
  'winter': [11, 12, 1, 2],
  'black friday': [10, 11],
  'black week': [10, 11],
  'cyber monday': [10, 11],
  'singles day': [10, 11],
  'weihnacht': [11, 12],
  'christmas': [11, 12],
  'advent': [11, 12],
  'silvester': [12, 1],
  'neujahr': [12, 1],
  'new year': [12, 1],
  'valentinstag': [1, 2],
  'valentine': [1, 2],
  'ostern': [2, 3, 4],
  'easter': [2, 3, 4],
  'frauentag': [2, 3],
  'muttertag': [4, 5],
  'vatertag': [4, 5],
  'oktoberfest': [9, 10],
  'halloween': [9, 10]
};

// Ein Saisonwort allein ist kein Fund. "Wintergarten" ist ein Produkt,
// "Winter-Sale" ist eine Aktion. Erst wenn im selben Text ein Aktionswort
// steht, wird ein Saisonwort ausserhalb seiner Monate gemeldet.
var PROMO_KONTEXT = /(sale|angebot|aktion|rabatt|deal|spar|%|gutschein|prozent|reduziert|sichern|sonderpreis|tiefpreis|schnäppchen|schnaeppchen|offer|discount|save)/i;

var MONATSNAMEN = {
  'januar': 1, 'jänner': 1, 'january': 1, 'jan': 1,
  'februar': 2, 'february': 2, 'feb': 2,
  'märz': 3, 'maerz': 3, 'march': 3, 'mrz': 3, 'mar': 3,
  'april': 4, 'apr': 4,
  'mai': 5, 'may': 5,
  'juni': 6, 'june': 6, 'jun': 6,
  'juli': 7, 'july': 7, 'jul': 7,
  'august': 8, 'aug': 8,
  'september': 9, 'sept': 9, 'sep': 9,
  'oktober': 10, 'october': 10, 'okt': 10, 'oct': 10,
  'november': 11, 'nov': 11,
  'dezember': 12, 'december': 12, 'dez': 12, 'dec': 12
};

// "bis 31.08.", "nur noch bis zum 31. August", "gueltig bis 31.08.2026"
var FRIST_PRAEFIX = '(?:bis|endet\\s+am|l(?:ä|ae)uft\\s+bis|g(?:ü|ue)ltig\\s+bis)';
var FRIST_ZAHL_RE = new RegExp(FRIST_PRAEFIX + '\\s+(?:zum\\s+)?(\\d{1,2})\\.\\s*(\\d{1,2})\\.\\s*(\\d{4}|\\d{2})?', 'gi');
var FRIST_WORT_RE = new RegExp(FRIST_PRAEFIX + '\\s+(?:zum\\s+)?(\\d{1,2})\\.?\\s*([a-zA-ZäöüÄÖÜ]+)\\s*(\\d{4})?', 'gi');

var JAHR_RE = /20\d{2}/g;
// "seit 2008", "gegruendet 2014", "© 2019" sind Belege, keine Ablaufdaten.
var JAHR_ALS_BELEG = /(seit|since|ab|©|est\.?|gegründet|gegruendet)\s*$/i;
// "2000+ Kunden", "5000 Paare" sind Mengen, keine Jahreszahlen.
var JAHR_ALS_MENGE = /^(\+|\s*(kunden|kund|customers|paare|mitglieder|projekte|jahre|teilnehmer|sterne|bewertungen|reviews|m²|qm))/i;

var MS_PRO_TAG = 24 * 60 * 60 * 1000;

// ---------------------------------------------------------------------------

function main() {
  var heute = stichtag();
  var jahr = heute.getUTCFullYear();
  var monat = heute.getUTCMonth() + 1;

  Logger.log('Stale-Copy-Waechter 1.0.0 — Konto ' + AdsApp.currentAccount().getCustomerId());
  Logger.log('Stichtag: ' + alsIsoDatum(heute) + ' (Monat ' + monat + ')');
  Logger.log('Dieses Skript liest nur. Es schreibt nichts ins Konto.');
  Logger.log('');

  var funde = [];
  var gelesen = 0;
  var rsaGelesen = 0;

  var anzeigen = AdsApp.ads()
    .withCondition('ad_group_ad.status = ENABLED')
    .withCondition('ad_group.status = ENABLED')
    .withCondition('campaign.status = ENABLED')
    .get();

  while (anzeigen.hasNext() && gelesen < CONFIG.MAX_ANZEIGEN) {
    var anzeige = anzeigen.next();
    gelesen++;
    if (!istResponsiveSuchanzeige(anzeige)) continue;
    rsaGelesen++;

    var texte = textbausteine(anzeige);
    for (var t = 0; t < texte.length; t++) {
      var treffer = pruefeText(texte[t].text, heute, jahr, monat);
      for (var f = 0; f < treffer.length; f++) {
        funde.push({
          kampagne: nameVon(anzeige.getCampaign()),
          anzeigengruppe: nameVon(anzeige.getAdGroup()),
          feld: texte[t].feld,
          text: texte[t].text,
          marker: treffer[f].marker,
          grund: treffer[f].grund,
          art: treffer[f].art
        });
      }
    }
  }

  berichte(funde, gelesen, rsaGelesen, heute);
}

// --- Erkennung -------------------------------------------------------------

/**
 * Alle Funde in einem einzelnen Textbaustein. Gibt eine Liste aus
 * {art, marker, grund} zurueck - leere Liste heisst sauber.
 */
function pruefeText(text, heute, jahr, monat) {
  var funde = [];
  if (!text) return funde;

  if (CONFIG.PRUEFE_SAISON && PROMO_KONTEXT.test(text)) {
    var klein = text.toLowerCase();
    for (var wort in SAISONWOERTER) {
      if (!SAISONWOERTER.hasOwnProperty(wort)) continue;
      var monate = SAISONWOERTER[wort];
      if (enthaelt(monate, monat)) continue;
      if (!saisonwortKommtVor(klein, wort)) continue;
      funde.push({
        art: 'Saison',
        marker: wort,
        grund: 'Saisonwort "' + wort + '" liegt ausserhalb seiner Monate (' +
          monate.join(', ') + ') — heute ist Monat ' + monat
      });
    }
  }

  if (CONFIG.PRUEFE_FRISTEN) {
    var fristen = fristenIn(text, jahr);
    for (var i = 0; i < fristen.length; i++) {
      if (fristen[i].datum.getTime() >= heute.getTime()) continue;
      funde.push({
        art: 'Frist',
        marker: fristen[i].roh,
        grund: 'Frist ' + alsIsoDatum(fristen[i].datum) + ' ist vorbei (heute ' +
          alsIsoDatum(heute) + ')'
      });
    }
  }

  if (CONFIG.PRUEFE_JAHRESZAHLEN) {
    JAHR_RE.lastIndex = 0;
    var m;
    while ((m = JAHR_RE.exec(text)) !== null) {
      var start = m.index;
      var ende = start + m[0].length;
      // Teil einer laengeren Zahl (Artikelnummer, Telefonnummer)? Dann nicht.
      if (/\d/.test(text.charAt(start - 1)) || /\d/.test(text.charAt(ende))) continue;
      var gefunden = parseInt(m[0], 10);
      if (gefunden >= jahr) continue;
      if (JAHR_ALS_BELEG.test(text.substring(0, start))) continue;
      if (JAHR_ALS_MENGE.test(text.substring(ende))) continue;
      funde.push({
        art: 'Jahr',
        marker: m[0],
        grund: 'Jahreszahl ' + gefunden + ' liegt in der Vergangenheit (heute ' + jahr + ')'
      });
    }
  }

  return funde;
}

/**
 * Mehrwortige Marker ("black friday") als schlichte Textsuche, einwortige
 * mit Wortgrenze und kurzer deutscher Beugung. Genau deshalb schlaegt
 * "Winter-Sale" an und "Wintergarten" nicht.
 */
function saisonwortKommtVor(kleinText, wort) {
  if (wort.indexOf(' ') !== -1) return kleinText.indexOf(wort) !== -1;
  var re = new RegExp('\\b' + wort + '(?:s|e|en|er|es|n)?\\b', 'i');
  return re.test(kleinText);
}

/** Alle Fristangaben eines Textes als {roh, datum}. */
function fristenIn(text, jahr) {
  var treffer = [];
  var m;

  FRIST_ZAHL_RE.lastIndex = 0;
  while ((m = FRIST_ZAHL_RE.exec(text)) !== null) {
    var datum = baueFrist(parseInt(m[1], 10), parseInt(m[2], 10), m[3], jahr);
    if (datum) treffer.push({ roh: trimmen(m[0]), datum: datum });
  }

  FRIST_WORT_RE.lastIndex = 0;
  while ((m = FRIST_WORT_RE.exec(text)) !== null) {
    var monat = MONATSNAMEN[m[2].toLowerCase()];
    if (!monat) continue;
    var datum2 = baueFrist(parseInt(m[1], 10), monat, m[3], jahr);
    if (datum2) treffer.push({ roh: trimmen(m[0]), datum: datum2 });
  }

  return treffer;
}

/**
 * Tag/Monat/optionales Jahr zu einem Datum.
 *
 * Fehlt das Jahr, ist das laufende gemeint - ausser die Frist laege dann mehr
 * als ein halbes Jahr in der Vergangenheit. "bis 31. Januar", im Dezember
 * gelesen, meint den kommenden Januar und ist keine abgelaufene Frist.
 */
function baueFrist(tag, monat, jahrRoh, jahr) {
  if (!tag || !monat || monat < 1 || monat > 12 || tag < 1 || tag > 31) return null;
  var zieljahr = jahr;
  if (jahrRoh) {
    zieljahr = parseInt(jahrRoh, 10);
    if (zieljahr < 100) zieljahr += 2000;
  }
  var datum = new Date(Date.UTC(zieljahr, monat - 1, tag));
  if (datum.getUTCMonth() !== monat - 1) return null; // 31. Februar
  if (!jahrRoh) {
    var heute = stichtag();
    if ((heute.getTime() - datum.getTime()) > 183 * MS_PRO_TAG) {
      datum = new Date(Date.UTC(zieljahr + 1, monat - 1, tag));
    }
  }
  return datum;
}

// --- Konto lesen -----------------------------------------------------------

function istResponsiveSuchanzeige(anzeige) {
  try {
    if (anzeige.isType && anzeige.isType().responsiveSearchAd) {
      return anzeige.isType().responsiveSearchAd() === true;
    }
  } catch (fehler) { /* aeltere Laufzeit: unten weiter */ }
  try {
    return anzeige.getType() === 'RESPONSIVE_SEARCH_AD';
  } catch (fehler2) {
    return false;
  }
}

/** Titel und Beschreibungen einer Responsive Suchanzeige, mit Feldnamen. */
function textbausteine(anzeige) {
  var rsa = anzeige.asType().responsiveSearchAd();
  var raus = [];
  var titel = rsa.getHeadlines() || [];
  for (var i = 0; i < titel.length; i++) {
    raus.push({ feld: 'Titel ' + (i + 1), text: textVon(titel[i]) });
  }
  var beschreibungen = rsa.getDescriptions() || [];
  for (var j = 0; j < beschreibungen.length; j++) {
    raus.push({ feld: 'Beschreibung ' + (j + 1), text: textVon(beschreibungen[j]) });
  }
  return raus;
}

function textVon(baustein) {
  if (!baustein) return '';
  if (typeof baustein === 'string') return baustein;
  if (typeof baustein.text === 'string') return baustein.text;
  if (typeof baustein.getText === 'function') return baustein.getText();
  return '';
}

function nameVon(entitaet) {
  if (!entitaet) return '(ohne)';
  try { return entitaet.getName(); } catch (fehler) { return '(ohne)'; }
}

// --- Ausgabe ---------------------------------------------------------------

function berichte(funde, gelesen, rsaGelesen, heute) {
  Logger.log('Gelesen: ' + gelesen + ' aktive Anzeigen, davon ' + rsaGelesen +
    ' Responsive Suchanzeigen.');

  if (funde.length === 0) {
    Logger.log('Ergebnis: keine veralteten Textstellen gefunden.');
    Logger.log('Das heisst: nichts, was diese drei Regeln beweisen koennen. Es heisst');
    Logger.log('nicht, dass jeder Text noch stimmt - ein Preis von gestern und ein');
    Logger.log('eingestelltes Produkt tragen kein Datum im Text.');
  } else {
    Logger.log('Ergebnis: ' + funde.length + ' Textstelle(n) zum Nachsehen.');
    Logger.log('');
    var grenze = Math.min(funde.length, CONFIG.MAX_PROTOKOLL_ZEILEN);
    for (var i = 0; i < grenze; i++) {
      var f = funde[i];
      Logger.log('[' + f.art + '] ' + f.kampagne + ' » ' + f.anzeigengruppe + ' » ' + f.feld);
      Logger.log('    Text:  ' + f.text);
      Logger.log('    Grund: ' + f.grund);
    }
    if (funde.length > grenze) {
      Logger.log('… und ' + (funde.length - grenze) + ' weitere. MAX_PROTOKOLL_ZEILEN erhoehen.');
    }
  }

  Logger.log('');
  Logger.log('Lauf beendet: ' + alsIsoDatum(heute) + ' · ' + funde.length + ' Fund(e).');
  vielleichtMailen(funde, gelesen, rsaGelesen, heute);
}

function vielleichtMailen(funde, gelesen, rsaGelesen, heute) {
  if (!CONFIG.EMAIL_SENDEN) return;
  if (funde.length < CONFIG.EMAIL_AB_FUNDEN) return;
  if (!CONFIG.EMPFAENGER || CONFIG.EMPFAENGER.indexOf('@') === -1) {
    Logger.log('E-Mail uebersprungen: EMPFAENGER ist keine Adresse.');
    return;
  }

  var zeilen = [];
  zeilen.push('Konto: ' + AdsApp.currentAccount().getCustomerId());
  zeilen.push('Stichtag: ' + alsIsoDatum(heute));
  zeilen.push('Gelesen: ' + rsaGelesen + ' Responsive Suchanzeigen von ' + gelesen + ' aktiven Anzeigen.');
  zeilen.push('Funde: ' + funde.length);
  zeilen.push('');
  var grenze = Math.min(funde.length, CONFIG.MAX_PROTOKOLL_ZEILEN);
  for (var i = 0; i < grenze; i++) {
    var f = funde[i];
    zeilen.push('[' + f.art + '] ' + f.kampagne + ' » ' + f.anzeigengruppe + ' » ' + f.feld);
    zeilen.push('  ' + f.text);
    zeilen.push('  ' + f.grund);
    zeilen.push('');
  }
  if (funde.length > grenze) zeilen.push('… und ' + (funde.length - grenze) + ' weitere.');
  zeilen.push('Dieses Skript liest nur. Es hat nichts im Konto veraendert.');

  MailApp.sendEmail(CONFIG.EMPFAENGER, CONFIG.BETREFF + ' (' + funde.length + ')', zeilen.join('\n'));
  Logger.log('E-Mail an ' + CONFIG.EMPFAENGER + ' verschickt.');
}

// --- Kleinkram -------------------------------------------------------------

function stichtag() {
  if (CONFIG.STICHTAG) {
    var teile = CONFIG.STICHTAG.split('-');
    return new Date(Date.UTC(parseInt(teile[0], 10), parseInt(teile[1], 10) - 1, parseInt(teile[2], 10)));
  }
  var jetzt = new Date();
  return new Date(Date.UTC(jetzt.getFullYear(), jetzt.getMonth(), jetzt.getDate()));
}

function alsIsoDatum(datum) {
  var m = datum.getUTCMonth() + 1;
  var t = datum.getUTCDate();
  return datum.getUTCFullYear() + '-' + (m < 10 ? '0' + m : m) + '-' + (t < 10 ? '0' + t : t);
}

function enthaelt(liste, wert) {
  for (var i = 0; i < liste.length; i++) if (liste[i] === wert) return true;
  return false;
}

function trimmen(text) {
  return String(text).replace(/^\s+|\s+$/g, '');
}

Einbau

In fünf Schritten im Konto

Der letzte Schritt ist der wichtigste: erst die Vorschau, dann der echte Lauf.

  1. 1

    Code kopieren

    Der vollständige Code steht weiter unten auf dieser Seite. Kein Download, kein Formular, keine E-Mail-Adresse — der Knopf legt ihn in die Zwischenablage.

  2. 2

    In Google Ads: Tools → Skripte

    Linke Leiste „Tools“ → „Bulk-Aktionen“ → „Skripte“. Dann auf das Plus, um ein leeres Skript anzulegen. In manchen Konten heißt der Punkt nur „Skripte“ — die Suchleiste oben findet ihn zuverlässig.

  3. 3

    Einfügen und benennen

    Den Beispielcode im Editor komplett markieren, überschreiben, und dem Skript oben einen Namen geben. Die Einstellungen im CONFIG-Block ganz oben anpassen — mehr musst du im Code nicht anfassen.

  4. 4

    Autorisieren

    Google fragt einmal nach Berechtigungen. Das Skript braucht Lesezugriff auf das Konto, und wenn du die E-Mail einschaltest, das Recht, in deinem Namen Mails zu schicken. Das ist eine Google-Abfrage, keine Anmeldung bei mir.

  5. 5

    Erst „Vorschau“, dann „Ausführen“

    Die Vorschau lässt das Skript durchlaufen und zeigt das Protokoll, ohne etwas zu speichern. Lies das Protokoll einmal ganz. Erst danach „Ausführen“ und, wenn es passt, einen Zeitplan setzen.

Ehrliche Grenzen

Was dieses Skript nicht sieht

Ein Waldstück, das niemand abgesucht hat, ist nicht leer. Deshalb steht hier, wo dieses Skript gar nicht erst hinschaut — und im Protokoll steht derselbe Satz noch einmal, direkt neben dem Ergebnis.

  • Nur Responsive Suchanzeigen in aktiven Anzeigengruppen aktiver Kampagnen. Pausiertes wird bewusst nicht gelesen — es kostet Laufzeit und ändert nichts an dem, was gerade ausgeliefert wird.
  • Nur Titel und Beschreibungen. Sitelinks, Snippets, Callouts und die Texte in Performance-Max-Assetgruppen hängen an anderen Objekten und sind nicht dabei.
  • Ein Preis von gestern und ein eingestelltes Produkt tragen kein Datum im Text. Die Regeln finden veraltete ZEITANGABEN, nicht veraltete Aussagen.
  • Die Saisonliste ist deutsch und englisch und deckt die üblichen Aktionen ab. Ein Branchenbegriff, den nur dein Markt kennt, muss in den CONFIG-Block.
  • Ein leeres Protokoll heißt „keine Regel hat angeschlagen“, nicht „alles in Ordnung“. Deshalb schreibt das Skript am Ende immer eine Abschlusszeile mit Zählern: Ein Lauf, der nichts sagt, ist ein Lauf, der abgebrochen ist.

Was mit Schreibrechten besser wäre — und trotzdem fehlt

Diese Bibliothek ist in Version 1 per Konstruktion nur lesend. Nicht aus Bequemlichkeit: Der Code landet in fremden, laufenden Konten, und ein Skript, das dort selbstständig pausiert oder umschreibt, richtet im Zweifel einen Schaden an, den niemand kommen sieht. Prüfungen, die mit einem Schreibzugriff besser wären, stehen deshalb hier statt im Code.

  • v2 Die gefundenen Anzeigen automatisch mit einem Label versehen, damit sie im Konto sofort auffindbar sind. Das wäre ein Schreibzugriff — bewusst nicht in v1.
  • v2 Die abgelaufene Jahreszahl direkt ersetzen. Technisch einfach, aber eine falsche Ersetzung in einer fremden Live-Anzeige ist ein Schaden, den ich nicht anrichten will.

FAQ

Stale-Copy-Wächter: häufige Fragen

Ist dieses Google Ads Script sicher?

Ja, und das ist nachprüfbar: Der komplette Code steht auf dieser Seite. Er enthält keinen einzigen Aufruf, der etwas schreiben könnte — kein pause(), kein enable(), kein applyLabel(), kein setzen irgendeines Werts. Wenn du unsicher bist, lass ihn zuerst über „Vorschau“ laufen: Dann siehst du das komplette Protokoll, ohne dass irgendetwas gespeichert wird.

Ändert das Skript etwas in meinem Konto?

Nein. Es liest die Titel und Beschreibungen der aktiven Anzeigen, vergleicht sie mit dem Datum und schreibt das Ergebnis ins Protokoll. Optional schickt es dir eine E-Mail — die ist im Auslieferungszustand ausgeschaltet. Anzeigen, Gebote, Budgets und Keywords bleiben unberührt.

Wie oft sollte ich es laufen lassen?

Einmal pro Woche ist der vernünftige Standard. Wenn du Aktionen mit hartem Enddatum fährst, stelle es auf täglich: Der teuerste Tag ist der erste nach Ablauf, an dem die Anzeige noch ein Versprechen macht, das du nicht mehr einhältst.

Warum sehe ich nichts im Protokoll?

Drei mögliche Gründe. Erstens: Es gibt wirklich keinen Fund — dann steht die Zeile „keine veralteten Textstellen gefunden“ da. Zweitens: Das Konto hat keine aktiven Responsive Suchanzeigen; das Skript nennt dann die Zahl der gelesenen Anzeigen. Drittens: Der Lauf ist abgebrochen. Deshalb endet jeder erfolgreiche Lauf mit einer Zeile „Lauf beendet“ samt Zählern — fehlt sie, war es kein sauberer Durchlauf, auch wenn Google den Lauf grün meldet.
Kundenstimmen und Case Studies

Ergebnisse, die ich gemessen habe.

14 Kundenstimmen · 22 Case Studies

Kundenstimmen
Das sagt die Kundin
„Mir ist besonders wichtig, dass es bei Google Ads nicht einfach darum geht, mehr Budget auszugeben und mehr Umsatz zu machen, sondern dass die Wirtschaftlichkeit für uns stimmt – und genau das berücksichtigt Daniils bei seiner Arbeit.“
Vollständige Bewertung lesenBewertung einklappen

„Wir arbeiten jetzt schon seit mehreren Jahren mit Danny zusammen und ich schätze vor allem den direkten und unkomplizierten Austausch mit ihm. Er schaut sich die Zahlen genau an, hinterfragt Dinge und kommt auch immer wieder selbst mit neuen Ideen und Ansätzen auf uns zu. Mir ist besonders wichtig, dass es bei Google Ads nicht einfach darum geht, mehr Budget auszugeben und mehr Umsatz zu machen, sondern dass die Wirtschaftlichkeit für uns stimmt – und genau das berücksichtigt Daniils bei seiner Arbeit. Ich schätze außerdem sehr, dass wir uns offen austauschen können.“

Natalja RichertGeschäftsführerin · MYTRAURINGstore Case Study lesen
Das sagt die Kundin
„Die Zusammenarbeit mit Daniils ist ein riesiger Gewinn für unsere Firma.“
Vollständige Bewertung lesenBewertung einklappen

„Die Zusammenarbeit mit Daniils ist ein riesiger Gewinn für unsere Firma. Bei Fragen oder auch Problemen, wir erreichen uns immer. Daniils hat sich in die Materie Sauna voll eingearbeitet und unterstützt uns wo er nur kann. Ich freue mich, dass ich Dich persönlich kennenlernen durfte und wir eine so tolle Zusammenarbeit pflegen. Wie schon angemerkt: Du bist ein Ausnahmetalent, Chapeau!“

Anke Della-BellaGeschäftsführerin · Finsterbusch – die Wellness Factory GmbH Case Study lesen
Google-Ads-Beratung
„Danach konnten wir unsere Conversion Rate auf Sales fast verdoppeln.“
Vollständige Bewertung lesenBewertung einklappen

„Ich betreue unsere Kampagnen selbst und habe Danny für die Google-Ads-Beratung dazugeholt. Wir standen unter Zeitdruck und brauchten eine klare Richtung.

Danny hat sich schnell in unser Geschäft hineingedacht und uns konkrete Empfehlungen zur Gebotsstrategie und zum passenden Conversion-Ziel gegeben. Danach konnten wir unsere Conversion Rate auf Sales fast verdoppeln.

Für mich hat sich vor allem das Gefühl verändert: Ich habe einen Ansprechpartner per Call oder WhatsApp und muss die schwierigsten Entscheidungen nicht mehr allein tragen. Der Austausch ist herzlich, cool und entspannt. Ich würde Danny jedem empfehlen, der seine Kampagnen selbst betreut und jemanden braucht, der wirklich mitdenkt.“

Merlin FaudeGeschäftsführer Fachzentrum für Wut & Aggression
Das sagt der Kunde
„Wir konnten unsere Marketingziele messbar verbessern.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils ist ein äußerst professioneller und zuverlässiger Partner. Die Zusammenarbeit verläuft effizient, lösungsorientiert und immer auf Augenhöhe. Besonders hervorzuheben sind die kurzen Kommunikationswege und seine schnelle Reaktionszeit — man fühlt sich jederzeit gut betreut. Wir konnten unsere Marketingziele messbar verbessern. Eine Zusammenarbeit, die wir uneingeschränkt weiterempfehlen können.“

Patrick ZitzerZitberg GmbH Case Study lesen
Google-Ads-Beratung
„Du hast uns nicht nur bei Google Ads geholfen, sondern unser gesamtes Online-Marketing auf ein neues Level gehoben.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils, danke für deine großartige Unterstützung! Du hast uns nicht nur bei Google Ads geholfen, sondern unser gesamtes Online-Marketing auf ein neues Level gehoben. Deine strukturierte Herangehensweise, dein Blick fürs Detail und dein ehrliches Feedback haben uns extrem weitergebracht. Besonders schätze ich, dass du dich wirklich in unser Business hineingedacht hast – es ging nie um schnelle Tricks, sondern um nachhaltige Performance. Man merkt einfach, dass du weißt, was du tust, und dass du Spaß daran hast. Ich kann dich von Herzen weiterempfehlen!“

Markus BrucknerBruckner Analysentechnik GmbH
Verständliche Beratung
„Daniils ist ein echtes Ausnahmetalent.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils ist ein echtes Ausnahmetalent. Er beherrscht nicht nur alles rund um Google, sondern bringt auch ein starkes unternehmerisches Gespür mit. Komplexe Themen erklärt er verständlich und hilft so, fundierte Geschäftsentscheidungen zu treffen. Eine klare Empfehlung!“

Chris CerraRemoteBase
Google-Ads-Beratung
„Besonders schätze ich, wie verständlich er komplexe Themen erklärt.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils ist ein großartiger Google-Ads-Experte! Er ist schnell, hands-on und bringt kreative Lösungen. Besonders schätze ich, wie verständlich er komplexe Themen erklärt. Wer Hilfe bei Google Ads braucht — Daniils ist die richtige Wahl!“

Samantha ScottAll Things Remote
Google-Ads-Beratung
„Daniils hat zwei meiner Kunden hervorragend bei ihren Google-Ads-Kampagnen unterstützt.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils hat zwei meiner Kunden hervorragend bei ihren Google-Ads-Kampagnen unterstützt. Mit seinem Fachwissen, seiner Professionalität und wertvollen Strategien konnten sie ihre Sichtbarkeit steigern und die Conversion Rates verbessern. Seine Expertise hat wirklich den Unterschied gemacht — absolut empfehlenswert!“

Tim RautenbergXM CROSSMEDIA
Ads & Tracking
„Daniils leistet super Arbeit und bringt absolute Kompetenz im Bereich Ads und Tracking mit!“
Pascal RähseFunnelforms-Gründer
Marketingstrategie
„Daniils hat uns mit seiner Marketingstrategie für die Events eines Kunden auf ein ganz neues Level gebracht.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils hat uns mit seiner Marketingstrategie für die Events eines Kunden auf ein ganz neues Level gebracht. Er war schnell, engagiert und persönlich dabei — und hat uns sogar mit weiteren Expert:innen vernetzt. Fachlich top und mit ganzem Herzen dabei — absolute Empfehlung!“

Alexandra MonitskaMar Sabi Studio
Technische Problemlösung
„Daniils hat mir in kürzester Zeit bei der Lösung eines Problems geholfen, bei dem ich selbst nicht weitergekommen bin.“
Vollständige Bewertung lesenBewertung einklappen

„Daniils hat mir in kürzester Zeit bei der Lösung eines Problems geholfen, bei dem ich selbst nicht weitergekommen bin. Reaktionsschnell, freundlich und höchst kompetent hat er den Auftrag erledigt und sich danach sogar noch Zeit für ein gemeinsames Videomeeting genommen, um die Ergebnisse zu besprechen. Vielen Dank dafür!“

Daniel Fischerdigitalpositiv
Marketingstrategie
„Sehr gute Kommunikation.“
Vollständige Bewertung lesenBewertung einklappen

„Sehr gute Kommunikation. In unseren zwei Beratungsterminen hat sich Daniils intensiv mit meiner Situation beschäftigt und mir klares, gut begründetes Feedback zu Google Ads, Analytics und meiner gesamten digitalen Strategie gegeben.“

FjodoraFiverr-Kunde
Ads & Tracking
„Top Beratung und Kompetenz. Daniils nimmt sich wirklich die Zeit, das Problem gemeinsam zu lösen. Kann ich jedem weiterempfehlen.“
Leon PawlickiFiverr-Kunde
Ads & Tracking
„Daniils kommuniziert sehr gut und hat den Auftrag super schnell und zuverlässig umgesetzt. Ich werde künftig öfter mit ihm arbeiten :)“
Homepage HeroesWebdesign-Agentur

Lieber jemanden, der hinschaut?

Die Skripte finden, was sich beweisen lässt. Struktur, Gebote und die Frage, ob überhaupt das Richtige gemessen wird, gehen wir im Gespräch durch.

Kostenlos & unverbindlich Antwort innerhalb 24h Du sprichst direkt mit mir