编写此脚本的目的:
本人从事软件测试工作,近两年发现项目成员总会提出一些内容相似的问题,导致开发抱怨。一开始想搜索一下是否有此类工具能支持查重的工作,但并没找到,因此写了这个工具。通过从纸上谈兵到着手实践,还是发现很多大大小小的问题(一定要动手去做喔!),总结起来就是理解清楚参考资料、按需设计、多角度去解决问题。
脚本进行相似度分析的基本过程:
1、获取Bug数据。读取excel表,获取到“BugID”和“Bug内容”
2、获取指定格式的Bug关键字集合。使用“jieba包”,采用“搜索模式”,对Bug内容进行分词,获取到分词表后,使用“正则表达式”过滤,拿到词语(词语长度>=2),提出掉单个字、符号、数字等非关键字
3、计算词频(TF)。在步骤2中获取到关键字总量,在本步骤,则对筛选后的关键字进行频率统计,最后得出“TF值(关键字出现频率/总词数)”
4、获取词逆文档频率(IDF)(也可以理解为权重)。在本步骤,有个重要的前提就是“语料库”,这里我没有使用开放、通用的语料库,而是使用本项目的“测试用例步骤、约束条件”等内容,唯一的原因就是“很适应Bug内容的语言场景”,在此模块,也是采用分词,然后利用参考资料中的计算公式,获取到每个“关键词”的IDF值,并存至名称为“get_IDF_value.txt”的文件中(一开始没放到这里,导致出现重复、多次进行本步骤计算,几kb的文本内容还能接受,几百kb的就......当时跑了一夜都没完!)
5、获取TF_IDF值,并据此对Bug关键字进行倒序排列,然后硬性截取所有Bug排位前50%的关键字,并形成集合,然后以冒泡的形式,从第一个Bug开始,进行“相似度计算”(公式见参考资料),最终将相似度大于阀值的Bug,以形式“Bug编号_1(被比对对象)-Bug编号_2(比对对象)”打印到名称为“bug_compare_result.xls”的Excel表中
过程中一些特殊点的处理:
1、“所有Bug排位前50%的关键字,并形成集合”之所以创建该列表是为减少单个列表或字典在多个函数使用的频率,肯定是可以减少脚本问题频率的。
2、语料库的选择是为适应使用需求
3、得到TF_IDF值的目的只是为了获取到“排位前50%的Bug关键字”
4、逆文档频率(IDF)值会存储至“get_IDF_value.txt”文件中,每次运行脚本时会提醒是否更改语料库
说明:
1、代码友好性差,技术能力有限。当然可塑性很高 ~ ~
2、出于练习的目的,独立编写“IDF计算”的脚本,并放到另一路径下
源码:(纯生肉,通过代码注释还是能看到本人设计过程的心态)
sameText.py
--------------------sameText.py--------------------------
#-*- coding:utf-8 -*- import jieba import re import CorpusDatabase.getIDF as getIDF import math import xlrd import xlwt import time #######获取原始bug数据 re_rule_getTF = re.compile(r"^([u4e00-u9fa5]){2,20}")#选择中文开头,且文字长度为2-20,一定要记住哇 Top_List = []#将分词的列表和TF_IDF值前50%的列表合于此处 Compare_CosResult = []#相似度大于??(可调整)的问题号 BugID_List = []#用于存放BugID getIDF_get_IDF_value = {}#存储语料库中各关键词的权重 xiangsidu = 0.8 ########### def read_Originalbase(filename): Original_Bug_Dict = {} book = xlrd.open_workbook(filename) sheet_name = book.sheet_by_index(0) nrows = sheet_name.nrows for i in range(nrows): Original_Bug_Dict[sheet_name.row_values(i)[0]] = sheet_name.row_values(i)[1] return Original_Bug_Dict ###########词频统计################### def get_TF_value(test_String_one): word_counts = 0#统计除空、单字、符号以外的总词数 counts = {} counts_two = {} words_re_TF = [] words = jieba.lcut_for_search(test_String_one) #这边得加段正则表达式的,筛除掉words非中文且单字的内容,并构建words_re_TF for i in words: if re.match(re_rule_getTF,i): words_re_TF.append(i) #统计每个分词的出现次数,即总词数量counts Top_List.append(words_re_TF)#全局的作用列表,很关键 for word in words_re_TF: word_counts = word_counts + 1 counts[word] = counts.get(word,0) + 1#为关键字出现次数进行统计,从0开始,get中第一个为key,0为赋给其对应的value #获取每个关键字在整句话中出现的频率,即最终词频 for count in counts: counts_two[count] = counts_two.get(count,(counts[count]/float(word_counts))) return counts_two ###########获取TF_TDF值,并向量化############# def get_TF_IDF_Key(test_String_two): global getIDF_get_IDF_value TestString_TF_value = {} TF_IDF_value = {} The_Last_TF_IDF_List = [] Corpus_weight_value = getIDF_get_IDF_value#此部分直接定义一个存储语料库权重的全局变量吧 TestString_TF_value = get_TF_value(test_String_two) for i in TestString_TF_value: balance_value = 1 for j in Corpus_weight_value: if i == j: TF_IDF_value[i] = float(TestString_TF_value[i]) * float(Corpus_weight_value[j]) elif i != j and balance_value != len(Corpus_weight_value):#遍历列表过程中,判断是否与TF的值一样,且是否遍历到最后一个元素了 balance_value = balance_value + 1#计算遍历次数 continue else: TF_IDF_value[i] = float(TestString_TF_value[i]) * 1#对于语料库中不存在的词,权重默认为1 The_Last_TF_IDF_List = sorted(TF_IDF_value,key = TF_IDF_value.__getitem__,reverse=True) The_Last_TF_IDF_List = The_Last_TF_IDF_List[0:int((len(The_Last_TF_IDF_List)/2) + 0.5)]#默认取一半关键字 Top_List.append(The_Last_TF_IDF_List)#全局的作用列表,很关键 return The_Last_TF_IDF_List def cosine_result(the_Top_List):#计算余弦相似度 global Compare_CosResult global BugID_List global xiangsidu cos_result = 0.0 keymix = [] compare_one = [] compare_two = [] control_i = 0 for i in range(0,len(the_Top_List) - 1,2): control_j = control_i + 1 for j in range(i + 2,len(the_Top_List) - 1,2): keymix = list(set(the_Top_List[i+1] + the_Top_List[j+1]))#去掉重复的关键字 compare_one = the_Top_List[i] compare_two = the_Top_List[j] compare_one_dict = {} compare_two_dict = {} fenzi_value = 0.0 fenmu_value = 1.0 fenmu_value_1 = 0.0 fenmu_value_2 = 0.0 for word_one in compare_one: for unit_keymix_one in keymix: if unit_keymix_one == word_one: compare_one_dict[unit_keymix_one] = compare_one_dict.get(unit_keymix_one,0) + 1 else: compare_one_dict[unit_keymix_one] = compare_one_dict.get(unit_keymix_one,0) + 0 for word_two in compare_two: for unit_keymix_two in keymix: if unit_keymix_two == word_two: compare_two_dict[unit_keymix_two] = compare_two_dict.get(unit_keymix_two,0) + 1 else: compare_two_dict[unit_keymix_two] = compare_two_dict.get(unit_keymix_two,0) + 0 ###########计算余弦相似度################## for k in compare_one_dict: fenzi_value = fenzi_value + compare_one_dict[k] * compare_two_dict[k] fenmu_value_1 = fenmu_value_1 + math.pow(compare_one_dict[k],2) fenmu_value_2 = fenmu_value_2 + math.pow(compare_two_dict[k],2) fenmu_value = math.sqrt(fenmu_value_1) * math.sqrt(fenmu_value_2) cos_result = fenzi_value / fenmu_value if cos_result >= xiangsidu:#调控值 Compare_CosResult.append(str(int(BugID_List[control_i])) + '-' + str(int(BugID_List[control_j])))#浮点数转整数再转字符串 control_j = control_j + 1 control_i = control_i + 1 return 0 #########将最终结果写入到新的xls文件中######### def write_BugBase(bug_base,CosResult_list): global xiangsidu key_list = list(bug_base.keys()) value_list = list(bug_base.values()) newbook = xlwt.Workbook(encoding = "utf-8", style_compression = 0) sheet_name = newbook.add_sheet('sheet1',cell_overwrite_ok = True) for i in range(0,len(key_list)): bug_com_key_list = [] if i == 0: sheet_name.write(i,0,key_list[i]) sheet_name.write(i,1,value_list[i]) sheet_name.write(i,2,u"相似问题(相似度大于%.1f)"%(xiangsidu)) else: re_CosResult = re.compile(r"^((%s)-d{0,3}$)"%str(int(key_list[i]))) sheet_name.write(i,0,key_list[i]) sheet_name.write(i,1,value_list[i]) for bug_com_key in CosResult_list: if re.match(re_CosResult,bug_com_key):######## bug_com_key_list.append(bug_com_key)######## else: continue for j in range(len(bug_com_key_list)): sheet_name.write(i,j + 2,bug_com_key_list[j] + " ")#重复写入 newbook.save("bug_compare_result.xls") def main(): global getIDF_get_IDF_value user_message = input("是否更改了语料库,请输入 Y or N:") starttime = time.time() if str(user_message) == "Y" or str(user_message) == "y": getIDF_get_IDF_value = getIDF.get_IDF_value() getIDF_file = open('get_IDF_value.txt','w') getIDF_file.write(str(getIDF_get_IDF_value)) getIDF_file.close() else: getIDF_file = open('get_IDF_value.txt','r') getIDF_get_IDF_value = eval(getIDF_file.read()) getIDF_file.close() ########## Bug_base = read_Originalbase('bug.xls') for main_key_value_1 in Bug_base.keys(): if main_key_value_1 == "Bug编号": continue else: BugID_List.append(main_key_value_1) for main_key_value_2 in Bug_base.values(): if main_key_value_2 == "Bug标题": continue else: get_TF_IDF_Key(main_key_value_2)#字符串 cosine_result(Top_List) write_BugBase(Bug_base,Compare_CosResult) endtime = time.time() print("共用时:%d秒!"%(endtime - starttime)) if __name__ == '__main__': main()
-----------------------------------------------------------------
getIDF.py
---------------------getIDF.py-------------------------------
#-*- coding:utf-8 -*- import os import re import jieba import math import re ######正则表达式规则###### re_rule_getIDF = re.compile(r"^([u4e00-u9fa5]){2,20}")#选择中文开头,且文字长度为2-20,一定要记住哇 #读取语料库,并计算出语料库中所有关键词的权重 def get_IDF_value(): Corpus_file_list = []#获取所有语料库文件,以列表形式保存 Corpus_text_list = [] words_re_IDF = [] Corpus_word_counts = 0 Corpus_counts = {} Corpus_counts_two = {} for root,dirs,files in os.walk('.',topdown=False): for name in files: str_value = "" str_value = os.path.join(root,name) Corpus_file_list.append(str_value) for i in Corpus_file_list: if i == '.\CorpusDatabase\__pycache__\getIDF.cpython-36.pyc' or i == '.\CorpusDatabase\__pycache__\__init__.cpython-36.pyc' or i == '.\CorpusDatabase\getIDF.py' or i == '.\CorpusDatabase\getIDF.pyc' or i == '.\CorpusDatabase\__init__.py' or i == '.\CorpusDatabase\__init__.pyc' or i == '.\sameText.py' or i == '.\bug.xls' or i == '.\bug_compare_result.xls' or i == '.\get_IDF_value.txt': continue else: file_object = open(i,'r',encoding = 'UTF-8')#避免出现编码问题,open文件时使用UTF-8编码 file_content = file_object.readlines() for j in file_content: Corpus_text_list.append(j) #对语料库进行分词,统计总词数和每个词出现频率,最终计算出权重 for split_words in Corpus_text_list: words = jieba.lcut_for_search(split_words) #这边得加段正则表达式的,筛除掉words非中文、单个汉字的内容,并构建words_re_IDF for k in words: if re.match(re_rule_getIDF,k): words_re_IDF.append(k) for word in words_re_IDF: Corpus_word_counts = Corpus_word_counts + 1 Corpus_counts[word] = Corpus_counts.get(word,0) + 1 for count in Corpus_counts: Corpus_counts_two[count] = Corpus_counts_two.get(count,(math.log(Corpus_word_counts / (float(Corpus_counts[count] + 1))))) return Corpus_counts_two
-------------------------------------------------------
文档结构:
NLP为主文件名,文件中包括:
——bug.xls(存有BugID和Bug内容的原始文件)
——bug_compare_result.xls(存储比对结果的文件,由程序生成)
——get_IDF_value.txt(存储IDF值的文件)
——sameText.py(主脚本文件)
——CorpusDatabase
——__init__.py(空脚本文件)
——getIDF.py(计算IDF值的文件)
——OAcase.txt(语料库)
——__pycache__(程序自动生成,不用关注)
Bug原文件样式:
最终结果:
相似的问题会从第3列开始写入
参考资料:
http://www.ruanyifeng.com/blog/2013/03/tf-idf.html
http://www.ruanyifeng.com/blog/2013/03/cosine_similarity.html