ga.jsのキャンペーン処理関連の動きを追う。

修正: 2009/11/16 16:45(いろいろ書き直しました)

前回の処理の続きです。

initData内の3つの処理を見るシリーズで、今回は3つめのutmzの処理(キャンペーンに関する部分)です。

  1. 新規セッションなのかどうか(utma, utmb, utmc)
  2. utmvの処理(setVar, setCustomVar)に関する所
  3. utmzの処理(referrerを、どうutmzに組み込むか) (この記事)

他の回と同様に、Ga.jsのコードを追います。(変数名は、私の独自定義です)

initData()の中で、

campaignManager = new Z.Camp(

b.domainhash, b.modifiedreferrer, b.time, defaultvalues);

という感じで、utmzを扱うオブジェクトを作っておいて、

b.Ta = campaignManager.cc(b.cookiemanager, b.isNewSession)

cookie全体の操作を扱うオブジェクトと,1の新規セッションかどうかを判定したフラッグ変数を引数に、メソッドを実行します。

返値は、””, utmcr=1, utmcn=1です。キャンペーンに関係なければ、””, nがnew campaignで、rがrepeart campaignだと思います。

それで、campaingManager.cc(b.cookiemanager, b.isNewSession)の動きです。

いろいろな条件分岐で、referrerとcookienのutmzの部分を見て、キャンペーンに関係のあるセッションか? 関係あるなら、新規キャンペーンか既存か?を判定しています。

最終的に、その判定を返り値で、メインのAPIを操作するオブジェクトのプロパティに返して、データ送信時に使います。

条件としては、以下の状態return “”になります。(3パラメータ条件=”id, source, gclid”のいずれかがある)

  1. pageTracker._linkのタイプで飛んだ先の場合、既にキャンペーンがあれば、return “”
  2. 3パラメータ条件を満たすけど、urlのパラメータにnooverride=1が入っていたら、return “”
  3. sourceに検索エンジンの値を入れる。このとき、無視するキーワード(自分host名とかにaddIgnoredOrganicで設定)なら、return “”
  4. 検索エンジンの値が無い+新規セッションの場合、sourceに参照サイトを入れる。無視する参照元なら、return “”
  5. 参照元もない+新規セッション+utmzの蓄えがない場合、sourceをDirectとする。
  6. 3パラメータ条件に当てはまらない場合には、return “”

で、上の条件で、return “”とならなかった場合には、

  1. (新規セッション Or utmzの値とreferrerからの値にズレ)  の時、utmcn=1を返す
  2. それ以外は、utmcr=1を返す

という作業になります。

これで、initDataの3つの処理は終わりになります。

その後、trackePageviewなら、特殊処理をして、requestを飛ばす作業に入るし、他の設定系のAPIの場合は、値を返したり、Setしたりする作業に移っています。

