设计题目: 利用flask+jQuery实现包含前后端的文本相似度分析项目
作者: moonchild
专 业:计算机科学与技术
完成日期:2022/11/9
如何在大量的文本之中查找到最具代表性的特征文本?如何在海量文本之中去除重复内容?这是NLP(Nature Language Processing)领域的重要方向.也就是计算文本之间的相似度.其具有广泛的应用场景,从论文查重到搜索引擎排除相似内容,不一而足.
这是一个利用后端利用Python之中的第三方库+前端jQuery+bootstrap实现的分析两段文本之间的相似度的项目.用户可以在可视界面中上传txt文件或者输入文本, 经后端运算之后返回前端.
scipy.spatial.distance.cosine
,主要是余弦定理的计算,此处也可以自己手动造轮子来计算类名 | 成员类别 | 成员名 | 描述 |
---|---|---|---|
ConsineSimilarity | 函数 | init() | 构造函数 |
@property 函数 | Similarity() | 获取用户输入的两段文本之间的相似度 | |
@staticmethod 函数 | preprocessing() | 对用户输入的文本进行预处理 | |
@staticmethod 函数 | oneHot() | 预处理,生成离散的One Hot编码 | |
属性 | string1 | 文本1 | |
属性 | string2 | 文本2 | |
SimhashSimilarity | |||
函数 | init() | 构造函数 | |
@property 函数 | getSimilarity | 获取两个文本之间的相似度 | |
属性 | string1 | 文本1 | |
属性 | string2 | 文本2 |
2.1 后端函数:
数据类型 | 函数名称 | 描述 |
---|---|---|
@app.router(“/”) | index() | 主页 |
@app.router(“/text/file/”) | uploadFile() | 用于上传文本的接口函数 |
@app.router(“/text/”) | getSimilarity() | 获取用户输入的文本 |
bool | isFileExtensionAllowed() | 判断用户上传文件的扩展名是否合法.本项目之中限制用户上传的文件格式为‘.txt’ |
str | readFile() | 读取文本文件,并且返回与处理以后的文本 |
None | ProcessInput | 考虑到Simhash方法在文本较短情况下的效果并不理想,而余弦定理方法处理效率较低,因此,文本较短时,此时不如使用余弦方法来进行计算.因此,该函数之中根据用户输入的文本的长度分别到用余弦方法和Simhash方法 |
2.2 前端函数
函数名 | 函数用途 |
---|---|
uploadText() |
该方法之中,调用jQuery提供的ajax() 方法来像后端服务器发送文本 |
uploadFile() |
该方法之中,调用jQuery提供的ajax() 方法,将文件添加到表单对象之中,发送到后端server |
一众隐函数 | 用于实现对界面按钮以及文本域的监听 |
余弦定理计算文本相似度
def __init__(self, string1, string2): # 如何判断用户输入的是一段文本还是一个文件路径????
# 先假设用户输入的一段文本,首先去空格,去标点符号
self.text1 = self.preprocessing(string1)
self.text2 = self.preprocessing(string2)
@staticmethod
def preprocessing(text: str) -> str:
return text.replace('\n', '').replace('\t', '').replace('\r', '').replace(' ', '')
@staticmethod
def oneHot(wordDict, keyWords): # 预处理,生成离散的oneHot()编码
oneHotCode = [0 for _ in range(len(wordDict))]
for word in keyWords:
oneHotCode[wordDict[word]] = 1
return oneHotCode
# 计算余弦相似度
@property
def Similarity(self):
# extract_tags本身返回的就是出现频率最高的20个词,那么接下来的union至多40个单词
text1 = jieba.analyse.extract_tags(self.text1)
text2 = jieba.analyse.extract_tags(self.text2)
union = set(text1).union(set(text2)) # 去重取并集
# 为每个词添加索引,使用字典
wordDict = dict(zip(union, range(0, len(union))))
text1OneHotCode = self.oneHot(wordDict, text1)
text2OneHotCode = self.oneHot(wordDict, text2)
# 计算余弦相似度,使用scipy包里的cosine_similarity
# return 1 - cosine_similarity([text1OneHotCode, text2OneHotCode])
try:
sim = cosine_similarity([text1OneHotCode, text2OneHotCode])
scipy
提供的cosine_similarity()
方法,可以得出文本之间的相似度simhash方法
class SimhashSimilarity:
"""
调用Simhash库,计算文本相似度
"""
def __init__(self, string1, string2):
self.text1 = string1
self.text2 = string2
@property
def getSimilarity(self):
# 生成Simhash对象
simhash1 = Simhash(self.text1)
simhash2 = Simhash(self.text2)
# 计算海明距离
distance = simhash1.distance(simhash2)
# 计算相似度
similarity = 1 - distance / 64
print("文本相似度:%.2f%%" % (similarity * 100))
print()
return similarity
前端JavaScript
$(function () {
let FileOrInput = 0 //判断用户是上传文件还是直接上传文本,1为默认状态,表示文本
let file1 = undefined;
let file2 = undefined;
let File = $("#inputFile");
let Text = $("#inputText");
Text.css("display", "none");
File.css("display", "block");
$("#formFile1").change(function (event) {
file1 = event.target.files[0];
if (file1) {
console.log(file1);
}
})
$("#formFile2").change((event) => {
file2 = event.target.files[0];
if (file2) {
console.log(file2);
}
})
function uploadText(event) {
let text = $("#floatingTextarea1").val();
let text2 = $("#floatingTextarea2").val();
console.log(text);
console.log(text2);
event.preventDefault();
let Form = new FormData();
Form.append("text1", text);
Form.append("text2", text2);
$.ajax({
url: "/text/",
type: "POST",
data: Form,
processData: false,
contentType: false,
success: (result) => {
alert("文本的相似度为: " + result*100 + "%");
}
})
}
function uploadFile(event) {
let Form = new FormData();
Form.append("file1", file1);
Form.append("file2", file2);
$.ajax({
url: "/text/file/",
method: "POST",
data: Form,
processData: false,
contentType: false,
success: (res) => {
console.log(res);
alert('文件的相似度为: ' + res*100 + '%');
},
error: (err) => {
console.log(err);
console.log(err.status);
}
})
}
$("#CommitBtn").click(function (event) {
if (FileOrInput)
uploadText(event)
else {
console.log(file1); //此处可以获取到file
console.log(file2);
uploadFile(event);
}
})
$("#btnradio1").click(function (event) {
// 点击了按钮一以后,表示用户要提交文件,那么文本框隐藏
console.log("click radio1")
Text.css("display", "none");
File.css("display", "block");
FileOrInput = 0;
console.log(FileOrInput);
})
$("#btnradio2").click(function (event) {
console.log("click radio 2")
File.css("display", "none");
Text.css("display", 'block');
FileOrInput = 1;
})
});
ajax()
方法来送出数据.flask框架
import os
import webbrowser
from flask import Flask, redirect, request, render_template, flash
from werkzeug.utils import secure_filename
import config
from main import SimhashSimilarity, CosineSimilarity, readFile, ProcessInput
app = Flask(__name__)
# 添加配置文件
app.config.from_object(config)
app.config.from_pyfile('config.py')
# 注册蓝图模块
# app.register_blueprint(bp)
@app.route('/')
def index():
# 在这个函数之中将页面返回
return render_template("index.html")
def isFileExtensionAllowed(filename: str) -> bool:
if filename.rsplit('.', 1)[-1].lower() in config.ALLOWED_EXTENSION: # 获取文件的扩展名
return True
else:
return False
@app.route("/text/file/", methods=["GET", "POST"])
def uploadFile():
file1 = request.files.get("file1")
file2 = request.files.get("file2")
fileList = os.listdir(config.UPLOAD_FOLDER)
num = fileList.count(file1.filename)
if num > 0:
file1.filename = file1.filename.split('.')[0]+'({0})'.format(num)+'.txt'
num = fileList.count(file2.filename)
if num > 0:
file2.filename = file2.filename.split('.')[0]+'({0})'.format(num)+'.txt'
if file1 and file2:
if isFileExtensionAllowed(file1.filename) and isFileExtensionAllowed(file2.filename):
file1.save(os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(file1.filename)))
file2.save(os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(file2.filename)))
text1 = readFile(os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(file1.filename)))
text2 = readFile(os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(file2.filename)))
return str(ProcessInput(text1, text2))
else:
return "Error"
# 此路由用于获取用户输入的文本
@app.route("/text/", methods=["GET", "POST"])
def getSimilarity():
text1 = request.form.get("text1")
text2 = request.form.get("text2")
print(text1, text2)
res = ProcessInput(text1, text2)
print(res)
return res
if __name__ == '__main__':
webbrowser.open("http://127.0.0.1:5000")
app.run()
用户可以选择提交文本还是提交txt文件,点击提交之后便会返回两段文本之间的相似度
输出结果: 文本相似度为 51.5625%
理想中的版本是添加了利用python强大的网页爬取能力在主流网站上爬取一些文本存到数据库之中,这样用户可以上传单一文本来判断该文本的重复度,但是受到了几点限制:数据库操作,网页爬虫的学习在期中考试之前这一段时间很难完成,而且想要做到一个完善的查重程序需要很丰富的语料库,这个在短期时间内很难做到
仅仅是单页面,较为简陋,而且操作逻辑\交互不够丰富,用户体验较差.时间受限,仅仅完成了单页面,尽管借助了bootstrap丰富的组件库,但是对于页面的设计,还是没有一个很好的思路.
在写完大作业的一段时间之后,我了解到了python之中的gensim
库,该库可以实现导入中文词向量模型,封装了使用余弦定理计算文本相似度的算法.不止于此,其中还包含更丰富的文本处理的算法.比如,返回几个词序列之中最不相似的一个词,再比如对一个词进行联想,返回其他类似的词语.等等等等.如果以后还有机会做类似的项目,会优先考虑使用gensim
可以让用户上传两个网站URL,通过爬虫获取网站文本内容,计算二者文本的相似度并返回.
将代码上传到了Github上.可以无需解压zip文件,直接点击下面的链接进行访问
moonchildink/TextSimilarity