|
: D I0 \1 T/ Z0 `; I4 Z 一、前言页面浏览时长是网站分析中很常见的一个指标,反映用户在某些页面上浏览时间的长短,体现了网站用户黏性然而精确的页面浏览时长是很难统计的,比如需要考虑单页面网页应用路由切换、用户切换浏览器 tab、最小化及恢复浏览器窗口等多种复杂场景。 2 Q/ | _/ S7 x
因此,为了解决上述问题,神策数据 Web JS SDK 1.18.14[1] 版本推出了 Web 页面浏览时长功能[2]该功能可以在用户授权同意后自动采集页面浏览时长,不耦合或入侵客户业务代码此外,在 1.21.5 版本[3]兼容了单页应用。
; v9 K5 g5 e( j+ k' [ 下面,我们一起来看一下神策数据 Web 端分析页面浏览时长的三种方案二、方案一对于网站或者 App 来说用户一系列的行为是一次访问,即 Session(会话)举个例子介绍一下:如果把网站或者 App 比喻成一个商场,那互联网用户的行为就如同逛街的顾客,他们从商场任意一扇门进入,然后东逛逛西瞅瞅,有的意兴阑珊很快就离开了商场,有的兴致不错买了一些商品后才离开。 $ m$ j8 C. ~8 w% G- S) r6 f, X
对于商场而言,顾客一系列的行为是一次来访,同理对于网站或者 App 来说,用户的一系列行为就是一次会话[4]Session 分析功能可以从页面浏览行为推算出用户大概的页面停留时间,一般情况下使用这个功能就足够了。
9 s. A* _2 U) {* t: Q 2.1 原理简介使用 Session 分析功能分析页面浏览时长,可以先在神策分析页面定义一个只包含 Web 页面浏览事件($pageview)的 Session用户访问页面时,会触发一次 $pageview 事件,在这个页面内无论点击哪个按钮触发点击事件,都还在当前页面。
& i1 F4 Z5 V, i j; Z$ l 因此计算两个页面 $pageview 事件的时间间隔,更符合计算逻辑根据行业经验值,一般网页端的 Session 切割时间建议设置为 30 分钟(可根据业务自定义时间),即相邻两个 $pageview 事件的间隔大于 30 分钟就切割。
. V% ` |/ H* M- s 比如用户 A 打开神策官网后,立即离开座位参加了 1 小时会议,回来后继续浏览其他页面,两次 $pageview 事件间隔大于 30 分钟明显是异常值,不能算作同一次会话中假设用户 A 会话从 10 点开始,具体行为序列如图 2-1 所示,切割为 3 个 Session: ) r0 ?. b/ c# k. r- N9 [
在神策分析环境中,按如图 2-2 所示查询某个具体页面的浏览时长: / i% L% d/ B* r
2.2 优缺点优点:1. 业务代码侵入性小;2. 埋点成本低,只需开启全埋点的 Web 页面浏览事件采集即可;3. 兼容切换 tab、单页应用场景这两种场景下 $pageview 事件均可正常采集缺点:
1 ?; v8 G/ q* W 1. 只能大致分析页面浏览时长,精确度不高;2. 一般都会少于用户实际在网站上停留的时间,因为无法知道最后一个页面上的停留时间三、方案二使用 Session 分析功能达不到严格的分析标准,而精确的页面停留时长又很难统计。 " v+ R6 i) a) N% w& q0 x" L. u
例如一个用户很可能同时打开多个网页窗口,然后一直不关闭如果只是想要统计页面打开到关闭的时间,可以使用手动埋点3.1 原理简介当进入某个页面时获取开始时间,页面关闭或者进入一个新的页面时(此时视为当前页面已经消失)获取结束时间,页面浏览时长 = 结束时间 - 开始时间。 / T% z" ^9 X6 \/ C0 S6 d
具体可参考以下代码(使用 jQuery 检测页面开始浏览和离开事件):var loadHandler = function(e) {
+ ?5 X, s7 ~3 ^. r' ~+ Q, b start = new Date();
% o7 i; ?; o* O- e };$ `& A( M/ i0 S' w. a( g7 O$ u3 Z& _
2 G2 z) n: y+ z* ~8 D% O( _
var unloadHandler = function (e) {: ]6 j4 _: N0 g& v
var end = new Date();
1 z! z) s# r% d // 如果用户一直不关闭页面,可能出现超大值,可以根据业务需要处理,例如设置一个上限% C+ v) h0 O: e9 J
var duration = (end.getTime() - start.getTime()) / 1000;: n4 @7 G/ o V/ x! e8 e
// 定义一个记录页面停留时间的事件 pageclose,并且保存需要的属性(停留时间和当前页面的地址)$ Q2 t4 ^7 |/ H
sensors.track(pageclose, {0 X5 G1 T. W9 K/ J2 }
$event_duration: duration,2 X1 N& r0 O) x8 Y; Q
$url: window.location.href
$ }: E. J; _6 D* }, C+ V$ S3 l });& e2 k+ f3 S; P7 Q% B! u
};
4 t2 y, L: I1 y: ~5 z& D4 c3 U9 `: W! l4 u
if (onpageshow in window) {1 L3 C! \* t* y; s( F! P! W
$(window).on(pageshow, loadHandler);
( z% B6 I1 [' m7 j, K $(window).on(pagehide, unloadHandler);5 p# \- j Y) l( n
} else {
% B$ e( w+ |: Y+ N $(window).on(load, loadHandler);
0 k9 ?& e0 [+ e6 o" y2 F $(window).on(unload, unloadHandler);
9 J# S7 i+ r. G( } }
6 B/ ?0 m# K( z+ y- z2 w: G& K 3.2 优缺点优点:1. 灵活性强,能满足各种特殊场景的时长采集;2. 精确度较高缺点:1. 业务代码侵入性大;2. 埋点成本高四、方案三通过上面的介绍可以知道,Session 分析功能准确性不够;手动埋点对客户业务代码侵入性大,并且客户使用的成本较高。
- l# E, {( y; J' t) s+ C 随着客户对页面浏览时长采集的需求日渐增长,神策数据重新调研和实现了精确统计页面浏览时长的方案,推出了 Web 页面浏览时长功能该功能可以在用户授权同意后自动采集页面浏览时长,不耦合或入侵客户业务代码,只需要简单一行代码,即可开启页面浏览时长采集。 * B( I$ w4 U( P! X# s$ J0 w$ v
并且在 Web JS SDK 1.21.5 版本兼容了单页应用4.1 原理简介我们可以把一个页面生命周期抽象为三个动作:「进入」、「活跃状态切换」、「离开」,如图 4-1 所示: / D" Z% @1 V- j
核心原理如下:1. 神策数据 Web JS SDK 会在页面的「活跃状态切换」、「离开」这两个生命周期,采集页面浏览时长事件($WebPageLeave 事件),其中浏览时长属性(event_duration)是指页面浏览的时长(单位是秒);2. 配合心跳备份方案,每隔 5 秒(默认)心跳模式更新 LocalStorage 中备份的页面浏览时长。 * S* i! ~; d0 |8 }9 ~/ Q
在异常关闭浏览器或 App 被强杀等事件上报失败的场景下,再次进入页面时会将 LocalStorage 中备份的页面浏览时长事件进行补发$WebPageLeave 事件数据上报到神策分析后,可以使用如下条件分析页面的浏览时长,如图 4-2 所示:
7 L, f: B4 x( c 4.2 采集流程如果想要使用采集页面浏览时长的功能,只需要在初始化 Web JS SDK 之后,添加一行代码使用 PageLeave 插件:sensors.use(PageLeave[, option]) 即可。
( G' \5 n3 ^+ o: b6 w- m) j0 B option 为页面浏览时长可选配置参数,示例代码如下:sensors.use(PageLeave, { custom_props: { page_title:我的标题 }, heartbeat_interval_time: 5 })0 c$ i* Z1 E/ k6 a
// 设置页面浏览时长自定义属性 page_title 为我的标题,并设置页面浏览时长心跳记录刷新时间为 5 秒。 " v2 \, Z+ \" ~9 E. x& z
采集页面浏览时长的流程如图 4-3 所示: 0 a! ~: z$ k" y4 L& `: a& \- Y
4.3 核心逻辑4.3.1. 页面展示检测页面的 pageshow 事件,获取当前时间为 PageLeave 的开始时间代码如下所示:/**
: _& Z: {1 [6 u( l) _5 p * 添加页面展示检测
1 q6 E) Q- W" s s: ]; e- \ */
& S( r9 \* Q0 m T! u, S5 q+ X PageLeave.prototype.addPageStartListener = function () {
" t2 E+ L- c' m; a; k- j; A U5 j8 w var _this = this;+ \" c! K4 R; j
if (onpageshow in window) {0 c/ ^2 E5 ]! X* Y
this._.addEvent(window, pageshow, function () {6 G! _* g9 Q8 _$ U1 r5 C/ p% m
_this.pageStartHandler();* s _: F! z& N9 A8 I2 S
_this.hiddenStatusHandler();
$ s9 Y& }- ^( o! R) ^" t3 e! A });
7 P/ u6 s* N0 o0 O }
, x& N; }/ S. K1 B };
5 h% v' p6 p% S$ ^2 i/ K* i& @' F4 u/ c' r ]6 y. p0 O8 z
/**+ G2 {+ F* [# }! w3 |4 ]
* 页面展示检测处理程序) T1 @7 k w* c# X
*/) y+ c& p* @ ^& D) ?$ g9 Q
PageLeave.prototype.pageStartHandler = function () {% H9 A# r' M' T* h$ |/ O: f
this.start_time = +new Date();) h$ t. M" h1 \2 v/ p% E% L
: k. p$ o/ K9 F# S% P0 d! t6 `
if (!document.hidden === true) {
; C2 K' |3 j, T1 ?' [5 y: O+ p this.page_show_status = true;8 u" @; p- q8 m: Q" ~
} else {- f8 G9 w# o) u
this.page_show_status = false;3 ~) J3 X6 s% c6 Q! c8 l6 o
}
/ w4 ]' j' |# K) Q6 w. Q this.url = location.href;
' F* r* F/ o; Y+ ` };。 3 ] f1 e. F% E
4.3.2. 页面切换检查是否支持 visibilitychange,页面获取焦点 focus 的时候去调用页面展示检测方法,页面失去焦点 blur 的时候去调用页面关闭检测方法,并发送 $WebPageLeave 事件。
. `; Z1 ^. E( L' Q: ^3 R4 f* s 代码如下所示:/**. U. n& Y& Z8 u- w
* 添加页面 tab 切换检测
6 @# I; `6 H8 m |) P1 I4 ?" ~ */* D1 e3 q0 ]' S) O
PageLeave.prototype.addPageSwitchListener = function () {
" Q$ ~5 q" z4 q8 \0 h. f& B var _this = this;
+ A, T* G/ c8 c9 T V% f6 b this._.listenPageState({
0 t8 {9 @* g+ q& n& M visible: function () {
. j0 b2 T) L' f- C; Y# k _this.pageStartHandler();
& W! n r& _" u B3 U3 }0 q! s# o // 修正 page_hidden_status
/ r! q8 v. z6 c7 V" b7 X. p7 } _this.hiddenStatusHandler();# C6 k2 s+ ^3 W; j4 c% }7 a, g
// 显示时,开始心跳记录及心跳补充
8 E$ ~- W! S! H* B9 s" w _this.startHeartBeatInterval();
. x) N9 V2 K% w# o },; _8 a1 U e# p/ W" M
hidden: function () {+ b0 u7 ^% k5 d# {
// 切换 tab 时,为当前页面的关闭,url 为当前页面 url,referrer 为改变前的 url
& s* C" G) z% I3 m* c, `9 p _this.url = location.href;8 Q7 x& B2 Y5 q3 X. E f
_this.pageEndHandlear();1 j; Z( P, p) a# h
// 隐藏时,停止心跳记录及心跳补充
5 ?6 r: b; q+ o _this.stopHeartBeatInterval();
, r2 L3 k, x4 f8 Z1 r5 ^ }5 e: M- U. o$ ~/ g
});$ r; h2 L6 h( K
}; , R" N6 ~) G) g; L1 f& o
4.3.3. 单页面跳转如果 Web JS SDK 发现 Hash 模式或 history 模式跳转,则执行 singlePage.prepend 方法此方法将里面的 function 插入到数组起始位置,先发送 $WebPageLeave 事件后停止心跳记录及心跳补充。
0 x4 K* j5 y( @! l 下个页面打开时记录开始时间,重新开始心跳记录及心跳补充,同时触发当前页面的 $pageview 事件/**
9 T9 U) d+ ]/ e6 ^! G- d * 添加单页面切换检测; A* w9 {# O* i% Q7 S/ r8 g
*/
' w* e: i& [$ `7 O0 Y PageLeave.prototype.addSinglePageListener = function () {
5 X. ]% ~; J; l. a var _this = this;& {4 T1 t% L$ }- `+ E: n
// 检测 Hash 及 history 跳转,根据开关进行控制
' H. j/ \8 v: S( l5 }' s Q( m this.sd.ee &&, B1 e X; J6 y- ~: n* e
this.sd.ee.spa.prepend(switch, function (last_url) {( B# x; v& `. e0 A) Y; ~/ p0 E
// 检测 Hash 及 history 跳转
- C) K5 t! @' j9 R# f+ ]; g. l if (last_url !== location.href) {
0 o1 P7 {1 f7 O% u* } // 指定上一个页面地址为pageleave的页面url# x1 h6 Q) Y: j+ U% n
_this.url = last_url;
6 I8 o7 } i( P // 触发页面关闭, o$ V3 e: s# c+ R: h- y0 a
_this.pageEndHandler();
4 s' _9 d& m0 q8 j // 停止心跳记录及心跳补充. R- A; @; ~- y: C2 a9 h2 E6 U
_this.stopHeartBeatInterval();: D* ~5 z8 C2 K$ v- ^) h8 D# o! ?& D
// pageleave发送完毕后,且url发生改变,将referrer切换为改变前的url. f7 k7 c8 @2 K1 t. c9 b
_this.current_page_url = _this.url;
) O3 S9 x8 C) O, Y // 触发页面打开3 k0 M# C1 O V2 _( ]
_this.pageStartHandler();+ h2 U6 e( f: r0 g9 o& Q7 O9 p' g
// 修正 page_hidden_status
) X4 N6 s) P- y* G _this.hiddenStatusHandler();
f0 J- m1 u2 n! G* Q3 H // 显示时,开始心跳记录及心跳补充
: C# }7 U2 o }; c0 U7 z& L+ ~2 d+ q: H _this.startHeartBeatInterval();
& B$ S* y5 F) E2 `4 ?7 R7 w }0 c. h! u- q( T4 \+ D% ]7 G
});5 @; X# h0 z, _/ F& `
}; 7 n. @( w! T8 t D/ o" N
4.3.4. 页面关闭页面的关闭事件是检测 pagehide、beforeunload、unload 这三个事件页面浏览时长(event_duration)= 当前时间 - 开始时间组装页面浏览时长等属性数据到 data 中,触发 $WebPageLeave 事件。 % J9 ]5 Z( @( h9 j4 Y: c9 n
刷新页面关闭状态,并删除当前页面心跳记录代码如下所示:/**0 K7 L7 g3 G3 Q6 i! r: T8 w* {
* 添加页面关闭监听1 I4 j' ?" D' B. f0 }
*/. n5 | \* k: a
PageLeave.prototype.addPageEndListener = function () {
6 C8 n. T( ^8 H( c! r8 ~. x5 e var _this = this;
( K2 r$ Q. d+ T3 a3 ?& e0 [% Y this._.each([pagehide, beforeunload, unload], function (key) {
& \% I! ~% d% c$ n if (on + key in window) {
% k; Q5 W, f$ ^# \ _this._.addEvent(window, key, function () {
8 a! w, ]; O: P+ w U _this.pageEndHandler();
, X1 V' i4 h/ V+ ], [1 H' _# X; [* [ k8 S& X
// 停止心跳记录及心跳补充: E x2 V3 ^3 h1 e# N7 U
_this.stopHeartBeatInterval();
% k- B% Z3 I7 C2 l8 c });
1 W: f2 ]7 P2 Q( I! @& e& u }: e: j, u; E# C+ u( D8 N
});
# l# }& J% e1 Y9 g& O* @& d$ E& Y };
$ u& f! P, O! }1 }! d& ~ C6 v, s5 Y) T0 L
/**+ p% L& H- k( m9 W; r- S0 C9 h4 T& ]
* 页面关闭检测处理程序, t Q1 S, r2 J K* |
*/8 Y7 T! o B7 r2 q( c
PageLeave.prototype.pageEndHandler = function () {
& U, K) B2 k( { q+ y if (this.page_hidden_status === true) return;4 d! J3 U0 V1 ~
q3 U* L& r2 b- k6 t
var data = this.getPageLeaveProperties();
8 T/ L7 U# Z* i4 j, b if (this.page_show_status === false) {/ |0 }9 e8 |5 w& P+ t
delete data.event_duration;5 i0 ]/ N% H: ~9 J/ ?9 l
} u. y, {* r9 h+ f; E; U0 S
// 切换页面状态; z" w) p0 T f9 u
this.page_show_status = false;7 i& d2 z( O* n& }+ S) x8 N5 p: k
this.page_hidden_status = true;
* Z' r# d7 }7 t, t0 w
% X9 N" O; t, N% |: y1 n) |$ Q if (this.isCollectUrl(this.url)) {! P0 G7 _8 `3 e
this.sd.track($WebPageLeave, data);9 s/ J. t1 B' Z( Z: Z! ]" b
}& ^: a8 \ I- e8 W" [! R' w
- r2 |& ]6 l- v* O+ t$ s; v // 刷新页面关闭状态
. @. @2 Z/ f% T" u$ r& ] this.refreshPageEndTimer();
3 l7 N" d/ X* L& ?* K // 删除当前页面心跳记录0 S, J- U0 s, L! F
this.delHeartBeatData();
0 I$ Z- k- A# i3 j( e3 x# ~ }; 5 j; x& O7 Y0 {; x1 ~
4.3.5. 心跳记录初始化时设置的心跳记录,默认页面浏览时长(event_duration)最小为 3.14s3.14s 是首次设置心跳补发写入 localStorage 的浏览时长,每隔一定时间会更新浏览时长,目的是为了解决检测不到页面关闭,导致 $WebPageLeave 事件丢失的问题。 - [2 i5 ~# L: W) s
然后组装 $WebPageLeave 数据,定时更新 localStorage 中记录的当前页面的 page_id 和 $WebPageLeave 的数据代码如下所示:/**
7 k1 ~ V8 [; v1 h" s. F * 心跳记录
( @6 {, X/ g- Y7 m# I! s */
9 i: d) M; x' @, X( ?9 o# T PageLeave.prototype.saveHeartBeatData = function (type) {6 n3 s5 Y3 [% f8 T" x9 m
var pageleave_properties = this.getPageLeaveProperties();
/ r. o* w+ W" }( x7 ]* | // 使用设备时间
1 ?) ~/ K' }$ [3 [% j* g9 p pageleave_properties.$time = new Date();
0 Z, S. d" b2 U0 ~, P% d if (type === is_first_heartbeat) {7 A& ~+ l6 y. G
pageleave_properties.event_duration = 3.14;; j& c6 @ g9 {" p' s2 e
}
3 k) D) a- N! ~1 ?" A+ J* X# o' a* w; L. u( M4 J
var data = this.sd.kit.buildData({
1 P/ z( f$ Q* Y8 A$ Y/ _* T' A type: track,
7 Q6 K" D! E! E3 h+ q9 x' W event: $WebPageLeave,1 j3 c0 ]8 n5 T/ h
properties: pageleave_properties
: o3 Z5 u/ r# K1 N3 Q });
/ x( I$ l6 \6 |) j // 记录当前页面的 heartbeat_interval_time
7 f6 Q% M8 ]8 u0 h. I% p( Z2 L# w // 作用:在对比超过阈值时间时,根据原页面所使用的配置。
! q4 U7 O; y z5 N+ K4 O0 e : Y7 e% Y: p, a6 r! O4 Z' F
data.heartbeat_interval_time = this.heartbeat_interval_time;
1 S: j+ [& S+ Q L; E8 u3 H& u- C this.sd.store.saveObjectVal(this.storage_name + - + this.page_id, data);& e R+ x9 v2 x1 v8 K
};。 * [3 [7 C2 B/ \3 P. f# y- Y3 E7 J
4.3.6. 补发数据获取 window 上 localStorage 的长度进行比较,检查是否有上次页面浏览时的心跳记录尚未发送,进行补发并删除上次页面浏览时长事件的记录代码如下所示:/**
* @& o- V- @- w' s! J0 m * 补充数据
) }: @7 x, g' L4 h, H% I) S */
0 a' C$ _$ s+ R2 P3 W PageLeave.prototype.reissueHeartBeatData = function () {/ B( l7 X- j& q- X
var storage_length = window.localStorage.length;' @: D8 p x: \) M1 W
% K4 g# k0 g# n, F& ]/ } for (var i = storage_length - 1; i >= 0; i--) {& V( J3 w7 z! u7 `
var item_key = window.localStorage.key(i);2 P# x- x% ?, \) H
if (item_key && item_key !== this.storage_name + - + this.page_id && item_key.indexOf(this.storage_name + -) === 0) {
. _% K$ Z( y0 U1 ~ var item_value = this.sd.store.readObjectVal(item_key);
1 M1 P) z1 p& W. d4 D1 h if (this._.isObject(item_value) && new Date() * 1 - item_value.time > item_value.heartbeat_interval_time + 5000) {" w6 r4 k2 w; i: p* B$ R, T+ X
delete item_value.heartbeat_interval_time;9 d5 \: x0 S" |: a+ g: Q# N
this.sd.kit.sendData(item_value);
- y9 ^3 Z: k: d/ p: k/ w; Y this.delHeartBeatData(item_key);. j. {! e+ Q0 S# F& O
}
9 F' ]+ w. {) x }
( T3 y) j8 Z- |& Q* o4 S } P' U' }, A# i# q: {0 c7 c8 G
};
7 r4 \8 Y7 P$ e2 e* A6 F* c3 N 。 8 I- V/ d1 y- c9 K7 G2 D
页面浏览时长功能的时序图如图 4-4 所示: % @! z! Q& }+ x4 S0 s
4.4 支持场景说到这里,大家一定想知道目前神策分析 Web JS SDK 支持采集哪些场景下的页面浏览时长这里总结了几种场景供大家参考,如下所示4.4.1. PC 端的支持情况注意:hash 兼容 IE8 以上,history 兼容 IE10 以上。 ) m$ f: n& `6 d9 \- }) I" J
IE9 hash 模式跳转支持,history 模式跳转不支持,所以 IE8 及以下采集不到单页面切换时的页面浏览时长PC 端的支持情况如图 4-5 所示:
2 J$ s8 A5 n& n7 a k% H 4.4.2. 手机端的支持情况手机端的支持情况如图 4-6 所示: # a m0 J- F( r6 x. {
另外,通过微信打开网页,微信浏览器关闭、刷新、前进后退、杀死微信等场景可以正常采集页面浏览时长App 内嵌 H5 页面,App 退到后台、点击 App 的返回键、杀死 App、息屏等场景也均可以正常采集页面浏览时长。 1 p8 H; Z: r, T* h1 _! a* }
五、总结本文对于神策数据 Web 端分析页面浏览时长的三种方案进行了讲解,介绍了 Session 分析功能、手动埋点、Web 页面浏览时长功能的实现原理及优缺点实际场景中,建议将分析方式与分析目标相结合:。 ; i& L! }: J3 T% b6 X; L
如果想大致分析各页面的浏览时长,不追求极致的精准度,使用 Session 分析功能方便快捷,业务代码侵入性小;如果有特殊需求,比如页面上具体某一部分的浏览时长,可以使用手动埋点,精准度高且满足实际需求,但对业务代码侵入性强;
3 W( l) Z( y# l" ~3 M9 _' i' r 除此以外,可以使用精确统计页面浏览时长功能,埋点成本低,精准度高,兼容单页面等多种场景当然,目前我们的采集页面浏览时长功能还不够完美,仍然在不断地更新迭代,欢迎大家在开源社区与我们进行交流,更多细节可以参考神策分析 Web JS SDK 源码[5]。 6 k! z, q3 F8 X( C: K( i, y
参考文献:[1]https://github.com/sensorsdata/sa-sdk-javascript/releases/tag/v1.18.4[2]https://manual.sensorsdata.cn/s ; I1 w5 o! u1 z$ O5 ^. L; N
a/latest/tech_sdk_client_web_high-42795073.html#id-.SDKAPI(Web)v2.3-%E9%A1%B5%E9%9D%A2%E6%B5%8F%E8%A7%88%E6%97%B6%E9%95%BF ; k0 p, }- M) _( d* s
[3]https://github.com/sensorsdata/sa-sdk-javascript/releases/tag/v1.21.5[4]https://www.sensorsdata.cn/blog/ru- % W7 Z+ t7 ~9 b# I% ~- U
he-ying-yong-sensors-analytics-jin-xing-session-fen-xi/[5]https://github.com/sensorsdata/sa-sdk-javascript
$ |, @* k! {8 S* C- r: I& E% V! A3 V4 ?3 D. P
3 h N! V& ^8 `8 b; i4 t# r
& t: H5 j( [" @# J0 ^0 u2 p3 I' f6 u+ c4 V
2 N9 |! m, B4 Q( Y
|