直奔主题,本文讲述的就是一种快速检测短文本重复率的方法,适用的场景类似内容发布,商品发布等,减少劣质的堆砌型文本,比如:“高压洗车水枪,一喷轻松洗车不等待,全铜4分6分高压水枪可调节喷枪接头套装浇花灌溉园,高压洗车水枪,一喷轻松洗车不等待”
核心难点
要解决这个问题的最大的难点是如何确定重复的关键词句,拿到后,就可以分别算出关键词句在总字符中的占比和出现次数,进而计算出重复率,所以我们先从这一步开始。
分析关键词句
我们以上面的例子为例,为了方便理解,这里我们先手动标识下重复的文案
“高压洗车水枪,一喷轻松洗车不等待,全铜4分6分高压水枪可调节喷枪接头套装浇花灌溉园,高压洗车水枪,一喷轻松洗车不等待”
重复的词句我通过相同的背景色进行了标识,我们可以看到重复的如下:
- “高压洗车水枪,一喷轻松洗车不等待” 出现了 2 次,这个是最明显的文字堆砌,我们希望最终分析出这个结果
- “水枪” 出现了 3 次
- “喷” 出现了3次
- “分” 出现了2次
- “枪” 出现了4次
如上,我们通过大脑回路就能够判断出来,这个文案不合格,显然有文字堆砌的嫌疑,那我们怎么通过代码快速识别出来呢?
通过如上手动分析过程我们发现有几个特点:
- 单词、字、标点符号等重复出现的概率比较大,并且不适合通过这种字符判断重复
- 长的重复词句会覆盖更短的重复词句,需要避免重复计算重复率,否则会增大重复率计算
▐ 去除特殊字符
综合如上特点,我确定的第一个思路就是去除特殊字符,毕竟在真实业务场景,大家不会写一堆标点符号,因为这个比文字堆砌更低级,这个比较简单,就像给字符串洗澡一样,一个正则命令就可以搞定
const demoText = '高压洗车水枪,一喷轻松洗车不等待,全铜4分6分高压水枪可调节喷枪接头套装浇花灌溉园,高压洗车水枪,一喷轻松洗车不等待'; const specialTextReg:RegExp = /[\s·!#¥(——):;“”‘、,|《。》?、【】[\]`~!@#$%^&*()_+<>?:"{},.\/;']/gim; const cleanText = demoText.replace(specialTextReg, '');
输出的结果如下,我们下文称这个字符串为“母字符串”:“高压洗车水枪一喷轻松洗车不等待全铜4分6分高压水枪可调节喷枪接头套装浇花灌溉园高压洗车水枪一喷轻松洗车不等待”
这样就比较简单了,方便做进一步的关键词句分析
▐ 找出关键词句
首先我们要将字符串拆成单字数组,这里记录了最原始的字符出现的顺序。['高', '压', '洗', '车', '水', '枪', '一', '喷', '轻', '松', '洗', '车', '不', '等', '待', '全', '铜', '4', '分', '6', '分', '高', '压', '水', '枪', '可', '调', '节', '喷', '枪', '接', '头', '套', '装', '浇', '花', '灌', '溉', '园', '高', '压', '洗', '车', '水', '枪', '一', '喷', '轻', '松', '洗', '车', '不', '等', '待']关键词句的出现有一个非常重要的特点,就是连续出现(好像是句废话),那如何分析连续性呢,这里我们可以将连续的字放在单独的数组中,以方便我们区分连续性,所以我们最终要得到一个二维数组,二维数组的生成我们遵循三个基本原则:
- 从来没有出现的字符放在第一个数组中(母数组),并按照出现的顺序排序
- 每一个字符和第一个字符进行比较,如果出现过则新增数组,并存储在新数组中对应的母数组位置
- 如果下一个字符也重复,则不用新增数组,只需要在原数组中新增字符,出现以下两种情况需要新开数组:重复的字符中断(就是出现了母数组没有的字符,这时候需要将字符 push 到母数组中)重复的字符在母数组中出现的序号小于等于上一个重复字符的序号
这里比较难以理解,通过图示帮大家理解,为了方便理解,我把一个连续的分析过程,强制分布讲解
- 上面的字符,首先分析到第 10 个字符
母数组 | 高 | 压 | 洗 | 车 | 水 | 枪 | 一 | 喷 | 轻 | 松 |
序列 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
➠ “高压洗车水枪一喷轻松洗车不等待全铜4分6分高压水枪可调节喷枪接头套装浇花灌溉园高压洗车水枪一喷轻松洗车不等待”
- 接下来分析到第12个字符(“洗车”),因为在母数组中出现过,所以需要新开数组存储,分析结果如下:
1 | 洗 | 车 | ||||||||
母数组 | 高 | 压 | 洗 | 车 | 水 | 枪 | 一 | 喷 | 轻 | 松 |
序列 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
➠ “高压洗车水枪一喷轻松洗车不等待全铜4分6分高压水枪可调节喷枪接头套装浇花灌溉园高压洗车水枪一喷轻松洗车不等待”
- 接下来分析到第20个字符,因为从 13 个开始,又出现了母数组中没有的字符,所以回到母数组中进行字符 push
1 | 洗 | 车 | ||||||||||||||||
母数组 | 高 | 压 | 洗 | 车 | 水 | 枪 | 一 | 喷 | 轻 | 松 | 不 | 等 | 待 | 全 | 铜 | 4 | 分 | 6 |
序列 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
➠ “高压洗车水枪一喷轻松洗车不等待全铜4分6分高压水枪可调节喷枪接头套装浇花灌溉园高压洗车水枪一喷轻松洗车不等待”
- 接下来第 21 个字符 “分”和母数组中第17 个字符重复,并且因为数组 1被终端 push 了,所以需要新开数组 2
2 |
分 |
|||||||||||||||||
1 |
洗 |
车 |
||||||||||||||||
母数组 |
高 |
压 |
洗 |
车 |
水 |
枪 |
一 |
喷 |
轻 |
松 |
不 |
等 |
待 |
全 |
铜 |
4 |
分 |
6 |
序列 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
➠ “高压洗车水枪一喷轻松洗车不等待全铜4分6分高压水枪可调节喷枪接头套装浇花灌溉园高压洗车水枪一喷轻松洗车不等待”
- 接下来我们分析到第 23 个字符(“高压”),由于高压在母数组中出现的序号分别是 1,2,第 4 步“分”在数组出现的序号是17,满足 <= 17,所以需要新开数组,结果如下:
3 | 高 | 压 | ||||||||||||||||
2 | 分 | |||||||||||||||||
1 | 洗 | 车 | ||||||||||||||||
母数组 | 高 | 压 | 洗 | 车 | 水 | 枪 | 一 | 喷 | 轻 | 松 | 不 | 等 | 待 | 全 | 铜 | 4 | 分 | 6 |
序列 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
➠ “高压洗车水枪一喷轻松洗车不等待全铜4分6分高压水枪可调节喷枪接头套装浇花灌溉园高压洗车水枪一喷轻松洗车不等待”
- 接下来分析到第25个字符(“水枪”),同样也是在我们的母数组中出现过,出现的序号分别是5,6,不满足 <= 2,所以可以继续在数组三中 push,结果如下:
3 |
高 |
压 |
水 |
枪 |
||||||||||||||
2 |
分 |
|||||||||||||||||
1 |
洗 |
车 |
||||||||||||||||
母数组 |
高 |
压 |
洗 |
车 |
水 |
枪 |
一 |
喷 |
轻 |
松 |
不 |
等 |
待 |
全 |
铜 |
4 |
分 |
6 |
序列 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
➠ “高压洗车水枪一喷轻松洗车不等待全铜4分6分高压水枪可调节喷枪接头套装浇花灌溉园高压洗车水枪一喷轻松洗车不等待”
- 到第 6 步,基本上规则讲清楚了,一次类推,可以得出如下结果:
7 |
洗 |
车 |
不 |
等 |
待 |
|||||||||||||||||||||||||
6 |
高 |
压 |
洗 |
车 |
水 |
枪 |
一 |
喷 |
轻 |
松 |
||||||||||||||||||||
5 |
枪 |
|||||||||||||||||||||||||||||
4 |
喷 |
|||||||||||||||||||||||||||||
3 |
高 |
压 |
水 |
枪 |
||||||||||||||||||||||||||
2 |
分 |
|||||||||||||||||||||||||||||
1 |
洗 |
车 |
||||||||||||||||||||||||||||
母数组 |
高 |
压 |
洗 |
车 |
水 |
枪 |
一 |
喷 |
轻 |
松 |
不 |
等 |
待 |
全 |
铜 |
4 |
分 |
6 |
可 |
调 |
节 |
接 |
头 |
套 |
装 |
浇 |
花 |
灌 |
溉 |
园 |
序列 |
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 |
通过如上表我们肉眼可以轻松判断出来词句的出现次数以及每一个词句在母数组中的长度占比,对于程序来说,只需要根据两个原则分析出数组 1-7 中重复出现的词句:
- 在同一个数组中连续的字符确定为重复词句
- 当遇到空或者切换数组,则连续中断
通过如上两个步骤,确定结果如下:
关键词句 | 重复出现次数 | 重复率 |
洗车 | 4 | 14.81% |
分 | 2 | 3.70% |
高压 | 3 | 11.11% |
水枪 | 3 | 11.11% |
喷 | 3 | 5.56% |
枪 | 4 | 7.40% |
高压洗车水枪一喷轻松 | 2 | 37.03% |
不等待 | 2 | 11.11% |
这里关键词句重复率 = 关键词句的长度/母字符串长度 x 出现的次数 x 100%
得出重复率
因为我们最终需要得到一个重复率的总值,需要通过如上的值得出最终结果,如果只是单纯的相加,得到的结果是 96.27%, 这个明显不合适,但是重复的词句越多,重复率越大是一定的,从我们感官上去分析,我们感受到的是“高压洗车水枪一喷轻松” 带来的重复是我们无法接受的,所以我们需要通过加权,减少短字、词、句带来的重复率计算影响,这里我采用最简单的规则,就是根据重复词句的长度返回权重值如下:
字符长度 | 权重 |
1 | 0.1 |
2 | 0.4 |
3 | 0.5 |
4 | 0.5 |
>=5 | 1 |
所以如上表加上权重后的结果如下:
关键词句 | 重复出现次数 | 重复率 |
洗车 | 4 | 5.92% |
分 | 2 | 0.37% |
高压 | 3 | 4.44% |
水枪 | 3 | 4.44% |
喷 | 3 | 0.55% |
枪 | 4 | 0.74% |
高压洗车水枪一喷轻松 | 2 | 37.03% |
不等待 | 2 | 5.55% |
重复率 | 59.07% |
所以我们最终得出的重复率数据 59.07%,如果界定阈值:30%,则如上字符串判断为重复
特殊场景分析
▐ 关键词句分析问题
如上字符,最长的重复词句是:“高压洗车水枪一喷轻松洗车不等待”,但是通过如上分析方法,由于“洗车”重复出现了,所以强制切换了数组,所以得到的重复词句是:“高压洗车水枪一喷轻松”、“不等待” 两个,这个不合理,所以上面的第 7 步需要作如下调整:
➠ “高压洗车水枪一喷轻松洗车不等待全铜4分6分高压水枪可调节喷枪接头套装浇花灌溉园高压洗车水枪一喷轻松洗车不等待”
所以除了根据是否切换数组和是否有空来判断连续性还不够,需要增加一个下标,这里的下标是字符在母字符串出现的序号,通过下标是否连续进行关键词句的拼合
得出重复率词句如下:
关键词句 |
洗车 |
分高压水枪 |
喷枪 |
高压洗车水枪一喷轻松洗车不等待 |
这里我们发现除了第1、4 个词句,2、3 只出现了一次,所以这里要和母字符串进行一次对比,对于只出现一次的进行过滤,得出结果如下:
关键词句 | 重复出现次数 | 重复率 |
洗车 | 4 | 5.92% |
高压洗车水枪一喷轻松洗车不等待 | 2 | 51.72% |
重复率 | 57.64% |
▐ 关键词句被重复计算
但是我们同时发现,“洗车” 这个关键词在 “高压洗车水枪一喷轻松洗车不等待” 就出现过2次,两次重复就是 4 次,所以是被重复计算了,这里应该把这四次移除掉(其实这个过程可以提前做,大家可以考虑下)
关键词句 | 重复出现次数 | 重复率 |
洗车 | 0 | 0% |
高压洗车水枪一喷轻松洗车不等待 | 2 | 51.72% |
重复率 | 51.72% |
最终得到的结果已经和我们最初的预期一样了,同样按照 30% 的阈值判断,该字符串也是重复的
总结
不得不说,以上实现还有很多可以优化的地方,比如在做关键词句分析上,但是准确率是经过逛逛的内容简介测试的,大家有想法也可以留言指正。
团队介绍
我们是大淘宝技术内容前端团队,主要负责淘宝的内容业务(直播、图文、短视频)和内容中台建设,涉及淘宝直播、逛逛、亲拍、有好货等业务,并通过平台化的方式支持集团其他团队的内容业务,包括饿了么、盒马、优酷、闲鱼、飞猪等 24 个 BU、160 个业务场景。
内容化是一个较新的战场,整个前端团队在多媒体、机器学习、播放器、视频剪辑、LowCode 等技术领域都有比较多挖掘和技术应用,欢迎留言技术交流。