(あどてく!) PlayFramework/Scalaで3rd Party Cookieを出力するピクセルトラッキングサーバーを作る


アドテク(広告テクノロジー)では、ブラウザでサイト見ているユーザー(オーディエンス)を特定する技術が求められます。
自社ECサイトで会員登録をしたユーザーを特定するのは簡単ですが、会員登録をしていないユーザー(初めて自社サイトに訪れたユーザー)を特定するには何らかの手段が必要になります。
つまり初めて自社サイトを訪れた人がどんな思考の人か、どんなサイトのどんなページを見ていたか・・。
その情報を特定するためにCookieに特定のIDを付与する技術がアドテクでは使われています。

3rd Party Cookieとは

今回の想定シナリオは以下のようになります
1. オーディエンスがサイト1(train.com)で大阪へいくための電車料金を調べる
2. サイト1には<img src="tracking.com/1.gif">が埋め込まれており、オーディエンスがサイトを見た瞬間にトラッキングサーバーへ通信が走る。(オーディエンスのブラウザ->トラッキングサーバー)
3. トラッキングサーバーはオーディエンスのブラウザにCookieを出力しトラッキングIDを付与する
4. オーディエンスは電車のチケットは当日駅で買うことにし、ホテルのサイト(hotel.com)でホテルを予約することにする
5. hotel.comにも<img src="tracking.com/1.gif">が埋め込まれており、オーディエンスがサイトを見た瞬間にトラッキングサーバーへ通信が走る。(オーディエンスのブラウザ->トラッキングサーバー)
6. トラッキングサーバーはオーディエンスのブラウザにtracking.comのクッキーが埋め込まれているのを確認し、再度IDを付与することはせずCookieのexpire(期限)だけを更新する。
7. オーディエンスのCookieIDからtrain.comで大阪の情報を調べていたことがログからわかるので、ホテルのページの広告枠に大阪のホテルの案内を出したり、ファーストビューを大阪ホテルのキャンペーンページに変更したりなど広告を最適化する。

トラッキング用のクッキーの種類は大きくわけて2つあります。

種類 説明     
1st Party Cookie 対象サイトのドメインのクッキーを使ってトラッキングする。絵でいう所のtrain.comのドメインでCookieを出力する
3rd Party Cookie 対象サイトとは別のトラッキングザーバーのドメインを使ってトラッキングする。絵でいう所のtracking.com

1st Party Cookieで代表的なのはGoogleAnalytics(GA)です。
参考:http://nexal.jp/blogs/2061.html

3rdPartyにはSafariをトラッキングできないという致命的な欠陥(※)がありそれを回避するには1stParyCookieが有効です。(他にもローカルストレージをモニョモニョする・・などもありますが)
(※)Safariのデフォルトの設定が「3rd Party Cookieを拒否する」となっているため事実上3rdのCookieを受け付けない。

ピクセルトラッキングとは

1x1の透明画像(だいたいはgif)をレスポンスとして返す裏でCookieを仕込み、Cookieにユーザーを特定するIDを付与する技術の事。
透明なのでブラウザからは見えない、というかほとんどはhtmlでhidden設定されてるのでブラウザには表示されない。
ピクセルトラッキングのHTMLの記述についてはDoubleClick(google)のサイトが超詳しい
https://support.google.com/dfp_premium/answer/1347585?hl=ja

画像でなくてもいいが、htmlのsrc=などでHTLMのオンロードで自動的に読み込めるものに限る。
またJSはサードパーティ製のサイトのJSを読み込めないブログサービスなどもあるので扱いづらい。
よって基本は画像になる。

PlayFramework/Scalaでピクセルトラッキングサーバーを作る

Playインストール

Activatorをダウンロードし
activator newでプロジェクトを新規作成する。(Scalaプロジェクト)

コントローラー実装

Tracking.scala
package controllers
import play.api._
import play.api.mvc._
import play.api.libs.Codecs
import java.security.MessageDigest
// Java8 lib
import java.util.Base64


object Tracking extends Controller {

