37DATA

浅谈统计页面访问时长方案【多页面时长篇】

前言


        大家好久不见,由于最近比较忙,距离上次单页面时长篇也过了几个月了,今天终于把上次留下的多页面时长篇给补上来了,由于有了点时间距离,在开始这节课之前,这里先大概地给大家温补下上一篇的内容,重新回顾下什么是单页面时长以及如何统计,接着我们再进入我们这节课的内容,即如何去统计多页面的时长汇总,防止大家对页面时长的知识点已经有点遗忘了。

回顾


        上次说到,单页面时长通俗易懂地来讲就是用户浏览一个页面时从进入该页面进行浏览到关闭该页面的这段时间间隔就是该页面的页面访问时长,但实际上由于需要更为精确地统计,在统计时需要减去页面被切至后台的非活跃时长、用户的无操作"挂机"时长等无效时长,也因此推导出了单页面时长统计的俩条计算公式:

        1. 页面访问实际时长 = 页面访问总时长 - 页面非活跃时长(总和) - 页面无操作上限时长(如果期间有无操作时间超过无操作上限时长)

       或

       2. 页面访问实际时长 = 页面单轮活跃时长 x N(轮) - 页面无操作上限时长(如果期间有无操作时间超过无操作上限时长)


       在知道计算方法后,通过给页面绑定系列事件,以监听页面切换、页面是否无操作等状态,进而判断页面当前属于哪个状态、需要执行什么样的统计逻辑操作以及计算出各个时长的值,最后就可以监听 beforeunload 事件判断页面关闭,进而进行一个单页面最终访问时长的计算。


       当然,我们之前有提到过遇到异常情况时可以采用 心跳机制 以及 设置退出标志 进行防范以进一步减小由于异常情况所带来的统计误差,这里篇幅有限就不再详细回顾这俩个方法了,如果有所遗忘的话请点击 浅谈统计页面访问时长方案【单页面时长篇】回归单页面篇进行查看,谢谢。

好了,回顾完上节知识点后,是时候进入正题了。


       首先进行后续涉及到的相关术语科普:


       - 同源页面:相同协议-相同域名-相同端口


       - 多页面一轮访问:以易览为例,从打开一个易览页面开始,不管期间怎么访问,到关闭至浏览器中不再有一个易览页面,就是多页面的一轮访问,当且仅当全程只访问一个易览页面时,多页面一轮时长 = 单页面时长

一、什么是多页面时长?


        一说多页面时长,这里肯定会有人认为是浏览器开启多个页面到关闭时各个页面的访问总时长的汇总,其实不是,首先我先解释下多页面的范围,多页面是指从访问一个页面开始,并由此页面打开1+个新的同源页面,后续新开的页面继续从里面打开新的同源页面也是算在同一个范围内的所有页面(图①),当这些页面逐个关闭到零或一次性全部关闭的时候,这些页面的访问时长累积起来就是多页面访问的总时长。

Image

图①

 当然,刚刚上面计算总时长是指各个页面的访问总时长,即还未减去那些无效时长,所以在真正统计的时候,实际上需要累加的是各个页面减去无效时长后的真实时长(即单页面访问时长),将这些时长累积起来就是多个页面最终的实际访问时长。

 
       那多页面访问时长计算公式我们便可以得出来了。

二、多页面访问时长计算公式

  • 多页面访问时长 = t1 + t2 + t3 + ... + tn(各单页面访问时长相加)

        这公式咋这一看甚是简单,那岂不是我监听各个单页面的关闭,然后将其提交给后台去进行一个汇总计算就完事了?啊确实可以,如果服务端愿意帮你实现汇总计算的话那是再好不过的,而且精确度也相对比较高,那其实这篇文章看到这里就可以the end了,但如果服务端没空帮你实现,只能由前端来进行累积计算呢,很无奈只好硬着头皮上了,那就跟随我一起继续往下看前端的累计实现方案吧。