コードにつけたメモを付けておきます。(中身はあまり信用しないで下さい、指摘してもらえるとありがたいです)

       //initDataで呼ばれるもの
     //return "" or "&utmcn=1" or "&utmcr=1"
     //arguments g=>"cookiemanager, h=>"isNewVisit?"
     cmp.cc = function (cookiemanager, newsession) { //?, h
       var k = "",
       n = "-",
       r, a = 0,
       d, l, domainhash = cmp.c;
       if (!cookiemanager) return "";
       l = cookiemanager.getCookieStr(); //lにcookie値を入れる
       k = cmp.getSearchTerm(dfvls.a["location"]); //kにはsearchが入る
       //_linkで遷移してきて、validataに通った場合
       if (dfvls.allowLinker && cookiemanager.verify()) {
         n = cookiemanager.na(); //utmzに関する値が、cookiemanager内変数のutmzArにあるか?
         //linkで飛んだ先に、既存のcampaignがあれば、return ""
         if (!NoVl(n) && !GaIndexOf(n, ";")) { //nに値があり、;がきちんとあれば、
           cookiemanager.Ga(); //utmzの値をcookieに書き込み
           return ""; //campaignには関係なしと
         }
       }
       n = Q(l, "__utmz=" + domainhash + ".", ";"); //nには今までのcookieにあるutmzが入る
       r = cmp.setAndGetDataHolder(k); //rに$.Camp.DataHolderオブジェクトがはいる, kはlocation.searchパ\
ラメータ

       //nooverride=1の時
       if (cmp.L(r)) {
         k = Q(k, dfvls.utm_nooverride + "=", "&"); //nooverrideの値を取得
         if ("1" == k && !NoVl(n)) return ""; //1で utmzに値があれば、""
       }

       //無視する検索ワードの場合に return ""
       if (!cmp.L(r)) { //id, source, gclidのどれもない
         r = cmp.setOrganic(); //new Z.Camp.DataHolder()が入る
         if (!NoVl(n) && cmp.isIgnoredTerm(r)) return ""; //追加の無視キーワードであれば
       }

       //新規セッションなら、referrerを調査して、無視する検索キーワードならreturn ""
       if (!cmp.L(r) && newsession) {
         r = cmp.setReferral(); //referralをsetしたcmp.DataHolderが入る
         if (!NoVl(n) && cmp.isIgnoredRef(r)) return ""; //無視する参照元なら、""
       }

       //新規セッションで、cookieが""なら直接参照をset
       if (!cmp.L(r)) if (NoVl(n) && newsession) r = cmp.setDirect(); //utmzが空なら、直接参照

       //directでsetしたなら、return ""
       if (!cmp.L(r)) return "";

       //utmzに値があれば、referrerのものと比較
       if (!NoVl(n)) {
         //a [ <domainhash><birth-time><num_of_session><num_of_camp> + csr]
         a = n.split(".");
         d = new Z.Cmp.DataHolder;
         d.setValueFromStr(a.slice(4).join("."));//utmzのutmc値から、dオブジェクトに値をset
         d = ToLower(d.getStr()) == ToLower(r.getStr()); //dオブジェクトの値を文字列化して、比較
         a = a[3] * 1; //aにはキャンペーンセッション回数
       }
       if (!d || newsession) {//cookieとreferrerが違うか、newsessionなら
         //utmaの値を取得
         newsession = Q(l, "__utma=" + domainhash + ".", ";");
         //最後の"."の場所を取得
         l = newsession.lastIndexOf(".");
         //session回数を取得 l>9にならない場合があるのか? domain-hashが1でも、session-idがあるし?
         newsession = l > 9 ? GaSubstr(newsession, l + 1) * 1 : 0;
         a++; //campain-counterを++
         //0を1に。他はそのまま
         newsession = 0 == newsession ? 1 : newsession;
         //referrerからの値で、cookiemanagerに値をSet
         cookiemanager.wb([domainhash, cmp.timenow, newsession, a, r.getStr()].join("."));
         cookiemanager.Ga(); //書き込み
         return "&" + "utmcn=1";
       } else return "&" + "utmcr=1"; //repeartの場合は、utmzは変更しない
     };
   };

ga.js : utmv処理部分(initData内で)

前回の記事(ga.js: 新規セッション管理部分)で、initData内の3つの処理

  1. 新規セッションなのかどうか(utma, utmb, utmc)
  2. utmvの処理(setVar, setCustomVar)に関する所
  3. utmzの処理(referrerを、どうutmzに組み込むか)

の1のセッションの新規・既存管理部分をみました。(b.pc())

今回は、2のutmv処理部分。

そのまえに、utmv部分についての話を少し。

これは、analyticsユーザが自由にデータにラベル付けをできる部分です。

ユーザ定義という表現でレポート画面に出ています。

(カスタム変数が使えるようになっているとのアナウンスがありますが、まだ私の所には導入されてないようです)

ユーザ定義は、apiを使って、利用者が自由にユーザをラベル付けできる部分なので、能動的?な解析が可能になります。

(カスタム変数では、ユーザ、セッション、ページの各レベルでラベリングが可能になってます。ラベリングというより、タグ付けという感じですが)

今回は、utmvmanager.tc()という処理。b.pcの後の処理です。(変数名は、前回同様、私が勝手に付けたものです)

  1. cookieからutmvに関する部分を読み出して、setVarの部分を除けておく。
  2. 読み出したものを、utmvmanager.data変数にsetする。
  3. cvmanager(送信時のutmv部分の文字列作成用オブジェ)に値をsetする
  4. scopeが1 ユーザレベルでのカスタム変数部分を取り出して、setVar部分の文字列に足す。
    1. 文字列があれば、cookieにutmvとして保存する。
    2. なければ、cookieに、utmvは ”” 空文字、セッション限りのものとする。

こんな流れになってるようです。cookieから値を読んで、送信用に値を設定してるだけでした。

