いすたの日記

備忘録から読んだ本や使ってみたガジェットの紹介など。 データ分析/マーケティング/EC周辺に関心ありです。

Google Analytics(GA) API v3 をjavaで実装する3(Servlet実装)

前回までの準備
Google Analytics(GA) API v3 をjavaで実装する1(準備) - isuta3's diary
Google Analytics(GA) API v3 をjavaで実装する2(Server側実装) - isuta3's diary

今回行うのは、以下を想定。

  • 諸々の準備(済)
  • 複数のプロファイルをまとめてGA API実行(済)
  • 出力に利用する指標(Metric, Dimension)は複数パタンの固定とする(済)
  • Servletを作成し実行してブラウザに表示させる。 (★)
  • jsp、angularJSを利用したクライアントからservlet経由でAPI実行のデータの取得

tomcatServletを実行するための環境を作る

tomcatプロジェクトを作成し、server.xmlにcontectPathを記述。
プロジェクトのコンテキストルートを設定し、Servletを作成したらweb.xmlに記述。
localhostで実行してブラウザに表示する。

1. server.xmlの記述
eclipsetomcatプロジェクトを作成すれば自動的にserver.xmlにContextPathが追記できるが、
追記された内容は例えば下記。今回はプロジェクト名を「AnalyticsSrv」コンテキストルートは「/」のままにした。

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

 <Context path="/AnalyticsSrv" reloadable="true"
                docBase="/workspace/AnalyticsSrv"
                workDir="/workspace/AnalyticsSrv/work" />

        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>


2. servletを作成
URLをたたいてGA APIを実行してjsonの結果Stringを表示するだけのServletを作成する。
GaExecMainのgetResultDataは前回までに作ったもので、アナリティクスの複数のプラファイルをまとめて実行しjson形式のテキストを返すメソッド。

public class GaSearchServlet extends HttpServlet  {

	private static final long serialVersionUID = 1L;

	@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    
	}

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json; charset=UTF-8");
        PrintWriter out = resp.getWriter();
        
        GaExecMain main = new GaExecMain();
        String json = main.getResultData("2013-12-01", "2013-12-31", 25, 1);
        out.println(json);
        out.close();
        
    }

3. web.xmlを設定
web.xmlには、上記のservletを登録する。

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         version="2.4"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

	<display-name>ga api</display-name>

    <servlet>
        <display-name>gaapi</display-name>
        <servlet-name>gaapi</servlet-name>
        <servlet-class>analytics.servlet.GaSearchServlet</servlet-class>
        <load-on-startup>50</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>gaapi</servlet-name>
        <url-pattern>/gaapi</url-pattern>
    </servlet-mapping>
</web-app>

これでtomcatを起動して確認、と思ったらlibを読み込んでなかったのでNoClassDefFoundErrorが出た。tomcatプロジェクトのWEB-INF/libに準備編で取得してライブラリをつっこむ。


4. 起動確認
tomcatはport8080で設定していたので、下記URLで確認した。
http://localhost:8080/AnalyticsSrv/gaapi
こんな感じでブラウザ上に出力された。

{
  "normal": [
    {
      "shopNm": "shop name",
      "pageTitle": "title",
      "pageviews": "45",
      "visits": "6",
      "percentNewVisits": "33.33333333333333",
      "bounces": "3",
      "timeOnSite": "1329.0",
      "exitRate": "22.22222222222222",
      "visitBounceRate": "50.0"
    },
    {
      "shopNm": "shop name2",
      "pageTitle": "title2",
      "pageviews": "12",
      "visits": "1",
      "percentNewVisits": "100.0",
      "bounces": "1",
      "timeOnSite": "0.0",
      "exitRate": "41.66666666666667",
      "visitBounceRate": "100.0"
    }
  ]
}

Google Analytics(GA) API v3 をjavaで実装する2(Server側実装)

前回の準備
Google Analytics(GA) API v3 をjavaで実装する1(準備) - isuta3's diary

