Clicks and Impressions from Google Adwords API using ColdFusion

Requests for simple data like clicks and impressions from the Google Adwords API can be made via SOAP requests. For more complex data and calculations, the client libraries are more aptly suited.

This example does not use the sandbox. Calls to it will counts against the Adwords units. It also uses the latest version of the API, version v201003.

The requests require an authorization token which can be obtained via the ClientLogin method. Store this token in a session or application variable to prevent a CAPTCHA challend from Google for multiple authorization requests.

<cfif NOT StructKeyExists(application, "adw_loginAuth")>
	<cfset googleLogin(api.email,api.password) />
</cfif>

<cffunction name="googleLogin" access="private" hint="GA account authorization">
        <cfargument name="email" type="string" required="yes" default="">
        <cfargument name="password" type="string"required="yes" default="">
        <cfargument name="gaLoginUrl" type="string" required="no" default="https://www.google.com/accounts/ClientLogin">
    
        <cfset var loginAuth = "" />
           
        <cfhttp url="#arguments.gaLoginUrl#" method="post">
            <cfhttpparam name="accountType" type="url" value="GOOGLE">
            <cfhttpparam name="Email" type="url" value="#arguments.email#">
            <cfhttpparam name="Passwd" type="url" value="#arguments.password#">
            <cfhttpparam name="service" type="url" value="adwords">
            <cfhttpparam name="source" type="url" value="my-adwords-not-yours">
        </cfhttp>
    
        <cfif NOT FindNoCase("Auth=",cfhttp.filecontent)>
            <cfset loginAuth = "Authorization Failed" />
        <cfelse>
            <cfset loginAuth = Mid(cfhttp.filecontent, FindNoCase("Auth=",cfhttp.filecontent) + (Len("Auth=")), Len(cfhttp.filecontent)) />
        </cfif>
        <!--- authToken in application var to prevent Google from sending captcha request (recommended by Google) --->
        <cflock scope="application" type="exclusive" timeout="5">
			<cfset application.adw_loginAuth = loginAuth />
		</cflock>
</cffunction>

The Adwords API is called with an http post request to the appropriate service with the data request specified using SOAP.

<cfsavecontent variable="CampaignRequestXML">
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="https://adwords.google.com/api/adwords/cm/v201003">
	<soapenv:Header>
		<RequestHeader>
            <authToken>#application.adw_loginAuth#</authToken>
            <userAgent>V2010 Get All Campaign Info</userAgent>
            <developerToken>ADWORDS-API-DEV-TOKEN</developerToken> 
            <clientEmail>CLIENT-EMAIL</clientEmail>           
        </RequestHeader>
    </soapenv:Header> 
    <soapenv:Body>
    	<get>
        	<selector>
            	<ids></ids>
					<statsSelector>
						<dateRange>
							<min>20100101</min>
							<max>20100715</max>
						</dateRange>
                        <startDate/>
						<endDate/>
                        <network>ALL</network>
						<clicks/>
						<impressions/>
					</statsSelector>
            </selector>
        </get>
    </soapenv:Body>
</soapenv:Envelope> 
</cfsavecontent>

Clicks and impressions are obtained using the campaign service. Data returned from the Adwords API is also in the SOAP format. After stripping the prefixes out of the SOAP response that will prevent the XMLSearch function in ColdFusion from working as expected, the data can be extracted and stored in variables and displayed.

<!--- http SOAP request and xml parse --->
<cfset CampaignResponseXML = adwordsSOAPresponse(CampaignRequestXML) />

<cffunction name="adwordsSOAPresponse" access="private" returnType="string">
	<cfargument name="xmlSOAPrequest" type="string" required="yes" default="">
	
	<cfif application.adw_loginAuth NEQ "Authorization Failed">
        <cfhttp url="https://adwords.google.com/api/adwords/cm/v201003/CampaignService" method="post">
           <cfhttpparam name="SOAPAction" type="header" value=""/>
           <cfhttpparam type="xml" value="#trim(arguments.xmlSOAPrequest)#"/>
        </cfhttp>
        <cfset responseXML = cfhttp.filecontent />
    <cfelse>
        <p>Google Authorization Failed.</p>
        <cfabort />
    </cfif>
    
    <!---remove soap: prefix or any other prefix from nodes that have it --->
    <cfset responseXML = responseXML.ReplaceAll("(</?)(\w+:)","$1") />
    <!--- remove xmlns: --->
    <cfset responseXML = responseXML.ReplaceAll("xmlns(:\w+)?=""[^""]*""","") />
    <!--- remove xsi element prefixes --->
    <cfset responseXML = responseXML.ReplaceAll('(/?)(xsi:)','$1') />
    
    <cfreturn responseXML />
