Serverless 實戰:3 分鐘實現文本敏感詞過濾

敏感詞過濾是隨着互聯網社區發展一塊兒發展起來的一種阻止網絡犯罪和網絡暴力的技術手段,經過對可能存在犯罪或網絡暴力可能的關鍵詞進行有針對性的篩查和屏蔽,不少時候咱們可以防患於未然,把後果嚴重的犯罪行爲扼殺於萌芽之中。html

隨着各類社交平臺等的日益火爆,敏感詞過濾逐漸成了很是重要的也是值得重視的功能。那麼在 Serverless 架構下,經過Python 語言,敏感詞過濾又有那些新的實現呢?咱們可否是用最簡單的方法,實現一個敏感詞過濾的API呢?python

瞭解敏感過濾的幾種方法

Replace方法

若是說敏感詞過濾,其實不如說是文本的替換,以Python爲例,說到詞彙替換,不得不想到replace,咱們能夠準備一個敏感詞庫,而後經過replace進行敏感詞替換:git

def worldFilter(keywords, text):
    for eve in keywords:
        text = text.replace(eve, "***")
    return text
keywords = ("關鍵詞1", "關鍵詞2", "關鍵詞3")
content = "這是一個關鍵詞替換的例子,這裏涉及到了關鍵詞1還有關鍵詞2,最後還會有關鍵詞3。"
print(worldFilter(keywords, content))

可是動動腦你們就會發現,這種作法在文本和敏感詞庫很是龐大的前提下,會有很嚴重的性能問題。例如我將代碼進行修改,進行基本的性能測試:github

import time

def worldFilter(keywords, text):
    for eve in keywords:
        text = text.replace(eve, "***")
    return text
keywords =[ "關鍵詞" + str(i) for i in range(0,10000)]
content = "這是一個關鍵詞替換的例子,這裏涉及到了關鍵詞1還有關鍵詞2,最後還會有關鍵詞3。" * 1000
startTime = time.time()
worldFilter(keywords, content)
print(time.time()-startTime)

此時的輸出結果是:0.12426114082336426,能夠看到性能很是差。算法

正則表達方法

與其用replace,還不如經過正則表達re.sub來的更加快速。express

import time
import re
def worldFilter(keywords, text):
     return re.sub("|".join(keywords), "***", text)
keywords =[ "關鍵詞" + str(i) for i in range(0,10000)]
content = "這是一個關鍵詞替換的例子,這裏涉及到了關鍵詞1還有關鍵詞2,最後還會有關鍵詞3。" * 1000
startTime = time.time()
worldFilter(keywords, content)
print(time.time()-startTime)

咱們一樣增長性能測試,按照上面的方法進行改造測試,輸出結果是0.24773502349853516。經過這樣的例子,咱們能夠發現,其性能磣韓劇並不大,可是實際上隨着文本量增長,正則表達這種作法在性能層面會變高不少。json

DFA過濾敏感詞

這種方法相對來講效率會更高一些。例如,咱們認爲壞人,壞孩子,壞蛋是敏感詞,則他們的樹關係能夠表達:api

用DFA字典來表示:瀏覽器

{
    '壞': {
        '蛋': {
            '\x00': 0
        }, 
        '人': {
            '\x00': 0
        }, 
        '孩': {
            '子': {
                '\x00': 0
            }
        }
    }
}

使用這種樹表示問題最大的好處就是能夠下降檢索次數,提升檢索效率,基本代碼實現:網絡

import time

class DFAFilter(object):
    def __init__(self):
        self.keyword_chains = {}  # 關鍵詞鏈表
        self.delimit = '\x00'  # 限定

    def parse(self, path):
        with open(path, encoding='utf-8') as f:
            for keyword in f:
                chars = str(keyword).strip().lower()  # 關鍵詞英文變爲小寫
                if not chars:  # 若是關鍵詞爲空直接返回
                    return
                level = self.keyword_chains
                for i in range(len(chars)):
                    if chars[i] in level:
                        level = level[chars[i]]
                    else:
                        if not isinstance(level, dict):
                            break
                        for j in range(i, len(chars)):
                            level[chars[j]] = {}
                            last_level, last_char = level, chars[j]
                            level = level[chars[j]]
                        last_level[last_char] = {self.delimit: 0}
                        break
                if i == len(chars) - 1:
                    level[self.delimit] = 0

    def filter(self, message, repl="*"):
        message = message.lower()
        ret = []
        start = 0
        while start < len(message):
            level = self.keyword_chains
            step_ins = 0
            for char in message[start:]:
                if char in level:
                    step_ins += 1
                    if self.delimit not in level[char]:
                        level = level[char]
                    else:
                        ret.append(repl * step_ins)
                        start += step_ins - 1
                        break
                else:
                    ret.append(message[start])
                    break
            else:
                ret.append(message[start])
            start += 1

        return ''.join(ret)



