Go+PHP实现敏感词检测

概述

广告,敏感词检测一直以来都是让人头疼的话题,仅仅通过添加敏感词列表是解决不了问题的。今天封禁了这个词,明天又会有新的违禁词冒出来,比起愚公无穷尽的子孙更甚。

敏感词匹配这种治标不治本的方法,在一定的语义下蛮有效的,但是这个场景对高并发,访问QPS高的服务来说,不是很合适。前段时间看到垃圾邮件检测用到的贝叶斯分类算法,这种“半学习”形式的方法的准确度依赖于先验概率的准确性,而公司长期以来整理到的违禁词列表就是一个很好的源,随着贝叶斯分类的数据越来越多,分类的准确性也会越来越高,后期仅仅需要对违禁词文件进行添加即可,方便又准确。

PHP做贝叶斯分类不能很好的利用内存,针对每一个请求都会创建一个进程,各个请求相互独立,所以每个请求都会重新来一遍贝叶斯分类数据集构建,这效率可想而知,因此不打算用PHP去实现。

go语言一直以来以快著称,就用它吧。那么问题又来了,怎么让go作为PHP的后端实现这个检测服务呢。进程间的数据通常来讲有这么几种方式:


实现

代码目录

golang 后端

package main

import (
    "src/github.com/ajph/nbclassifier-go"
    "log"
            "os"
    "bufio"
    "io"
    "net"
    "syscall"
        "fmt"
        "src/github.com/yanyiwu/gojieba"
    "strings"
)

const SPAM_CHECK_SOCKET_FILE = "/tmp/spamcheck.sock"

// 使用go 实现简单的贝叶斯分类
func getWords(filepath string)[]string {
    file, err := os.Open(filepath)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()
    reader := bufio.NewReader(file)
    ret := []string{}
    for {
        line, err := reader.ReadString('\n')
        if err != nil || io.EOF == err {
            if line == "" {
                break
            }
        }
        line = strings.Trim(line, "\n")
        fmt.Println("处理单词:" + line)
        ret = append(ret, line)
    }
    return ret
}

func learn(){
    m := nbclassifier.New()

    m.NewClass("normal")
    normalwords := getWords("normalwords.txt")
    //fmt.Println(normalwords)
    m.Learn("normal", normalwords...)
    //m.Learn("normal", "a", "need")

    m.NewClass("forbidden")
    forbiddenwords := getWords("forbiddenwords.txt")
    //fmt.Println(forbiddenwords)
    m.Learn("forbidden", forbiddenwords...)
    //m.Learn("forbidden", " design ", "banner", " picture", " logo ", "clip art", " ad ", "clipart", "hairstyles", " drawing", " rendering", " diagram ", " poster", "изображение")


    m.NewClass("terror")
    terrorwords := getWords("terrorwords.txt")
    //fmt.Println(terrorwords)
    m.Learn("terror", terrorwords...)
    //m.Learn("terror", "...", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "…", "image", "pinterest", ".c", "ltd.", "vector", "quote", "video", "search", "?", "click", "psd", "ai", "print", "file", "related", "download", "submit", "view", "buy", "how", "maker", "online", " on", "by")


    m.SaveToFile("materiel.json")

}

func reloadModel() *nbclassifier.Model{
    model, _ := nbclassifier.LoadFromFile("materiel.json")
    //fmt.Println(model.Classes[0].Items[0])
    //fmt.Println(model.Classes[1])
    //fmt.Println(model.Classes[2])
    return model
}

func match(model *nbclassifier.Model, content string) string {
    // 分词
    jieba := gojieba.NewJieba()
    defer jieba.Free()
    words := jieba.Cut(content, true)
    cls, unsure,_ := model.Classify(words...)
    fmt.Println("检测到分类为:" + cls.Id)

    result := "normal"
    if unsure == false {
        result = cls.Id
        fmt.Println(cls, unsure)
    }
    return result
}

func run() {
    socket, _ := net.Listen("unix", SPAM_CHECK_SOCKET_FILE)
    defer syscall.Unlink(SPAM_CHECK_SOCKET_FILE)
    learn()
    // 训练物料
    model := reloadModel()

    for {
        client, _ := socket.Accept()

        buf := make([]byte, 1024)
        datalength, _ := client.Read(buf)
        data := buf[:datalength]
        fmt.Println("client msg:" + string(data))

        checkret := match(model, string(data))
        fmt.Println("check result: " + checkret)
        response := []byte("")
        if len(checkret) > 0 {
            response  = []byte(checkret)
        }
        _,_ = client.Write(response)
    }
}

func main() {
    // 开启sock,检测服务
    run()
    //fmt.Println(reloadModel())
}

php 前端

<?php

$msg = "你说谎, 你放屁,你这个傻子";
$SOCKET_FILE = "/tmp/spamcheck.sock";
$socket = socket_create(AF_UNIX, SOCK_STREAM, 0);
socket_connect($socket, $SOCKET_FILE);
socket_send($socket, $msg, strlen($msg), 0);
$response = socket_read($socket, 1024);
socket_close($socket);

var_dump($response);

测试

开启服务
确认服务已开启
服务调用

源码

https://github.com/guoruibiao/spamcheck

总结整理

目前看起来,sock的形式还有蛮多局限性的。目前只是单机,后续可以考虑继续进行优化。先这么着吧,后面应该还要继续跟进...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容