Google Tag Manager Form Tracking: 7 Ways To Reach Your Goal
表单提交是web用户交互很重要的一个类目,登陆、注册、订阅、投票等都是营销人员非常重视的 应用场景。如何利用GTM追踪表单提交?
谷歌代码管理器追踪表单提交:基本操作
表单提交有很多种方式,细心的你不妨留意:当你完成表单提交后,有的页面会刷新页面,有的则不会,有的会有一个弹窗,有的会重定向到一个感谢页面thanks.html。这也是表单追踪的难点之一。
你也许已经注意到GTM中已经有内置好的表单相关的触发器和变量。

启用它们并且创建一个自动表单监听器,它会监听所有页面上的标准表单提交。但是有大量的表单使用AJAX技术,跟标准表单提交有区别,自动表单监听器无法追踪AJAX表单提交。那么问题来了。
准备工作:触发器和代码
你应该理解了GTM的运作原理:每一个你想追踪的页面交互需要代码(Tag)和触发器(Trigger)。同理,表单追踪也需要代码和触发器,我们先来创建一个代码。
- 去代码模块,新建代码
- 代码设置,代码类型选择GA-Universal Analytics,跟踪类型选择事件,事件类别填写form submission,事件动作填写submit
- 选择你的GA(分析)设置变量
- 保存(暂时不添加Trigger)
事件类别,动作可以自己定义名字,保证一致性和可阅读性即可,事件标签可以填写{{Page Path}},这样我们可以知道用户在哪个页面完成此事件,事件价值可以根据预估来填写,例如平均每5个订阅用户会产生一个转化,客单价是100,那么这里可以填写20。

标题中我提到的7种表单提交就是接下来要阐述的7个不同的触发器,选择适合你的,对症下药即可:
错误配置的触发器会导致错误的数据报表,从而产生错误的结论。因此请务必仔细阅读,如果有任何问题请留言。
GTM Sonar
强烈安利一个chrome插件,安装并启用这个插件,用以做一些点击或表单提交测试,是GTM大神Simo Ahava开发,非常好用。试想,如果做测试的时候不断提交一些测试信息,会给服务器造成巨大压力,我们勾选Form Submit Listener并Switch on。


应该选择哪种追踪方法呢
我们先看看究竟要选择哪一种追踪方法,我用Xmind思维导图绘制了一个流程图,帮助你选择最合适的方案。

- 自动表单监听器
- 感谢页面
- AJAX表单追踪
- 利用元素可见性触发器
- 自己撰写的自动表单监听器
- 数据层事件(dataLayer.push)
- DOM爬取
困惑?继续读下去吧。
#1 GTM默认表单追踪
首先我们需要开启GTM内置的表单相关的变量(默认是隐藏的),变量-内置变量-配置:

然后打开触发器列表,新建一个“所有表单提交”的触发器,这里我选择我博客的文章评论提交作为测试,因此选择所有页面触发。
- 触发器类型-表单提交
- 等待代码触发完毕-勾选
- 检查验证结果-勾选,它表示用户必须成功提交评论才能触发,提交失败则不触发。

打开预览和调试模式。


再次打开网页,可以看到调试模式已开启。

打开GTM Sonar,开始测试!
填写表格并提交,打开开发者模式(ctrl+shift+j),然后输入debugDL。

#2 “Thank you”页面表单提交追踪
如果标准表单提交追踪无效,而在用户表单成功提交后跳转到一个感谢提交的页面例如:https://somewhere.com/post-review/thank-you.html,考虑这两个问题:
- 这个页面是独一无二的?
- 用户不提交也能到达这个页面吗?
这两个问题的主要在于防止用户即使没成功提交表单也访问,造成数据的不准确,得到错误的结论。
如果都回答“Yes”,我们开始创建“Thank you”页面触发器。
- 触发器类型: 页面浏览-某些页面
- Page path:包含 /post-review/thank-you.html,越具体越好。

