事实:网站跳出率高怎么办,关键词锚文本自动插入

优采云 发布时间: 2022-11-30 05:49

  事实:网站跳出率高怎么办,关键词锚文本自动插入

  网站跳出率过高怎么办?网站跳出是用户进入我们的网站,只浏览了一个页面就离开的情况。跳出率是跳出人数占所有访问的百分比。良好的页面布局和与内容相关的锚文本吸引用户点击是降低跳出率的技巧之一。

  网站的跳出率可以从侧面反映我们的页面对用户的吸引力。页面布局和页面内容吸引用户点击下一步关键词。当然,网站的跳出率并不是一个绝对的指标。用户一般都是搜索解决方案,需求解决后就会跳出,所以跳出率和停留时间要结合分析。

  不同类型网站的跳出率是不同的。比如小说网站的跳出率就远低于问答网站,所以我们应该理性分析网站的跳出率。当然,降低跳出率是提高用户转化率的关键。

  页面优化

  

" />

  根据一些人的说法,页面应该有 10% 关键词 密度,但不同的 SEO 专家有很多意见。关键词密度应该是5%,别人说应该是20%。总而言之,10% 是最终数字,即平均值。

  使用 关键词 锚文本

  关键词锚文本吸引用户点击,网页设计师可以通过找出哪些短语最常被正在寻找有关该主题的信息的人使用,以及哪些关键词和问题将被定位来做到这一点,其他 关键词 和他们用来实现某些操作的查询。

  如果网页设计师对 关键词 的可能性进行了一些分析,那就太好了,即使客户已经决定了 关键词 的潜在用途。还有其他的关键词和短语,客户可能不会选择,但在搜索频率或竞争方面更好。如果客户在选择 关键词 时没有进行任何分析,那么展示客户对竞争阶段的比较研究和他选择的 关键词 的相似短语将是有益的。

  

