精华内容
下载资源
问答
  • 前言 相信业务团队对这样的场景不会太陌生: 打点 需求 : 每新上一个功能,数据产品便会同步加上打点需求,当数据打点上线后一段时间,数据产品/业务产品...由此数据是很重要的,我们接下来从数据采集的重要性、数据
  • 数据采集之全埋点数据采集分析方法的一些整理,包含一些思路、方法逻辑的整理分析,供数据采集分析爱好者参考。 埋点的定义: 埋点分析,是网站分析的一种常用的数据采集方法。数据埋点分为初级、中级、高级三种...

    数据采集之全埋点数据采集分析方法的一些整理,包含一些思路、方法逻辑的整理分析,供数据采集分析爱好者参考。

    埋点的定义

         埋点分析,是网站分析的一种常用的数据采集方法。数据埋点分为初级、中级、高级三种方式。数据埋点是一种良好的私有化部署数据采集方式。

    埋点技术如何采集数据,有何优缺点?

    数据埋点分为初级、中级、高级三种方式,分别为:

    • 初级:在产品、服务转化关键点植入统计代码,据其独立ID确保数据采集不重复(如购买按钮点击率);
    • 中级:植入多段代码,追踪用户在平台每个界面上的系列行为,事件之间相互独立(如打开商品详情页——选择商品型号——加入购物车——下订单——购买完成);
    • 高级:联合公司工程、ETL采集分析用户全量行为,建立用户画像,还原用户行为模型,作为产品分析、优化的基础。

    无疑,数据埋点是一种良好的私有化部署数据采集方式。例如V统计,针对网页微信复制统计的埋点。数据采集准确,满足了企业去粗取精,实现产品、服务快速优化迭代的需求,

         但,因手动埋点工程量极大,且一不小心容易出错,成为很多工程师的痛。且其开发周期长,耗时费力,很多规模较小的公司并不具备自己埋点的能力。无埋点成为市场新宠。最后埋点、无埋点两种技术谁能成为最后赢家,我们拭目以待。

    全埋点采集分析

        全埋点,也叫无埋点、无码埋点、自动埋点。全埋点是指预先收集用户的所有行为数据,然后再根据实际分析需求从中提取行为数据。

    全埋点采集的事件主要包括下面四种:

    • $AppStart 事件:指 App 启动,包括冷启动和热启动。

    • $AppEnd 事件:指 App 退出,包括正常退出、进入后台、 App 崩溃、App 被强杀。

    • $AppViewScreen 事件:指 App 页面浏览 ,对于 Android 来说,就是指切换 Activity。

    • $AppClick 事件:指 App 控件被点击。

    在采集的这四种事件当中,最重要并且采集难度最大的是 $AppClick 事件。所以,全埋点基本上也都是围绕着如何采集 $AppClick 事件。

    全埋点的整体解决思路,就是要找到那个被点击的 View 的点击处理逻辑(也叫原处理逻辑),然后利用一定的技术原理,对原处理逻辑进行“拦截”,或者在原处理逻辑的前面或者后面“插入”相应的埋点代码,从而达到自动埋点的效果。

    至于如何做到自动“拦截”View 的原点击处理逻辑,V统计认为一般都是参考 Android 系统 View 点击事件处理机制来进行的。 至于如何做到自动“插入”埋点代码,基本上都是参考编译器对 Java 代码的处理流程来进行的即:

    JavaCode --> .java --> .class --> .dex

    选择在不同的处理阶段“插入”代码,其所用的技术或者原理也不尽相同,所以全埋点的解决方案也是多种多样的。

    面对这么多的全埋点方案,我们究竟该如何选择呢?

    在选择全埋点的方案时,需要从效率、兼容性、扩展性等 方面综合考虑。

    • 效率

    全埋点的基本原理,其实就是利用某些技术对某些方法 (View 被点击时的处理逻辑)进行代理(或者叫 Hook),或者“插入”代码。按照“在什么时候去代理或者插入代码” 这个条件来区分的话,Android 全埋点技术可以大致分为 下面两种方式 :

    静态代理

    所谓静态,就是指通过 Gradle Plugin 在编译期间“插入” 或者修改代码(.class 文 件)。比如 AspectJ、ASM、javassist、AST 等方案均是这种方式,我们后面介绍的第四种方案到第七种方案均属于静态代理。

    这几种方式处理的时机可以参考下图:

    全埋点数据采集分析
    全埋点数据采集分析 图片来自( 软盟网

     

    动态代理

    所谓动态,就是指在代码运行的时候去进行代理。比如我们比较常见的代理 View.OnClickListener、Window.Call- back、View.AccessibilityDelegate 等方案均是这种方式。我们后面介绍的第一种方案到第三种方案均属于动态代理。

    不同的方案,其处理、运行效率也各不相同,同时对 App的浸入程度以及对 App 的整体性能的影响也各不相同。整理上来说,静态代理明显优于动态代理,这是因为静态代理的“动 作”是在 App 的编译阶段处理的,不会对 App 的正常业务(App 运行时)逻辑有太大的影响。

    • 兼容性

    随着 Android 生态系统的快速发展,不管是 Android 系统本身,还是与 Android App 开发相关的组件和技术,都在飞速 发展快速迭代,从而也给研发全埋点方案带来一定的难度。比如不同的 Android App 有不同的开发语言(Java 、Kotlin)、 也有不同的 Java 版本(Java7、Java8)、也有不同的开发 IDE(eclipse、Android Studio)、更有不同的开发方式(原生开发、 H5、混合开发)、不同的第三方开发框架(React Native、APICloud、Weex)、不同的 Gradle 版本、以及 Lambda、D8、 Instant Run、DataBinding、Fragment 等,都会给全埋点带来很多兼容性方面的问题。

    • 扩展性

          随着业务的发展和数据分析需求的不断提高,即使对于全埋点,也提出了更高的采集要求。一方面要求能全部自动埋点(采集的范围),同时又要求能有更精细化的采集控制(采集自定义)。比如,如何给某个控件添加自定义属性 ? 如果不想采集 某个控件的点击事件如何处理 ?如果不想采集某种控件类型(ImageView)的点击事件如何处理 ?如果某个页面 (Activity)上所有控件的点击事件都不想采集如何处理 ?...... 任何一种全埋点的方案,都有其优点和缺点。没有普适的完美方案。针对不同的应用场景,选择最合适的数据采集方案即可。

    部分内容来自网络整理和参考自:http://vtongji.ibixue.com,供参考学习交流。

    展开全文
  • </script> </head> <body> <h1><a href="/end">点我跳转end页面</a></h1> </body> </html> 向服务器发送请求ma.js ma.js 整合了页面需要采集数据,然后用encodeURIComponent方法将字符串作为 URI 组件进行编码,用...

    参考链接
    http://blog.codinglabs.org/articles/how-web-analytics-data-collection-system-work.html

    准备工作
    1 . openresty
    下载路径: http://openresty.org/cn/download.html

    openresty安装
    http://openresty.org/cn/installation.html

    实验过程
    需要收集数据的页面代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="jquery-3.4.1.js"></script>
        <title>start页面</title>
        <script type="text/javascript">
            var _maq = _maq || [];
            _maq.push(['_setAccount','u-z1234']);
            (function() {
                var ma = document.createElement('script');
                ma.type = 'text/javascript';
                ma.async = true;
                ma.src = 'https://[服务器ip]/ma.js';
                var s = document.getElementsByTagName('script')[0];
                s.parentNode.insertBefore(ma, s);
            })();
        </script>
    </head>
    <body>
         <h1><a href="/end">点我跳转end页面</a></h1>
    </body>
    </html>
    

    向服务器发送请求ma.js
    ma.js 整合了页面需要采集的数据,然后用encodeURIComponent方法将字符串作为 URI 组件进行编码,用image对象携带参数向服务器发送请求,ma.js 文件放在Nginx根目录的HTML文件夹里就行

    (function () {     
    var params = {};     
    //Document对象数据     
    if(document) {
        params.domain = document.domain || '';          
        params.url = document.URL || '';          
        params.title = document.title || '';  
        params.referrer = document.referrer || '';  
        }    
    //Window对象数据 
    if(window && window.screen) { 
        params.sh = window.screen.height || 0;         
        params.sw = window.screen.width || 0; 
        params.cd = window.screen.colorDepth || 0; 
    }    
    
    //navigator对象数据
    
    if(navigator) { 
            params.lang = navigator.language || '';  
    }        
    
    //解析_maq配置     
    if(_maq) { 
        for(var i in _maq) { 
            switch(_maq[i][0]) {                 
                case '_setAccount': 
                          params.account = _maq[i][1]; 
                          break;                 
                default:                   
                    break; 
                }    
            }    
        }    
    
    //拼接参数串 
    var args = '';      
    for(var i in params) {         
        if(args != '') { 
                args += '||'; 
            }    
            args += i + '=' + encodeURIComponent(params[i]); 
    }    
       
    //通过Image对象请求后端脚本 
    var img = new Image(1, 1);  
    img.src = 'https://[服务器ip地址]/log.gif?' + args; })();
    

    Nginx.conf 配置
    Nginx 我这里支持HTTPS,所以有一些SSL配置,自己做实验可以将HTTPSserver里面的代码移到httpserver里,页面请求的ma.js 最后用image对象携带参数向服务器发送了请求,请求/log.gif ,nginx 匹配到 location /log.gif ,然后对cookie 做了处理,然后请求了一个内部的 /i-log ,然后location /i-log 处理参数args ,按照log_format 的格式输出到指定目录,然后返回空字符串,这样就采集到自己所需要的数据了

    
    #user  nobody;
    worker_processes  2;
    
    #error_log  logs/error.log;
    #error_log  logs/error.log  notice;
    #error_log  logs/error.log  info;
    
    #pid        logs/nginx.pid;
    
    
    events {
        worker_connections  1024;
    }
    
    
    http {
        include       mime.types;
        default_type  application/octet-stream;
    
        #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
        #                  '$status $body_bytes_sent "$http_referer" '
        #                  '"$http_user_agent" "$http_x_forwarded_for"';
    
        log_format tick escape=json  
        "$msec||$remote_addr||$status||$body_bytes_sent||$u_domain||$u_url||$u_title||$u_referrer||$u_sh||$u_sw||$u_cd||$u_lang||$http_user_agent||$u_account"; 
        #access_log  logs/access.log  main;
    
        sendfile        on;
        #tcp_nopush     on;
    
        #keepalive_timeout  0;
        keepalive_timeout  65;
    
        #gzip  on;
        # HTTP server
        server {
            listen       80;
            server_name  localhost;
            #重定向
            rewrite ^(.*)$ https://$host$1 permanent;
            #charset koi8-r;
    
            #access_log  logs/host.access.log  main;
    	}    
        
        # HTTPS server
        server {
            listen       443 ssl;
            server_name  localhost;
            root html;
            index  index.html index.thm;
    	
            #ssl 参数配置
            ssl_certificate      /usr/local/openresty/nginx/cert/xxx.crt;
            ssl_certificate_key  /usr/local/openresty/nginx/cert/xxx.key;
    
            ssl_session_timeout  5m;
    
            ssl_ciphers  ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
            ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
            ssl_prefer_server_ciphers  on;
    
    	
    
            location /log.gif { 
    		#伪装成gif文件 
    		default_type image/gif;     
    		#本身关闭access_log,通过subrequest记录log     
    		access_log off; 
    		access_by_lua "
            -- 用户跟踪cookie名为__utrace
            local uid = ngx.var.cookie___utrace        
            if not uid then
                -- 如果没有则生成一个跟踪cookie,算法为md5(时间戳+IP+客户端信息)
                uid = ngx.md5(ngx.now() .. ngx.var.remote_addr .. ngx.var.http_user_agent)
            end 
            ngx.header['Set-Cookie'] = {'__utrace=' .. uid .. '; path=/'}
            if ngx.var.arg_domain then
            -- 通过subrequest到/i-log记录日志,将参数和用户跟踪cookie带过去
                ngx.location.capture('/i-log?' .. ngx.var.args .. '&utrace=' .. uid)
            end 
            ";    
    	   
    		#此请求资源本地不缓存 
    		add_header Expires "Fri, 01 Jan 1980 00:00:00 GMT";     
    		add_header Pragma "no-cache"; 
    		add_header Cache-Control "no-cache, max-age=0, mustrevalidate"; 
    		#返回一个1×1的空gif图片 
    		empty_gif; 
            }
            
            location /i-log { 
            #内部location,不允许外部直接访问     
            internal; 
           
            #设置变量,注意需要unescape,来自ngx_set_misc模块 
            set_unescape_uri $u_domain $arg_domain;
            set_unescape_uri $u_url $arg_url;
            set_unescape_uri $u_title $arg_title;
            set_unescape_uri $u_referrer $arg_referrer;
            set_unescape_uri $u_sh $arg_sh;
            set_unescape_uri $u_sw $arg_sw;
            set_unescape_uri $u_cd $arg_cd;
            set_unescape_uri $u_lang $arg_lang;
            set_unescape_uri $u_account $arg_account;
            #打开日志  
            log_subrequest on; 
            #记录日志到ma.log 格式为tick 
            access_log /usr/local/openresty/nginx/nginx_logs/ma.log tick; 
           
            #输出空字符串 
            echo ''; 
            }
    
            #error_page  404              /404.html;
    
            # redirect server error pages to the static page /50x.html
            #
            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
    
            # proxy the PHP scripts to Apache listening on 127.0.0.1:80
            #
            #location ~ \.php$ {
            #    proxy_pass   http://127.0.0.1;
            #}
    
            # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
            #
            #location ~ \.php$ {
            #    root           html;
            #    fastcgi_pass   127.0.0.1:9000;
            #    fastcgi_index  index.php;
            #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
            #    include        fastcgi_params;
            #}
    
            # deny access to .htaccess files, if Apache's document root
            # concurs with nginx's one
            #
            #location ~ /\.ht {
            #    deny  all;
            #}
        }
        
    
    
        # another virtual host using mix of IP-, name-, and port-based configuration
        #
        #server {
        #    listen       8000;
        #    listen       somename:8080;
        #    server_name  somename  alias  another.alias;
    
        #    location / {
        #        root   html;
        #        index  index.html index.htm;
        #    }
        #}
    
    
        
        #server {
        #    listen       443 ssl;
        #    server_name  localhost;
    
        #    ssl_certificate      cert.pem;
        #    ssl_certificate_key  cert.key;
    
        #    ssl_session_cache    shared:SSL:1m;
        #    ssl_session_timeout  5m;
    
        #    ssl_ciphers  HIGH:!aNULL:!MD5;
        #    ssl_prefer_server_ciphers  on;
    
        #    location / {
        #        root   html;
        #        index  index.html index.htm;
        #    }
        #}
    
    }
    
    

    每小时日志切分脚本

    #!/bin/bash 
    #安装目录下日志文件
    log_path='/usr/local/openresty/nginx/nginx_logs/ma.log'
    #需要保存的目录位置
    new_log_path='/usr/local/openresty/nginx/nginx_logs_byhour'
    
    #获取年月日小时
    curr_time=`date -d "1 hour ago" +"%Y%m%d_%H%M%S"`
    
    #将安装目录下的日志文件,移动到指定存储位置
    mv $log_path $new_log_path/$curr_time.log
    
    kill -USR1 `cat /usr/local/openresty/nginx/logs/nginx.pid`
    

    定时任务

    0 * * * * /root/nginx_cplog.sh
    
    展开全文
  • 行业分类-物理装置-埋点数据采集方法及其装置.zip
  • android 无埋点数据采集gradle插件(ASM字节码插桩) 掘金原理篇: 更新记录: 0.2.0 1.去除v4、v7包支持 2.支持androidx 基于tools.build:gradle:4.0.1, gradle:6.6, sdkVersion:29 开发测试 配置变动点: 1.需要在...
  • 本文将从技术选型、技术实现方案角度详细介绍Android端无埋点数据采集技术。 一、技术选型 首先,技术是为需求提供服务的,WMDA的定位是采用无埋点技术来实现用户行为的分析。同时辅助解决手动埋点不易维护,容易...

    640?wx_fmt=jpeg

    本文转载自公众号 58架构师,已经作者授权!

    作为国内最大分类信息生活服务平台,58集团旗下各个产品都会投入大量人力进行用户行为的分析,来提升运营效率。但是各个产品对用户行为的分析需求基本是相似的。在这样的背景下,我们自研了WMDA 无埋点用户行为分析平台,并提供对PC、M、APP三端支持,帮助各个业务线更好的挖掘用户真实行为。

    对于SDK的使用,业务方不需要手动埋点,几行代码,即可实现数据的全量采集。对于移动端SDK来说,采集数据的准确性、及时性、全面性等因素直接决定后续用户行为的分析。本文将从技术选型、技术实现方案角度详细介绍Android端无埋点数据采集技术。

    一、技术选型

    首先,技术是为需求提供服务的,WMDA的定位是采用无埋点技术来实现用户行为的分析。同时辅助解决手动埋点不易维护,容易出现错埋、漏埋等痛点问题。所以SDK在采集用户行为数据的同时,对开发效率、采集性能、准确性、实时性等有很高的要求,而且需要支持数据的可回溯。  

    通过对市面上现有埋点技术调研,目前技术方案上大体分为三类:

    1. 传统代码埋点

      实现方案:Coding阶段手动埋点。

      代表解决方案:友盟、百度统计。

      优点:灵活、准确,可以定制化。

      缺点:业务埋点量非常大,开发成本高,不易维护,如果要修改、新增埋点,需要重新发版。

    2. 动态埋点

      实现方案:利用AccessibilityDelegate对每个view实例设置代理,监听控件点击事件。

      代表方案:Github上开源的Mixpanel

      优点:无需手动埋点,通过可视化圈选,动态下发配置监听指定控件。

      缺点:不支持数据可回溯,采集不到Fragment页面数据,只支持API 14及以上,同时该监听方式对app性能影响严重,每个控件都需要动态绑定,在界面变更时,需要重新刷新ViewTree,效率低下。

    3. 编译时字节码插桩埋点

      实现方案:利用Gradle插件,在编译阶段在代码中插入埋点代码,进行数据采集。

      代表方案:GrowingIO、美团的替换UI控件方案。

      优点:开发效率高,无需手动埋点,编译时插入代码,性能高,支持数据可回溯。

      缺点:埋点灵活性低。

    通过以上简短分析,我们可以看出三种方案的优缺点都比较明显。最后,我们采取了利用Gradle插件自动注入埋点代码为主,并辅以手动埋点进行数据定制化补全的技术方案。

    注:通过查看GrowingIO官方文档,GrowingIO现在也已提供对手动埋点的支持

    二、技术实现

    WMDA SDK Android端整体架构主要分为圈选模块、事件采集上报、配置管理三部分,如下图所示。

    640?wx_fmt=png

    下面根据事件采集上报流程分别来介绍事件采集、处理、存储、上报和圈选。

    2.1 事件采集

    WMDA移动端数据采集类型主要分三种:页面浏览事件、控件点击事件和自定义事件。作为无埋点解决方案,SDK核心点就是事件的无痕采集。  其中,这三种事件又对应不同的采集处理方式,WMDA通过不同的技术方案进行采集,最后将事件统一处理,然后存储、上报。

    2.1.1 插桩入口

    事件采集是无埋点技术的核心,其中WMDA对Fragment和控件点击事件拦截,使用的是自己开发的gradle插件wmda plugin,编译时使用ASM以字节码插桩的方式注入代码,实现事件的采集。

    对于事件拦截,首先需要确定插入时机和待修改字节码文件。这里我们使用Transform API作为插桩入口,在Java Compiler之后,class文件打包成dex文件之前修改字节码文件。由于Transform API是在Gradle插件版本1.5.0出现的,所以项目开发中Gradle插件版本不能低于1.5.0。

    classpath 'com.android.tools.build:gradle:1.5.0'
    

    然后在transform中遍历并操作字节码文件,因为现在很多大型项目,都会进行组件化操作,拆分成多个Library。所以这里除了要修改我们的应用源码,还需要对第三方库中的字节码文件进行扫描操作。

    void transform(Context context, Collection&lt;TransformInput&gt; inputs, Collection&lt;TransformInput&gt; referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
        inputs.each { TransformInput input -&gt;
            input.directoryInputs.each {
                DirectoryInput directoryInput -&gt;
                //对应用源码生成的class操作
            }
            input.jarInputs.each { JarInput jarInput -&gt;
                //对第三方库中class操作
            }
        }
    }

    同时我们也引入了注入白名单机制,如果app不希望某些包名下的类被注入代码,比如使用的第三方sdk,可以在项目级别根目录下创建wmda-rules.properties文件并填写不希望注入的类路径。以下为示例,可以添加多个,每行一条,以 “/” 结尾:

    com/wuba/sdk/
    com/wuba/test/

    2.1.2 页面事件

    针对页面浏览事件,WMDA分两种不同的方式进行采集。  

    2.1.2.1 Activity采集  针对Activity,WMDA采用的方式是使用LifecycleCallback来监听页面的开启和关闭。  在页面开启时,拦截生命周期方法onResume,然后在事件处理模块处理,将其格式化成事件结构,并进行存储上报。

    @Override
    public void onActivityResumed(Activity activity) {
        // 页面浏览事件采集处理
        PageManager.getInstance().onActivityResumed(activity);
    }
    

    不过该方案有个适配问题,在Android4.0(API14)以下,系统并不支持该方法。

    注:目前App最低版本基本都是android 4.0及以上

    在低版本中,我们也可以通过Hook方式拦截。通过拦截主线程的Instrumentation实例,来实现低版本页面的监听。这块同时还需要考虑第三方插件也Hook该实例的情况,执行Hook前对应方法,保证对app中其他插件没有影响。缺点是如果其他SDK也使用了这种方式,可能会影响我们的拦截。

    @Override
    public void callActivityOnResume(Activity activity) {
        // 页面浏览事件采集处理
        PageManager.getInstance().onActivityResumed(activity);
        //执行Hook前Instrumentation实例的onResume方法
        oldInstrumentation.callActivityOnResume(activity);
    }

    2.1.2.2 Fragment采集  针对Fragment,由于Android系统并没有关于Fragment生命周期的回调监听,所以这里WMDA通过Gradle插件,在编译时期,利用ASM库进行字节码操作,对Fragment注入WmdaAgent相应的页面采集方法,完成事件采集。在注入策略上,我们只需要对Fragment父类为下面两个的页面注入采集代码即可。

    android/app/Fragment
    android/support/v4/app/Fragment

    对Fragment相关方法注入代码示例:

    @Override
    MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
        MethodVisitor wrappedMv = mv;
        if (wrappedMv != null) {
            // 在onResume中插入WmdaAgent.onFragmentResumed方法
            if (name.equals("onResume") &amp;&amp; desc.equals("()V")) {
                wrappedMv.visitCode()
                wrappedMv.visitVarInsn(Opcodes.ALOAD, 0)
                wrappedMv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/wuba/wmda/autobury/WmdaAgent",
                        "onFragmentResumed", "(Landroid/app/Fragment;)V")
            }
        }
    }

    WmdaAgent代码:

    public static void onFragmentResumed(Fragment fragment) {
        // 页面浏览事件采集处理
        PageManager.getInstance().onFragmentResumed(fragment);
    }

    2.1.3 控件点击事件

    关于点击事件的采集,WMDA在早期研发过程中采取的是Mixpanel开源方案。在上文中已经提到过,该方案开发效率不错,不过性能问题、Fragment页面采集不到问题、版本适配问题,导致该方案存在瓶颈和风险。

    在后续的持续探索中,我们发现,使用Gradle插件在编译时埋点可以完美继承Mixpanel方案的各项优点,同时又可以规避其性能、数据准确性和版本适配问题。于是,在控件点击事件的采集上,我们调整了技术实现方案,从动态对View设置代理演进为编译时插入埋点代码。

    WMDA对点击事件拦截支持常用的一些第三方框架,比如:

    ButterKnife、databinding、AndroidAnnotations、RxBinding
    

    具体的技术和之前的Fragment插桩埋点是一样的,编译时对onClick方法注入代码,以AOP方式对事件拦截处理。核心实现思路如下:

    插件代码

    @Override
    MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
        MethodVisitor wrappedMv = mv;
        if (wrappedMv != null) {
            // 查找出方法名为onClick,入参为View的方法,注入WmdaAgent.onViewClick(View view)
            if (name.equals("onClick") &amp;&amp; desc.equals("(Landroid/view/View;)V")) {
                wrappedMv.visitCode()
                wrappedMv.visitVarInsn(ALOAD, 1);
                wrappedMv.visitMethodInsn(INVOKESTATIC, "com/wuba/wmda/autobury/WmdaAgent", "onViewClick", "(Landroid/view/View;)V", false);
            }
        }
    }

    WmdaAgent对应代码:

    public static void onViewClick(View view){
        // 控件点击事件采集处理
        AutoEventManager.getInstance().onEvent(view);
    }
    

    2.1.4 自定义事件

    无埋点是WMDA的核心功能,但是由于业务场景特点,无埋点并不能完全满足所有业务场景需求,所以WMDA也提供了对手动埋点支持,使得WMDA在实际的使用中更加灵活,数据统计也更全面。  

    这部分没有特别关键的技术点,就是普通的代码埋点,这里就不做过多介绍了。

    2.2  事件处理

    事件收集完成之后,就会发送到事件处理线程,对原生的事件进行加工,以便服务端能更好的进行分析处理。在页面事件处理中,我们将页面的class全路径作为页面的特征值。APP_PAGE示例:

    com.wuba.wmdademo.MainActivity

    采集到页面事件后将其传入子线程处理,然后再提取出业务开发人员在页面onCreate方法中设置的页面ID及页面自定义属性,将这些数据统一格式化,构造成页面浏览事件,传给事件存储模块。

    在对控件事件处理中,我们面临一个最大的问题就是,如何区分每一个控件,即如何定义控件的特征值。在这里,我们借鉴了Mixpanel的方法,即将View自身的类名及index,以及其逐级向上的所有父View的类名和index统一收集起来,然后将所有遍历信息拼接起来,当做该View在当前页面的唯一特征值。控件的唯一标识:页面APP_PAGE + ViewPath + index

    ViewPath举例:

    /MainWindow/LinearLayout[0]/FrameLayout[1]/ActionBarOverlayLayout[0]#decor_content_parent/ContentFrameLayout[0]/LinearLayout[0]/ScrollView[0]/LinearLayout[0]/AppCompatButton
    

    对于采集事件的后续处理,我们在UI性能上做了进一步优化。由于采用字节码插桩方式拦截事件,所以事件处理最耗时的点其实是在生成View特征值。在Android中,由于在子线程持有、操作view会引发内存泄漏问题。

    在WMDA中,我们将构造特征值方法进行了拆分,在UI线程只进行对View数据提取,可以理解为定向的View遍历拷贝,除此之外不做任何其他耗时操作,然后将拷贝完成的ViewData传递给子线程,构造特征值,整合数据构造格式化的点击事件,最后再将事件传给存储模块。

    点击事件处理时序图如下:

    640?wx_fmt=png

    2.3 事件存储

    事件处理完成之后,要交由存储模块进行本地持久化。在存储之前,先会检查存储策略,满足策略后再进行存储。

    存储这里,使用的是本地SQLite存储Protobuf实例的二进制,然后使用AES-256进行加密存储。

    2.4 事件上报

    事件存储完成之后,会触发上报请求。在上报之前,WMDA会先检查上报策略,满足策略后进行上报。

    上报这里为了缩小WMDA包,只使用了HttpUrlConnection来处理网络操作。在数据上报的时候使用了GZIP+ProtoBuf来减少流量消耗,保证收集数据的同时,提升用户体验。  

    2.5 圈选模块

    之前只是介绍数据采集方案,数据全量采集上报后,并不会直接分析处理,还需要一个圈选指标的过程。关于圈选的介绍,大家可以查看数据驱动增长:58无埋点用户行为分析实践之路这篇的圈选部分,这里就不做重复介绍了。

    通常,我们圈选时会在一个页面停留较长时间,这时其实是不需要一直将当前页面快照数据发送给服务端的,因为页面并没有变化。这块有一个优化策略,SDK会根据当前屏幕快照生成一指纹,只有当前屏幕有变更时才会将当前页面快照数据发给用户分析平台。

    2.6 其他技术点

    2.6.1 多进程数据采集

    子进程中只存在事件采集事件处理两个模块,为了保证事件的连续性,数据的存储和上报则放到主进程来统一进行处理,这样也避免了数据库的同步问题,增加了数据的准确性,提升了系统性能。

    因为事件采集是一种触发式的,所以我们在进程间通信上采用的是应用内广播,广播的优势是耦合度低,对子进程影响较小,同时性能相对来说可以接受。应用场景是技术选择的重要参考依据,所以这里并没有用Socket或者是AIDL来处理进程间通信。

    640?wx_fmt=png

    2.6.2 多进程界面圈选

    考虑到圈选是一个实时、持续的过程,所以SDK采用Socket方式实现进程间通信,所有子进程都将页面快照信息发给主进程,由主进程和服务端交互。

    640?wx_fmt=png

    三、现存问题

    当然,现阶段无埋点技术采用的字节码插桩方案还是存在一些短板,需要我们后续探索和解决。

    • click监听如果是在layout中使用android:onClick="xxxMethod"设置的暂时无法进行采集。这个设置监听的方法是利用Java的反射原理,去寻找对应的Method,在WMDA中是通过拦截OnClickListener点击事件来进行监听的,因此无法实现监听。

    • 由于目前采用的是编译时插入埋点,所以不支持目前比较流行的RN框架。

    • 同样因为是编译时插入埋点,所以对热更新的补丁支持可能也不到位。

    四、总结

    本文主要介绍了58无埋点数据采集技术在Android端实践。包括字节码插桩在无埋点的使用、对采集事件的处理等。同时现阶段无埋点技术还是存在一些问题需要我们后续探索和解决,欢迎感兴趣的同学和我们一起交流。

    展开全文
  • 但是,埋点数据的上送、解析、存储、分析的整个流程较长,涉及多团队协作,为了让感兴趣的读者有个整体认识,本节将结合工作实践,重点介绍 H5 埋点数据采集和应用的生命周期。 2埋点采集内容 埋点采集内容...

    ​作者介绍

    @hrd-0.618(栩梵)

    新网银行数据分析师。

    专注于数据分析、埋点采集及用户行为分析、BI 数据可视化。

    “数据人创作者联盟”成员。

    背景介绍

    产品的精细化运营、千人千面的个性化推荐等各类业务,均依赖于标准化、高质量的埋点数据。但是,埋点数据的上送、解析、存储、分析的整个流程较长,涉及多团队协作,为了让感兴趣的读者有个整体认识,本节将结合工作实践,重点介绍 H5 埋点数据采集和应用的生命周期。

    埋点采集内容

    埋点采集内容主要包括两方面:前端埋点数据采集、后端埋点数据采集。前者主要包括 3 种事件类型:用户事件、页面事件、点击事件。后者主要包括:接口调用事件。事件通过“串联码”关联到一起。数据模型设计也以此 4 种事件为基础。详见下图。

    埋点数据流向

    3.1 数据上送至日志采集服务

     前端+后端——>日志采集服务 

    前、后端数据以类 json 的格式,实时异步送行为事件到日志采集服务进行解析。

    3.1.1 用户事件:user

    {
    data:[{ 
       userid:用户唯一标识ID
         ,equipment:{               //header中获取,包括浏览器、设备、网络等
            equipment_os:操作系统 
          , equipment_os_version:操作系统版本
          , equipment_brand:品牌  
          …
          }
          ,location:{ 
           gps:{ 
                gps_lon:经度       
               ,gps_lat:维度       
               ,gps_country:gps国家           
                   ,gps_province:gps省       
               ,gps_city:gps市          
                   ,gps_district:gps区     
                 }    
                 ,ip:{  
                   … 
                   }
                 }
               }]  
                  ,time:时间   
                  ,cookie:串联码   
                  ,event_type:user   
                  ,from:{    
                  channel:渠道    
                  ,product:产品      
                    }
                }
    

    3.1.2 页面事件:page

    {
    data:[{   
       page_id:页面ID  
       ,page_name:页面名称  
       ,page_url:页面url  
       ,src_page_url:来源页url
     }]  
       ,time:时间   
       ,cookie:串联码   
       ,event_type:page   
       ,from:{   
          channel:渠道    
          ,product:产品      
          }
       }
    

    3.1.3 点击事件:click

    {
    data:[{   
       click_id:点击ID  
       ,click_name:点击名称  
       ,click_other_attr:{  
       remarks:备注  
       …
     }
     }]   
        ,time:时间   
        ,cookie:串联码   
        ,event_type:click   
        ,from:{    
        channel:渠道    
        ,product:产品        
             }
      }
    

    3.1.4 接口事件:interface

    {
    data:[{   
       interface_id:接口ID  
       ,interface_name:接口名称  
       ,result:接口调用结果  
       ,result_remarks:接口调用说明  
       ,response_time:接口响应时长
     }]   
       ,start_time:接口调用开始时间   
       ,end_time:接口调用结束时间   
       ,cookie:串联码   
       ,event_type:interface   
       ,from:{   
          channel:渠道      
          ,product:产品     
          }
     }
    

    3.2 实时数仓建模

    日志采集服务——>实时数仓(kafka)

    3.2.1 基础字段处理

    a. 将日志采集服务采集到的 4 种事件的 json 数据进行解析,得到 4 个事件的基础字段,并实时写入 kafka 消息队列的 4 个 topic 中。

    b. 通过 Flink/StreamSQL,实时或者微批消费 4 个 topic 数据,存储至 4 张 Hbase 表中。

    3.2.2 用户事件关联至行为事件

    消费 user 事件 topic,根据串联码 cookie,将用户信息关联至行为信息,构建实时用户行为宽表。

    3.3 离线数仓建模

    3.3.1 贴源层

    通过 ETL 抽取 4 个事件 HBase 表。

    3.3.2 模型层

    根据贴源层 4 个事件的串联码 cookie,将用户信息关联至行为信息,构建离线用户行为宽表。

    埋点数据应用

    4.1.1 用户行为查询

    根据实时用户宽表,数据写入 Elasticsearch,或者写入数据对外接口,即可查询实时用户行为记录。

    根据离线用户宽表,数据写入 Elasticsearch,或者写入数据对外接口,即可查询离线用户行为记录。

    4.1.2 用户行为统计

    根据 4 个事件 topic 数据,结合用户行为指标体系,通过聚合统计分析方法,得到不同维度的用户行为指标。

    页面级:

        数据日期

        渠道名称

        操作系统

        日期类型:日、7日、30日、总计

        维度类型:页面/环节/渠道

        可视化字段:渠道名称、环节、页面名称、PV、UV、访问用户数、平均停留时长、页面跳出次数、页面跳出率

    按钮级:

           数据日期

           渠道名称

           操作系统

           日期类型:日、7日、30日、总计

           可视化字段:渠道名称、操作系统、环节、页面名称、点击名称、点击次数、点击用户数

    4.1.3 用户留存分析

    维度:

           数据日期:2021-08-02

           渠道名称:如“xxx”,无汇总

           用户类别:汇总、新用户

           留存类型:产品级、功能级(页面、点击)(可下拉选择某个页面,或者选择某个点击)

           数据类型:留存人数、留存率

    产品级,且选中留存人数

       

    产品级,且选中留存率 

    功能级:比如美团 APP,对使用 “单车” 功能的用户做留存分析。 

    4.1.4 用户行为标签和客群筛选

    构建用户行为标签,用于筛选目标客群。

    根据客户实时/离线业务状态,在满足某种行为特征时,筛选出不同的目标客群给业务人员,通过营销平台做不同方式触达。

    实时行为特征如:时间段内的点击次数、停留时长、页面访问次数等。

    场景如:分别针对新客户/老客户、有存款客户、有提前支取记录客户,根据不同页面和点击的行为特征,设定不同营销策略。

    对于产品品类少的企业,不同场景的客群,实时推送给业务人员,与营销平台联动,进行精准营销。

    当然,对于产品品类较多的企业,如电商相关的场景,构建基于用户行为的实时推荐系统,是业界主流。

    4.1.5 基于用户行为的断点触达

    可结合实时、离线的用户行为和业务状态,对于存在行为断点的用户进行断点外呼或者其他方式触达。

    结语

    本文主要结合实际工作中的一些经历,做了简要概述,埋点采集主要是代码埋点,人工维护成本较大,后续可结合实际场景,采用业界更优的采集技术;用户行为分析也有待逐步完善,欢迎大家批评指正,感兴趣的小伙伴可以联系我,一起探讨。

    展开全文
  • iOS无埋点数据采集

    千次阅读 2017-03-10 17:24:16
    数据采集
  • 公司在大数据领域积累的核心关键技术,包括在海量数据采集、存储、清洗、分析挖掘、可视化、智能应用、安全与隐私保护等领域。 SDK 简介 SensorsAnalytics SDK 是国内第一家开源商用版用户行为采集 SDK,目前支持...
  • 埋点,是互联网获取数据的基础;数据采集系统,则是提升埋点效率、保障埋点规范与数据质量的利器。”埋点,在互联网里,可以说是再常见不过的技术了。大到BAT,小到创业公司,如果没有埋点,那...
  • 今天收到一封售前的邮件,关于H省网运营商要做手机APP数据分析的需求。客户对要分析的数据内容没有很清楚的想法,只给了百度移动统计的页面。这个需求本身不复杂,只是涉及的厂商较多:APP由我方提供,但其中有部分...
  • 前段时间刚做完公司无埋点数据采集项目,跟大家分享一下。 以下只有部分核心代码,完整源码及接入流程请移步 github:https://github.com/harvie1208/TracePoint 项目背景 当前手动代码埋点的方式,效率低、成本高、...
  • 一篇讲清:数据采集埋点

    万次阅读 2018-06-23 09:39:39
    在这篇文章里面,我们会对数据采集的一些基本概念进行阐述,然后,会针对目前市面上新增的一些前端埋点技术,如可视化埋点与“无埋点”的技术细节做一个具体的介绍,并且阐述我们自己对于这些技术的理解和认识。...
  • 数据采集埋点

    千次阅读 2019-04-12 16:14:35
    在这篇文章里面,我们会对数据采集的一些基本概念进行阐述,然后,会针对目前市面上新增的一些前端埋点技术,如可视化埋点与“无埋点”的技术细节做一个具体的介绍,并且阐述我们自己对于这些技术的理解和认识。...
  • 在来之前,昨天晚上我在跟我们组同事讨论的时候,我说我发给他们简要写的我是GrowingIO的大数据工程师,我们组的几个工程师都非常的不屑,说现在大数据已经烂大街了,所以他们对外自称数据工程师,不叫大数据工程师...
  • 前端埋点数据采集、后端埋点数据采集 前端采集事件类型:用户事件、页面事件、点击事件 数据上传至日志服务采集服务 前、后端数据以类json的格式,实时异步的被日志采集服务采集解析 用户事件:user {data:[{ ...
  • cookie Nginx $http_cookie 网站标识 javascript 自定义对象 状态码 web server Nginx $status 发送内容量 web server Nginx $body_bytes_sent 1.2 确定埋点代码 埋点,是网站分析的一种常用的数据采集方法。...
  • 而无埋点的实现方案也有多种,我们今天讨论的问题是数据采集的一种方案,是无需开发人员重复进行采集事件的代码埋点就能达到采集客户端所需数据的解决方案,也就是无埋点数据采集方案。 介绍无埋点数据采集方案之前...
  • 数据埋点是一种常用的数据采集方法。经过不断演化发展,由此所演变出的数据采集方法,已出现很多类型,并各具特点。经过不断演化发展,由此所演变出的数据采集方法,已出现很多类型,并各具特点。 数据埋点的原理是...
  • 最近在做无埋点数据采集SDK的研发工作,从立项到开发出一个稳定产品总共给的时间是2周,真是时间紧,任务重。。。 由于之前没怎么接触过插件化开发相关的知识,所以刚开始还真是萌萌哒 ,但是任务接了就得把他做完...
  • 数据采集埋点、无埋点

    万次阅读 2018-05-16 21:18:20
    在数据分析的道路上,数据采集是重中之重。数据采集的质量直接决定了你的分析是否准确。而随着企业对数据的要求越来越高,埋点技术也被推到了“风口浪尖”。今天就来聊聊在数据采集的道路上经常会遇到各的问题。比较...
  • 数据采集之js埋点

    2019-05-23 11:03:00
    web点数据采集后台配置nginx:https://blog.csdn.net/weixin_37490221/article/details/80894827 下载数据源:wget -O lua-nginx-module-0.10.0.tar.gz ...
  • 本文作者:随风丶逆风本文链接:https://juejin.cn/post/6938075086737899534什么是埋点埋点,它的学名是事件追踪(Event Tracking),主要是...
  • Web网页实现数据采集之js埋点代码

    千次阅读 2020-06-04 21:58:54
    进行web网站流量数据统计埋点分析,可以帮助网站管理员、运营人员、推广人员等实时获取网站流量信息,例如网页微信复制统计V统计分析并从流量来源、网站内容、网站访客特性等多方面提供网站分析的数据依据。...
  • 通过埋点搜集日志数据的简单架构

    千次阅读 2019-11-28 15:03:44
    数据埋点 后台数据库和日志文件一般只能够满足常规的统计分析,对于具体的产品和项目来说,一般还要根据项目的目标和分析需求进行针对性地“数据埋点”工作。 所谓埋点,就是在额外的正常功能逻辑上添加针对性的统计...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,426
精华内容 2,970
关键字:

埋点数据采集