三、如何累计各页面时长?


       举个例子,如果从当前页打开一个同域名的新页面,那么这个新页面要如何取得上一个页面的时长?请读者花3-5分钟时间想一想。

       ......

       时间到!让我猜猜读者是不是想到了以下方案,肯定有的人会说将时长带在链接上传过去计算(想到这种方法的读者想一下这种的话你如果切换回去又切换回来,你如何更新上一个页面的时间),又或者是设置Cookie,然后下一个页面获取此Cookie(想到此方法的读者已经很接近了,但cookie容易因忘记设置过期时间而受异常关闭影响,那有没有另一种类似的方法),想到了?对没错,那便是通过设置可以长久存储的 LocalStorage !

视频②


       即通过设置一个LocalStorage,我暂且将这个 localstorage 命名为 total_time,多个页面 共享 使用此 localstorage,当打开一个新页面或切换至一个新页面时,通过获取 total_time 的值,并与自己的该轮单页面时长进行 累加,计算完毕后将其 重新赋值覆盖 给 total_time,那么当下个页面需要用到total_time时,就能保证拿到的是最新的值,通过各个页面的切换、打开与关闭,不断对total_time这个localstorage进行累加覆盖操作,并在最后一个同源页面关闭时,也就是 监听到最后一个同源页面的beforeunload事件 时将total_time的值上传给服务端(PS:当然这里不建议一直等到关闭最后一个页面再上传,可以像单页面统计一样一有切换标签页操作就上传一次,通过“后覆盖前”的思路,以防止异常关闭等情况带来的误差影响-图③),就达成对多个同源页面一轮访问操作的时长统计,那接着你便可以把期间用到的localStorage清除,来维持localStorage环境的干净(图④)。

Image

图③

Image

图④

四、如何判断一轮访问的开始与结束?

       我们知道完如何汇总累加各个页面的时长后,那接着就是如何区分一轮访问的开始以及它的结束,以易览为例子来说,一轮多页面的访问的无非就是从打开第一个易览的页面开始,中间你可能只访问这一个页面,又或者会从里面开启其他同源页面,当易览这一轮操作下拉,最后一个页面被关闭或者一次性关闭多个标签页,那么就表示一轮访问结束了。
       概念已经给大家形容出来了,那我们应该如何去判断当前还有多少个同源页面还打开着呢?
       这又引入了另外两个localStorage,我这里将其命名为timestamp 和 pageNum。

  • timestamp:轮次唯一标识(图⑤)

       顾名思义,它代表时间戳的意思,为什么这里要用时间戳?你可以当它是每轮访问的一个唯一标识,用来区分不同轮次的访问,因为当你提交数据时,按的是上面推荐的第二种方法,如果你不提交一个类似ID标识的字段给服务端,那服务端是并不知道你提交过来的是旧一轮访问的时长还是新一轮访问的时长,数据库那边就不知道是执行插入还是覆盖操作了,所以这里的timestamp主要起一个ID标识轮次的作用。

  • pageNum:该轮次正在访问页面数(图⑤)

       对,这个localStorage就是用来计数当前仍未关闭的同源页面数,当你打开第一个页面时其初始值为1,后续每打开一个同源页面,便可判断是否存在此localStorage,存在则加1,否则则初始为1,当你没关闭页面时,即监听到beforeunload事件,判断其是否为1,否则将其减1,是则代表是最后一个页面,预示这一轮多页面访问即将结束,可以做相关提交等操作。这时你可能会问,那上面说的一次性关闭所有页面呢,一次性关闭所有页面时每个页面都会依次触发beforeunload事件,当然顺序你不得而知,但肯定的是必有一个页面是最后一个触发,那执行的逻辑流程也就跟上面是一样的了。

       当然了,毕竟是要统计各个用户的数据,所以设置这些localStorage时,命名肯定是要拼上能够标识用户的标志,比如user_id等这个就看自己要拼什么判断标识了。

Image

图⑤

