Přejdi na obsah

Tomáš Bzirský – Nejen o marketingu

Cestou necestou internetem

Menu
  • Marketingová konzultace
  • Kontakt
  • Perplexity Pro zdarma?
  • Máte Bitcoin a nechcete ho prodat?
Menu

Automatizované hlídání PPC rozpočtů: YTD přehled

Zveřejněno v 8.9.20258.9.2025 od Tomáš Bzirský

Správa rozpočtů napříč Google Ads, Sklikem a Facebookem je časově náročná, zvláště pokud spravujete více účtů. V tomto článku ukážu, jak jsem postupně vylepšil původní „MCC Skript na kontrolu rozpočtů“ (autor: Stanislav Jílek), až vznikla verze zaměřená na YTD (Year-To-Date) porovnání letošního roku a loňska, bez zahrnutí app konverzí, a přizpůsobená pro ranní běh „do včerejška“. Použití chatGPT na scriptování dostává nový rozměr, protože neumím scriptovat.


1. Z čeho jsme vycházeli

Původní skript nabízel:

  • Centralizovanou konfiguraci přes Google Sheets (včetně e-mailu, rozpočtu),

  • Kontrolu Google Ads, Sklik API „Drak“ a Facebook,

  • Měřil procento času v měsíci, využití rozpočtu a doporučený denní budget, vše pro aktuální a předchozí období (např. měsíc, včera),

  • V roce 2025 rozšířen o komunikaci s Facebook budgetem a API Drak. 


2. Kam jsem to dotáhl: rychlý YTD přehled

Po iteracích a optimalizacích vznikla zrychlená verze s několika klíčovými vylepšeními:

Funkce

Původní skript

Rychlá YTD verze

Období

více časových segmentů (měsíce, včera)

„Letošek k včerejšku“ a „Loňsko k včerejšku“

Výkon

Sleep mezi API voláními → dlouhé běhy

Žádné sleepy, přímý agregát

Facebook

detaillní data

agregát jedním výstupem


3. Přínosy téhle verze

  • Rychlejší běh skriptu — žádné zbytečné pauzy mezi API dotazy.

  • Srozumitelnost — porovnání YTD let vs. YTD loňska zachycuje trendy.

  • Flexibilita — můžeš snadno modulárně rozdělit platformy (GA, Sklik, FB) a případně rozšířit o souhrnné řádky.


4. Jak implementovat

  1. Kopie config spreadsheetu – jako ve scriptu od Standy, vlož URL do ss_config.

  2. Nastav tokeny pro Sklik (API Drak) a Facebook (Graph API token + účet).

  3. Vlož hlavní funkci main(), která obsahuje zrychlený YTD výpočet:

    • Výpočet dat „do včerejška“

    • Agregace Sklik a Facebook dat

    • Odeslání formátovaného HTML e-mailu

  4. Naplánuj spouštění — ideálně ráno, po dokončení datového zpracování.

  5. Pokud chcete takto sledovat pouze nějaké účty, tak si list budget_control zduplikujte a nechte pouze účty, které vám dávají smysl, změna názvu listu je v části ss_config.getSheetByName

5. Co skript neřeší

  • Detaily kampaní / sestav / reklam — jde hlavně o účetní přehled.

  • Alerty — nehlásí anomálie nebo nutnost úpravy rozpočtu (může se doplnit).

  • SKLIK zboží – tyto data nejsou v API a nejsou ani v původním scriptu od Standy
  • Historické trendy — YTD je užitečné porovnání, ale trendový report je zase jiné narativní vyprávění.


6. Závěr

Automatizace hlídání rozpočtů je RTL (real-time labor-saving). Tato verze pomáhá rychle porovnat hlavní metriky mezi jednotlivými roky. Slouží primárně k trackování celého roku.

Celý upravený script: nic složitého, ale přišlo mi fajn se podělit.


// FAST YTD kontrola (GA, Sklik, Facebook) — do včerejška
var ss_config = SpreadsheetApp.openByUrl("URL souboru s daty");

/*********************************************************************************************************
Skript: MCC Skript na kontrolu rozpočtů pro Google Ads, Sklik a Facebook
Verze: Budget control 09.04.2025
Vytvořil: Stanislav Jílek [standajilek.cz]
Navrhli a testovali: Karel Rujzl [rujzl.cz] a Petra Větrovská [vetrovka.cz]
Prvotní myšlenka na kontrolu rozpočtů: Hana Kobzová [hanakobzova.cz]
EDIT na YTD: Tomáš Bzirský [bzirsky.cz]
/********************************************************************************************************/

