Django敏感詞檢測(cè)
作者:vk
鏈接:https://0vk.top/zh-hans/article/details/65
來(lái)源:愛(ài)尚購(gòu)?
點(diǎn)擊閱讀更多獲取極致閱讀體驗(yàn)

為了識(shí)別和過(guò)濾用戶提交的惡意內(nèi)容,每一條人工檢查的成本又太大,需要開(kāi)發(fā)一個(gè)自動(dòng)檢測(cè)敏感詞的程序
運(yùn)行效果如下:

?
整體設(shè)計(jì)思路:
獲取用戶提交內(nèi)容通過(guò)自定義中間件的方式獲取用戶提交內(nèi)容
要在請(qǐng)求來(lái)的時(shí)候就將惡意內(nèi)容扼殺在搖籃中,使用process_request方法。獲取到內(nèi)容中如果有敏感詞直接在中間件里返回處理內(nèi)容即可。
新建一個(gè)py文件,自定義一個(gè)類,并且該類必須繼承MiddlewareMixin,然后在process_request方法里寫(xiě)入自己的過(guò)濾方法。這個(gè)方法名字是不能改的
from?django.utils.deprecation?import?MiddlewareMixin# 定義一個(gè)類繼承MiddlewareMixin 并實(shí)現(xiàn)下面的方法,在這兩個(gè)方法 中定義或者攔截對(duì)應(yīng)的請(qǐng)求# 可以在中間件中添加用戶認(rèn)證和登錄設(shè)置等信息class CustomMiddle(MiddlewareMixin):def process_request(self, request):print('過(guò)濾代碼',request)
然后在settings.py里面注冊(cè)該中間件即可使用,位置最好放在最后面,因?yàn)檎?qǐng)求經(jīng)過(guò)中間件是從上到下的,指不定自定義的中間件要依賴上面哪個(gè)中間件的結(jié)果
判斷是否屬于敏感詞
這里有三種方法:
replace過(guò)濾
import?timeold = time.time()with open("1", encoding='utf-8') as f:????????word?=?'可愛(ài)'for keyword in f:if keyword == word:print(word.replace(word, '*'))now = time.time()????print(now?-?old,'replace方法')
這個(gè)文件1里存放的就是敏感詞庫(kù)
正則過(guò)濾
import?reold = time.time()def check_filter(keywords, word):return re.sub("|".join(keywords), "***", word)with open("1", encoding='utf-8') as f:keywords=[]for i in f:keywords.append(i.strip('\n'))print(check_filter(keywords, word))now = time.time()print(now - old, '正則方法')
DFA算法(推薦)
這個(gè)算法的核心是把敏感詞庫(kù)構(gòu)造成樹(shù)結(jié)構(gòu),比如現(xiàn)在敏感詞庫(kù)有:“大家好,大人,帥氣,大部隊(duì)”
前兩種方法就是循環(huán)詞庫(kù)每一行,然后對(duì)比,假設(shè)詞庫(kù)很大,這樣逐行對(duì)比效率很低
DFA算法將敏感詞生成一個(gè)下圖這種結(jié)構(gòu):

組成樹(shù)形結(jié)構(gòu)的好處就是可以減少索引次數(shù),理論上只需要遍歷?遍代檢測(cè)的?本,看看是否在敏感詞庫(kù)中即可。
?如輸?"我感覺(jué)我?分帥?",前?個(gè)均未在詞庫(kù)中匹配,全部pass掉。直到出現(xiàn)”帥”字時(shí)在?樹(shù)中找到了,接下來(lái)繼續(xù)遍歷發(fā)現(xiàn)”?”字出現(xiàn)在?樹(shù)的?節(jié)點(diǎn) 中,就說(shuō)明帥?是敏感詞。
在python中我們可以?字典儲(chǔ)存敏感詞詞庫(kù)樹(shù)結(jié)構(gòu),理論上字典的查詢時(shí)間復(fù)雜度為 O(1)。
我們把第?個(gè)字符作為字典的鍵,值為另?個(gè)字典以此類推,字典最后?項(xiàng)定義為{'\x00': 0}?來(lái)表示最后?個(gè)字符

定義一個(gè)類,并且初始化變量,生成樹(shù)結(jié)構(gòu):