  val COOKIE_KEY = "Z_SYSTEM_3RD_PARTY_COOKIE_ID_SCALA"
  val COOKIE_MAX_AFTER_AGE = Some(31622400 + 31622400)

  //ref https://css-tricks.com/snippets/html/base64-encode-of-1x1px-transparent-gif/
  val onePixelGifBase64 = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
 val onePixelGifBytes = Base64.getDecoder().decode(onePixelGifBase64)

  def pixelTracking = Action {request =>
    val cookies = request.cookies
    val cookieValue = cookies.get(COOKIE_KEY).map { cookie =>
      Logger.debug(s"Cookie Exist! ${cookie.value}")
      cookie.value
    }.getOrElse {
      val newValue = uniqueIdGenerator()
      Logger.debug("Cookie Generate! $newValue")
      newValue
    }
    Ok(onePixelGifBytes).withCookies(Cookie(COOKIE_KEY, cookieValue, COOKIE_MAX_AFTER_AGE)).as("image/gif")
  }

  val uniqueIdGenerator = () => {
    val milliTime = System.currentTimeMillis()
    val nanoTime = System.nanoTime()
    val baseString = s"$milliTime $nanoTime"
    Logger.debug(baseString)

    val md = MessageDigest.getInstance("SHA-256")
    md.update(baseString.getBytes())

    val id = Codecs.toHexString(md.digest())
    Logger.debug(id)
    id
  }
}

val uniqueIdGenerator 関数 (CookieID生成関数)

トラッキング用のIDはどようなアクセス状況下、どのようなサーバーマシン構成でもオーディエンスに対してユニークなIDを割り当てることが求められる。
今回はミリ秒とナノ秒(※)をスペースでつなげてSHA256した16進法文字列をIDとした。
これであれば同じミリ秒でアクセスしたユーザーがいない限りはユニークなIDとなる。
よりアクセス数があるサーバーであればユーザーのIPやサーバーのIPやMACアドレス、プロセスID、Javaプロセスのフリーメモリ数などを足してハッシュ化するといいだろう。
ロジックを使いまわして別トラッキングサーバーを作る場合は、トラッキングサーバーの種別ID("hogehoge-TrankingServer1")などを連結すればよいだろう。
(※)現在の一般のCPUでは正確なナノ秒は測定できないがSystem.nanoTime()はミリ秒よりは正確な値を返す。

def pixelTracking メソッド

このメソッドが実際のリクエストを受け付ける処理となる。

処理は単純で
1. リクエストにCookieの値("Z_SYSTEM_3RD_PARTY_COOKIE_ID_SCALA")があるかを検索
2. Cookieの値があったらCookieのExpire(有効期限)を更新して1x1ピクセルgifを返却
3. Cookieの値がなかったらuniqueIdGenerator関数を実行しCookieIDを生成
4. 生成したCookiIDをレスポンスに付与し、同時に1x1ピクセルgifを返却

ルーティングの設定

/conf/route
GET        /1.gif               controllers.Tracking.pixelTracking

http://サーバーのドメイン/1.gif にアクセスすると処理が実行されるようになる。

トラッキングサーバー起動

activatorを起動し、runでポート9000で起動する

activator
[info] Loading project definition from play_scala_study/play_scala_1/project
[info] Set current project to play_scala_1 (in build file:/play_scala_study/play_scala_1/)
[play_scala_1] $ run

--- (Running the application, auto-reloading is enabled) ---

[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

http://localhost:9000/1.gif にアクセスし、画面にgifが表示され、Cookieが書き込まれていることを確認する。(ブラウザの開発者ツールで確認する)
二回目のアクセスでクッキーIDが変更されていないことを確認する。

実際に運用する場合はport80で起動するようポートを指定して起動する。

activator
start 80

自社サービスのサイトのHTMLの実装

トラッキングサーバーは起動したので、別のドメインのサイトのページに以下のような記述を埋め込めばトラッキングは完了です。

train.com/index.html
<p>安い電車予約どっとこむ</p>
<img src="http://tracking.com/1.gif" style="display:none">
hotel.com/index.html
<p>安いホテル予約どっとこむ</p>
<img src="http://tracking.com/1.gif" style="display:none">