" />

  最小化页面加载时间

  页面加载时间对网站用户来说非常重要。此外,最近几个月,搜索引擎开始强调加载时间会显着影响搜索结果。最小化页面加载时间是一个可能值得单独写一篇文章来讨论的主题,但我们只关注几个基本点。图片、音频和视频、数据库查询等对象会增加页面加载时间。另一方面,我们也需要关注视频保护和管理我们作为创作者的权利。

  总的来说,近年来大多数用户的互联网连接速度都有显着提高,因此页面过大和页面加载缓慢对大多数用户来说不再是烦恼。但是,优化您的文件并使用 SEO 工具来确定还可以优化的内容仍然很方便。

  解决方案:51*敏*感*词* Android自动埋点实践

  背景

  随着公司业务的发展,对业务团队的敏捷性和创新性提出了更高的要求,而大数据的手段可以在一定程度上帮助我们实现这一愿景,良好的数据分析也可以帮助我们进行越来越好的决策。对于数据本身,其处理流程主要可以概括为:

  数据采集​​数据上报数据存储数据分析数据展示

  所谓数据采集,就是对特定的用户行为或事件进行捕捉和处理。这一步无疑是非常重要的,因为数据采集的准确性和多样性也会直接影响到后面的步骤。本文也主要讨论了几种数据采集方式,而我们常说的“埋点”是数据采集领域的一个名词,数据采集的方法也可以说是埋点的几种方式。

  现状、痛点

  目前公司主要采用代码嵌入的方式进行数据采集。所谓代码嵌入是指在事件发生时通过预先编写的代码发送数据。

  基于预编码的埋码优点是:控制精确,采集灵活性强,可以自由选择何时发送什么数据;但是缺点也很明显,开发测试成本高,而且对于客户端来说修改线上埋点需要等release。

  在日常的开发过程中,同事经常会报错,漏埋点。根源在于代码埋点本身的特性。这种情况促使我们尝试其他埋葬方法。

  行业情况

  第一阶段:无法追踪的埋点

  分析公司常用的一些数据指标,我们发现,对于大部分指标,我们只需要有一些发送时机和相对固定的埋点,比如页面曝光事件,控件点击事件等,而这部分埋点,恰恰可以更方便地使用自动埋点(相对于代码埋点等人工埋点,无痕埋点和视觉埋点都可以称为自动埋点)进行采集。

  与可视化埋点相比,无痕埋点前期不需要可视化工具采集埋点,SDK开发投入也相对较小。因此,我们进行了从人工埋点到无痕埋点的第一次迭代。

  无痕埋点技术的实现

  无痕埋点需要自动采集数据,所以对于页面、控件等元素,需要生成它们的ID,并且ID要尽可能“唯一”和“稳定”。“唯一性”很好理解,因为对于任何一个元素,它的ID都应该不同于其他所有元素,这样我们就可以根据ID来唯一标识我们想要的元素,并且采集到的数据是准确的,不重复的。而“稳定”是指元素的ID尽量不要因为版本的变化而改变,这样后期业务意义相关的操作会更加方便。

  页面 ID 规则

  页面的 ID 更容易定义。提到上面提到的“唯一性”和“稳定性”,我们很容易想到用页面所在类的类名作为ID。类名用作 ID。首先,它比较独特。除了页面复用,没有其他页面有相同的类名,可以通过页面标题名的方式避免页面复用的情况;其次,它相对稳定。Only ID只有在修改页面类名时才会发生变化,而在我们日常的开发过程中,除了一些大的页面改版外,不会轻易修改类名。在Android中,有两种类型的页面,Activity和Fragment,Fragment可以嵌入到不同的Activity中,所以两者的ID定义规则有些不同:

  页面PV、UV

  有了页面唯一ID的生成规则,我们只需要在页面曝光的时候生成这个ID,然后上传即可实现页面的PV和UV指标。至于页面暴露的时机,在Android开发中很容易找到,因为Activity和Fragment都有标准的生命周期。对于业务中PV和UV的定义,我们可以使用Activity的onResume()方法、Fragment的onResume()方法、setUserVisibleHint(boolean isVisibleToUser)、onHiddenChanged(boolean hidden)方法作为曝光机会。以上方法回调时,SDK调用埋点方法,生成ID,上传埋点。

  控件 ID 规则

  与页面相比,控件的ID定义规则更加复杂。一开始我们会想到使用R.id。Android aapt在编译时会为每一个用xml编写的控件生成一个唯一的ID,但是从aapt的生成规则来看,这个ID并不是固定的。在资源文件发生变化时,id也可能发生变化,即同一个控件在不同版本中的ID可能不同。根据ID需要具备的“唯一性”和“稳定性”,这个ID具有“唯一性”,但是“稳定性”很差,所以这个方案不可行。

  然后我们想到,每个界面的所有控件都可以根据它们的父子关系绘制出页面的视图树。从控件本身开始,根据控件的类名及其层次结构的位置等特征信息,逐级向上遍历,直到找到根节点,这样就可以得到一个控件的控制路径在视图树中;反之,根据控件路径,我们可以在视图树中唯一确定一个控件。下图是一个简单的ViewTree模型:

  根据上述控件路径生成规则,对于Button,其路径为:FrameLayout[0]/LinearLayout[1]/Button[0]。在一个页面中,这个路径可以帮助我们唯一定位到这个 Button ,但是对于不同的页面,不同的控件还是有相同的路径,所以控件ID的生成规则应该是:“页面ID:控件路径”。

  上面页面ID生成规则中提到,对于Android来说,有两种类型的页面,Activity和Fragment,因为一个Activity可以收录

不同的Fragment,所以如果控件存在于Fragment中,页面ID需要是它的页面ID所在的Fragment的,如果不在Fragment中,就收录

Activity的页面ID,那么如何从控件本身的实例中获取到所在的Activity或者Fragment。

  对于Activity来说就比较简单了,我们可以通过下面的代码来实现:

  对于Fragment来说,就比较麻烦了。我们只能预先将Fragment对应的页面ID绑定到控件本身,即通过打标签的方式,在Fragment的OnViewCreated方法中,获取Fragment容器中的根View,并用Fragment页面ID进行标记,以及然后遍历View标记它的所有子控件。核心代码如下:

  所以我们在获取View的实例时,首先检查是否可以获取到tag对应的页面ID。如果获取不到,我们可以找到它所属的Activity,然后获取页面ID,然后根据自己的控件路径,去拼凑控件的ID,核心代码如下:

  控件ID优化

  