</cffunction>

Now the data can be extracted to an array using XMLSearch. Then put into an array of structures to make it easy to dump.

<cfset CampaignResponseXML = adwordsSOAPresponse(CampaignRequestXML) />

<cfset CampaignEntryNodes = XmlSearch(CampaignResponseXML, '//entries/') />

<cfloop from="1" to="#ArrayLen(CampaignEntryNodes)#" index="num">
	<cfset entryStruct = StructNew() /> 
	
    	<cfset entryStruct.id = CampaignEntryNodes[num].id.XmlText />
        <cfset entryStruct.name = CampaignEntryNodes[num].name.XmlText />
        <cfset entryStruct.status = CampaignEntryNodes[num].status.XmlText />
        <cfset entryStruct.clicks = CampaignEntryNodes[num].campaignStats.clicks.XmlText />
        <cfset entryStruct.impressions = CampaignEntryNodes[num].campaignStats.impressions.XmlText />
     
		<cfset totalClicks = totalClicks + CampaignEntryNodes[num].campaignStats.clicks.XmlText />
        <cfset totalImpressions = totalImpressions + CampaignEntryNodes[num].campaignStats.impressions.XmlText />
        <cfset arrayAppend(campaignStatsArray,duplicate(entryStruct)) />
      
</cfloop>

<cfdump var="#campaignStatsArray#">
<p>Total clicks: #totalClicks#<br />
Total Impressions: #totalImpressions#</p>

Code in its entirety:

<!--- cfapplication is not needed if application.cfc exists --->
<cfapplication name="adwordsapi" applicationtimeout="#createtimespan(2,0,0,0)#" />

<cfset totalClicks = 0 />
<cfset totalImpressions = 0 />
<cfset campaignStatsArray = ArrayNew(1) />
<cfset campaignID = "" />
<cfset campaignStatusArray = ArrayNew(1) />
<!---
	Adwords API parameters:
	Set email and password to adwords account
	Dev token is from MCC API details
--->
<cfscript>
   api = structnew();
   api.email = "GMAIL-ADDRESS-HERE";
   api.password = "GMAIL-PASSWORD";
   api.devtoken = "API-DEV-TOKEN";
   api.campaignService = "https://adwords.google.com/api/adwords/cm/v201003/CampaignService";
   api.clientEmail = "CLIENT-EMAIL-HERE";
   api.startDate = DateFormat(Now(), 'yyyymmdd');
   api.endDate = DateFormat(Now(), 'yyyymmdd');
</cfscript>

<cfif NOT StructKeyExists(application, "adw_loginAuth")>
	<cfset googleLogin(api.email,api.password) />
</cfif>

<cffunction name="googleLogin" access="private" hint="GA account authorization">
        <cfargument name="email" type="string" required="yes" default="">
        <cfargument name="password" type="string"required="yes" default="">
        <cfargument name="gaLoginUrl" type="string" required="no" default="https://www.google.com/accounts/ClientLogin">
    
        <cfset var loginAuth = "" />
           
        <cfhttp url="#arguments.gaLoginUrl#" method="post">
            <cfhttpparam name="accountType" type="url" value="GOOGLE">
            <cfhttpparam name="Email" type="url" value="#arguments.email#">
            <cfhttpparam name="Passwd" type="url" value="#arguments.password#">
            <cfhttpparam name="service" type="url" value="adwords">
            <cfhttpparam name="source" type="url" value="adwords-clicks-impressions">
        </cfhttp>
    
        <cfif NOT FindNoCase("Auth=",cfhttp.filecontent)>
            <cfset loginAuth = "Authorization Failed" />
        <cfelse>
            <cfset loginAuth = Mid(cfhttp.filecontent, FindNoCase("Auth=",cfhttp.filecontent) + (Len("Auth=")), Len(cfhttp.filecontent)) />
        </cfif>
        <!--- authToken in application var to prevent Google from sending captcha request (recommended by Google) --->
        <cflock scope="application" type="exclusive" timeout="5">
			<cfset application.adw_loginAuth = loginAuth />
		</cflock>
</cffunction>

<cffunction name="adwordsSOAPresponse" access="private" returnType="string">
	<cfargument name="xmlSOAPrequest" type="string" required="yes" default="">
	
	<cfif application.adw_loginAuth NEQ "Authorization Failed">
        <cfhttp url="#api.campaignService#" method="post">
           <cfhttpparam name="SOAPAction" type="header" value=""/>
           <cfhttpparam type="xml" value="#trim(arguments.xmlSOAPrequest)#"/>
        </cfhttp>
        <cfset responseXML = cfhttp.filecontent />
    <cfelse>
        <p>Google Authorization Failed.</p>
        <cfabort />
    </cfif>
    
    <!---remove soap: prefix or any other prefix from nodes that have it --->
    <cfset responseXML = responseXML.ReplaceAll("(</?)(\w+:)","$1") />
    <!--- remove xmlns: --->
    <cfset responseXML = responseXML.ReplaceAll("xmlns(:\w+)?=""[^""]*""","") />
    <!--- remove xsi element prefixes --->
    <cfset responseXML = responseXML.ReplaceAll('(/?)(xsi:)','$1') />
    
    <cfreturn responseXML />
