淺談使用者行為分析之使用者身份識別:cookie 知多少?
對於資料統計分析或者資料探勘而言,使用者是個非常重要的維度,也是統計分析能落地的基礎。一般而言,咱們追蹤或者識別一個使用者的首選方案是 userID,大多數公司的產品都會要求使用者註冊、登入操作,都存在一個類似 UMC 的資料庫,管理和標示所有的使用者。但這有個前提條件,就是你所在的公司業務必須以閉環為主(比如 qq、微信、淘寶等)。如果產品沒有形成閉環,使用者就不會主動去註冊、登入,那上面通過 userID 資料庫來管理、追蹤使用者行為的方案就不行了。比如BBS站點或者廣告聯盟都會非常想要一種技術方式可以在網路上精確定位到每一個個體,這樣可以通過收集這些個體的資料,通過分析後更加精準的去推送廣告(精準化營銷)或其他有針對性的一些活動。當用戶訪問一個網站時,網站生成一個含有唯一標示符(UUID)的資訊,並通過這個資訊將使用者所有行為(瀏覽了哪些頁面?搜尋了哪些關鍵字?對什麼感興趣?點了哪些按鈕?用了哪些功能?看了哪些商品?把哪些放入了購物車等等)關聯起來。那這種情況下有沒有可能有其它的技術方案去管理追蹤這種遊客態使用者呢?
答案或許很多同學會回答用 cookie。是的,對於遊客態使用者而言,常用的身份識別方案就是使用 cookie,技術實現難度小,成本相對很低廉。那是不是使用 cookie 就萬事大吉了呢?準確性、穩定性、可辨識性怎麼樣?下面咱們就來深入探討下 cookie 追蹤使用者的利弊及其發展與移動網際網路時代下使用者身份識別面臨的新問題。
1、追蹤/標示使用者的方法
先上一張圖,可以看到大部分流行的方法還是基於Cookie,只是這些 Cookie 會稍有不同,本文會按照整張圖的脈絡來一一介紹各種 cookie 及其利與弊。
2、HTTP Cookie
2.1 由來
為什麼會有 HTTPCookie 呢?因為HTTP協議是無狀態的,即伺服器不知道使用者上一次做了什麼,這嚴重阻礙了互動式Web應用程式的實現。在典型的網上購物場景中,使用者瀏覽了幾個頁面,買了一盒餅乾和兩瓶飲料。最後結帳時,由於HTTP的無狀態性,不通過額外的手段,伺服器並不知道使用者到底買了什麼。 所以Cookie就是用來繞開HTTP的無狀態性的“額外手段”之一。伺服器可以設定或讀取Cookies中包含資訊,藉此維護使用者跟伺服器會話中的狀態。
2.2 實現方式
Cookie是由伺服器端生成(webserver或者cgi),response 給User-Agent(一般是瀏覽器),瀏覽器會將Cookie的key/value儲存到某個目錄下的文字檔案內,下次請求同一網站時就傳送該Cookie給伺服器(前提是瀏覽器設定為啟用cookie)。
2.3 缺陷
- Cookie會被附加在每個HTTP請求中,所以無形中增加了流量。
- 由於在HTTP請求中的Cookie是明文傳遞的,所以安全性成問題。(除非用HTTPS)
- Cookie的大小限制在4KB左右。對於複雜的儲存需求來說是不夠用的。
- 瀏覽器安全策略不允許種植 cookieID、使用者清除 cookieID,不同的瀏覽器也會生成不同的 cookieID,大量的爬蟲也可能帶上隨機 cookieID
- 識別不準確:瀏覽器安全策略不允許種植 cookieID、使用者清除 cookieID,不同的瀏覽器生成不同的 cookieID,大量的爬蟲帶上隨機 cookieID
3、Flash Cookie
3.1 由來
在客戶端Cookie裡儲存資料是不穩 定的,因為使用者可能隨時會清除掉瀏覽器的Cookie,在這種情況下,一般的解決方案是重新向伺服器端傳送一個請求,以獲得一個新的HTTP Cookie資料,並將其儲存,就一般的互動需求而言,這是沒有問題的。但是,倘若我的需求是:要求恢復到原來的Cookie裡儲存資料持久的追蹤使用者的行為呢?這種情況,倘若伺服器端沒有做特殊的處理的話,顯然是很難實現的,這裡就該 Flash Cookie 登場了。
FlashCookie是由FlashPlayer控制的客戶端共享儲存技術,它具備以下特點:
- 類似 HTTPCookie,FlashCookie利用SharedObject類實現本地儲存資訊,SharedObject類用於在使用者計算機上讀取和存 儲有限的資料量,共享物件提供永久貯存在使用者計算機上的物件之間的全域性實時資料共享;
- 本地共享物件是作為一些單獨的檔案來儲存的,它們的副檔名 為.SOL。預設時,它們的尺寸為不超過100kB,並且不會過期——這一點與傳統的HTTP Cookie不同(4KB);
- 本地共享物件並不是基於瀏覽器的,所以普通的使用者不容易刪除它們。如果要刪掉它們的話,首先要知道這些檔案所在的具體位 置。這使得本地共享物件能夠長時間的保留在本地系統上。
3.2 實現方式
要實現Flash Cookie永遠儲存的功能,顯然,首先要實現Flash Cookie與Http Cookie的互通,所以,在技術上使用JavaScript與ActionScript的來進行溝通顯然是最好的選擇,因為在這兩種語言之間,除了語法 上相近,從溝通上也有著完美的實現。下面我們來看看實現流程(如圖所示):
這裡給一個實現的 demo,點視窗→動作,我們就可以寫actionscript3的程式碼了,然後檔案→釋出成 .swf 檔案:
//匯入ExternalInterface類
import flash.external.ExternalInterface;
flash.system.Security.allowDomain("http://localhost");
flash.system.Security.allowDomain("http://127.0.0.1");
//允許任何域都可以訪問
flash.system.Security.allowDomain("*");
function setFC(userName:String, sex:String) {
var FlashCookie:SharedObject = SharedObject.getLocal("testFlashCookie");
FlashCookie.data.cookie["userName"] = userName;
FlashCookie.data.cookie["sex"] = sex;
FlashCookie.flush();
}
function getFC():String {
var FlashCookie:SharedObject = SharedObject.getLocal("testFlashCookie");
return FlashCookie.data.cookie["userName"];
}
function setFCUserObj(obj:Object) {
var FlashCookie:SharedObject = SharedObject.getLocal("testFlashCookie");
if (FlashCookie.data.cookie == undefined) {
//var obj:Object = {};
//obj[key] = value;
FlashCookie.data.cookie = obj;
} else {
for (var key:String in obj) {
FlashCookie.data.cookie[key] = obj[key];
}
}
//FlashCookie.data.userName = obj.userName;
//FlashCookie.data.sex = obj.sex;
FlashCookie.flush();
}
//允許js)呼叫flash中的getFC(),setFC(),setFCUserObj
ExternalInterface.addCallback("getFC", getFC);
ExternalInterface.addCallback("setFC", setFC);
ExternalInterface.addCallback("setFCUserObj", setFCUserObj);
再用 flask 搭一個簡單的頁面:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
<title>testFC</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css" media="screen">
html, body {
height: 100%;
background-color: #ffffff;
}
body {
margin: 0;
padding: 0;
overflow: hidden;
}
#flashContent {
width: 100%;
height: 100%;
}
</style>
<script type="application/javascript">
function setCookie(c_name, value, expiredays) {
var exdate = new Date()
exdate.setDate(exdate.getDate() + expiredays)
document.cookie = c_name + "=" + escape(value) +
((expiredays == null) ? "" : ";expires=" + exdate.toGMTString())
}
function getCookie(c_name) {
if (document.cookie.length > 0) {
c_start = document.cookie.indexOf(c_name + "=")
if (c_start != -1) {
c_start = c_start + c_name.length + 1
c_end = document.cookie.indexOf(";", c_start)
if (c_end == -1) c_end = document.cookie.length
return unescape(document.cookie.substring(c_start, c_end))
}
}
return ""
}
</script>
<script type="text/javascript">
//搭建js與flash互通的環境
function thisMovie() {
if (navigator.appName.indexOf("Microsoft") != -1) {
return window["testFC"];
} else {
return document["testFC"];
}
}
function setFCUseObj() {
c_name = getCookie("userName")
c_sex = getCookie("sex")
if (c_name == "") {
alert("當前 jCookie: " + c_name + "n" + "當前 flash cookie: " + thisMovie().getFC())
var ajaxRequest = new XMLHttpRequest();
ajaxRequest.open("GET", "http://127.0.0.1:5000/add", false);
ajaxRequest.send(null);
c_name = getCookie("userName") + Math.random();
c_sex = getCookie("sex") + Math.random();
}
{# expiredays = 1#}
{# setCookie(key, value, expiredays)#}
var obj = new Object();
obj.userName = c_name;
obj.sex = c_sex;
thisMovie().setFCUserObj(obj);
}
function getFC() {
alert(thisMovie().getFC());
}
function setFC() {
thisMovie().setFC("June_flashCookie", "male");
}
</script>
</head>
<body>
<input type="button" onclick="setFC()" value="setFC"/>
<input type="button" onclick="getFC()" value="getFC"/>
<input type="button" onclick="setFCUseObj()" value="setFCUseObj"/>
<div id="flashContent">
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,19,0" width="1"
height="1" id="testFC" title="testFC">
<param name="allowScriptAccess" value="always"/>
<param name="movie" value="testFC.swf">
<param name="quality" value="high">
<param name="wmode" value="transparent"/>
<embed src="static/testFC.swf" name="testFC" quality="high" allowScriptAccess="always" swLiveConnect="true"
pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" width="1"
height="1"></embed>
</object>
</div>
</body>
</html>
最後再配個簡單的 cgi:
from flask import Flask, request, Response, make_response, render_template
import time
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'hello world'
@app.route('/add')
def login():
res = Response('add cookies')
res.set_cookie(key='userName', value='lisi_jsCookie', expires=time.time() + 10 * 60)
res.set_cookie(key='sex', value='unKnown', expires=time.time() + 10 * 60)
return res
@app.route('/testFC')
def cookietest():
return render_template("testFC.html")
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
3.3 缺陷
優勢是顯而易見的,缺陷在於:
- 部署相對 HTTP Cookie 複雜了,存在一定的開發維護成本。
- 平臺相容性不夠好,目前只支援特定的平臺與特定的瀏覽器,比如 蘋果的全系列產品都不支援 flash,以至於2012年開始 adobe 已經徹底放棄了移動端 flash 的更新,這樣 Android 平臺未來也不會存在 flash 這一技術了,因此至少在移動端 Flash Cookie 不能作為長久之計。
- 可以跨瀏覽器使用,只要瀏覽器呼叫的是同一個Flash元件,但彼此相容性不夠(如Internet Explorer和Mozilla Firefox之間資訊便可共享,但Google Chrome和Mozilla Firefox便不行)。
- 雖然可以跨瀏覽器,但隱身模式無效。可以被很容易地清除。
4、EverCookie
4.1 由來
前面的兩種方法都存在一定的缺陷,在複雜多變的使用者場景裡,資料可能和真實的誤差很大,那有沒有辦法進一步提高 Cookie 追蹤使用者身份的準確度呢?也有,那就是最後聊到的 EverCookie,其實它也不是什麼新的 cookie 技術,只是利用客戶端各種儲存區域,儘可能的儲存多的 cookie 副本,以防某處 cookie 被刪除可以恢復,相當於 cookie 也有了類似 hadoop 多副本災備機制,同時生成 cookieID 的演算法參考了更多的客戶端標識和軟硬體特徵,讓 cookieID 具有更高的穩定性、唯一性、可辨識性,不隨演算法本身隨機性的影響。
4.2 實現方式
Evercookie不僅僅是難刪除,而是會積極“反抗”刪除。方法就是在使用者電腦裡,利用不同的儲存機制不斷地複製自己,或者在副本丟失或到期作廢時讓自己重新復活。具體來說,Evercookie在建立cookie時會使用如下儲存機制:
- 標準HTTP cookie
- Local Shared Objects (Flash cookie)
- Silverlight Isolated Storage
- 以自動生成、強制快取的PNG畫素圖片的RGB值形式儲存cookie,使用HTML5 Canvas標籤讀取畫素圖片(cookie)
- 在瀏覽器歷史記錄中儲存cookie
- 在HTTP ETag中儲存cookie
- 在瀏覽器快取中儲存cookie
- window.name快取
- Internet Explorer userData
- HTML5 Session Storage
- HTML5 Local Storage
- HTML5 Global Storage
- HTML5 Database Storage(SQLite)
開發人員計劃增加如下功能:
- HTTP Authentication快取
- 使用Java基於NIC資訊產生唯一鍵
4.3 缺陷
上面的 EverCookie 看起來氣場十足,很完美,其實只是理想太美好,顯示依舊很殘酷——一樣的存在諸多缺陷,只是它把一些缺陷不足儘可能降低了而已。
以 canvas指紋 為例:
雖然解決了 cookie 的穩定性——無法刪除,但是唯一性、可辨識性並沒有解決——重複率太高、ID容易變化:
這裡有個線上指紋測試的例子:
5、移動網際網路時代下的新挑戰
從文初的圖上可以看到,在 PC 時代,追蹤使用者身份技術方案多,也挺靠譜的,但是隨著移動網際網路大潮的到來,使用者逐漸轉向了 M 和 APP,形成三大平臺三足鼎立的局面,這三大平臺的軟硬體技術方案各異,比如蘋果系列的產品不支援 flash、不允許隨便種植 cookie,而 Android 雖然開放,但是開放的尺度太大了,導致了很嚴重的軟硬體碎片化的問題,這給技術方案的通用相容性帶來了嚴重的問題。應用又分為 NativeAPP 和 webAPP,前者可以很好的和系統結合,拿到系統的硬體資訊特徵,比如 MAC、IMEI,而 webAPP 大都受限於瀏覽器隱私策略保護和前端技術限制,沒法拿到系統的硬體資訊,這就直接導致無法生成一個基於硬體的唯一的、穩定的、準確的“使用者ID”,而且想要三端使用者身份都打通就成了一個難事,比如:公司三端的使用者重合度是 100%,每端 UV 都是一億,那麼三端的總 UV 應該是一億,但是以現有業界的 cookieID 技術方案來統計 UV,會得出三端總 UV 是三億的錯誤結論,而這目前業界也還沒有很完善、通用的解決方案。
總結下在移動網際網路時代,使用者身份識別與追蹤的新挑戰有兩點:
- 三端使用者身份無法打通、統一
- 追蹤識別的成本越來越高,方案越來越複雜化
這或許是商業行為與使用者隱私的一場持久博弈,而在這場博弈的背後技術又將會扮演什麼角色呢?
6、Refer:
[1] Javascript-Flash-Cookies
https://github.com/nfriedly/Javascript-Flash-Cookies
[2] flash cookie的製作和使用例子詳解 一
http://ylq365.iteye.com/blog/1873382
[3] 使用者資料跟蹤之Flash Cookies
http://www.biaodianfu.com/flash-cookies.html
[4] 使用Flash Cookie技術在客戶端永久儲存HTTP Cookie
http://www.cnblogs.com/dcba1112/archive/2011/05/05/2037715.html
[5] 不用Cookie的“Cookie”技術:etag
http://blog.jobbole.com/46266/
[6] 網站資料收集
https://support.google.com/partners/answer/6083646?hl=zh-Hans
[7] php 如何對客戶端 pc 生成唯一標識?
[8] 防惡意點選程式碼系統思路與實現
http://wenku.baidu.com/view/6c0b0749be1e650e52ea9917
[9] Evercookie(永遠刪不掉的cookie)
http://www.ituring.com.cn/article/35102
[9] 如何設定一個永遠無法刪除的Cookie
http://www.biaodianfu.com/zombie-cookie.html
[9] 關於瀏覽器身份追蹤技術的研究與整理
http://blog.zsxsoft.com/post/11
[10] evercookie
https://github.com/samyk/evercookie
https://github.com/decli/flask-fingerprint
[11] 取代cookie的網站追蹤技術:”帆布指紋識別”初探
http://security.tencent.com/index.php/blog/msg/59
[12] canvas指紋驗證測試報告
http://blog.csdn.net/huangm_fat/article/details/38522939
[13] 線上指紋測試例子:
[14] 自由之裝置,獨立之人格:從裝置識別到跨屏營銷