分类目录归档:Shopify

Google Shopping广告优化的一些script

function main() {
  
  var productGroups = AdsApp.productGroups()
  .withCondition("CampaignName = 'new_S_PLA_US_'")
  .withCondition("Cost < 50")
  .withCondition("AverageCpc < 1.5")
  .forDateRange("LAST_30_DAYS")
  .get();
  while (productGroups.hasNext()) {
    var productGroup = productGroups.next();
    productGroup.setMaxCpc(productGroup.getMaxCpc() + 0.1)
  }
}
function main() {
  var targetCPA = 10;
  
  var productGroups = AdsApp.productGroups()
  .withCondition("CampaignName = 'new_S_PLA_US_'")
  .withCondition("Cost > 50")
  .withCondition("Conversions >= 1")
  .forDateRange("LAST_30_DAYS")
  .get();
  while (productGroups.hasNext()) {
    var productGroup = productGroups.next();
    var conversionRate = productGroup.getStatsFor("LAST_30_DAYS").getConversionRate();
    productGroup.setMaxCpc(targetCPA * conversionRate + 0.1)
  }
}
function main() {
  
  var productGroups = AdsApp.productGroups()
  .withCondition("CampaignName = 'new_S_PLA_US_'")
  .withCondition("Cost > 50")
  .withCondition("Conversions < 1")
  .withCondition("AverageCpc > 0.3")
  .forDateRange("LAST_30_DAYS")
  .get();
  while (productGroups.hasNext()) {
    var productGroup = productGroups.next();
    productGroup.setMaxCpc(productGroup.getMaxCpc() - 0.1);
  }
}
function main() {
  // Put your spreadsheet's URL here:
  var spreadsheetUrl = "https://docs.google.com/spreadsheets/d/xxx";
  // Make sure the keywords are in columns A to C in the first sheet.
  
  
  //////////////////////////////////////////////////////////////////////////////
  var dateRange = "LAST_30_DAYS";
  // By default the script just looks at yesterday's search queries.
  
  var impressionThreshold = 10;
  // The script only looks at searches with impressions higher than this threshold.
  // Use this if you get a wide range of searches and only want to exclude the highest volume ones.
  
  // Read the spreadsheet
  try {
    var spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl);
  } catch (e) {
    Logger.log("Problem with the spreadsheet URL: '" + e + "'");
    Logger.log("Make sure you have correctly copied in your own spreadsheet URL.");
    return;
  }
  var sheet = spreadsheet.getSheets()[0];
  var spreadsheetData = sheet.getDataRange().getValues();
  
  // Record each campaigns' keywords, and the words (of more than 4 characters) that are in the keywords
  var keywords = {};
  var words = {};
  var numberOfKeywords = 0;
  var numberOfadGroups = 0;
  for(var i=1; i<spreadsheetData.length; i++) {
    var campaignName = spreadsheetData[i][0];
    var adGroupName = spreadsheetData[i][1];
    if (keywords[campaignName] == undefined) {
      keywords[campaignName] = [];
      words[campaignName] = {};
    }
    if (keywords[campaignName][adGroupName] == undefined) {
      keywords[campaignName][adGroupName] = [];
      words[campaignName][adGroupName] = {};
      numberOfadGroups++;
    }
    var keyword = spreadsheetData[i][2];
    keyword = keyword.toLowerCase().replace(/[^\w\s\d&]/g," ").replace(/ +/g," ").trim();
    keywords[campaignName][adGroupName].push(keyword);
    numberOfKeywords++;
    var keywordWords = keyword.split(" ");
    for (var k=0; k<keywordWords.length; k++) {
      if (keywordWords[k].length > 4) {
        words[campaignName][adGroupName][keywordWords[k]] = true;
      }
    }
  }
  var campaignNames = Object.keys(keywords);
  Logger.log("Found " + numberOfKeywords + " keywords for " + numberOfadGroups + " ad groups in " + campaignNames.length + " campaign(s).");
  
  // Get the IDs of the ad groups named in the spreadsheet
  var adGroupIds = [];
  var campaignIds = [];
  var campaignReport = AdWordsApp.report(
    "SELECT CampaignName, AdGroupName, CampaignId, AdGroupId " +
    "FROM   ADGROUP_PERFORMANCE_REPORT " +
    "WHERE Impressions > 0 " + 
    'AND CampaignName IN ["' + campaignNames.join('","') + '"] ' +
    "DURING " + dateRange
    );
  var campaignRows = campaignReport.rows();
  while (campaignRows.hasNext()) {
    var row = campaignRows.next();
    if (campaignIds.indexOf(row["CampaignId"]) < 0) {
      campaignIds.push(row["CampaignId"]);
    }
    if (keywords[row["CampaignName"]][row["AdGroupName"]] != undefined) {
      adGroupIds.push(row["AdGroupId"]);
    }
  }//end while
  
  if (adGroupIds.length == 0) {
    Logger.log("Could not find any ad groups with impressions that matched the given names.");
    return;
  }
  Logger.log("Found " + adGroupIds.length + " ad groups in " + campaignIds.length + " campaign(s) with impressions that matched the given names.");
  
  // Initialise the arrays for each campaign, and sorts the keywords from longest to shortest
  var negativeQueries = {}; // Contains the queries
  var exactNegatives = {};  // Contains any negatives to add with exact match
  var phraseNegatives = {}; // Contains any negatives to add with phrase match
  for (var campaignName in keywords) {
    negativeQueries[campaignName] = {};
    exactNegatives[campaignName] = {};
    phraseNegatives[campaignName] = {};
    for (var adGroupName in keywords[campaignName]) {
      negativeQueries[campaignName][adGroupName] = [];
      exactNegatives[campaignName][adGroupName] = [];
      phraseNegatives[campaignName][adGroupName] = [];
      keywords[campaignName][adGroupName].sort(function(a,b) {return b.length - a.length;});
    }
  }
  
  // Get the queries that don't exactly match keywords
  var report = AdWordsApp.report(
    "SELECT Query, AdGroupId, CampaignId, CampaignName, AdGroupName, Impressions " +
    "FROM SEARCH_QUERY_PERFORMANCE_REPORT " +
    "WHERE AdGroupId IN [" + adGroupIds.join(",") + "] " +
    "AND Impressions > " + impressionThreshold + " " +
      "DURING " + dateRange);
  var rows = report.rows();
  var numberQueries = 0;
  while (rows.hasNext()) {
    var row = rows.next();
    var query = row["Query"].toLowerCase().replace(/[^\w\s\d&]/g," ").replace(/ +/g," ").trim();
    var campaignName = row["CampaignName"];
    var adGroupName = row["AdGroupName"];
    if (keywords[campaignName][adGroupName].indexOf(query) < 0) {
      negativeQueries[campaignName][adGroupName].push(query);
      numberQueries++;
    }
  }
  
  // Process queries
  Logger.log("Processing " + numberQueries + " queries that do not match any keywords.");
  var numberExactNegatives = 0;
  var numberPotentialPhraseNegatives = 0;
  for (var campaignName in negativeQueries) {
    for (var adGroupName in negativeQueries[campaignName]) {
      for (var i=0; i<negativeQueries[campaignName][adGroupName].length; i++) {
        var query = negativeQueries[campaignName][adGroupName][i];
        var queryDone = false;
        
        // If the query is contained within a keyword, it has to be an exact match negative
        if (isStringInsideKeywords(query, keywords[campaignName][adGroupName])) {
          exactNegatives[campaignName][adGroupName].push(query);
          numberExactNegatives++;
          continue;
        }
        
        // Check each word (that's over 4 characters) in the query - if it's not in the words array
        // then it isn't in the keywords, so it's fine to use as a phrase negative
        var queryWords = query.split(" ");
        for (var w=0; w<queryWords.length; w++) {
          if (queryWords[w].length > 4) {
            if (words[campaignName][adGroupName][queryWords[w]] == undefined) {
              phraseNegatives[campaignName][adGroupName].push(queryWords[w]);
              queryDone = true;
              break;
            }
          }
        }
        
        // Check if there is a keyword inside the query. If there is, see if the part of the query before
        // or after the keyword could be used as a phrase negative.
        for (var k=0; k<keywords[campaignName][adGroupName].length && !queryDone; k++) {
          var keyword = keywords[campaignName][adGroupName][k];
          if ((" " + query + " ").indexOf(" " + keyword + " ") > -1) {
            var queryBits = (" " + query + " ").split(" " + keyword + " ");
			queryBits[0] = queryBits[0].trim();
			queryBits[1] = queryBits[1].trim();
            if (queryBits[0].length > 0 && !isStringInsideKeywords(queryBits[0], keywords[campaignName][adGroupName])) {
              phraseNegatives[campaignName][adGroupName].push(queryBits[0]);
              queryDone = true;
              break;
            }
            if (queryBits[1].length > 0 && !isStringInsideKeywords(queryBits[1], keywords[campaignName][adGroupName])) {
              phraseNegatives[campaignName][adGroupName].push(queryBits[1]);
              queryDone = true;
              break;
            }
          }
        }
        
        // If nothing smaller than the full query would work, then add the full query as a negative
        if (!queryDone) {
          phraseNegatives[campaignName][adGroupName].push(query);
        }
        numberPotentialPhraseNegatives++;
      }
    }
  }
  Logger.log("Found " + numberPotentialPhraseNegatives + " potential phrase match negatives and " + numberExactNegatives + " exact match negatives.");
  
  // Remove any redundant phrase negatives
  Logger.log("Checking for redundant negatives.");
  var numberPhraseNegatives = 0;
  for (var campaignName in negativeQueries) {
    for (var adGroupName in negativeQueries[campaignName]) {
      // Order the phrases from shortest to longest
      phraseNegatives[campaignName][adGroupName].sort(function(a,b) {return a.length - b.length;});
      
      for (var i=0; i<phraseNegatives[campaignName][adGroupName].length; i++) {
        var shorterPhrase = " " + phraseNegatives[campaignName][adGroupName][i] + " ";
        
        // As the array is now ordered, any phrase negatives with higher indices must be longer than shorterPhrase
        for (var j=i+1; j<phraseNegatives[campaignName][adGroupName].length; j++) {
          var longerPhrase = " " + phraseNegatives[campaignName][adGroupName][j] + " ";
          
          // If the shorterPhrase is within the longerPhrase, then the longerPhrase is redundant
          // so it is removed from the array. This also means duplicates are removed.
          if (longerPhrase.indexOf(shorterPhrase) > -1) {
            phraseNegatives[campaignName][adGroupName].splice(j,1);
            j--;
          }
        } 
      }
      numberPhraseNegatives += phraseNegatives[campaignName][adGroupName].length;
    }
  }
  Logger.log("Going to create " + numberPhraseNegatives + " phrase match negatives and " + numberExactNegatives + " exact match negatives");
  
  // Iterate through the Shopping ad groups and add the negative keywords
  var groupIterator = AdWordsApp.shoppingAdGroups()
  .withIds(adGroupIds)
  .get()
  
  while (groupIterator.hasNext()) {
    var adGroup = groupIterator.next();
    var adGroupName = adGroup.getName();
    var campaignName = adGroup.getCampaign().getName();
    
    for (var i=0; i<exactNegatives[campaignName][adGroupName].length; i++) {
      adGroup.createNegativeKeyword("[" + exactNegatives[campaignName][adGroupName][i] + "]");
    }
    
    for (var i=0; i<phraseNegatives[campaignName][adGroupName].length; i++) {
      adGroup.createNegativeKeyword('"' + phraseNegatives[campaignName][adGroupName][i] + '"');
    }
  }
  
  Logger.log("Finished.");
} // end main function