今回行うのは、以下を想定。

  • 諸々の準備(済)
  • 複数のプロファイルをまとめてGA API実行(★)
  • 出力に利用する指標(Metric, Dimension)は複数パタンの固定とする(★)
  • jsp、angularJSを利用したクライアントからservletでサーバー側からAPI実行のデータの取得

複数のプロファイルをまとめて実行する

複数のプロファイルはjson形式のファイルを読み込む形にした。
//gaTableList.json

[
{"profile":"ga:68430111","profileNm":"dummy"},
{"profile":"ga:68430112","profileNm":"dummy"},
{"profile":"ga:68430113","profileNm":"dummy"},
{"profile":"ga:68430114","profileNm":"dummy"},
{"profile":"ga:68430115","profileNm":"dummy"}
]

これをjavaで読み取り、プロファイルごとにAPIをループさせて実行する。実際には、

  1. jsonファイルから該当のプロファイルを読み込む
  2. dataStoreDirという認証データのディレクトリを指定する。
  3. 取得する指標のパタンを指定する(Metric&Dimension)
  4. プロファイルごとにAPIを実行し、結果データのリストを生成する。
  5. 結果データのリストを出力やファイル生成する。(今回はGsonを利用してjsonに変換)

//table(プロファイル)用のclass

public class GaProfile {
	private String profile;
	private String profileNm;
}

//table(プロファイル)のリストを取得する(TODO DB設定化)

private static final java.io.File TABLE_LIST =
        new java.io.File(System.getProperty("user.home"), "gaTableList.json");


public List<GaProfile> execProfileList(){
	List<GaProfile> list = null;
	try {
		JsonReader reader = new JsonReader(new BufferedReader(new FileReader(TABLE_LIST)));
		list = fromListJson(reader);
	} catch (IOException e) {
		System.err.println("ホームディレクトリに'gaTableList.json'ファイルを配置してください。\n" +
				"Ex.Jsonfile \n" +
				"[{\"profile\":\"ga:xxxxxxx\",\"profileNm\":\"appNm\"}]");
	}
	return list;
}

//認証データのディレクトリ(TODO DB設定化)

private static final java.io.File DATA_STORE_DIR =
        new java.io.File(System.getProperty("user.home"), ".store/analytics_main");

//指標のパタン、これに好きな条件を突っ込む
//Metric&Dimensionの詳細:
https://developers.google.com/analytics/devguides/reporting/core/dimsmets?hl=ja

public class GaSearchPtn {
    public String sDate;
    public String eDate;
    public String metric;
    public String dimension;
    public String sort;
    public String filter;
    public int maxResults;
}

//プロファイルごとにAPIを実行する(ほぼsampleから拝借)
//sortとFiterは今のところ利用しない。

public class GaApiExec {

    private static FileDataStoreFactory dataStoreFactory;
    /** Global instance of the JSON factory. */
    private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
    
    /** Global instance of the HTTP transport. */
    private static HttpTransport httpTransport;
    @SuppressWarnings("unused")
    private static Analytics client;