</cffunction>

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Adwords API</title>
</head>
<body>
<cfoutput>

<!---Campaign Info --->
<cfsavecontent variable="CampaignRequestXML">
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="https://adwords.google.com/api/adwords/cm/v201003">
	<soapenv:Header>
		<RequestHeader>
            <authToken>#application.adw_loginAuth#</authToken>
            <userAgent>V2010 Get All Campaign Info</userAgent>
            <developerToken>#api.devtoken#</developerToken> 
            <clientEmail>#api.clientEmail#</clientEmail>           
        </RequestHeader>
    </soapenv:Header> 
    <soapenv:Body>
    	<get>
        	<selector>
            	<ids></ids>
					<statsSelector>
						<dateRange>
							<min>#api.startDate#</min>
							<max>#api.endDate#</max>
						</dateRange>
                        <startDate/>
						<endDate/>
                        <network>ALL</network>
						<clicks/>
						<impressions/>
					</statsSelector>
            </selector>
        </get>
    </soapenv:Body>
</soapenv:Envelope> 
</cfsavecontent>

<!--- http SOAP request and xml parse --->
<cfset CampaignResponseXML = adwordsSOAPresponse(CampaignRequestXML) />

<cfset CampaignEntryNodes = XmlSearch(CampaignResponseXML, '//entries/') />

<cfloop from="1" to="#ArrayLen(CampaignEntryNodes)#" index="num">
	<cfset entryStruct = StructNew() /> 
	
    	<cfset entryStruct.id = CampaignEntryNodes[num].id.XmlText />
        <cfset entryStruct.name = CampaignEntryNodes[num].name.XmlText />
        <cfset entryStruct.status = CampaignEntryNodes[num].status.XmlText />
        <cfset entryStruct.clicks = CampaignEntryNodes[num].campaignStats.clicks.XmlText />
        <cfset entryStruct.impressions = CampaignEntryNodes[num].campaignStats.impressions.XmlText />
     
		<cfset totalClicks = totalClicks + CampaignEntryNodes[num].campaignStats.clicks.XmlText />
        <cfset totalImpressions = totalImpressions + CampaignEntryNodes[num].campaignStats.impressions.XmlText />
        <cfset arrayAppend(campaignStatsArray,duplicate(entryStruct)) />
      
</cfloop>

<cfdump var="#campaignStatsArray#">
<p>Total clicks: #totalClicks#<br />
Total Impressions: #totalImpressions#</p>

</cfoutput>
</body>
</html>
Posted in Adwords, ColdFusion. Tags: , . Permalink. Both comments and trackbacks are closed.

7 Comments

  1. Fred
    October 7, 2011 at 9:30 am | Permalink

    one more thing – Jen may be ritght with the leading/trailing spaces thing.
    You can’t parse XML data with leading whitespace (unlike java/c/perl/etc…).
    I get past this with a Trim() :

    cfhttpparam
    type=”xml”
    value=”#trim( SOAPBody )#”

  2. Fred
    October 7, 2011 at 9:26 am | Permalink

    cfhttpparam
    type=”header”
    name=”accept-encoding”
    value=”no-compression”

  3. Fred
    October 7, 2011 at 9:12 am | Permalink

    Jen – Great post!
    This is a note for Tammy:
    When you convert this to classic asp, asp assumes a pass-back of gzip encoding is ok.
    Well – its not. You probably need to pass the cfhttpparam of “no-compression” – similar to this:

  4. November 23, 2010 at 8:10 pm | Permalink

    Great post.

  5. August 26, 2010 at 4:45 am | Permalink

    I found this article very useful, thanks for writing it up!

  6. August 5, 2010 at 12:32 pm | Permalink

    @Tammy
    If I had to guess, it would be the SOAP request. Something may be wrong with it's structure or it has leading or trailing spaces.

  7. Tammy
    August 5, 2010 at 11:59 am | Permalink

    i converted this to classic asp following your code exactly and i am getting a fault code for string index out of range, any clues?

One Trackback

  1. […] Google from sending captcha request (recommended by Google) —&gt; … [ Source : http://www.jensbits.com/2010/07/18/clicks-and-impressions-from-google-adwords-api-using-coldfusion/ […]