制作的字典如下圖
現(xiàn)在我們需要做的就是編寫(xiě)add?法的具體內(nèi)容:

?
如“大家好”,循環(huán)結(jié)束后的self.key_chains應(yīng)該變?yōu)?
?{大:{家:{好:{}}}}?。當(dāng)“大家好”處理完成后輪到“大人”時(shí),發(fā)現(xiàn)“大”已經(jīng)為字典的鍵,所以進(jìn)入If 判斷,update level 的value。
使level update為之前“大”對(duì)應(yīng)的值,這仍舊是一個(gè)字典。接著進(jìn)入else將“大”加到和“家”同級(jí)別的dict中
此時(shí)的結(jié)構(gòu)如下圖:

?
樹(shù)字典構(gòu)建好了接下來(lái)需要做的就是過(guò)濾了

import?jiebafrom django.conf import settingsimport timeclass DFAFilter():''' Filter Messages from keywordsUse DFA to keep algorithm perform constantlyf = DFAFilter()f.add("sexy")f.filter("hello sexy baby")hello **** baby'''def __init__(self):self.stopwords = ['!']self.keyword_chains = {}self.delimit = '\x00'with open(settings.FILTER_PATH, encoding='utf-8') as f:for keyword in f:self.add(keyword.strip())def add(self, keyword):if not isinstance(keyword, str):keyword = keyword.decode('utf-8')keyword = keyword.lower()chars = keyword.strip()if not chars:returnlevel = self.keyword_chainsfor i in range(len(chars)):if chars[i] in level:level = level[chars[i]]else:if not isinstance(level, dict):breakfor 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}breakif i == len(chars) - 1:level[self.delimit] = 0def filter(self, message, repl="*"):stop_list1 = []stop_list2 = []stop_index = -1if not isinstance(message, str):message = message.decode('utf-8')message = message.lower()wordlist = jieba.lcut(message)stop = set(self.stopwords)for i in wordlist:if i == ' ' or '':stop_index = message.index(i, stop_index + 1, len(message))stop_list1.append(stop_index)stop_list2.append('')wordlist.remove(i)if i in stop:stop_index = message.index(i, stop_index + 1, len(message))stop_list1.append(stop_index)stop_list2.append(i)wordlist.remove(i)message = ''.join(wordlist)ret = []start = 0danger = 0while start < len(message):level = self.keyword_chainsstep_ins = 0for char in message[start:]:if char in level:step_ins += 1if self.delimit not in level[char]:level = level[char]else:danger += 1ret.append(repl * step_ins)start += step_ins - 1breakelse:ret.append(message[start])print(ret)breakelse:ret.append(message[start])start += 1result = list(''.join(ret))for i, j in zip(stop_list1, stop_list2):result.insert(i, j)return ''.join(result), danger
沒(méi)有數(shù)據(jù)支持的對(duì)比都是詐騙:




可以看到:正則方法所用的時(shí)間是隨著詞庫(kù)的擴(kuò)大而增大,replace是詞庫(kù)少于2000個(gè)詞的時(shí)候表現(xiàn)良好,而DFA算法是大于2000時(shí)表現(xiàn)良好
后臺(tái)控制敏感詞名單的增減
DFA算法生成字典那里我們可以做出優(yōu)化,不用每次運(yùn)行都生成字典,而是把字典用pickle.dump持久話存儲(chǔ)在內(nèi)存中,用的時(shí)候再讀,這樣就快了很多。
把敏感詞庫(kù)的詞所對(duì)應(yīng)的字段在admin中注冊(cè)就可以后臺(tái)直接控制詞庫(kù)了,我就不再操作拿IP黑名單字段演示。
類似下圖,這樣我們就可以在后臺(tái)管理是否啟用特定的敏感詞或者增刪特定的敏感詞了

持久化存儲(chǔ)文件更新
當(dāng)我們?cè)黾踊騽h除了敏感詞的時(shí)候,對(duì)應(yīng)的持久化文件也應(yīng)該發(fā)生改變,可以在model里面重寫(xiě)save和delete方法即可。

不用信號(hào)的原因是無(wú)需操作別的數(shù)據(jù)庫(kù)模型和第三方app,只是重新pickle.dump update了一個(gè)新的持久化文件
?單詞數(shù):349字符數(shù):5138