// Check if a word is a substring of any strings in the keywords array
function isStringInsideKeywords(word, keywords) {
  for (var k=0; k<keywords.length; k++) {
    var keyword = " " + keywords[k] + " ";
    if (keyword.indexOf(" " + word + " ") > -1) {
      return true;
    }
  }
  return false;
}

Shopify建站之营销跟踪设置

由于Shopify提供14天的免费试用,特别在周末尝试了下,也算是与时俱进了。

Shopify内集成了Google Smart Shopping这个免费App,这样完全不用跳出Shopify后台,通过简单的点选操作就可以完成广告账户注册、Google Merchant Center设置、广告投放设置,除去广告审核时间,基本可以在半小时内投完成一个新广告的投放,非常方便。并且Shopify还提供了一个$100的新用户coupon(需要在前30天花够$20)。

不过,还需要做一些后续的设置,才能更好的优化广告,包括GA设置、Ads转化跟踪设置以及动态再营销代码设置。

  1. GA设置:Online Store——Preference——Google Analytics,填写GA的跟踪ID,并且点击开启增强型电商即可,非常方便。Facebook Pixel也是在这个界面对应的选项中添加,Facebook的营销代码是完全自动化的,包括feed的创建,都不需要再额外设置的了。
  2. 订单跟踪转化设置:Settings——Checkout——Order processing,在additional script内填入以下代码(代码1,加入了只执行一次的判断规则),代码是从Google Ads后台获取,并且根据Shopify的变量进行了相应修改,其中页面类型基于不同模版进行自动适配,变量用Shopify特有的代码串进行了替换。
  3. 添加动态再营销跟踪代码:Online Stores——Themes——Actions——Edit Code——theme.liquid,分别在<head></head>区域中间(代码2),以及</body>前添加(代码3)。
{% if first_time_accessed %}
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-123456789"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'AW-123456789');
</script>
<script>
  gtag('event', 'conversion', {
      'send_to': 'AW-abcdefghijklmnopqrst123456789',
      'value': {{ checkout.total_price | money_without_currency }},
      'currency': '{{ shop.currency }}',
      'transaction_id': '{{ order_number  }}'
  });
  gtag('event', 'page_view', {
    'send_to': 'AW-123456789',
    'ecomm_pagetype': 'purchase',
    'ecomm_prodid': [{% for item in order.line_items %}'shopify_US_{{ item.product.id }}_{{ item.variant.id }}'{% unless forloop.last %}, {% endunless %}{% endfor %}],
    'ecomm_totalvalue': '{{ order.total_price | money_without_currency }}'
  });