function main() {
var settings_sheet = ss_config.getSheetByName("budget_control_year");
var mail = settings_sheet.getRange("B2").getValue();
var subject = settings_sheet.getRange("B3").getValue();

// --- Datum: do včerejška, časová zóna účtu (fallback Praha) ---
var TZ = (typeof AdsApp !== "undefined" && AdsApp.currentAccount && AdsApp.currentAccount().getTimeZone)
? (AdsApp.currentAccount().getTimeZone() || "Europe/Prague")
: "Europe/Prague";

var now = new Date();
now.setDate(now.getDate() - 1); // včera
var thisYear = now.getFullYear();
var lastYear = thisYear - 1;

var startTY = new Date(thisYear, 0, 1);
var endTY = new Date(thisYear, now.getMonth(), now.getDate());
var startLY = new Date(lastYear, 0, 1);
var endLY = new Date(lastYear, now.getMonth(), now.getDate());

var periods = [
[startTY, endTY, "Letošek (YTD TY)"],
[startLY, endLY, "Loňský rok (YTD LY)"]
];

// --- Tabulka ---
var table_header = "<tr bgcolor='#ffd75d'><th>Účet</th><th>Období</th><th>Náklady</th><th>Obrat</th><th>Konverze</th><th>CPA</th><th>PNO</th></tr>";
var table = "<table border='1' style='border-collapse:collapse' cellpadding='5'>";

// ===== GOOGLE ADS =====
try {
var lastRow = settings_sheet.getLastRow();
var adwords_settings = lastRow >= 6 ? settings_sheet.getRange("D6:D" + lastRow).getValues() : [];
if (settings_sheet.getRange("D6").getValue() != "") {
table += "<tr><td colspan='7' bgcolor='#4fabe5'><strong>GOOGLE ADS</strong></td></tr>" + table_header;
}

for (var i = 0; i < adwords_settings.length; i++) {
var accId = adwords_settings[i][0];
if (!accId) continue;

try {
var it = MccApp.accounts().withIds([accId]).get();
if (it.hasNext()) MccApp.select(it.next());
var account_name = AdsApp.currentAccount().getName();
var currency = AdsApp.currentAccount().getCurrencyCode();

for (var p = 0; p < periods.length; p++) {
var ds = Utilities.formatDate(periods[p][0], TZ, 'yyyyMMdd');
var de = Utilities.formatDate(periods[p][1], TZ, 'yyyyMMdd');

var rows = AdsApp.report(
"SELECT Cost, ConversionValue, Conversions " +
"FROM ACCOUNT_PERFORMANCE_REPORT DURING " + ds + "," + de
).rows();

var cost = 0, convValue = 0, conv = 0;
if (rows.hasNext()) {
var r = rows.next();
cost = toFixed0(r.Cost);
convValue = toFixed0(r.ConversionValue);
conv = toFixed0(r.Conversions);
}
var pno = pctOrZero(cost, convValue);
var cpa = divOrZero(cost, conv);

table += add_html_simple(i, p, periods[p][2], account_name, currency, cost, convValue, conv, cpa, pno);
}
} catch (e) {
Logger.log("GA ERR: " + e);
}
}
} catch (e) {
Logger.log("GA BLOCK ERR: " + e);
}

// ===== SKLIK =====
try {
var sklik_settings = lastRow >= 6 ? settings_sheet.getRange("A6:A" + lastRow).getValues() : [];
if (settings_sheet.getRange("A6").getValue() != "") {
table += "<tr><td colspan='7' bgcolor='#ff4646'><strong>SKLIK</strong></td></tr>" + table_header;
}

var token = settings_sheet.getRange("B4").getValue();
var client_login = sklik_api([token], 'client.loginByToken');
var client_get = sklik_api([{ session: client_login.session }], 'client.get');

// map uživatelů
var userMap = {};
userMap[client_get.user.username.toLowerCase()] = [client_get.user.userId, client_get.user.username];
if (client_get.foreignAccounts && client_get.foreignAccounts.length) {
for (var fa = 0; fa < client_get.foreignAccounts.length; fa++) {
userMap[client_get.foreignAccounts[fa].username.toLowerCase()] = [client_get.foreignAccounts[fa].userId, client_get.foreignAccounts[fa].username];
}
}

for (var s = 0; s < sklik_settings.length; s++) {
var uname = (sklik_settings[s][0] || "").toLowerCase();
if (!uname || !userMap[uname]) continue;

var uid = userMap[uname][0];
var accountName = userMap[uname][1];

for (var p2 = 0; p2 < periods.length; p2++) {
var ds2 = Utilities.formatDate(periods[p2][0], TZ, 'yyyy-MM-dd');
var de2 = Utilities.formatDate(periods[p2][1], TZ, 'yyyy-MM-dd');

var stats = sklik_api([
{ session: client_login.session, userId: uid },
{ dateFrom: ds2, dateTo: de2, granularity: 'total' }
], 'client.stats');

var cost2 = 0, convVal2 = 0, conv2 = 0;
if (stats && stats.report && stats.report.length) {
cost2 = toFixed0(stats.report[0].price / 100);
convVal2 = toFixed0(stats.report[0].conversionValue / 100);
conv2 = toFixed0(stats.report[0].conversions);
}
var pno2 = pctOrZero(cost2, convVal2);
var cpa2 = divOrZero(cost2, conv2);

table += add_html_simple(s, p2, periods[p2][2], accountName, "CZK", cost2, convVal2, conv2, cpa2, pno2);
}
}

sklik_api([{ session: client_login.session }], 'client.logout');
} catch (e) {
Logger.log("SKLIK ERR: " + e);
}

// ===== FACEBOOK =====
try {
var facebook_settings = lastRow >= 6 ? settings_sheet.getRange("G6:H" + lastRow).getValues() : [];
if (settings_sheet.getRange("G6").getValue() != "") {
table += "<tr><td colspan='7' bgcolor='#3b5998'><strong>FACEBOOK</strong></td></tr>" + table_header;
}

var FB_API = 'v22.0';
for (var f = 0; f < facebook_settings.length; f++) {
var tokenFB = facebook_settings[f][0];
var accFB = facebook_settings[f][1];
if (!tokenFB || !accFB) continue;

for (var p3 = 0; p3 < periods.length; p3++) {
var ds3 = Utilities.formatDate(periods[p3][0], TZ, 'yyyy-MM-dd');
var de3 = Utilities.formatDate(periods[p3][1], TZ, 'yyyy-MM-dd');

// požádáme o agregát za celé období (time_increment=all_days) + jen potřebná pole
var url = "/" + FB_API + "/act_" + accFB + "/insights?" +
"fields=account_name,account_currency,spend,actions,action_values" +
"&level=account" +
"&time_range[since]=" + ds3 + "&time_range[until]=" + de3 +
"&time_increment=all_days" +
"&filtering=" + encodeURIComponent('[{"field":"action_type","operator":"IN","value":["offsite_conversion.fb_pixel_purchase"]}]') +
"&access_token=" + encodeURIComponent(tokenFB);

var resp = fb_api(url);
var hasData = resp && resp.data && resp.data.length;

var accNameFB = hasData ? resp.data[0].account_name : ("act_" + accFB);
var currFB = hasData ? resp.data[0].account_currency : "CZK";
var costFB = hasData ? toFixed0(parseFloat(resp.data[0].spend)) : 0;

var convFB = 0, convValFB = 0;
if (hasData && resp.data[0].actions) {
for (var a = 0; a < resp.data[0].actions.length; a++) {
var ia = resp.data[0].actions[a];
if (ia.action_type == "offsite_conversion.fb_pixel_purchase") { convFB = toFixed0(parseFloat(ia.value)); break; }
}
}
if (hasData && resp.data[0].action_values) {
for (var v = 0; v < resp.data[0].action_values.length; v++) {
var iv = resp.data[0].action_values[v];
if (iv.action_type == "offsite_conversion.fb_pixel_purchase") { convValFB = toFixed0(parseFloat(iv.value)); break; }
}
}

var pnoFB = pctOrZero(costFB, convValFB);
var cpaFB = divOrZero(costFB, convFB);

table += add_html_simple(f, p3, periods[p3][2], accNameFB, currFB, costFB, convValFB, convFB, cpaFB, pnoFB);
}
}
} catch (e) {
Logger.log("FB ERR: " + e);
}

table += "</table>";
MailApp.sendEmail({ to: mail, subject: subject, htmlBody: table });
}