五、异常关闭的防范

       异常情况的处理跟单页面是一样的,都是利用心跳机制以及正常退出标识来进行防范,具体可以返回单页面篇查看异常处理的流程。这里就稍稍提一下多页面存在的些许差别。


       首先,心跳机制 是以当前正在视口中浏览的网页为主,被切至非活跃状态的页面,其心跳计时器是被停止的,所以一轮多页面访问下来任意时刻其实只有一个心跳计时器在运作而已,那心跳计时器传送的多页面总时长值为 localStorage 的 total_time 的值 + 当前该页面心跳计时器的累计跳动总时长(若页面是切换回来的,从切换回来的时刻重新开始累计)


       其次,页面的 正常退出标识 不再是页面正常关闭才置为true,在统计多页面情况下,前一页面成功被切至非活跃状态就可将其设置为true,因为多页面我们在切换页面时就会上报一次当前的累计总时长,当新打开页面判断前一页面正常切换后再将其置为pending状态,此时正常关闭标识就变更为当前页面的关闭标识了,其实相当于多个页面共用一个状态变量,该状态变量时刻代表当前正在浏览的页面的状态。

六、特殊情况的兼容

1、右键新建标签页打开目标页

由于我们设置pageNum变量是在新页面打开的时候且判断前一面已将正常关闭标识置为true(即被切至非活跃状态)才进行设置,而右键新建标签页打开目标页我们无法监听到此事件,且其不会将当前页切至隐藏状态,刚好跳过了设置正常关闭标识为true这一步操作,那就导致打开的新页面没有成功设置pageNum,所以后续执行关闭操作时肯定会提前提交最终总时长,所以这里需要设置多个last_tag用来拷贝上一轮的timestamp标识,然后在提交总时长时不清空localStorage环境,这样后续的页面在pageNum为0时判断自身归属的timestamp标识是否跟last_tag一样,一样则继续取total_time的值进行计算然后进行一波提交覆盖操作来保证精确度,直到下轮访问时当判断timestamp与last_tag不同时再把last_tag相关的localStorage清空。

2、iframe切换页面机制

       由于易览这边采用的iframe切换页面的机制,当打开新页面时,是往容器元素里添加新的iframe窗口,并将旧的iframe窗口进行一个display:none,切换页面则是对各iframe的display状态进行切换,如果你的网站系统也很有缘采用了此种机制,那你可以跟我一样做下以下的兼容,通过 MutationObserver 去观察容器元素或者导航栏的变化,当其添加子元素或子元素属性发生变化时会触发事件,这时可以利用 H5 postMessage 进行一个全局广播,将处于显示状态的iframe的链接或唯一标识属性作为内容,各iframe监听message事件进行内容的比较,从而来判断自身是处于显示还是隐藏的状态,进而进行相关的逻辑操作。

Image

像易览这边是通过去观察导航栏的变化,当打开新页面时导航栏的列表项的类会发生改变,点击到的项将获得 active类 也就是高亮啦,并将其属性 lay-id 进行广播,由于iframe的链接对应的列表项的lay-id,当iframe监听message事件并比较message里的内容跟自身的 src属性 是否一致,一致则自身处于显示状态,可以开始计时,否则则处于隐藏状态,是非活跃状态停止计时。

七、结语

       好了,讲完对特殊情况的兼容后,也差不多接近文章的尾声了,其实多页面说杂也不杂,它就是单页面时长的一个汇总版,即同源多个页面你要如何去把各个单页面的时长给加起来,如果可以不区分轮次的话,那其实将各单页面的时长上报给后台,由后台去进行汇总累加是最为精准稳妥的,但如果要区分轮次,就需要传多一个唯一的标识,同时后台也愿意帮你多做点判断处理的话(标识区分这些),作为作者我还是希望你建议后台帮你去进行多页面时长的一个汇总计算,因为说到底,由前端累加计算的话其实受浏览器内核以及电脑环境等客观因素影响较多,误差相对还是比较大的,但确实只能由前端实现的话,那就需要从多个页面窗口如何去共用一个变量出发去思考,因为这个变量就是你汇总计算的变量,至于这个变量定义在哪就需要从窗口存储这边出发去思考了,毕竟需要共享,你总不能说我这个窗口去调用另一个窗口里的JS中的变量吧。最后就是去考虑各种异常及特殊情况,针对它们去做对应的防范或兼容处理,一步一步地减小这些罕见的客观因素所带来的误差影响。

      那页面时长统计方案的讲解也就由此告下一段落了,下次我将带来从古至今仍让无数人为之兼容而烦恼的媒体标签之video标签的认识与讲解,敬请期待~

      感谢您的阅读,如有发现错误,欢迎指正,谢谢!