Gem 【Sensitive】基于 DFA 算法的 Ruby 敏感词过滤 Gem

luolinae86 · 2020年06月24日 · 最后由 lazybios 回复于 2020年08月08日 · 3653 次阅读

分享一个最近写的 基于 DFA 算法的 Ruby 敏感词过滤 Gem

项目地址

Sensitive 代码

背景

项目开发中,会涉及到敏感词的过滤,大家多是自己在实现自己的算法,读了 sanvi 的 敏感词过滤实现 文章,了解到了 DFA 算法,看了 huobazi 在 4 楼的回复,收获颇大,但最终让我写这个 Gem 的原因是 Rei 在 3 楼的一句留言 完善成 Gem 有前途(词库自己加)

经过几天的努力,基于 DFA 算法的 Ruby 敏感词过滤 Gem 它来了

使用方法

选择是否加载 Gem 自带敏感词库

本着主厨思想,Gem 内置了 1 万多条敏感词库。 我花了一整天时间,将词库的内容和格式进行了整理,整理完之后,视力下降了不少,自己慢慢也变成了合格的鉴黄师。

Gem 自带敏感词,涉及

  • 广告
  • 政治
  • 色情
  • 其它

你可以根据自己选择是否加载,加载方法:

Sensitive.load_default

加载自己的敏感词文件

你也可以加载自己的一个或多个敏感词文件,文件格式支持 txt,不同的敏感词条独立一行

Sensitive.load_file(file_path)

动态添加单个敏感词

Sensitive.add_word('赌博')

清空敏感词

Sensitive.empty!

敏感词过滤

# filter 方法传入需要检测的敏感字符串,如果字符串中有敏感词,则返回,如果没有,则返回空
Sensitive.filter('不要赌博') #=> '赌博'
irb(main):001:0> require 'sensitive'
=> true
irb(main):018:0> puts Sensitive.words
{}
irb(main):019:0> Sensitive.add_word('敏感词')
=> ["敏", "感", "词"]
irb(main):020:0> puts Sensitive.words
{"敏"=>{:is_end=>false, :value=>{"感"=>{:is_end=>false, :value=>{"词"=>{:is_end=>true, :value=>{}}}}}}}
irb(main):021:0> puts Sensitive.filter('检测敏感词的算法')
敏感词
irb(main):022:0> puts Sensitive.filter('色情信息')
irb(main):023:0> Sensitive.load_default
irb(main):024:0> puts Sensitive.filter('色情信息')
色情信息
irb(main):025:0> Sensitive.empty!
=> {}
irb(main):026:0> puts Sensitive.words
{}

使用建议

Gem 自带敏感词库里面的内容,如不能满足业务需要的,请自己添加和丰富词库,词库的内容一定要精减和准确。

有趣又有料👏 👏 学习了

上学都要摇号了?

pynix 回复

就是,所以能够摇中理想的学校,还是很开心的

试用了下,有点问题诶,比如敏感词是发票,然后发现了一张电影票也 hit 到了。。还有加油也会 hit 到。。

lihuazhang 回复

你好,感谢你的反馈,能不能说一下场景

  1. 是否加载了 默认敏感词库
  2. 请将执行的结果用 ruby irb 帖一下

谢谢

luolinae86 回复

是否加载了 默认敏感词库 加载了

2.6.2 :006 > Sensitive.load_default
 => #<File:/Users/lihuazhang/.rvm/gems/ruby-2.6.2@rails6/gems/sensitive-1.0.0/lib/../sensitives.txt>
2.6.2 :007 > Sensitive.filter('加油')
 => "油"
2.6.2 :008 >
luolinae86 回复

2.6.2 :018 > Sensitive.filter('恒温') => "温"

luolinae86 回复

小学不是按地区划分吗?

pynix 回复

工作原因,不能按时接送小孩子,所以得上离家近的私立学校,这就需要摇号了

lihuazhang 回复

@lihuazhang 非常感谢你提出的这一个问题,是算法逻辑上的一个缺陷,我已经修改,提交记录