/*** UTILITIES ***/
function toFixed0(val) {
var num = parseFloat(String(val).split(",").join(""));
if (isNaN(num)) return 0;
return Number(num.toFixed(0));
}
function pctOrZero(cost, value) {
if (value == 0) return 0;
return Number(((cost / value) * 100).toFixed(2));
}
function divOrZero(a, b) {
if (b == 0) return 0;
return Number((a / b).toFixed(0));
}
function number_format(number) {
number = number.toString();
number = number.split("").reverse().join("");
number = number.substr(0, 3) + " " + number.substr(3, 3) + " " + number.substr(6, 3) + " " + number.substr(9, 3) + " " + number.substr(12, 3);
number = number.split("").reverse().join("");
return number.trim();
}
function row_color(row) {
return (row % 2 == 0) ? "#ffffff" : "#d5d5d5";
}
function add_html_simple(i, j, label, account_name, currency, cost, convVal, conv, cpa, pno) {
var tr1 = "<tr bgcolor='" + row_color(i) + "'>";
if (j == 0) {
return tr1 +
"<td nowrap rowspan='2'><strong>" + account_name + "</strong></td>" +
"<td nowrap><strong>" + label + "</strong></td>" +
"<td nowrap align='right'><strong>" + number_format(cost) + " " + currency + "</strong></td>" +
"<td nowrap align='right'><strong>" + number_format(convVal) + " " + currency + "</strong></td>" +
"<td nowrap align='right'><strong>" + number_format(conv) + "</strong></td>" +
"<td nowrap align='right'><strong>" + number_format(cpa) + " " + currency + "</strong></td>" +
"<td nowrap align='right'><strong>" + pno + " %</strong></td></tr>";
} else {
return tr1 +
"<td nowrap>" + label + "</td>" +
"<td nowrap align='right'>" + number_format(cost) + " " + currency + "</td>" +
"<td nowrap align='right'>" + number_format(convVal) + " " + currency + "</td>" +
"<td nowrap align='right'>" + number_format(conv) + "</td>" +
"<td nowrap align='right'>" + number_format(cpa) + " " + currency + "</td>" +
"<td nowrap align='right'>" + pno + " %</td></tr>";
}
}
function sklik_api(parameters, method) {
var url = 'https://api.sklik.cz/drak/json/' + method;
var options = { method: 'post', contentType: 'application/json', muteHttpExceptions: true, payload: JSON.stringify(parameters) };
try { return JSON.parse(UrlFetchApp.fetch(url, options)); }
catch (e1) { try { return JSON.parse(UrlFetchApp.fetch(url, options)); } catch (e2) { return JSON.parse(UrlFetchApp.fetch(url, options)); } }
}
function fb_api(path) {
var url = 'https://graph.facebook.com' + path;
var options = { method: 'get', contentType: 'application/json', muteHttpExceptions: true };
try { return JSON.parse(UrlFetchApp.fetch(url, options)); }
catch (e1) { try { return JSON.parse(UrlFetchApp.fetch(url, options)); } catch (e2) { return JSON.parse(UrlFetchApp.fetch(url, options)); } }
}

