自去年开始,中台话题的热度不减,很多公司都投入到中台的建设中,从战略制定、组织架构调整、协作方式变动到技术落地实践,每个环节都可能出现各种各样的问题。技术中台最坏的状况是技术能力太差,不能支撑业务的发展,其次是技术脱离业务,不能服务业务的发展。前者是能力问题,后者是意识问题。在本专题中,伴鱼技术团队分享了从 0 到 1 搭建技术中台的过程及心得。
可视化埋点,也称圈选埋点,是建立在全埋点技术基础上的一种数据埋点机制。通过全埋点技术,尽可能地将用户的所有交互行为进行采集上报,然后通过可视化圈选的方式筛选出感兴趣的行为统计数据,为产品运营提供决策支持。可视化埋点具有“全面、便捷、低技术门槛”的特点,能够有效降低研发、运营成本,是对传统代码埋点技术的有力补充。
本文结合伴鱼iOS端在圈选埋点技术上的一些实践经验,对圈选埋点方案的设计和实现进行探讨。
从数据采集到生成统计报表,一般需要经过三个步骤,如下图所示
1.用户行为数据采集:通过全埋点技术采集用户行为事件;
2.圈选配置匹配规则:由产品或运营人员通过可视化圈选工具,对感兴趣的用户行为事件进行标定,生成事件匹配规则,并上传到服务端;
3.匹配计算生成报表:数据研发人员根据已配置的事件匹配规则进行数据统计,生成报表
这里采用全埋点的方式采集用户行为数据,会增加 App 端数据流量和服务端数据存储压力。选择该方案的理由参见4.2 前后端配合方式的选择 。
全埋点采集用户行为,需要解决的最大问题是:如何精确描述行为事件。通常对页面和页面中的可交互元素分别进行定义。
页面标识通常采用 2 种方式来标定:
1.页面路径:从 Window 的根控制器开始直到页面所在视图控制器的路径。例如
UITabBarController-UINavigationController(1)-MyViewController(2)
括号中的数字代表控制器在父控制器中的索引。
2.页面类名: 直接已控制器的类名作为页面标识。被 Presented 的控制器也适用于该方式。
例外情况
a. 页面所属控制器存在自定义的父控制器
例如:一个控制器包含了若干子控制器,且通过 UIScrollView 分页的方式呈现各子控制器的视图。对于此类控制器,无法通过 hook viewDidAppear: 的方式来记录PV。
解决办法:通过 UIScrollViewDelegate 的 scrollViewDidScroll: 和 scrollViewDidEndScrollingAnimation: 方法来监听 UIScrollView 的内容偏移事件,根据 contentOffset 计算当前显示的视图属于哪一个控制器,最后手动触发控制器的 viewDidAppear: 方法。
b. 一些页面需要避免被采集
一些用于调试的页面,或经产品确认不参与采集的页面,通过下发 ignore list 的方式来过滤。
理论上,页面中所有可交互的元素都应该能够被采集到。但考虑到 App 交互的多样性和现实成本,这里仅讨论支持点击操作的元素。
通常,元素标识由三部分组成
1.元素在页面视图树中的路径
路径由视图树根节点开始,到该元素节点的父节点为止。例如:假设视图中有一个按钮控件,那么它的路径可以表示成如下形式:
UIWindow-UITransitionView-UIDropShadowView-UILayoutContainerView-UITransitionView-
UIViewControllerWrapperView-UILayoutContainerView-UINavigationTransitionView-
UIViewControllerWrapperView-UIView
2.元素的类型名称+索引
以上述按钮为例:它的类型名为UIButton,索引为其在父视图中的添加顺位。
3.元素的内容
元素的内容可能是文本、图片、其他包含图片或文字的子元素组合。类似于 UILabel、UIImageView 这样的元素,直接获取其文本信息或图片URL即可。对于 UIButton,获取其 currentTitle 文本或 UIControlStateNormal 状态下的图片 URL。文本内容优先于图片内容。
对于图文并存,或包含子元素组合的元素,需要根据元素类型及圈选方式确定,且元素内容标识需要单独生成。在 元素内容 一节中有详细介绍。
上述按钮的完整标识可以表示如下:
UIWindow-UITransitionView-UIDropShadowView-UILayoutContainerView-UITransitionView-
UIViewControllerWrapperView-UILayoutContainerView-UINavigationTransitionView-
UIViewControllerWrapperView-UIView-UIButton(0)_[click me]
UIButton后面小括号中的数字”0”表示其在父视图中的索引,中括号内的 “click me” 来自其 currentTitle 的值。
建议只从视图控制器所在的视图开始添加元素索引。系统内置的视图,如 UITransitionView 会在运行时修改其子元素的索引,造成元素路径发生变化,因此在进行路径追溯时,到达 UIViewController (注意不含 UITabBarController 和UINavigationController) 就不再添加索引。
独立元素是指在视图中独立绘制的元素,通常与其他元素无关联。对于此类型元素,标识定义为:”路径”“类型+索引”[“内容”]。
可重复元素是指在列表中绘制的元素。在 iOS 中只考虑 UITableViewCell 和 UICollectionReusableView。通过元素在父视图中的 indexPath 来确定元素的索引,即 (indexPath.section-indexPath.row),那么可重复元素的路径可以定义为:
... UIView-UITableView(0)-UITableViewCell(indexPath.section-indexPath.row)
我们将元素内容的分为图片和文本两类。文本类内容可以从控件的 text、title 等属性获取,这里不再赘述。图片内容的获取,有2种方式:
通过 imageNamed: 方法设置的图片,通过 description 方法打印其信息,可以得到类似如下结果:
<UIImage:0x6000005de2e0 named(main: home_search_icon) {24, 24}>
这里的 “main: home_search_icon” 表示图片名称为 “home_search_icon”,来自 “main bundle”。我们可以截取 “main: home_search_icon” 作为图片内容。
如果通过 description 方法打印的信息如下:
<UIImage:0x6000005a1f80 anonymous {75, 75}>
这说明图片是通过其他方式进行设置的,需要通过第二种方式来获取其内容。
NSString *imageContent;
SEL sel = NSSelectorFromString(@"sd_imageURL");
if ([self respondsToSelector:sel]) {
NSURL *url = [self performSelector:sel];
if ([url isKindOfClass:[NSURL class]])
{
imageContent = url.absoluteString;
}
}
如果一个元素只含有一项文本或图片,则称这个元素的内容为单一内容。单一内容本身作为其内容标识。
如果一个元素包含多个文本或图片、或其子元素内也包含文本或图片,则称其内容为复合内容。我们对复合内容进行遍历,遍历结果按键值对保存:
{
"UIView-UILabel(0)": "text 1",
"UIView-UIImageView(1)": "main: search_icon",
}
其中,key 对应的是子元素相对路径,作为改内容的内容标识,即从当前元素到子元素的路径,value 对应的是该内容具体的文本或图片内容。
对于具有复合内容的元素,有时会对其中某一项内容进行统计,该内容的内容标识可以参与到事件匹配。
考虑到性能影响,一个元素的内容遍历深度一般不超过5。
我们通过定义事件匹配规则来对事件进行过滤,符合匹配规则的事件被认为是需要进行统计的。匹配规则实质上是对页面标识、元素标识、元素内容定义的一系列正则表达式。将用户行为相关的页面、元素标识、元素内容与事先定义的正则表达式进行匹配,匹配成功则进行统计。
正则表达式符号定义:
为了简化正则表达式的书写,我们将正则表达式中需要精确匹配的字符串进行如下约定:
假设我们要采集一个元素的标识为
UIWindow-UITransitionView-UIDropShadowView-UILayoutContainerView-UITransitionView-
UIViewControllerWrapperView-UILayoutContainerView-UINavigationTransitionView-
UIViewControllerWrapperView-UIView-UITableView-UITableViewCell(1-2)_[null]
根据上述约定,我们可以定义如下正则表达式来采集该元素:
^UIWindow-UITransitionView-UIDropShadowView-UILayoutContainerView-UITransitionView-
UIViewControllerWrapperView-UILayoutContainerView-UINavigationTransitionView-
UIViewControllerWrapperView-UIView-UITableView-UITableViewCell(1-2)_[[\S|\s]+\]
用fixedPrefix表示”UIWindow-UITransitionView-UIDropShadowView-UILayoutContainerView-UITransitionView-
UIViewControllerWrapperView-UILayoutContainerView-UINavigationTransitionView-
UIViewControllerWrapperView-UIView-UITableView-UITableViewCell”
用 (fixedSection-fixedRow) 表示 “(1-2)”
用 fixedSuffix 表示 “_”
可以得到简化后的正则表达式
^fixedPrefix\(fixedSection-fixedRow\)fixedSuffix\[[\S|\s]+\]$
该规则表示:匹配某个视图列表中 section 为 1,row 为 2 的元素,不关注内容变化。
根据页面标识进行精确匹配即可。
产品或运营完成圈选配置后,需要验证圈选是否生效。App 可以通过集成圈选SDK来实现所见即所得的验证方式。如下图所示,符合匹配规则的页面和元素会以不同颜色高亮显示。
无论何种原因导致元素的路径或内容发生变化,最终会使得元素事件无法被事先配置的圈选规则匹配。有 2 种典型场景:
某些元素的父视图层级固定,只是索引会发生变化,例如导航栏右上角的下拉菜单列表,列表中的元素顺序可能会变化,但都限定在菜单容器内。对于这种元素,我们可以在生成圈选配置时,限定元素的文本内容。只要内容不变,仍然能够被匹配命中。
总而言之,如果导致元素的标识变化的场景是可以被枚举的,我们只需枚举所有感兴趣的场景,然后分别进行圈选埋点;如果元素的视图层级固定,仅索引会变,我们可以根据元素内容进行限定,只匹配特定内容的元素;其他情况下建议直接使用代码埋点。
本文尝试阐述这样一种理念:通过全埋点的方式采集用户行为,通过正则匹配的方式构建统计规则,最终为产品决策提供数据支持。圈选埋点技术有效地提高了研发效率,让产品和运营能够更直观便捷地定义指标;但对于复杂的业务场景,代码埋点仍然不可或缺。
参考:
GrowingIO JavaScript SDK 的实现细节和扩展 网易HubbleData无埋点SDK在iOS端的设计与实现
领取专属 10元无门槛券
私享最新 技术干货