ちょっと長いですが、今回の流れで登場するオブジェクトのUtmvManager, CvManager(私がつけた変数名)に関するコードのコメントを貼り付けて起きます。いろいろ勘違いはあると思います。

  //utmv管理オブジェクト   initDataから呼ばれる
  Z.UtmvManager = function (f, i, b, j) {
    //qは初期化オブジェクト, pはドメインハッシュ, c.Oはcookiemanager, eは$.CvManager
    var utmvmanager = this,
    domainhash = i,
    m = "=",
    dfvls = f,
    cvmanager = j; //cvmanager

    utmvmanager.cookiemanager = b; //cookiemanager O
    utmvmanager.oldsetvar = ""; //sa
    utmvmanager.data = {}; //値がstoreされる

    //initDataから呼ばれる
    utmvmanager.tc = function () {
      var h;
      //cookieの値を読んで、domain-hashを見て、utmv値を取り出す("."以降)
      h = GaSplit(Q(utmvmanager.cookiemanager.getCookieStr(), "__utmv=" + domainhash + ".", ";"), domainhash + ".")[1];
      if (!NoVl(h)) {
        h = h.split("|");
        //1, {}, "|" ?? utmvmanager.dataに値がsetされる gの定義は下
        // {index: [key,value,scope]}の形
        setting(1, utmvmanager.data, h[1]);
        //旧式のsetvarの値
        utmvmanager.oldsetvar = h[0];
        //cvmanagerオブジェにset
        utmvmanager.setInKvoperator();
      }
    };

    //実際にcookieにutmvの値を書き込みする
    //utmvmanager.dataオブジェクトが書き込みされる
    utmvmanager.setInKvoperator = function () {
      utmvmanager.clearAndSet(); //値を設定し直す、内部でkvopertorにデータをset
      var h = utmvmanager.oldsetvar, //setvarの代入
      k, n, r = "";
      //scope-level=>1 "user-level" rに代入
      for (k in utmvmanager.data){
        if ((n = utmvmanager.data[k]) && 1 === n[2]){
          r += k + m + n[0] + m + n[1] + m + 1 + ",";
        }
      }
      //rに値があれば(user-levelがあれば)
      NoVl(r) || (h += "|" + r);

      //setvar, user-levelに値がなければ
      if (NoVl(h)){
        //utmvに、値なし期限無しのcookie書き込み,
        utmvmanager.cookiemanager.Vb();
      //あれば
      }else {
        //utmvに値をset
        utmvmanager.cookiemanager.Aa(domainhash + "." + h);
        //utmvに、実際に、cookie書き込み
        utmvmanager.cookiemanager.Fa();
      }
    };

    //setVar用の設定
    utmvmanager.setSetVar = function (h) {
      utmvmanager.oldsetvar = h;
      utmvmanager.setInKvoperator();
    };

    //setCustomVarの設定用
    //b._setCustomVarをcallした時は、initData()のあとに、これが呼ばれる
    //pageTracker内で作られたutmv
    utmvmanager.setSetCustomVar = function (h, k, n, r) { //[index, name, value, scope]
      if (1 != r && 2 != r && 3 != r) r = 3; //指定がない場合は3(page-level)
      var a = false;
      if (k && n && h > 0 && h <= dfvls.maxSlot) { // dfvls.obは indexの最大値制限 5
        k = GaEncode(k);
        n = GaEncode(n);
        if (k["length"] + n["length"] <= 64) {
          utmvmanager.data[h] = [k, n, r]; //{index, [name, value, scope]}というデータ構造
          utmvmanager.setInKvoperator();
          a = true;
        }
      }
      return a;
    };

    //getVisitorCustomVar用
    utmvmanager.mc = function (h) {
      if ((h = utmvmanager.data[h]) && 1 === h[2]) return h[1];
    };

    utmvmanager.Ub = function (h) {
      var k = utmvmanager.data;
      if (k[h]) {
        delete k[h];
        utmvmanager.setInKvoperator();
      }
    };

    //utmvの値をclearして、setし直す
    utmvmanager.clearAndSet = function () {
      cvmanager._clearKey(8); //"k"が8のvalueをclear
      cvmanager._clearKey(9); //"k"が9のvalueをclear
      cvmanager._clearKey(11); //"k"が11のvalueをclear
      var h = utmvmanager.data,
      k, n;
      //転置のイメージ、utmvmanager.dataの長さと同じ長さの配列を作る
      //cvmanagerにデータをsetしていく
      for (n in h){
        if (k = h[n]) { //=に注意、==でない
          //cvmanager._setKey(8or9or11, index, value)という形
          cvmanager._setKey(8, n, k[0]); //nameは8
          cvmanager._setKey(9, n, k[1]); //valueは9
           //3 != k => user, sessionなら書き込み scopeをset
          (k = k[2]) && 3 != k && cvmanager._setKey(11, n, "" + k); //scopeは11
        }
      }
    };

    //値のset
    function setting(h, data, n) {
      var r;
      if (!NoVl(n)) {
        n = n.split(",");
        for (var a = 0; a < n["length"]; a++) {
          r = n[a];
          if (!NoVl(r)) {
            r = r.split(m); //m=>"="
            if (r["length"] == 4) data[r[0]] = [r[1], r[2], h]; //kに値が入る
          }
        }
      }
    }
  };

  //key-value操作用 utmv用オジェ、 メインオブジェから呼ばれる
  //3階層オブジェクトを作り、読む keyは文字列、valueは数字
  //Mainオブジェクトのプロパティとして入る
  Z.CvManager = function () {
    var cvmanager = this, //f = this
    i = {}, //メインーインスタンスーオブジェクト
    //b = "k",
    //j = "v",
    c = ["k", "j"]; //"k", "v"
    //p = "(",
    //m = ")",
    /*
    q = "*",
    e = "!",
    g = "'",
    h = {};
    h[g] = "'0";
    h[m] = "'1";
    h[q] = "'2";
    h[e] = "'3";
    */
    var k = 1,
    h = {"'":"'0", ")":"'1", "*":"'2", "!":"'3"};

    //Setter
    function setter(o, u, y, B) {
      if (undefined == i[o]) i[o] = {};
      if (undefined == i[o][u]) i[o][u] = [];
      i[o][u][y] = B;
      debugger;
    }

    //Getter
    function getter(o, u, y) {
      return undefined != i[o] && undefined != i[o][u] ? i[o][u][y] : undefined;
    }

    //Deleter
    function deleter(o, u) {
      if (undefined != i[o] && undefined != i[o][u]) {
        i[o][u] = undefined;
        u = true;
        var y;
        for (y = 0; y < ["k","v"]["length"]; y++) {
          if (undefined != i[o][["k","v"][y]]) {
            u = false;
            break
          }
        }
        if (u) i[o] = undefined;
      }
    }

    function buildStrByNum(o) { //d
      var u = "",
      y = false,
      B, O; //c => ["k", "v"]
      for (B = 0; B < c["length"]; B++) {
        O = o];
        if (undefined != O) {
          if (y) u += c[B];
          u += l(O);
          y = false;
        }else{
          y = true;
        }
      }
      return u;
    }

    function l(o) {
      var u = [],
      y, B;
      for (B = 0; B < o["length"]; B++) if (undefined != o[B]) {
        y = "";
        if (B != k && undefined == o[B - 1]) y += B.toString() + "!";
        y += t(o[B]);
        GaPush(u, y);
      }
      return "(" + u.join("*") + ")";
    }

    function t(o) {
      var u = "",
      y, B, O;
      for (y = 0; y < o["length"]; y++) {
        B = o.charAt(y);
        O = h[B];
        u += undefined != O ? O : B;
      }
      return u;
    }
    cvmanager.qc = function (o) {
      return undefined != i[o];
    };

    //b.Lc(trackPageview)から呼ばれる,送信用文字列を作成
    cvmanager.getCustomVarStr = function () {
      var o = "",
      u;
      for (u in i){
        if (undefined != i[u]){
          o += u.toString() + buildStrByNum(i[u]);
        }
      }
      return o;
    };

    //b._sendXEventからcallされる,送信文字列作成用
    cvmanager.Ac = function (cvmanager_local) {
      if (cvmanager_local == x) return cvmanager.getCustomVarStr();
      //uの例 : "5(category*action)"
      var u = cvmanager_local.getCustomVarStr(),
      y;
      for (y in i){
        if (undefined != i[y] && !cvmanager_local.qc(y)){
          u += y.toString() + buildStrByNum(i[y]);
        }
      }
      return u;
    };

    //clearAndSetから呼ばれる
    cvmanager._setKey = function (either_8_9_11, i, name) {
      if (typeof name != "string") return false;
      //elementOfUtmvDataは順番i["8"]["k"] = x xが配列でnがインデクサで、nameがvalue
      setter(either_8_9_11, "k", i, name);
      return true;
    };

    //真っ当な数字であること
    cvmanager._setValue = function (o, u, y) {
      if (typeof y != "number" && (undefined == Number || !(y instanceof Number)) || Math.round(y) != y || y == NaN || y == Infinity) return false;
      //uは順番
      setter(o, "v", u, y.toString());
      return true;
    };
    cvmanager._getKey = function (o, u) {
      return getter(o, "k", u);
    };
    cvmanager._getValue = function (o, u) {
      return getter(o, "v", u);
    };
    cvmanager._clearKey = function (o) {
      deleter(o, "k"); //o は 8 or 9 0r 11
    };
    cvmanager._clearValue = function (o) {
      deleter(o, "v");
    };
  };
  //End of CvManager