Navigace pro příspěvek

← Jak na AI shrnutí hodinového videa?
Airalo boostlo tarify a přidalo neomezená data →

Napsat komentář Zrušit odpověď na komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *

Nejnovější příspěvky

  • E-Sim Revolut na Maledivách
  • Anyma v Dubaji: Epická show, tragická organizace
  • Datový roaming
  • Na čem teď aktuálně pracuju #2
  • Na čem teď aktuálně pracuju #1

Reklama

Rubriky

  • Analytika
  • Audit
  • Cestování
    • Afrika
    • Amerika
    • Asie
    • Evropa
    • Karibik
  • E-shopy
  • Kryptoměny
  • Marketing
  • Nástroje
  • Novinky
  • Ostatní
  • PPC
  • Tag Manager

adwords ai analytika aplikace audiokniha bitcoin branding cestování google google summit gtm heureka hogan Japonsko kahou nosatý kambodza kniha konference Kryptoměny marketing notebooklm novinky nástroje oceán památky portugalsko ppc příroda redbull remarketing retargeting revolut singapure sintra sklik skóre kvality Spánek tag manager testy thor twitter Udawalawa události zanzibar zasilkovna

  • E-Sim Revolut na Maledivách
  • Anyma v Dubaji: Epická show, tragická organizace
  • Datový roaming
  • Na čem teď aktuálně pracuju #2
  • Na čem teď aktuálně pracuju #1

Reklama

Twitter
Instagram
Linkedin
© 2026 Tomáš Bzirský – Nejen o marketingu | Powered by Minimalist Blog WordPress Theme