Tuesday, February 26, 2013

Update Your Bids from a Google Spreadsheet

Today we have a little script to help adjust bids at a large scale.  This is an example of how you can use the integration with Google Spreadsheets to update some of your bids for keywords.  I have provided a sample spreadsheet which you can make a copy of.

As long as the Adwords Account and the Google Docs account use the same login, it will be able to access any spreadsheet you have.  I suggest opening my sample spreadsheet and then making a copy if it in your account.  You can then replace the URL from the script below with the url of your copy of the spreadsheet.

Link to my sample spreadsheet: https://docs.google.com/spreadsheet/ccc?key=0Aotb6eheEOpodEhDM2hqQmhfeTdHT3BLMl80aDRxTkE

Thanks,
Russ
/****************************************
 * Update Bids Using a Google Spreadsheet
 * Version 1.1
 * Created By: Russ Savage
 * FreeAdWordsScripts.com
****************************************/
function main() {
  var SPREADSHEET_URL = "Insert Url Here";
   
  var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
  var sheet = spreadsheet.getSheetByName('KeywordBids');
  var data = sheet.getRange("A:E").getValues();
  
  var kwBidHash = {};
  for(var i in data) {
    if(i == 0) { continue; }
    if(data[i][0] === '') { break; }
    var kwKey = Utilities.base64Encode([data[i][0],data[i][1],data[i][2]].join('~~!~~'));
    kwBidHash[kwKey] = data[i];
  }
  
  var kwIter = AdWordsApp.keywords()
    .withCondition("Status = ENABLED")
    .get();
  
  while(kwIter.hasNext()) {
    var kw = kwIter.next();
    var campName = kw.getCampaign().getName();
    var kwKey = Utilities.base64Encode([campName,kw.getText(),kw.getMatchType()].join('~~!~~'));
    if(kwBidHash[kwKey]) {
      if(kwBidHash[kwKey][3] === "FIXED") {
        kw.setMaxCpc(kwBidHash[kwKey][4]);
      }else{
        kw.setMaxCpc(kw.getMaxCpc() * (1+kwBidHash[kwKey][4]));
      }
    }
  }
}

Authenticating to OAuth Services Using AdWords Scripts

Many APIs today use OAuth in order for users to authenticate and call their service.  One very popular example is the Twitter REST API.  Digging through the AdWords Scripting reference, you'll notice that one of the cool things you can do with AdWords Scripts is to pull data from a URL using the UrlFetchApp.

So the question I asked was "Could I write a script to authenticate using OAuth and retrieve data from the Twitter REST API?"

Obviously, the first place I looked was in the OAuthConfig class of the UrlFetchApp.  I set up the configuration and tried to make a call but I kept getting an authentication error.  A little research showed that I wasn't the only one having this problem.  The issue stems from the fact that normally, users need to authenticate through some sort of dialog box before they can access data. See this example about connecting to Picasa Web Albums for more details.

But then I found a post about someone managing to get around this by re-implementing the OAuth authentication system in their script and using a standard UrlFetchApp.fetch() request.

I was up for a challenge so I recreated it myself.  It turned out to be quite a bit of code, but in the end, the only thing you really need to worry about is getting the correct keys from Twitter and calling _build_authorization_string().

Thanks,
Russ