    public GaData gaSearchExec(GaProfile profile, File dataStoreDir, GaSearchPtn ptn ){

        gaSearchConfig(profile.getProfileNm(), profile.getProfile(), dataStoreDir);
        GaData gaData = null;
        try {
            gaData = executeDataQuery(client, profile.getProfile(), ptn);
            if (gaData.getTotalResults() <= 0) {
                System.out.println("No data");
                return null;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return gaData;
    }

    public void gaSearchConfig(String appNm, String tableId, File dataStoreDir) {
        try {
            // initialize the transport
            httpTransport = GoogleNetHttpTransport.newTrustedTransport();
            // initialize the data store factory
            dataStoreFactory = new FileDataStoreFactory(dataStoreDir);
            // authorization
            Credential credential = authorize();
            // set up global Analytics instance
            client = new Analytics.Builder(httpTransport, JSON_FACTORY, credential)
                    .setApplicationName(appNm).build();
            
        } catch (GoogleJsonResponseException e) {
            System.err.println(e.getDetails().getCode() + ":" + e.getDetails().getMessage());
        } catch (IOException e) {
            System.err.println(e.getMessage());
        } catch (Throwable t) {
            t.printStackTrace();
        }
        //System.exit(1);
    }

    /** Authorizes the installed application to access user's protected data. */
    private static Credential authorize() throws Exception {
        // load client secrets
        GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY,
                new InputStreamReader(GaApiExec.class.getResourceAsStream("/client_secrets.json")));
        if (clientSecrets.getDetails().getClientId().startsWith("Enter") ||
                clientSecrets.getDetails().getClientSecret().startsWith("Enter ")) {
            System.out.println(
                    "Overwrite the src/main/resources/client_secrets.json file with the client secrets file "
                            + "you downloaded from the Quickstart tool or manually enter your Client ID and Secret "
                            + "from https://code.google.com/apis/console/?api=analytics#project:414826239625 "
                            + "into src/main/resources/client_secrets.json");
            System.exit(1);
        }

        // Set up authorization code flow.
        // Ask for only the permissions you need. Asking for more permissions
        // will
        // reduce the number of users who finish the process for giving you
        // access
        // to their accounts. It will also increase the amount of effort you
        // will
        // have to spend explaining to users what you are doing with their data.
        // Here we are listing all of the available scopes. You should remove
        // scopes
        // that you are not actually using.
        Set<String> scopes = new HashSet<String>();
        scopes.add(AnalyticsScopes.ANALYTICS);
        scopes.add(AnalyticsScopes.ANALYTICS_EDIT);
        scopes.add(AnalyticsScopes.ANALYTICS_MANAGE_USERS);
        scopes.add(AnalyticsScopes.ANALYTICS_READONLY);

        GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
                httpTransport, JSON_FACTORY, clientSecrets, scopes)
                .setDataStoreFactory(dataStoreFactory)
                .build();
        // authorize
        return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");

    }

    private GaData executeDataQuery(Analytics analytics, String tableId, GaSearchPtn ptn) throws IOException {
        return analytics.data().ga().get(tableId,
                ptn.sDate, ptn.eDate, ptn.metric)
                .setDimensions(ptn.dimension)
                //.setSort(ptn.sort)
                //.setFilters(ptn.filter)
                .setMaxResults(ptn.maxResults).execute();
    }
}

//結果データをStringで取得しjsonで出力する
//※取得するパタンが固まったら、mapはやめて結果データ用のclassファイルを作成する

    public String resultToJson(List<GaData> gaDataList) {
        List<LinkedHashMap<String, String>> dataList = new ArrayList<LinkedHashMap<String, String>>();
        GaAccountSettingService svc = new GaAccountSettingService();
        for(GaData gaData : gaDataList){
            if (gaData.getTotalResults() <= 0|| gaData == null) continue;

            int columnQty = gaData.getColumnHeaders().size();
            Query query = gaData.getQuery();
            String shopNm = svc.getShopNameFromJson(query.getIds());
            
            for (List<String> rowValues : gaData.getRows()) {
                LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
                map.put("shopNm", shopNm);
                for(int i=0; i<columnQty; i++){
                    String header = gaData.getColumnHeaders().get(i).getName();
                    String data   = rowValues.get(i);
                    header = gaHeaderReplace(header);
                    map.put(header, data);
                }
                dataList.add(map);
            }
        }
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        Map<String, List<LinkedHashMap<String, String>>> result = new HashMap<String, List<LinkedHashMap<String,String>>>();//TODO mapはやめてentity化
        result.put("normal", dataList);
        String json = gson.toJson(result);
        return json;
    }

//GAのAPIからプロファイルの名称に良いのがないので後から、
//idからshopNmを大本のgaTableList.jsonから取得(GaAccountSettingService.class)

