对于数据统计分析或者数据挖掘而言,用户是个非常重要的维度,也是统计分析能落地的基础。一般而言,咱们追踪或者识别一个用户的首选方案是 userID,大多数公司的产品都会要求用户注册、登录操作,都存在一个类似 UMC 的数据库,管理和标示所有的用户。但这有个前提条件,就是你所在的公司业务必须以闭环为主(比如 qq、微信、淘宝等)。如果产品没有形成闭环,用户就不会主动去注册、登录,那上面通过 userID 数据库来管理、追踪用户行为的方案就不行了。比如BBS站点或者广告联盟都会非常想要一种技术方式可以在网络上精确定位到每一个个体,这样可以通过收集这些个体的数据,通过分析后更加精准的去推送广告(精准化营销)或其他有针对性的一些活动。当用户访问一个网站时,网站生成一个含有唯一标示符(UUID)的信息,并通过这个信息将用户所有行为(浏览了哪些页面?搜索了哪些关键字?对什么感兴趣?点了哪些按钮?用了哪些功能?看了哪些商品?把哪些放入了购物车等等)关联起来。那这种情况下有没有可能有其它的技术方案去管理追踪这种游客态用户呢?
答案或许很多同学会回答用 cookie。是的,对于游客态用户而言,常用的身份识别方案就是使用 cookie,技术实现难度小,成本相对很低廉。那是不是使用 cookie 就万事大吉了呢?准确性、稳定性、可辨识性怎么样?下面咱们就来深入探讨下 cookie 追踪用户的利弊及其发展与移动互联网时代下用户身份识别面临的新问题。
先上一张图,可以看到大部分流行的方法还是基于Cookie,只是这些 Cookie 会稍有不同,本文会按照整张图的脉络来一一介绍各种 cookie 及其利与弊。
为什么会有 HTTPCookie 呢?因为HTTP协议是无状态的,即服务器不知道用户上一次做了什么,这严重阻碍了交互式Web应用程序的实现。在典型的网上购物场景中,用户浏览了几个页面,买了一盒饼干和两瓶饮料。最后结帐时,由于HTTP的无状态性,不通过额外的手段,服务器并不知道用户到底买了什么。 所以Cookie就是用来绕开HTTP的无状态性的“额外手段”之一。服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态。
Cookie是由服务器端生成(webserver或者cgi),response 给User-Agent(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。
在客户端Cookie里保存数据是不稳 定的,因为用户可能随时会清除掉浏览器的Cookie,在这种情况下,一般的解决方案是重新向服务器端发送一个请求,以获得一个新的HTTP Cookie数据,并将其保存,就一般的交互需求而言,这是没有问题的。但是,倘若我的需求是:要求恢复到原来的Cookie里保存数据持久的追踪用户的行为呢?这种情况,倘若服务器端没有做特殊的处理的话,显然是很难实现的,这里就该 Flash Cookie 登场了。
FlashCookie是由FlashPlayer控制的客户端共享存储技术,它具备以下特点:
要实现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)
优势是显而易见的,缺陷在于:
前面的两种方法都存在一定的缺陷,在复杂多变的用户场景里,数据可能和真实的误差很大,那有没有办法进一步提高 Cookie 追踪用户身份的准确度呢?也有,那就是最后聊到的 EverCookie,其实它也不是什么新的 cookie 技术,只是利用客户端各种存储区域,尽可能的存储多的 cookie 副本,以防某处 cookie 被删除可以恢复,相当于 cookie 也有了类似 hadoop 多副本灾备机制,同时生成 cookieID 的算法参考了更多的客户端标识和软硬件特征,让 cookieID 具有更高的稳定性、唯一性、可辨识性,不随算法本身随机性的影响。
Evercookie不仅仅是难删除,而是会积极“反抗”删除。方法就是在用户电脑里,利用不同的存储机制不断地复制自己,或者在副本丢失或到期作废时让自己重新复活。具体来说,Evercookie在创建cookie时会使用如下存储机制:
开发人员计划增加如下功能:
上面的 EverCookie 看起来气场十足,很完美,其实只是理想太美好,显示依旧很残酷——一样的存在诸多缺陷,只是它把一些缺陷不足尽可能降低了而已。
以 canvas指纹 为例:
虽然解决了 cookie 的稳定性——无法删除,但是唯一性、可辨识性并没有解决——重复率太高、ID容易变化:
这里有个在线指纹测试的例子:
从文初的图上可以看到,在 PC 时代,追踪用户身份技术方案多,也挺靠谱的,但是随着移动互联网大潮的到来,用户逐渐转向了 M 和 APP,形成三大平台三足鼎立的局面,这三大平台的软硬件技术方案各异,比如苹果系列的产品不支持 flash、不允许随便种植 cookie,而 Android 虽然开放,但是开放的尺度太大了,导致了很严重的软硬件碎片化的问题,这给技术方案的通用兼容性带来了严重的问题。应用又分为 NativeAPP 和 webAPP,前者可以很好的和系统结合,拿到系统的硬件信息特征,比如 MAC、IMEI,而 webAPP 大都受限于浏览器隐私策略保护和前端技术限制,没法拿到系统的硬件信息,这就直接导致无法生成一个基于硬件的唯一的、稳定的、准确的“用户ID”,而且想要三端用户身份都打通就成了一个难事,比如:公司三端的用户重合度是 100%,每端 UV 都是一亿,那么三端的总 UV 应该是一亿,但是以现有业界的 cookieID 技术方案来统计 UV,会得出三端总 UV 是三亿的错误结论,而这目前业界也还没有很完善、通用的解决方案。
总结下在移动互联网时代,用户身份识别与追踪的新挑战有两点:
这或许是商业行为与用户隐私的一场持久博弈,而在这场博弈的背后技术又将会扮演什么角色呢?
[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] 自由之设备,独立之人格:从设备识别到跨屏营销