//-----------------------------------
// Authenticate and Connect to OAuth Service
// Created By: Russ Savage
// FreeAdWordsScripts.com
//-----------------------------------
function main() {
  //Define the Twitter Keys and Secrets
  //More info on obtaining these can be found at https://dev.twitter.com/docs/auth/tokens-devtwittercom
  var oauth_key_stuff = {
    "consumer_key" : "your consumer key",
    "consumer_secret" : "your consumer secret",
    "access_token" : "your access token",
    "access_token_secret" : "your access token secret"
  };

  // Update this with the REST url you want to call.  I only tested it with GET
  // but i don't think there is anything stopping a POST request from working.
  var url_stuff = {  
    "http_method" : 'GET',
    "base_url" : "https://api.twitter.com/1.1/statuses/user_timeline.json"
  };

  //Add the parameters for the REST url you want to call.
  var url_param_stuff = {
    "screen_name" : "russellsavage" //hey that's me!
  };
  
  // Don't touch this stuff
  var other_oauth_data = {
    "oauth_nonce" : Utilities.base64Encode(Math.random() +
          "secret_sauce" +
          (new Date()).getTime()).replace(/(?!\w)/g, ''),
    "oauth_signature_method" : "HMAC-SHA1",
    "oauth_timestamp" : Math.round((new Date()).getTime() / 1000.0),
    "oauth_version" : "1.0"
  };
  
  // Here is where the magic happens
  var auth_string = _build_authorization_string(oauth_key_stuff,url_stuff,url_param_stuff,other_oauth_data);

  var options = {
    "headers" : { "Authorization" :  auth_string }
  };
    
  var url = _build_url(url_stuff,url_param_stuff);
  var response = UrlFetchApp.fetch(url, options);
  var tweets = JSON.parse(response.getContentText());
  
  //now let's log my amazing tweets!
  for(var tweet in tweets) {
    var t = tweets[tweet];
    Logger.log(t.text);
  }
 
  // HELPER FUNCTIONS BELOW
  
  function _build_url(base_url,param_stuff){
    var url = base_url.base_url;
    if(param_stuff != {}) {
      url += '?';
    }
    for(var key in param_stuff) {
      url += key + "=";
      url += encodeURIComponent(param_stuff[key]);
      url += '&';
    }
    return url.slice(0,-1);
  }
  
  function _build_param_string(auth_keys,url_data,oauth_data) {
    var data_for_param_string = {
      "oauth_consumer_key" : auth_keys.consumer_key,
      "oauth_nonce" : oauth_data.oauth_nonce,
      "oauth_signature_method" : oauth_data.oauth_signature_method,
      "oauth_timestamp" : oauth_data.oauth_timestamp,
      "oauth_token" : auth_keys.access_token,
      "oauth_version" : oauth_data.oauth_version
    };
    
    // add additional url values
    for(var my_key in url_data) { 
      data_for_param_string[my_key] = url_data[my_key]; 
    }
    
    // find and sort the keys for later
    var keys = [];
    for(var key in data_for_param_string) {
      keys.push(key);
    }
    keys.sort();
    
    //finally build and return the param string
    var param_string = "";
    for(var i in keys) {
      param_string += keys[i] + "=" + encodeURIComponent(data_for_param_string[keys[i]]);
      if(i < keys.length - 1) {
        param_string += "&";
      }
    }
    
    return param_string;
  }
  
  function _build_sig_base_string(my_url_stuff,my_param_string) {
    return my_url_stuff.http_method +
      "&" + encodeURIComponent(my_url_stuff.base_url) +
      "&" + encodeURIComponent(my_param_string);
  }
  
  function _build_sigining_key(my_key_stuff) {
    return encodeURIComponent(my_key_stuff.consumer_secret) + 
      "&" + encodeURIComponent(my_key_stuff.access_token_secret);
  }
  
  function _build_oauth_signature(base_string,sign) {
    return Utilities.base64Encode(
      Utilities.computeHmacSignature(
        Utilities.MacAlgorithm.HMAC_SHA_1, 
        base_string, 
        sign
      )
    );
  }
  
  function _build_authorization_string(my_key_stuff,my_url_stuff,my_url_param_stuff,my_oauth_stuff) {
    var param_string = _build_param_string(my_key_stuff,my_url_param_stuff,my_oauth_stuff);
    var sig_base_string = _build_sig_base_string(my_url_stuff,param_string);
    var signing_key = _build_sigining_key(my_key_stuff);
    var oauth_signature = _build_oauth_signature(sig_base_string,signing_key);
    return "OAuth " +
           encodeURIComponent("oauth_consumer_key") + '="' + 
             encodeURIComponent(my_key_stuff.consumer_key) + '", ' +
           encodeURIComponent("oauth_nonce") + '="' + 
             encodeURIComponent(my_oauth_stuff.oauth_nonce) + '", ' +
           encodeURIComponent("oauth_signature") + '="' + 
             encodeURIComponent(oauth_signature) + '", ' +
           encodeURIComponent("oauth_signature_method") + '="' + 
             encodeURIComponent(my_oauth_stuff.oauth_signature_method) + '", ' +
           encodeURIComponent("oauth_timestamp") + '="' + 
             encodeURIComponent(my_oauth_stuff.oauth_timestamp) + '", ' +
           encodeURIComponent("oauth_token") + '="' + 
             encodeURIComponent(my_key_stuff.access_token) + '", ' +
           encodeURIComponent("oauth_version") + '="' + 
             encodeURIComponent(my_oauth_stuff.oauth_version) + '"';
    
  }
  
}

Saturday, January 26, 2013

Unique Bidding Rules For Each Campaign

All right folks, today we are going to get a little more complicated.  Let's say you have a set of campaigns that you happen to be managing.  Now, to save time, you want to set up a script to automatically adjust the bids for each campaign based on a set of rules.

The script below will help you do just that.  I am assuming that many of your optimizations are based on cost per conversion as well as the current position on the page. Now we can start to put together a generic script for these optimizations.

The script starts with a CAMP_LIST variable that holds all the campaign names and their rules.  You can add as many campaigns and rules per client as you like.

Take a look and let me know if you have any questions.

Thanks,
Russ