" />

  根据我们上面的控件ID定义,当页面元素不发生变化时基本可以保证“稳定性”和“唯一性”,但是当页面元素发送动态变化,或者不同版本之间修改UI时,我们的控件ID会变得不稳定,比如下面这样:

  插入一个FrameLayout后,我们Button的控件路径就变成了FrameLayout[0]/LinearLayout[2]/Button[0]。和之前的ID相比,变了,变得不那么“稳定”了。,所以我们做了以下优化:

  将兄弟节点中的位置更改为同类控件的位置。优化后的控制路径为:FrameLayout[0]/LinearLayout[1]/Button[0]。即使插入了FrameLayout之后,路径也保持不变,比之前更加稳定。但是如果插入一个LinearLayout,或者重构整个页面的UI,控制路径还是会发生变化。

  因为不同的系统版本或者手机厂商都会对页面的根View做一定的处理,所以我们需要屏蔽这种情况。对于我们来说,我们只关心自己自定义的布局部分,也就是通过setContentView传入的Layout。我们可以通过判断控件ID是否等于android.R.id.content来获取我们自定义布局的根View,并将其作为我们控件路径的起点。

  在Android中,除了R.id和控件路径外,还有一个常用的特性信息可以作为控件ID,即开发者在布局文件中写入关联控件的Resource ID . Resource ID是开发者定义的关联View的标识。理论上在一个页面中是唯一的(为什么是理论上的呢?因为还是有多个相同的Resource ID,比如动态添加多个layout,收录

相同的Resource ID,但是这种情况很少见),而Resource ID一般不会在页面重建过程中被修改,所以用Resource ID作为控件ID是很合适的。但并不是所有的控件都有Resource ID,我们可以先尝试获取这个ID,如果存在Resource ID,则使用Resource ID作为控件ID,如果 Resource ID 不存在,则降级并使用控制路径作为控制 ID。核心代码如下:

  单击并长按控件的指示器

  有了控件ID的生成规则,我们就可以很方便的对控件的点击和长按指标进行统计,因为在Android中,控件的点击和长按都有非常标准的回调函数,即onClick(View v)和onLongClick(View v) 方法。在回调函数中,调用SDK封装好的方法,传入被点击控件的View对象,通过View对象本身的特征信息获取控件的唯一ID,然后上传埋点,然后我们可以统计我们想要的相关控件的点击和长按指标。

  代码检测

  通过上面的描述,我们得到了页面和控件的ID的定义规则,也知道我们只需要在相应的回调函数中编写SDK代码,获取我们想要的对象,然后就可以计算了我们想要的索引。那么如何才能将获取对象的代码自动写到我们已有的项目中呢。

  在指定的切点处插入指定的代码。这个业务场景可能很多同学都非常熟悉。我们经常使用AOP来解决这类问题,将所有的代码插入逻辑集中在一个SDK中进行处理,可以最大限度的做到不侵入业务。

  Javasist

  Javassist 是一个基于字节码操作的 AOP 框架,它允许开发者自由地为一个编译好的类添加新的方法,或者修改现有的方法。但与其他类似的库不同,Javassist 不需要开发人员对字节码有深入的了解。同样,它也允许开发人员忽略修改类本身的细节和结构。一个修改方法体的简单例子如下:

  摇篮插件

  Javassist 需要对编译好的类进行操作。Android的打包过程可以从下图理解。我们可以使用Javassist在Java编译器编译项目代码并将.class文件转换成dex之前,进行我们需要的代码插入工作。

  了解过gradle plugin的同学可能知道,当Android Gradle Plugin版本为1.5.0以上时,我们可以使用官方最新的Transform API对class文件进行处理,然后再将.class打包编译成dex。具体的自定义插件过程不做详细描述,我们只需要定义自己的Transform,继承系统的Transform,重写transform方法即可。

  在transform方法的第二个参数中,我们可以得到项目中源码编译出的所有.class文件和所有依赖的jar包,我们将所有的.class文件一一遍历,解压所有的jar包,取进入jar包中的.class文件,实现所有文件的代码插入。核心代码如下:

  得到.class文件后,我们将按照上述Javassist工作流程进行代码插桩:

  先根据类名得到CtClass对象,然后根据我们要找的入口,找到页面的onResume()方法,控件的onClick(View view)方法,然后得到CtMethod object根据方法名和参数类型调用CtMethod对象body API的edit方法,在原方法body之前调用insertBefore,然后调用insertAfter,传入要插入的代码块调用CtClass的writeFile()方法保存编辑

  在遍历了项目中的所有源文件后,我们就完成了整个项目代码的插桩。在我们想要的入口点(页面曝光,控件点击等回调函数),我们已经成功插入对应的捕获页面,控件对象的代码,当页面曝光或者控件被点击时,对应的获取对象,生成唯一ID并上报对应的埋点事件,完成无缝埋点的*敏*感*词*。

  第二阶段:可视化管理后台

  完成stage 1的无痕埋点后,我们可以通过对接SDK轻松实现页面曝光、控制点击等指标的数据采集。但是从上面我们可以知道,我们定义的ID其实对于业务方(product、Operations、BI等非业务开发者)并不友好,他们无法根据ID中的类名,Resource ID等特征信息,所以我们需要一些工具帮助他们将埋点元素ID关联到具体的业务意义,甚至是跨平台的(Android和iOS的自动埋点ID不一致) ).

  从另一个角度来说,有了这样的可视化管理后台,我们也可以通过下发配置表的方式来采集