</script>
{% endif %}
<!-- Global site tag (gtag.js) - Google Ads: 123456789 -->
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-123456789"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'AW-123456789');
</script>
{% assign send_to_id = 'AW-123456789' %}
{% assign shopify_store_country = 'US' %}
{% if shop.currency == 'CAD' %}
{% assign shopify_store_country = 'CA' %}
{% endif %}
<script type="text/javascript">
  {% if template contains 'product' %}
  gtag('event', 'page_view', {
    'send_to': '{{ send_to_id }}',
    'ecomm_pagetype': 'product',
    'ecomm_prodid': 'shopify_{{ shopify_store_country }}_{{ product.id }}_{{ product.selected_or_first_available_variant.id }}',
    'ecomm_totalvalue': '{{ product.price | money_without_currency }}'
  });
  {% elsif template contains 'cart' %}
  gtag('event', 'page_view', {
    'send_to': '{{ send_to_id }}',
      'ecomm_pagetype': 'cart',
    {% if cart.item_count > 1 %} 
      'ecomm_prodid': [{% for item in cart.items %}'shopify_{{ shopify_store_country }}_{{ item.product.id }}_{{ item.variant.id }}'{% unless forloop.last %}, {% endunless %}{% endfor %}],
    {% elsif cart.item_count == 1 %}
      'ecomm_prodid': {% for item in cart.items %}'{{ shopify_store_country }}_{{ item.product_id }}_{{ item.variant.id }}'{% endfor %},
    {% else %}
      'ecomm_prodid': '',
    {% endif %}
      'ecomm_totalvalue': '{{ cart.total_price | money_without_currency }}
  });
    {% elsif template contains 'collection' %}
  gtag('event', 'page_view', {
    'send_to': '{{ send_to_id }}',
    'ecomm_pagetype': 'category',
    'ecomm_prodid': [{% for item in collection.products limit:5 %}'shopify_{{ shopify_store_country }}_{{ item.id }}_{{ item.variants.first.id }}'{% unless forloop.last %}, {% endunless %}{% endfor %}],
    'ecomm_category': '{{ collection.handle }}',
    'ecomm_totalvalue': ''
  });
  {% elsif template contains 'search' %}
  gtag('event', 'page_view', {
    'send_to': '{{ send_to_id }}',
    'ecomm_pagetype': 'searchresults',
    'ecomm_prodid': [{% for item in search.results limit:5 %}'shopify_{{ shopify_store_country }}_{{ item.id }}_{{ item.variants.first.id }}'{% unless forloop.last %}, {% endunless %}{% endfor %}],
    'ecomm_totalvalue': ''
  });
  {% elsif template contains 'index' %}
  gtag('event', 'page_view', {
    'send_to': '{{ send_to_id }}',
    'ecomm_pagetype': 'home',
    'ecomm_prodid': '',
    'ecomm_totalvalue': ''
  });
  {% else %}
  gtag('event', 'page_view', {
    'send_to': '{{ send_to_id }}',
    'ecomm_pagetype': 'other',
    'ecomm_prodid': '',
    'ecomm_totalvalue': ''
  });
  {% endif %}
</script>

PS:请将以上跟踪ID替换为自己的。

通过以上设置,我们就可以正式开始Google和Facebook广告的投放了。我们可以从GA看到流量和增强型电商的相关数据,在Ads后台看到订单转化数据,并可以将以上代码收集到的人群用于投放动态再营销广告,可以在Shopify后台直接创建Google Smart Shopping广告或者简单的Facebook广告,当然也可以到对应账户内创建更加高级的其它广告。需要注意的是,Shopify API方式导入的feed,其item id格式并不是像Facebook的那样一串数字,而且带有Shopify_US_这种字符串(具体参考以上代码)。

代码加上之后,我们可以通过Google Tag Assistant和Facebook Pixel Helper这2个Chrome扩展进行相关数据的验证,也可以在GA的实时数据,或者晚点在Google Ads及Facebook Ads的相关界面查看数据。