//-----------------------------------
// Unique Bid Updates By Campaign
// Created By: Russ Savage
// FreeAdWordsScripts.com
//-----------------------------------
function main() {
  // this is the structure that holds all the bid information about your accounts.
  var CAMP_LIST = [
    {
      'camp_name' : 'camp name 1',
      'rules' : [
        { 
          'cpv_min' : 0, 'cpv_max' : 10,
          'avg_pos_min' : 2, 'avg_pos_max' : 6,
          'bid_change_amt' : 1.1, 'bid_limit' : 10
        },
        { 
          'cpv_min' : 10, 'cpv_max' : 20,
          'avg_pos_min' : 6, 'avg_pos_max' : 10,
          'bid_change_amt' : 1.2, 'bid_limit' : 10
        }
      ]
    },
    {
      'camp_name' : 'camp name 2',
      'rules' : [
        { 
          'cpv_min' : 0, 'cpv_max' : 5,
          'avg_pos_min' : 3, 'avg_pos_max' : 5,
          'bid_change_amt' : .9, 'bid_limit' : .01
        },
        { 
          'cpv_min' : 5, 'cpv_max' : 20,
          'avg_pos_min' : 5, 'avg_pos_max' : 8,
          'bid_change_amt' : 1.2, 'bid_limit' : 10
        }
      ]
    }
  ];
  var date_range = 'LAST_7_DAYS';
  
  for (index in CAMP_LIST) {
    var camp = CAMP_LIST[index];
    var camp_name = camp.camp_name;
    var rules = camp.rules;
    
    var kw_iter = AdWordsApp.keywords()
      .withCondition("CampaignName CONTAINS_IGNORE_CASE '" + camp_name + "'")
      .get();
    
    while(kw_iter.hasNext()) {
      var kw = kw_iter.next();
      var kw_stats = kw.getStatsFor(date_range);
      var conv = kw_stats.getConversions();
      
      if (conv == 0) { continue; } //skip anything with no conversions
      
      var cost = kw_stats.getCost();
      var cpv = cost/conv;
      var avg_pos = kw_stats.getAveragePosition();
      var max_cpc = kw.getMaxCpc();
      
      for(i in rules) {
        var r = rules[i];
        
        if(cpv >= r.cpv_min && 
           cpv < r.cpv_max && 
           avg_pos >= r.avg_pos_min && 
           avg_pos < r.avg_pos_max) 
        {  
          kw.setMaxCpc(calculate_bid(max_cpc,r.bid_change_amt,r.bid_limit));
          break;
        }
      }
    }
  }
    
  function calculate_bid(current_bid,perc_to_change,max_min_amt) {
    if(perc_to_change >= 1) {
      return (current_bid * perc_to_change > max_min_amt) ? max_min_amt : (current_bid * perc_to_change);
    } else {
      return (current_bid * perc_to_change < max_min_amt) ? max_min_amt : (current_bid * perc_to_change);
    }
  }
}

Wednesday, January 16, 2013

Update Ad Params from Google Spreadsheet

2013-07-11 - NOTE: For those wanting to update AdParams at an AdGroup level, check out an updated version of this script.

Today we have a little script to help adjust keyword ad params at a large scale.  This is an example of how you can use the integration with Google Spreadsheets to update param1 and param2 values for keywords.  I have provided a sample spreadsheet which you can make a copy of.

As long as the Adwords Account and the Google Docs account use the same login, it will be able to access any spreadsheet you have.  I suggest opening my sample spreadsheet and then making a copy if it in your account.  You can then replace the URL from the script below with the url of your copy of the spreadsheet.

Link to my sample spreadsheet: https://docs.google.com/spreadsheet/ccc?key=0Aotb6eheEOpodC1yVjUwc2Y1NDIzUVFLLThJdTFPUnc#gid=0

Thanks,
Russ
/************************************************
* Update Ad Params Using a Google Spreadsheet
* Version 1.1
* ChangeLog v1.1
*  - Added the ability to enable param1 or 2 individually
*  - Looks for Keywords on all sheets
*  - Runs much faster
* Created By: Russ Savage
* FreeAdWordsScripts.com
************************************************/
var SPREADSHEET_URL = "THE URL FOR YOUR GOOGLE SPREADSHEET GOES HERE";
var SET_PARAM1 = true;
var SET_PARAM2 = false;
var DATA_RANGE = 'A:E'; // A - CampaignName, B - AdGroupName, 
                        // C - Keyword, D - Param1, E - Param2

function main() {
  var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
  var allSheets = spreadsheet.getSheets();
  var allData = [];
  for(var i in allSheets) {
    var sheet = allSheets[i];
    var data = sheet.getRange(DATA_RANGE).getValues();
    data.shift(); //get rid of headers
    allData = allData.concat(data);
  }
  
  var allDataHash = {};
  for(var i in allData) {
    var row = allData[i];
    if(row[0] === '') { continue; }
    var rowKey = [row[0],row[1],row[2]].join('~~!~~');
    allDataHash[rowKey] = { param1 : row[3], param2: row[4] };
  }
  
  var kwIter = AdWordsApp.keywords()
    .withCondition('CampaignStatus = ENABLED')
    .withCondition('AdGroupStatus = ENABLED')
    .withCondition('Status = ENABLED')
    .get();
  
  while(kwIter.hasNext()) { 
    var kw = kwIter.next();
    var campName = kw.getCampaign().getName();
    var adGroupName = kw.getAdGroup().getName();
    var rowKey = [campName,adGroupName,kw.getText()].join('~~!~~');
    if(allDataHash[rowKey]) {
      if(SET_PARAM1) { kw.setAdParam(1, allDataHash[rowKey].param1); }
      if(SET_PARAM2) { kw.setAdParam(2, allDataHash[rowKey].param2); }
    }
  }
}