gfw = DFAFilter()
gfw.parse( "./sensitive_words")
content = "這是一個關鍵詞替換的例子,這裏涉及到了關鍵詞1還有關鍵詞2,最後還會有關鍵詞3。" * 1000
startTime = time.time()
result = gfw.filter(content)
print(time.time()-startTime)

這裏咱們的字典庫是:

with open("./sensitive_words", 'w') as f:
    f.write("\n".join( [ "關鍵詞" + str(i) for i in range(0,10000)]))

執行結果:

0.06450581550598145

能夠看到性能進一步提高。

AC自動機過濾敏感詞算法

接下來,咱們來看一下 AC自動機過濾敏感詞算法:

AC自動機:一個常見的例子就是給出n個單詞,再給出一段包含m個字符的文章,讓你找出有多少個單詞在文章裏出現過。

簡單地講,AC自動機就是字典樹+kmp算法+失配指針

代碼實現:

import time
class Node(object):
    def __init__(self):
        self.next = {}
        self.fail = None
        self.isWord = False
        self.word = ""


class AcAutomation(object):

    def __init__(self):
        self.root = Node()

    # 查找敏感詞函數
    def search(self, content):
        p = self.root
        result = []
        currentposition = 0

        while currentposition < len(content):
            word = content[currentposition]
            while word in p.next == False and p != self.root:
                p = p.fail

            if word in p.next:
                p = p.next[word]
            else:
                p = self.root

            if p.isWord:
                result.append(p.word)
                p = self.root
            currentposition += 1
        return result

    # 加載敏感詞庫函數
    def parse(self, path):
        with open(path, encoding='utf-8') as f:
            for keyword in f:
                temp_root = self.root
                for char in str(keyword).strip():
                    if char not in temp_root.next:
                        temp_root.next[char] = Node()
                    temp_root = temp_root.next[char]
                temp_root.isWord = True
                temp_root.word = str(keyword).strip()

    # 敏感詞替換函數
    def wordsFilter(self, text):
        """
        :param ah: AC自動機
        :param text: 文本
        :return: 過濾敏感詞以後的文本
        """
        result = list(set(self.search(text)))
        for x in result:
            m = text.replace(x, '*' * len(x))
            text = m
        return text


acAutomation = AcAutomation()
acAutomation.parse('./sensitive_words')
startTime = time.time()
print(acAutomation.wordsFilter("這是一個關鍵詞替換的例子,這裏涉及到了關鍵詞1還有關鍵詞2,最後還會有關鍵詞3。"*1000))
print(time.time()-startTime)

詞庫一樣是:

with open("./sensitive_words", 'w') as f:
    f.write("\n".join( [ "關鍵詞" + str(i) for i in range(0,10000)]))

使用上面的方法,測試結果爲0.017391204833984375

敏感詞過濾方法小結

能夠看到這個全部算法中,在上述的基本算法中DFA過濾敏感詞性能最高,可是實際上,對於後二者算法,並無誰必定更好,可能某些時候,AC自動機過濾敏感詞算法會獲得更高的性能,因此在生產生活中,推薦時候用二者,能夠根據本身的具體業務須要來作。

實現敏感詞過濾API

將代碼部署到Serverless架構上,能夠選擇API網關與函數計算進行結合,以AC自動機過濾敏感詞算法爲例:咱們只須要增長是幾行代碼就好,完整代碼以下:

# -*- coding:utf-8 -*-

import json, uuid


class Node(object):
    def __init__(self):
        self.next = {}
        self.fail = None
        self.isWord = False
        self.word = ""


class AcAutomation(object):

    def __init__(self):
        self.root = Node()

    # 查找敏感詞函數
    def search(self, content):
        p = self.root
        result = []
        currentposition = 0

        while currentposition < len(content):
            word = content[currentposition]
            while word in p.next == False and p != self.root:
                p = p.fail

            if word in p.next:
                p = p.next[word]
            else:
                p = self.root

            if p.isWord:
                result.append(p.word)
                p = self.root
            currentposition += 1
        return result

    # 加載敏感詞庫函數
    def parse(self, path):
        with open(path, encoding='utf-8') as f:
            for keyword in f:
                temp_root = self.root
                for char in str(keyword).strip():
                    if char not in temp_root.next:
                        temp_root.next[char] = Node()
                    temp_root = temp_root.next[char]
                temp_root.isWord = True
                temp_root.word = str(keyword).strip()

    # 敏感詞替換函數
    def wordsFilter(self, text):
        """
        :param ah: AC自動機
        :param text: 文本
        :return: 過濾敏感詞以後的文本
        """
        result = list(set(self.search(text)))
        for x in result:
            m = text.replace(x, '*' * len(x))
            text = m
        return text