複数ドメインのtracking

Google AnalyticsのAPIの説明をしながら解説してみたい。

まず、Google Analytisはfirst party cookieを使ってデータを取得・送信している。first-party cookieとは、個々のドメインに対して機能するものなので、違うドメインのcookieとは、連携する事はできない。(行動ターゲティングなどは、画像一つ一つがドメインとなれる事を利用して、複数ドメインでのユーザーの行動追跡を可能にしている、たぶん)

とにかく、データ収集範囲は、ドメインに限定される。

(2010/03/07 cookieの値に関する限りです。google analyticsは、web property IDが同じなら、サイトが違っても、同じレポート上のデータとして扱う)

が、ドメインを超えて、データを収集したい。そこで、Google Analyticsでは、URLにデータを乗せて(post送信もURLにくっつく形)、遷移した別のドメインで、そのデータをga.jsが読み書きするになっている。

これを行うAPIは、_link, _getLinkerUrl , _linkByPost 3つ。同時に、_setAllowLinker(true)が、この3つが機能させるフラグ、trueで機能する、デフォルトはfalse。

また、_ga.jsの中で、ドメインの相違をcheckしているので、それを外すのが_setDomainName()。この引数には、デフォルトで “auto”(そのページのドメインが使われる) “none”(ドメインハッシュを1にする), または、自分で任意の文字列をドメインとして設定することもできるよう。