#3 AJAX表单提交追踪
AJAX表示用JavaScript执行异步网络请求,这种表单提交可能没有发送标准的表单提交事件,也没有重定向到Thank-you页面。它只是刷新页面,显示“感谢您的信息”等。让我们直接跳过这里所有的技术术语,我们只需要知道AJAX监听器。
Lunametrics 为GTM提供了一个免费的AJAX监听器,你可以阅读此博文,了解背后的原理,这里我们直接借用他们的代码来追踪AJAX表单提交。复制下面的代码到GTM自定义HTML代码中,在所有页面触发:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
<script id="gtm-jq-ajax-listen" type="text/javascript"> (function() { 'use strict'; var $; var n = 0; init(); function init(n) { // Ensure jQuery is available before anything if (typeof jQuery !== 'undefined') { // Define our $ shortcut locally $ = jQuery; bindToAjax(); // Check for up to 10 seconds } else if (n < 20) { n++; setTimeout(init, 500); } } function bindToAjax() { $(document).bind('ajaxComplete', function(evt, jqXhr, opts) { // Create a fake a element for magically simple URL parsing var fullUrl = document.createElement('a'); fullUrl.href = opts.url; // IE9+ strips the leading slash from a.pathname because who wants to get home on time Friday anyways var pathname = fullUrl.pathname[0] === '/' ? fullUrl.pathname : '/' + fullUrl.pathname; // Manually remove the leading question mark, if there is one var queryString = fullUrl.search[0] === '?' ? fullUrl.search.slice(1) : fullUrl.search; // Turn our params and headers into objects for easier reference var queryParameters = objMap(queryString, '&', '=', true); var headers = objMap(jqXhr.getAllResponseHeaders(), '\n', ':'); // Blindly push to the dataLayer because this fires within GTM dataLayer.push({ 'event': 'ajaxComplete', 'attributes': { // Return empty strings to prevent accidental inheritance of old data 'type': opts.type || '', 'url': fullUrl.href || '', 'queryParameters': queryParameters, 'pathname': pathname || '', 'hostname': fullUrl.hostname || '', 'protocol': fullUrl.protocol || '', 'fragment': fullUrl.hash || '', 'statusCode': jqXhr.status || '', 'statusText': jqXhr.statusText || '', 'headers': headers, 'timestamp': evt.timeStamp || '', 'contentType': opts.contentType || '', // Defer to jQuery's handling of the response 'response': (jqXhr.responseJSON || jqXhr.responseXML || jqXhr.responseText || '') } }); }); } function objMap(data, delim, spl, decode) { var obj = {}; // If one of our parameters is missing, return an empty object if (!data || !delim || !spl) { return {}; } var arr = data.split(delim); var i; if (arr) { for (i = 0; i < arr.length; i++) { // If the decode flag is present, URL decode the set var item = decode ? decodeURIComponent(arr[i]) : arr[i]; var pair = item.split(spl); var key = trim_(pair[0]); var value = trim_(pair[1]); if (key && value) { obj[key] = value; } } } return obj; } // Basic .trim() polyfill function trim_(str) { if (str) { return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); } } })(); /* * v0.1.0 * Created by the Google Analytics consultants at http://www.lunametrics.com * Written by @notdanwilkerson * Documentation: http://www.lunametrics.com/blog/2015/08/27/ajax-event-listener-google-tag-manager/ * Licensed under the Creative Commons 4.0 Attribution Public License */ </script> |
我们打开或者刷新预览模式,就会看到这段自定义HTML代码被触发,当提交一个AJAX表单时,如果会呼出一个ajaxComplete事件,那么可以使用AJAX表单提交方法,否则请看下一章节。

这对于没有代码基础的营销人员有点难度,但背后的原理其实特别简单,ajaxComplete(自定义代码的名称)这段自定义代码被触发后,dataLayer的数据,而有帮助的数据是第24行response。
据此,我们可以创建一个数据层变量:
- 到变量模块
- 新建一个用户自定义变量
- 变量类型-数据层变量
- 数据层变量名-attributes.response
建议将这个变量命名为dlv – attributes.response,dlv表示dataLayer variable。

你也许已经猜到了,这个attributes.response就是访问的dataLayer中的response对象,刷新调试模式,我们观察到这个response是一个object,因此还要继续往下挖掘,将数据层变量名变更为attributes.response.ret 。(后来我有向开发了解到,这个ret就是表单提交的一个状态码,1表示提交成功。)
注意:这里的response对象如果是string就可以直接应用于触发器。
因此,我们可以根据这个状态码来创建一个触发器:
- 新建触发器
- 触发器类型-自定义事件
- 事件名称- ajaxComplete
- 此触发器的触发条件-某些自定义事件-dlv – attributes.response.ret等于1。
注意事项:
实际表单的response可能会有差别,请合理调整。
如果开发者修改response会改变触发器,一定要告知开发者这次代码部署。
如果页面有不同的表单提交,在变量模块中看有无其他对象来区分表单。
#4 利用元素可见性触发器追踪表单
元素可见性触发器用于追踪特定元素在屏幕可见度。
有些表单提交成功后会出来一个Thank you的消息,与Thankyou页面追踪同理,这个消息元素的可见性可以用于追踪用户是否成功提交表单。

为了应用这个触发器,我们右键点击这个消息,选择“检查”。

目前元素可见性触发器支持ID和CSS选择器两种方法,选择合适你的,例如本例,我们只能用CSS选择器新建触发器:
- 新建触发器,触发器类型-元素可见性
- 选择方法-CSS选择器
- 元素选择器-div.dialog-content h5.dialog-title
- 勾选观察DOM更改,如果不是滚动窗口类型的元素可见性追踪,一般都要勾选。

#5 撰写自己的表单自动事件监听器
这一章需要更高的JS知识,尝试之前先了解一下,这个表单有没有文档齐全的JS API,没有的话,我们还是直接跳入下一章节。
作为例子,我们选择Gravity Forms,一个Wordpress的表单插件。
第一步 查找是否有成熟的解决方案
google或百度搜索“表单名称” + GTM/GA Track,看是否有靠谱的代码部署方式,部署完一定要做较完备的测试。
第二步 查找是否有文档齐全的API
一定要善用搜索引擎,提高工作的效率。

它表示当表单提交成功,确认页面加载后,可以触发一个函数(见截图红字)。
第三步 集成dataLayer.push与表单API
dataLayer.push就是第二步中可以触发的一个函数,我们可以将自定义事件push到数据层中:
1 2 3 4 5 6 7 8 9 10 11 |
<script type="text/javascript"> jQuery(document).ready(function() { jQuery(document).bind("gform_confirmation_loaded", function(event, formID) { window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "formSubmission", formID: formID }); }); }); </script> |
将这段代码复制,在GTM中创建一个自定义HTML代码,然后打开预览和调试模式,尝试提交表单,看数据层变量是否如预期。
第四步 部署上线
测试成功后,我们可以创建数据层变量获取formID,以formSubmission为自定义事件触发器来追踪表单提交。
#6 利用dataLayer事件追踪
实际上,这个方法是性能最好,最直观的方式,但是它需要开发者的支持,因此我把它放在靠后的位置。
联系开发者,找到表单提交的代码所在。

第5,6,7行表示,如果返回码是1,那么现实一个“Thank you ~”的信息,并且给Facebook pixel传递一个“CompleteRegistration”的事件。
同理,我们可以用dataLayer.push一个自定义事件进去。
dataLayer.push({‘event’ : ‘subscribed’});
然后,我们在用自定义事件作为触发条件即可做表单追踪。
当然,别忘了测试。
#7 利用DOM爬取追踪表单提交
这个方法不应该是首选,因为伴随着不稳定性,主要是因为开发人员如果做一些网页的修改可能就会导致追踪信息失败或错误。
无论如何,这种方法还是有一定的价值,前提是网站开发有良好的开发习惯且与营销人员有很好的信息共享。
这次会使用DOM元素变量,它让我们能从任意DOM元素获取变量传递给GA。
我们还是以第四步的表单提交举例,右键检查“Thanks for signing up”。

上图我们可以看到,这段文案的CSS选择器是p.dialog-intro,为了数据的准确性,我们可以把CSS选择器做的更加精确一点,例如div.dialog-content p.dialog-intro。
下面开始构建DOM元素变量。
- 新建DOM元素变量,选择方法-CSS选择器
- 元素选择器-div.dialog-content p.dialog-intro
- 将它命名为DOM 元素-感谢注册
创建触发器:
- 新建触发器,触发器类型-页面浏览
- 选择某些网页浏览
- 条件为DOM 元素-感谢注册等于Thanks for signing up.
结语
本文总共介绍了7种表单追踪的方法,其中第六种是最简单实用的,选择适合你的,然后监测注册率,留评率等关键性指标吧!如果有不足,欢迎指正讨论,期待您的留言!
http://xzh.i3geek.com
感谢大佬的教程,学习了很多
填写表格并提交,打开开发者模式(ctrl+shift+j),然后输入debugDL。
我按照这个方法打开开发者模式,然后输入debugDL。出现的提示是:
VM120:1 Uncaught ReferenceError: debugDL is not defined
at :1:1
(anonymous) @ VM120:1
Failed to load resource: net::ERR_EMPTY_RESPONSE
Failed to load resource: net::ERR_EMPTY_RESPONSE
这是怎么回事呢?谢谢
GTM sonar安装了吗?
表单提交后, 页面有刷新吗?
如果页面有刷新那么一定要安装GTM sonar;
如果安装了依然报错, 那建议接着往下找其他的solution