Google Analytics, OAuth and Ruby. Oh, my.
I recently had the
opportunity to take a peek at accessing
Google Analytics data using their
OAuth endpoint. We did this in Ruby using the Ruby OAuth
gem. There were a few snags along the way so I figured I’d put up some notes
for other people to take a peek.
First, you’re going to need a newer version of the OAuth gem (0.3.5 seems to
work). Google uses some features from the 1.0a version of the OAuth spec, the
oauth_verifier
specifically, that aren’t available in older versions of the
OAuth gem. That took a while to track down.
The second thing you’re going to need is a consumer token and secret from google. You can retrieve this from your domain management page (here as it’s a bit difficult to find.)
require 'oauth'
con = OAuth::Consumer.new(CONSUMER_TOKEN, CONSUMER_SECRET,
{:site => 'https://www.google.com',
:request_token_path => '/accounts/OAuthGetRequestToken',
:access_token_path => '/accounts/OAuthGetAccessToken',
:authorize_path => '/accounts/OAuthAuthorizeToken'})
To start we create our OAuth connection. You’ll need to substitute your token and secret for the consumer constants above. We need to specify all of the OAuth paths to match the endpoints provided by Google.
rt = con.get_request_token({}, {:scope => 'https://www.google.com/analytics/feeds'})
We use the connection to create a request token. get_request_token
takes two
parameters. The token parameters and other parameters. Above, I’m not specifying
any request parameters but, if you want Google to redirect the user to a site of
your choice you would provide {:oauth_callback => URL}
as the token
parameter. After authenticating the user would be redirected to the provided URL
and the parameters oauth_token=TOKEN
and oauth_verifier=VERIFIER
will be
passed to the URL. You’ll need to use those parameters when creating the access
token.
The second parameter when creating the request token is used by Google to
restrict the users access to specific portions of Google. In our case we’re
requesting access to the Analytics data. A list of available scopes is
here. You can specify
multiple scope values by putting a space between them. e.g.
"http://www.google.com/calendar/feeds/%20http://docs.google.com/feeds/"
.
rt.authorize_url => "https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token=4%2F-1o2aZgHxJmjhaExv9htRsGWLlwy"
With our request token in hand we can
use it to generate the authorization URL. You’ll need to redirect the user to
this URL to do the authentication.
Since we aren’t providing a callback URL our users will just get redirected to a standard Google page and you’ll need to copy their verifier code into the application.
at = rt.get_access_token(:oauth_verifier => 'vQH9bpXateNpsISpEDZax')
When the user has authorized we either
get them to paste the verifier code or we can retrieve it from the parameters
passed to our
oauth_callback
. We use the verifier code to create an access
token. This is the token we’ll be using to access the Google resources. We can
save the at.token
and at.secret
values into our database to create new
access tokens next time the user needs data from Google. We don’t need to
authenticate each time.
at = OAuth::AccessToken.new(con, USER_TOKEN, USER_SECRET)
We can also, if we’re using the oauth_callback
store some of our request
token information to the session. If we store rt.token
and rt.secret
we
can re-build our request token in a simlar fashion to the access token.
rt = OAuth::RequestToken.new(con, REQUEST_TOKEN, REQUEST_SECRET)
With the authentication out of the way we can get on with accessing the analytics data.
puts at.get("https://www.google.com/analytics/feeds/accounts/default?prettyprint=true").body
We grab our profile data first. You’ll need the dxp:tableId
value in order
to make calls to get profile specific analytic data. (The prettyprint=true
is a handy little flag to get Google to nicely indent the XML returned. Makes
for easier reading/debugging.)
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:opensearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:dxp="http://schemas.google.com/analytics/2009">
<id>http://www.google.com/analytics/feeds/accounts/dan.sinclair@gmail.com</id>
<updated>2009-07-06T11:00:09.000-07:00</updated>
<title type="text">Profile list for dan.sinclair@gmail.com</title>
<link rel="self" type="application/atom+xml" href="http://www.google.com/analytics/feeds/accounts/default" />
<author>
<name>Google Analytics</name>
</author>
<generator version="1.0">Google Analytics</generator>
<opensearch :totalresults>1</opensearch>
<opensearch :startindex>1</opensearch>
<opensearch :itemsperpage>1</opensearch>
<entry>
<id>http://www.google.com/analytics/feeds/accounts/ga:123xxxx</id>
<updated>2009-07-06T11:00:09.000-07:00</updated>
<title type="text">everburning</title>
<link rel="alternate" type="text/html" href="http://www.google.com/analytics" />
<dxp :tableid>ga:123xxxx</dxp>
<dxp :property name="ga:accountId" value="73xxxx" />
<dxp :property name="ga:accountName" value="everburning" />
<dxp :property name="ga:profileId" value="123xxxx" />
<dxp :property name="ga:webPropertyId" value="UA-73xxxx-1" />
<dxp :property name="ga:currency" value="USD" />
<dxp :property name="ga:timezone" value="America/Toronto" />
</entry>
</feed>
puts at.get("https://www.google.com/analytics/feeds/data?ids=ga:123xxxx&start-date=2009-06-05&end-date=2009-06-06&dimensions=ga:pagePath&metrics=ga:pageviews,ga:uniquePageviews&prettyprint=true").body
The data I want from Google Analytics is the number of ga:pageviews
and
ga:uniquePageviews
for each ga:pagePath
between June 5th and 6th.
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:opensearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:dxp="http://schemas.google.com/analytics/2009">
<id>http://www.google.com/analytics/feeds/data?ids=ga:123xxxx&dimensions=ga:pagePath&metrics=ga:pageviews,ga:uniquePageviews&start-date=2009-06-05&end-date=2009-06-06</id>
<updated>2009-06-06T16:59:59.999-07:00</updated>
<title type="text">Google Analytics Data for Profile 123xxxx</title>
<link rel="self" type="application/atom+xml" href="http://www.google.com/analytics/feeds/data?end-date=2009-06-06&start-date=2009-06-05&metrics=ga%3Apageviews%2Cga%3AuniquePageviews&ids=ga%3A123xxxx&dimensions=ga%3ApagePath" />
<author>
<name>Google Analytics</name>
</author>
<generator version="1.0">Google Analytics</generator>
<opensearch :totalresults>52</opensearch>
<opensearch :startindex>1</opensearch>
<opensearch :itemsperpage>52</opensearch>
<dxp :startdate>2009-06-05</dxp>
<dxp :enddate>2009-06-06</dxp>
<dxp :aggregates>
<dxp :metric confidenceinterval="0.0" name="ga:pageviews" type="integer" value="162" />
<dxp :metric confidenceinterval="0.0" name="ga:uniquePageviews" type="integer" value="152" />
</dxp>
<dxp :datasource>
</dxp><dxp :tableid>ga:1239xxxx</dxp>
<dxp :tablename>everburning</dxp>
<dxp :property name="ga:profileId" value="123xxxx5" />
<dxp :property name="ga:webPropertyId" value="UA-73xxxx-1" />
<dxp :property name="ga:accountName" value="everburning" />
<entry>
<id>http://www.google.com/analytics/feeds/data?ids=ga:123xxxx&ga:pagePath=/&start-date=2009-06-05&end-date=2009-06-06</id>
<updated>2009-06-05T17:00:00.001-07:00</updated>
<title type="text">ga:pagePath=/</title>
<link rel="alternate" type="text/html" href="http://www.google.com/analytics" />
<dxp :dimension name="ga:pagePath" value="/" />
<dxp :metric confidenceinterval="0.0" name="ga:pageviews" type="integer" value="9" />
<dxp :metric confidenceinterval="0.0" name="ga:uniquePageviews" type="integer" value="8" />
</entry>
<entry>
<id>http://www.google.com/analytics/feeds/data?ids=ga:123xxxx&ga:pagePath=/quotes/&start-date=2009-06-05&end-date=2009-06-06</id>
<updated>2009-06-05T17:00:00.001-07:00</updated>
<title type="text">ga:pagePath=/quotes/</title>
<link rel="alternate" type="text/html" href="http://www.google.com/analytics" />
<dxp :dimension name="ga:pagePath" value="/quotes/" />
<dxp :metric confidenceinterval="0.0" name="ga:pageviews" type="integer" value="6" />
<dxp :metric confidenceinterval="0.0" name="ga:uniquePageviews" type="integer" value="6" />
</entry>
</feed>
You may notice that all of the paths returned are relative to some base URL. I haven’t been able to figure out how to get Google Analytics to tell me what that base URL happens to be. If you figure it out, please leave a comment so I can update my code.
That’s it. You can take a deeper look at the
Google Analytics API
documentation to learn about the different dimensions
and metrics
that
can be queried.
If you’re doing this in Ruby you may also want to take a look at the Happy Mapper gem to make your XML using life easier.
Have fun.