private List<GaProfile> profileList = null;
public String getShopNameFromJson(String ids) {
        if(profileList == null) {
            profileList = execProfileList();
        }
        for(GaProfile profile : profileList){
            if(ids.equals(profile.getProfile())){
                return profile.getProfileNm();
            }
        }
        return "NoProfileNm";
    }

//最終的に出力されるjson形式の結果イメージ

{
  "normal": [
    {
      "shopNm": "dummy",
      "visitorType": "New Visitor",
      "pageviews": "369",
      "visits": "96",
      "percentNewVisits": "100.0",
      "bounces": "40",
      "timeOnSite": "15078.0",
      "exitRate": "26.01626016260163",
      "visitBounceRate": "41.66666666666667"
    },
    {
      "shopNm": "dummy",
      "visitorType": "Returning Visitor",
      "pageviews": "83",
      "visits": "30",
      "percentNewVisits": "0.0",
      "bounces": "10",
      "timeOnSite": "2910.0",
      "exitRate": "36.144578313253014",
      "visitBounceRate": "33.33333333333333"
    }
  ]
}

ソースはまとめてこちらにアップ
https://github.com/isuta/ga-api/tree/master/GaApiApps
プログラム初心者なので、書き方が悪いところがあるはずです。

実行中のエラーやコンパイルエラー

  • Exception in thread "main" java.lang.NoClassDefFoundError: com/google/common/collect/Maps

これもライブラリが足りていない。
google-collect-1.0-rc1.jarというjarが必要なんだけど、
2013/12時点ではguavaというライブラリにかわっている。
https://code.google.com/p/google-collections/

なのでguava-15.0.jarをダウンロードしてビルドパスにさせばOK。

  • com.google.api.services.analytics.model.GaDataが見つからない

下記ライブラリがビルドパスに刺さっていない。
これだけダウンロード時のlibsに一緒に入っていないので注意。
google-api-services-analytics-v3-rev78-1.17.0-rc.jar

Google Analytics(GA) API v3 をjavaで実装する1(準備)

GA APIで複数のサイトのアクセス解析結果をまとめて出力するプログラムをjavaで実装する。

複数のサイトを一つのアナリティクスアカウントで管理している場合に、
それぞれのプロファイルごとの解析となるが、それをまとめて出力できるようにする。
今回行うのは、以下を想定。

  • 諸々の準備(★)
  • 複数のプロファイルをまとめてGA API実行
  • 出力に利用する指標(Metric, Dimension)は複数パタンの固定とする
  • jsp、angularJSを利用したクライアントからservletでサーバー側からAPI実行のデータの取得

下準備・環境(eclipse)準備

APIを実行するために、プロジェクトとAPIの登録が必要であり、最終的にclient_secret.jsonのファイルを取得する。
また、eclipseで実行するために、諸々の依存ライブラリなどがあるので、その設定をする。

あらかじめアナリティクスで利用しているgoogleユーザでログインしておき、
下記のQuickstartから一括で行うのが最も早い。
https://developers.google.com/api-client-library/java/apis/analytics/v3

Quickstartから「Command Line」を選択し、プロジェクトを作成する。
新規もしくは既存のプロジェクトを選択して進めると下記のように表示される。

f:id:isuta3:20140104161455p:plain

3.のzipはsampleなので、利用する場合はこれを使う(maven必要)
4.でclient_secret.jsonをダウンロードする。

maven利用する場合はこれで依存ライブラリを気にする必要はないが、
maven利用しない場合は、同じページの下記から必要ライブラリなどをダウンロードする。

f:id:isuta3:20140104161842p:plain

直接ライブラリなどをダウンロードしてビルドパスにさす場合は、
zip解凍後のgoogle-api-services-analytics-v3-rev78-1.17.0-rc.jarと
libs以下のjarを利用した。

※2013/12時点で、アナリティクスのAPIはV3が最新なのでV2を誤って選択しないように注意。
日本語リファレンスはV2だったかと。

後は、sampleやチュートリアルにそって進めると実行できる。
実際の実装は次で。