def response(msg, error=False):
    return_data = {
        "uuid": str(uuid.uuid1()),
        "error": error,
        "message": msg
    }
    print(return_data)
    return return_data


acAutomation = AcAutomation()
path = './sensitive_words'
acAutomation.parse(path)


def main_handler(event, context):
    try:
        sourceContent = json.loads(event["body"])["content"]
        return response({
            "sourceContent": sourceContent,
            "filtedContent": acAutomation.wordsFilter(sourceContent)
        })
    except Exception as e:
        return response(str(e), True)

最後,爲了方便本地測試,咱們能夠增長:

def test():
    event = {
        "requestContext": {
            "serviceId": "service-f94sy04v",
            "path": "/test/{path}",
            "httpMethod": "POST",
            "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
            "identity": {
                "secretId": "abdcdxxxxxxxsdfs"
            },
            "sourceIp": "14.17.22.34",
            "stage": "release"
        },
        "headers": {
            "Accept-Language": "en-US,en,cn",
            "Accept": "text/html,application/xml,application/json",
            "Host": "service-3ei3tii4-251000691.ap-guangzhou.apigateway.myqloud.com",
            "User-Agent": "User Agent String"
        },
        "body": "{\"content\":\"這是一個測試的文本,我也就呵呵了\"}",
        "pathParameters": {
            "path": "value"
        },
        "queryStringParameters": {
            "foo": "bar"
        },
        "headerParameters": {
            "Refer": "10.0.2.14"
        },
        "stageVariables": {
            "stage": "release"
        },
        "path": "/test/value",
        "queryString": {
            "foo": "bar",
            "bob": "alice"
        },
        "httpMethod": "POST"
    }
    print(main_handler(event, None))


if __name__ == "__main__":
    test()

完成以後,咱們就能夠測試運行一下,例如個人字典是:

呵呵
測試

執行以後結果:

{'uuid': '9961ae2a-5cfc-11ea-a7c2-acde48001122', 'error': False, 'message': {'sourceContent': '這是一個測試的文本,我也就呵呵了', 'filtedContent': '這是一個**的文本,我也就**了'}}

接下來,咱們將代碼部署到雲端,新建serverless.yaml:

sensitive_word_filtering:
  component: "@serverless/tencent-scf"
  inputs:
    name: sensitive_word_filtering
    codeUri: ./
    exclude:
      - .gitignore
      - .git/**
      - .serverless
      - .env
    handler: index.main_handler
    runtime: Python3.6
    region: ap-beijing
    description: 敏感詞過濾
    memorySize: 64
    timeout: 2
    events:
      - apigw:
          name: serverless
          parameters:
            environment: release
            endpoints:
              - path: /sensitive_word_filtering
                description: 敏感詞過濾
                method: POST
                enableCORS: true
                param:
                  - name: content
                    position: BODY
                    required: 'FALSE'
                    type: string
                    desc: 待過濾的句子

而後經過sls --debug進行部署,部署結果:

最後,經過PostMan進行測試:

總結

敏感詞過濾是目前很是常見的需求/技術,經過敏感詞過濾,咱們能夠在必定程度上下降惡意言語或者違規言論的出現,在上述實踐過程,有如下兩點內容:

  • 對於敏感詞庫額得到問題:Github上有不少,能夠自行搜索下載,由於敏感詞詞庫裏面有不少敏感詞,因此我也不能直接放在這個上面供你們使用,因此還須要你們自行在Github上搜索使用;
  • 這個API使用場景的問題:徹底能夠放在咱們的社區跟帖系統/留言評論系統/博客發佈系統中,防止出現敏感詞彙,能夠下降沒必要要的麻煩出現。

Serverless Framework 30 天試用計劃

咱們誠邀您來體驗最便捷的 Serverless 開發和部署方式。在試用期內,相關聯的產品及服務均提供免費資源和專業的技術支持,幫助您的業務快速、便捷地實現 Serverless!

詳情可查閱:Serverless Framework 試用計劃

One More Thing

3 秒你能作什麼?喝一口水,看一封郵件,仍是 —— 部署一個完整的 Serverless 應用?

複製連接至 PC 瀏覽器訪問:https://serverless.cloud.tencent.com/deploy/express

3 秒極速部署,當即體驗史上最快的 Serverless HTTP 實戰開發!

傳送門:

歡迎訪問:Serverless 中文網,您能夠在 最佳實踐 裏體驗更多關於 Serverless 應用的開發!


推薦閱讀:《Serverless 架構:從原理、設計到項目實戰》

相關文章
相關標籤/搜索