想要的埋点。这其实就是我们开头提到的可视化埋点。那么有了这样的管理背景和基于自动埋点的数据采集方式,我们就可以根据具体的业务场景,灵活选择埋点无痕(全量采集)还是可视化埋点(根据配置表定向采集)。

  一个简单的用户操作可视化管理后台时序图如下:

  从图中我们可以知道,可视化管理后台的核心内容是上传手机界面截图和控件相关信息,允许用户在后台绑定相关页面、控件和自定义业务ID,并在后台生成配置. 实际效果如下:

  在上图中的可视化管理平台中,主要有几大块的内容。最上面是当前接入管理后台的设备信息。下面是手机当前界面在管理平台上的映射,并标注了界面中可以嵌入的所有控件。已经绑定自定义业务ID的控件标为绿色,未绑定的控件标为红色,方便用户使用。选择要操作的控件。

  

" />

  要实现上图所示的效果,我们只需要遍历当前页面,上传所有可以埋点的控件信息即可。对于我们目前要实现的数据指标,我们只关心控件的点击和长按事件。也就是说,我们只需要找到当前页面中所有可以点击或者长按的控件即可。

  上报控制信息

  对于需要报告的控制,需要满足以下条件:

  可以点击或长按在当前界面可见

  至于控件是否可以点击或长按,我们无法通过系统API直接获取,但是从源码中可以看出,View内部还是有私有变量来存放点击或长按的*敏*感*词*器, API14之前的mOnClickListener对象和API14之后的mListenerInfo对象都可以用来判断当前View对象是否设置了点击*敏*感*词*功能。我们可以通过反射得到这些对象,进行判断。长按判断也是如此。核心代码如下:

  处理完可以点击或者长按的条件后,我们需要判断该控件在当前界面是否可见,因为我们需要选中屏幕截图上的所有控件。如果控件本身是不可见的,就会被圈起来,用户会比较迷茫。经过一番研究,我们发现满足以下条件,即表示该控件在屏幕上可见:

  确定视图本身的可见性属性。

  View本身的可见性属性比较容易判断,我们只需要判断View.isShown()和View.getVisibility() == View.VISIBLE

  判断View的位置是否在当前屏幕内。当一个Activity加载多个Fragment时,可能会出现控件back body的visibility属性符合标准,但实际上不在屏幕内的情况。

  这种情况下,我们可以根据View.getLocationOnScreen(int[] outLocation)判断outLocation[0]是否大于等于0且小于等于屏幕宽度来判断控件是否在当前屏幕中

  确定该控件是否被其他控件完全阻止。

  遍历与该控件关联的所有控件(同层控件、父控件、父控件的同层控件等),通过View.getGlobalVisibleRect(Rect viewRect)获取该控件对应的Rect信息,然后通过Rect.contains(Rect r) 判断两个控件对应的Rects是否被完全收录

  如果控件满足以上两个条件可点击或长按在当前界面可见,则其信息会上传到管理后台,用户可以编辑控件,绑定自定义业务ID,管理后台获取控件与自定义服务ID的关联关系后,即可生成配置表发送给APP。这样采集到的埋点会带有一个自定义的业务ID,用户在后续的数据使用过程中可以方便的查看对应的业务指标。

  可视化管理后台的核心逻辑是将上述客户端与管理后台建立连接,并上传相应的信息。其他配置的生成和分发都很好办,这里不再赘述。

  第 3 阶段:隐藏的 DSL

  我们在文章开头提到过,无论是无痕埋点还是可视化埋点,都是建立在自动采集埋点的基础上的。在这种采集方式中,我们无法通过埋点承载更多的信息。这也是我们面临的一个痛点。基于这样的需求,我们认为可以使用DSL来解决这个问题。

  什么是DSL

  DSL是Domain-specific language,翻译成domain-specific language,意思是在特定领域解决特定任务的语言。

  哪些场景需要使用DSL?

  上面提到的自动埋点以页面和控件为入口,hook页面曝光和控件点击事件,获取页面和控件的信息作为特征值写入埋点。在简单的场景下,这样的逻辑还是可以胜任的,但是在一些复杂的场景下,比如典型的banner轮播,资源位暴露等,无法根据控制信息区分出控制相同但实际内容不同的埋点。对于手动埋点,可以通过获取界面中的信息,然后传入埋点来区分,而自动埋点则无法关联这部分界面信息,

  如何实现DSL

  DSL 的构造实际上类似于编程语言。想想当我们重新实现一种编程语言时我们需要做什么;实现一门编程语言的过程可以简化为定义语法和语义,然后实现编译器或解释器的过程,而 DSL 的实现也和它很相似,我们也需要设计其语法和语义数字用户线。综上所述,实现DSL一共需要完成两个任务:

  设计语法和语义,定义 DSL 中的元素是什么样的,以及元素代表什么。实现一个解释器,解析DSL,最后通过反射(runtime)执行

  设计语法和语义

  这部分实际上是非常多样化的。我们可以根据业务需要不断迭代,但核心思想是定义一些特殊的字符串,并据此调用各自的API。一些简单的语法大致如下:

  利用 ”。” 标识对象调用,如“test.a”表示实例test中的a字段使用“.()”表示方法调用,如“test.test()”表示实例test中的test()方法instance test 调用使用“[]”表示数组或列表

  实施解释器

  说是解释器,其实只是SDK中预先写好的一段代码逻辑。业务开发人员通过预先约定好的语法和语义,在可视化平台上为某个控件编写代码,然后交付这部分代码。SDK根据规则解析这部分代码,然后通过反射(runtime)获取相应的控件。数据写入自动埋点。

  平台支持

  可视化平台可以在输入或后期编辑元素时,在事件发生时,额外输入要获取的数据的路径。这部分内容需要业务开发者根据SDK给出的规则进行录入。进入成功后,生成配置文件发送给App。当事件发生时,SDK获取对应事件携带的数据路径,根据DSL约定的规则解析路径并获取对应数据,存入埋点对应字段并上传。

  总结

  从最早的人工埋点,到后来的无痕埋点,再到可视化管理平台的搭建,再到DSL的实现,一步一步我们可以看到,虽然与人工埋点相比,自动埋点有很多优点,但也有不足之处也很明显。即使我们使用一些工具和技术不断优化和弥补它的不足,它仍然不能完全替代人工点埋。因此,结合业务本身的特点,选择最合适的埋点采集方式才是最正确的做法。在一些相对稳定、不经常变化的页面和控件中使用自动埋点,可以大大节省各个环节的时间;但是,如果页面和控件本身是频繁迭代的,

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线