请更新一下 Gem 最新版本 1.0.1

irb(main):002:0> Sensitive.load_default
=> #<File:/Users/luolin/.rvm/gems/ruby-2.6.4/gems/sensitive-1.0.1/lib/../sensitives.txt>
irb(main):003:0> Sensitive.filter('加油')
=> ""
irb(main):004:0> Sensitive.filter('恒温')
=> ""

也期待大家继续提出问题,或好的建议,意见,谢谢

luolinae86 回复
2.6.2 :004 > Sensitive.filter('up')
 => "up"

让 up 主们怎么办。。。

lihuazhang 回复

我这边本地演示 对 up过滤,不会命中关键词,请问你那边是否自己有添加过关键词呢?

irb(main):001:0> require 'sensitive'
=> true
irb(main):002:0> Sensitive.load_default
=> #<File:/Users/luolin/.rvm/gems/ruby-2.6.4/gems/sensitive-1.0.1/lib/../sensitives.txt>
irb(main):003:0> Sensitive.filter('up')
=> ""

这种需求下,你的 DFA 结构没有必要添加 is_end / value,直接用 hash 是否为空来判断就可以知道是否是最后一个,另外代码里面用了很多 clone,会很消耗内存,可以用 inject:

def add_word(word)
  word = word.strip.gsub(%r{[^\p{Han}+/ua-zA-Z0-9]}, '')
  word.chars.inject(self.words) do |words, char|
    if !words.key?char)
      words[char] = {}
    end
    words[char]
  end
end
def filter(word)
  sensitive_word = ''
  word = word.strip.gsub(%r{[^\p{Han}+/ua-zA-Z0-9]}, '')
  word.chars.each_with_index.inject(self.words) do |words, (char, index)|
    if words.key?(char)
      sensitive_word += char
      break if words[char].empty?
      # 如果被检测的词已是最后一个,但关键字还不是最后,则返为空
      return '' if index == word.size - 1
      words[char]
    else
      # 如果上一步在关键字中,这次又不在关键字中,需要重新初始化检测
      if !sensitive_word.empty?
        sensitive_word = ''
        words = self.words and redo
      else
        words
      end
    end
  end
  sensitive_word
end

以上代码是手写的,filter 部分应该还能再简化一下

😅 最近好像带了一股不好的风气,大家都在努力肉测自己写的程序

sanvi 回复

哈哈。。。刚用作者 gem 的时候,测试工程师的职业病发了。。。想怎么没单测么。。

luolinae86 回复

找到问题了。。是 rubychina 屏蔽了 up 这个。。。

quakewang 回复

@quakewang 非常感谢你的回复,inject方法的使用,提升了整个算法的性能和代码的质量,我已经将代码合入is_end的结构也是冗余的。

lihuazhang 回复

对的,肉测不是办法,gem 先出发来让大家体验,我把单元测试也补上 😀

luolinae86 回复
➜  ~ irb
2.6.2 :001 > require 'sensitive'
 => true
2.6.2 :002 > Sensitive.load_default
 => #<File:/Users/lihuazhang/.rvm/gems/ruby-2.6.2@rails6/gems/sensitive-1.0.1/lib/../sensitives.txt>
2.6.2 :003 > Sensitive.filter('up')
 => ""
2.6.2 :004 > Sensitive.filter('8')
 => "8"
2.6.2 :005 > Sensitive.filter('8')

8 是为啥。。。

lihuazhang 回复

默认敏感词库的问题,1 万多条,有些还是不够规范,我刚将 8 相关的再次整理了一下,更新一下包即可。

irb(main):001:0> require 'sensitive'
=> true
irb(main):002:0> Sensitive.load_default
=> #<File:/Users/luolin/.rvm/gems/ruby-2.6.4/gems/sensitive-1.0.2/lib/../sensitives.txt>
irb(main):003:0> Sensitive.filter('8')
=> ""

是不是应该有个remove_word方法?

需要 登录 后方可回复, 如果你还没有账号请 注册新账号