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

luolinae86 · June 24, 2020 · Last by lazybios replied at August 08, 2020 · 3653 hits

分享一个最近写的 基于 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 自带敏感词库里面的内容,如不能满足业务需要的,请自己添加和丰富词库,词库的内容一定要精减和准确。

非常不错

有趣又有料👏 👏 学习了

上学都要摇号了?

Reply to pynix

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

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

Reply to lihuazhang

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

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

谢谢

Reply to 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 >
Reply to luolinae86

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

Reply to luolinae86

小学不是按地区划分吗?

Reply to pynix

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

Reply to 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('恒温')
=> ""

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

Reply to luolinae86
2.6.2 :004 > Sensitive.filter('up')
 => "up"

让 up 主们怎么办。。。

Reply to 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 部分应该还能再简化一下

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

Reply to sanvi

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

Reply to luolinae86

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

Reply to quakewang

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

Reply to lihuazhang

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

Reply to 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 是为啥。。。

Reply to 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方法?

You need to Sign in before reply, if you don't have an account, please Sign up first.