这几天,朋友打算上一个创业项目,我帮忙做一点数据采集的事情。遇到两个问题,一难一易,孰难孰易,一时到不易辨别。

这是一个 Single Page Application,前端是 Vue,后端是 PHP,整体接在微信公众号上。且先不提他的业务,以及我预想的数据运营框架,单说数据采集的实现过程。

我参与之时,已是即将上线之际。虽有 Slack 做总体的项目及沟通管理,奈何进入太晚,不宜兴师动众,贸然影响他人的既定节奏。所以,初期的想法是先请前端部署 Google Tag Manager Snippets,我独立远程思考、创作、埋点。

问题一:Single Page Application Tracking

第一个问题是如何把 Google Analytics 应用到 SPA 的数据采集上。

SPA 内容看起来页面流转不停,其实全是单页内的组件更替,并没有整页刷新。这个例子里面,产品列表、用户注册、订单管理等组件,都是 Vue 框架在操作 #app 这个容器,进行内容更迭。

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- meta -->
  
  <title>placeless</title>
  <!-- Google Tag Manager -->
  <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
  new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
  j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
  'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
  })(window,document,'script','dataLayer','GTM-PLACELESS');</script>
  <!-- End Google Tag Manager -->
  
</head>
<body>
  <!-- Google Tag Manager (noscript) -->
  <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PLACELESS"
  height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
  <!-- End Google Tag Manager (noscript) -->
  
  <div id="app"></div>
  
  <!-- scripts -->
</body>
</html>

方案:History Change vs MutationObserver

把前端仓库 git clone 回来,勉勉强强梳理了一下基本的路由逻辑,把本地环境跑了起来。斟酌之后,觉得,确实还是不要麻烦前端人员为好,我可以尝试通过 Google Tag Manager 的 History Change 事件来实现路由变更的跟踪。

经测试,确实可行。但有两个问题:一是 dataLayer 数量急剧增长,担心影响客户端性能;二是 History Change 发生之后,DOM 变更需要时间,响应的慢,往往是 GA 采集代码先执行了,导致抓不到正确的 Page Title,即不能正确采集到 Page Path 与 Page Title 的组合。这时候,要么设置 Timeout,要么设置一个别的 GTM Event 去触发 GA。前者既不优雅,又没法确定多长的 Timeout 最合适;后者的话,就不如照此思路,舍弃 History Change,另起一个新办法了。

这个新办法就是 MutationObserver。

Feature Android Chrome for Android Edge Firefox Mobile (Gecko) IE Phone Opera Mobile Safari Mobile
Basic support 4.4 18 -webkit 26 (Yes) 14.0 (14) 11 (8.1) 15 6 -webkit 7
Mobile 端 MutationObserver() 支持情况

采用 MutationObserver API 解决了上面两个问题,但微信与非微信环境的 DOM 不太一样,而且页面上还有不少动态元素,需要一些琢磨时间,去判断 Observe 什么样的 Mutation 最合适。还好,与第二个问题相比,都是解决有望的小问题。

<!-- custom html tag -->
<script>
  (function () {
    // 尝试控制 dataLayer 长度
    window.dataLayer = window.dataLayer || []
    if (dataLayer.length > 50) { dataLayer.splice(5, dataLayer.length - 5) }

    // MutationObserver
    var target = document.querySelector('#app.wx') || document.querySelector('#app header') ;
    if (target) {
      var observer = new MutationObserver(function(mutations) {
        dataLayer.push({
          'custom_page_title': document.title.trim(),
          'custom_page_path': document.location.hash.substr(1),
          'event': 'header_changed'
        });
      });
      var config = {
        attributes: true,
        childList: true,
        characterData: true
      };
      observer.observe(target, config);
    }
  })();
</script>

问题二:一九八四

没想到过这会是一个问题:gtm.js 请求阻塞了页面😱。

通过 Charles,限速、拦截、抓包,问题得到确定。我大清国情如此,又恰逢大会临近,虽然 gtm.js 是异步加载,不阻塞其他资源。但其他资源加载完毕之后,终归也还是要等它的。注意下图微信顶部的进度条👇。

gtm loading

它对应的是下图,模拟 gtm.js 资源受限之时,其 HTTP request 的请求情况👆👇。

gtm failed

即便不在微信环境里,也是一样的👇。

gtm loading 2

gtm failed 2

GTM 依托于 Google 强大的全球网络,本该是地球上最可靠的服务之一,不巧,我们这里却已是新话世界,乡愿社会,似无它的容身之所。由于朋友的业务特殊,服务的实时性和一致性要求很强,所以我们暂时做了如下的处理。

detele ga

接下来,我还会继续尝试其他的办法,比如 Server 端的 GA 实现,Measurement Protocol,以及其他的连接服务,比如 Segment。不为别的。

Rick.and.Morty.S02E06