https://github.com/zlplease/sensitiveWord
一、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 10 | 20 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 360 | 480 |
· Design Spec | · 生成设计文档 | 60 | |
· Design Review | · 设计复审 | 30 | |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | ||
· Design | · 具体设计 | 90 | |
· Coding | · 具体编码 | 600 | 720 |
· Code Review | · 代码复审 | ||
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | |
Reporting | 报告 | ||
· Test Repor | · 测试报告 | ||
· Size Measurement | · 计算工作量 | 8 | |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | ||
· 合计 | 1290 | 1748 |
二、计算模块接口
代码第一步,首先上百度。在比较DFA与AC自动机的难易程度后,果断选择了DFA。
2.1 计算模块接口的设计与实现过程
2.1.1算法概述
DFA通过event和当前的state得到下一个state,event+state=nextstate。即建立嵌套字典,并在字典的末尾设立限制符。由于本题存在偏旁部首拆分,繁体以及同音字的干扰项,在建立树的时候选择用拼音建树。建树之前对敏感词初始化,并通过排列组合得到改敏感词所有出现的可能性。例如
你好 >>> ['你好', '你hao', '你h', '你女子', 'ni好', 'nihao', 'nih', 'ni女子', 'n好', 'nhao', 'nh', 'n女子', '亻尔好', '亻尔hao', '亻尔h', ' 亻尔女子']
同时还建立了将各项单独拆分的列表。便于对拼音整体的处理
你好 >>> [('你', '好'), ('你', 'hao'), ('你', 'h'), ('你', '女子'), ('ni', '好'),
('ni', 'hao'), ('ni', 'h'), ('ni', '女子'), ('n', '好'), ('n', 'hao'), ('n', 'h'), ('n', '女子'),
('亻尔', '好'), ('亻尔', 'hao'), ('亻尔', 'h'), ('亻尔', '女子')]
通过对字典元素的查询判断不断增加新的分支结点,并在末尾设置限制符。考虑到题目输出要求,限制符作为敏感词字典的结束标志,可以为其设置不同的值,从而还起到标志为哪一个敏感词的作用。按照以上步骤得到的部分树如下
{
'ni': {
'h': {
'a': {
'o': {
'is_end': 0
},
},
},
'hao': {
'is_end': 0
},
},
'n': {
'i': {
'h': {
'a': {
'o': {
'is_end': 0
},
},
},
'hao': {
'is_end': 0
},
}
}
}
将检测文本逐字转换为拼音进行判断,并更新状态。读到限制符就记录结果,利用切片截取所需字符串。
2.1.2 函数设计
所有通用函数都封装在solve.py文件中,便于处理和复用
- isChinese函数:根据unicode划分进行汉字判断
def isChinese(word): return '\u4e00' <= word <= '\u9fff‘
- isMatch函数:检测匹配项,剔除符号数字等干扰
def isMatch(word): if isChinese(word): return True if word.isalpha(): return True if word == '/n': return True
- transform函数:将敏感词的每个字的不同情况进行一键对多值的处理,统一英文为小写
def transform(words):
- forest函数:生成多种敏感词情况,并返回整体与分割两种情况
def forest(sameDic): forest = [] loop_val = [] finalDic = [] for key in sameDic: loop_val.append(sameDic[key]) for i in product(*loop_val): forest.append(i) for i in range(len(forest)): newWord = '' for item in forest[i]: newWord += item finalDic.append(newWord) # return forest return finalDic, forest
2.1.3 类设计
本次作业仅设计了一个DFA类,初始化该类成员如下
def __init__(self):
# 生成的敏感词字典
self.keyword_chains = {}
# 敏感词字典限制符
self.delimit = 'is_end'
# 敏感词检测的结果
self.answer = []
# 敏感词
self.sensitiveWords = []
# 敏感词对应数量
self.counts = []
# 检测出的总数
self.total = 0
并对该类设计了一下函数:
- 建树函数
def generate_dict(self, word, count):
- 匹配文本的过滤器函数
def filter(self, texts, line):
- 首行插入检测总数,然后返回检测结果
def getAnswer(self): self.answer.insert(0, 'Total: ' + str(self.total)) return self.answer
2.1.4 词云图
词云图采用pyecharts包的WordCloud,将收集的敏感极其数量进行处理以符合WordCloud数据格式,设置相关属性进行渲染,生成sensitiveWordCloud.html文件,通过浏览器查看
def wordCloud(words, counts) -> WordCloud:
initData(words, counts)
final = (
WordCloud()
.add(series_name="敏感词统计", data_pair=data, shape="circle", word_size_range=[6, 66])
.set_global_opts(
title_opts=opts.TitleOpts(
title="敏感词统计", title_textstyle_opts=opts.TextStyleOpts(font_size=23)
),
tooltip_opts=opts.TooltipOpts(is_show=True),
)
.render("sensitiveWordCloud.html")
)
return final
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5CNycDZhRTMhNjM1gTY0cTYhJGN1QDOkVzMyEjZ3QDO08CX2EzLclDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLzM3Lc9CX6MHc0RHaiojIsJye.png)
(数据量太小了,效果不是很好,第一次用pyecharts,不知道为啥有些属性没有生效
2.2 计算模块接口部分的性能改进
函数总时间降序图
函数自用时间降序图
不难发现,由于引用pypinyin包,代码中存在着多次拼音转换,存在多次调用lazy_pinyin,占用时间较大。加之filter函数前期没想清楚,造成很多代码冗余未优化。起初想的是将所有情况按字符建树,这样会造成过滤繁体或者同音字时,需要一个字母一个字母匹配,匹配完成后状态改变,这样会造成循环嵌套过多;后重新将分割的敏感词组进行建树,这样就能快速处理拼音的过滤,但这会导致像“功”这种偏旁部首读音跟原字一样的不会完全匹配,所有最后干脆直接建一个全是英文的树,过滤文本时通过转换拼音再进行处理。
2.3 计算模块部分单元测试展示
- 验证是否为汉字:test_isChinese函数
- 验证敏感词的不同情况:test_transform函数
def test_transform(self): global_ans = ['你好', '你hao', '你h', '你女子', 'ni好', 'nihao', 'nih', 'ni女子', 'n好', 'nhao', 'nh', 'n女子', '亻尔好', '亻尔hao', '亻尔h', ' 亻尔女子'] separate_ans = [('你', '好'), ('你', 'hao'), ('你', 'h'), ('你', '女子'), ('ni', '好'), ('ni', 'hao'), ('ni', 'h'), ('ni', '女子'), ('n', '好'), ('n', 'hao'), ('n', 'h'), ('n', '女子'), ('亻尔', '好'), ('亻尔', 'hao'), ('亻尔', 'h'), ('亻尔', '女子')] global_test, separate_test = solve.transform('你好') self.assertEqual(global_test.sort(), global_ans.sort()) self.assertEqual(separate_test.sort(), separate_ans.sort())
- 验证DFA过滤器是否正常运行:test_DFAfilter函数
def test_DFAfilter(self): sensitiveWord = ['哈利波特', '作家'] texts = ['《哈利·波$!*&特是英国zjJ.K.罗琳(J. K. Rowling)于1997~2007年所著的魔幻文学系列小说,共7部。', '其中前六部以霍哥沃z魔法学校(Hogwarts School of Witchcraft and Wizardry)为主要舞台,描写的是主人公——年轻的巫师学生h利·波特在霍格沃茨前后六年的学习生活和冒险故事', '第七本描写的是hl&*%^·b特在第二次魔法界大战中在外寻找魂器并消灭fd的故事。'] ans = ['Total: 4', 'line1: <哈利波特> 哈利·波$!*&特', 'line1: <作家> zj', 'line2: <哈利波特> h利·波特', 'line3: <哈利波特> hl&*%^·b特'] test = DFA() count = 0 for keyword in sensitiveWord: test.initWords(keyword.strip()) finalKey, pinyinKey = solve.transform(keyword.strip()) for i in pinyinKey: test.generate_dict1(i, count) for i in finalKey: test.generate_dict(i, count) count += 1 for i in range(len(texts)): test.filter(texts[i].strip(), i + 1) answer = test.getAnswer() self.assertEqual(ans, answer)
单元测试覆盖率:
2.4 计算模块部分异常处理说明
def test_IOError(self):
try:
with open(path.join(path.dirname(__file__), 'chai_zi.json'), 'r', encoding='utf-8') as f:
pass
except IOError:
print('IOError:未找到文件或读取文件失败')
else:
print("文件读取成功")
三、心得