また、_setAllowHashというものもあり、引数をfalseにすると、ドメインハッシュを行わず、checkも行わない。よって、cookieのドメインcheckをせず、複数ドメインに対応可能になる。又、domain-hashをしないので、実行効率もあがるらしい。

サブドメインの間でデータを取る場合に、_setDomainName(“.sub.hoge.com”)のように、頭に ” . “ を書く事が推奨されているけど、この理由はよくわからない。これは、sub-domain共通でデータを取得してますというのを、わかりやすくするのが理由だろうか?
(2010/03/07 cookieは、.hoge.com のように書くと、サブドメイインでの値の共有になるとの事)

cookieの値の受け渡しができれば、ドメインハッシュ値を同じにして、ga.jsでのdomainチェックを通し、データを送信でき、複数ドメインでのデータを同じプロファイルで扱えることになる。

Iframeでも同様。Iframenも平行してアクセスがあると考える(ページ遷移などでレポートされる遷移の順番は、utmbの番号などを見れば、いいだろうか?)

また、注意するべき点として、setDomainNameは、できるだけ早い順番にかくべき。pageTracker変数を作った後に。_setVarなど(もう廃止項目だけど)を使う場合に、setDomainNameでドメインをそろえてないと、変更前のドメインとなるので、trackePageviewに反映されない。(_setVarの変更でリクエストは飛ぶけど、これも_setCustomVarだと飛ばないよう)なので、

  1. var pageTracker = _gat._getTracker(“UA-XXXXXX-11”)
  2. pageTracker._setDomainName(“none”);
  3. pageTracker._setAllowLinker(true);
  4. pageTracker._setVar(“hogehoge);
  5. pageTracker._trackPageview();

この順番で試したら問題なく、utmvの値も引き継げた。また、htmlの中のリンクに直接 pageTracker変数を使う(<a>タグのonclickなどで)なら、bodyの頭に、上記のコード(1-5)を入れる必要がある。

しかし、外部リンクにいちいち、pageTrackr._linkerなどと書いていくのは、実用上無理がある気がする。めんどくさい。

ので、下の記事にも書いたけど、一括でEventListenerに登録する方が漏れが少ないと思う。google analytics 外部へのリンクを計測する