简介:本资源是一套面向高校计算机相关专业毕业设计场景的文本相似度计算系统完整资料,适合正在准备NLP方向毕设、课程设计或需要Python文本处理实战参考的学生与开发者。压缩包内共1个docx文件,约749KB,内容为系统源码与论文文档的整合稿,涵盖绪论、可行性分析、技术选型与系统实现等章节,可据此梳理从文本清洗、分词、TF-IDF关键词提取到余弦相似度计算与可视化展示的完整链路。文档同时给出Python、Django、MySQL等技术栈的搭配思路,并附摘要、目录与实验评估部分,便于读者快速理解系统架构、复用算法模块或作为论文写作与答辩准备的参考。目前已有138人学习下载,适合希望以较低成本获取一份结构完整、可直接借鉴的文本相似度项目方案的学习者。
1. 文本相似度计算系统:从一份毕业设计源码看 Django 与 NLP 的落地组合
如果你手头正好有一份基于 Python 的文本相似度计算系统源码,打开后发现它不只是一个跑在终端里的算法脚本,而是带登录界面、新闻管理、文件分析、可视化结果展示的完整 Web 应用,那它大概率就是我今天要拆的这类项目。这类系统在毕业设计里出现频率很高,核心目标很明确:用户输入两段文本或上传两个 txt 文件,系统经过清洗、分词、向量化之后,用余弦相似度等算法算出相似度分数,再通过前端页面把结果直观地呈现出来。它适合谁?一是正在做 NLP 方向课程设计、需要一套可运行参考实现的同学;二是想找一个 Django + MySQL + jieba 的完整项目来练手二次开发的后端新手。整套代码不算庞大,但链路完整,从表单提交到算法计算再到结果渲染,每一层都能看到具体实现,拿来改造成新闻去重、问答匹配或者文档查重都算顺手。
2. 技术栈与目录结构:Django 怎么把算法包成 Web 服务
2.1 为什么是 Django + MySQL + jieba 这套组合
这个项目的技术选型没有走冷门路线,基本是 Python Web 开发里最稳的那一档。后端用 Django,原因很直接:它自带 ORM、表单组件、模板引擎和用户认证,对于文本相似度这种需要快速搭出输入页、结果页和后台管理页的场景,能省掉大量重复代码。数据库选 MySQL,是因为系统里要存用户信息、新闻文本、操作记录和关键词词向量表,关系型数据库在结构化存储和查询上更顺手。文本处理这块,中文分词用 jieba,英文分词和停用词过滤用 nltk,向量化用词袋模型配合 TF-IDF 加权,相似度计算默认走余弦相似度,同时保留欧几里得距离、曼哈顿距离和 Jaccard 相似度作为可切换选项。
我一般会先确认项目根目录下有没有 requirements.txt,里面通常会列 Django、jieba、nltk、numpy、mysqlclient 这几个包。如果没有,按下面这个清单手动装也能跑起来:
pip install django==3.2 pip install jieba pip install nltk pip install numpy pip install mysqlclient提示:mysqlclient 在 Windows 上编译容易报错,常见做法是换成 pymysql,然后在项目init.py 里加两行
import pymysql和pymysql.install_as_MySQLdb()。
2.2 目录结构长什么样,每个文件夹负责什么
这类项目的目录结构通常比较规整,和标准 Django 工程差别不大,但会多出算法相关的模块。下面是我根据源码正文和常见实现整理出来的典型结构:
text_similarity_system/ ├── manage.py ├── requirements.txt ├── text_similarity/ # 项目配置目录 │ ├── settings.py # 数据库、静态文件、中间件配置 │ ├── urls.py # 总路由 │ └── wsgi.py ├── apps/ │ ├── users/ # 用户管理、登录注册 │ ├── news/ # 新闻管理、新闻列表与详情 │ ├── analysis/ # 文本分析、文件分析核心业务 │ │ ├── views.py # 接收表单、调用算法、返回结果 │ │ ├── forms.py # TextSimilarityForm 等表单类 │ │ ├── models.py # 新闻、操作记录、词向量表模型 │ │ └── utils/ │ │ ├── preprocess.py # 清洗、分词、去停用词 │ │ ├── feature.py # 词袋模型、TF-IDF 加权 │ │ └── similarity.py # 余弦、欧几里得、Jaccard 计算 │ └── dashboard/ # 首页统计、个人信息 ├── templates/ # 前端模板,基于 Bootstrap ├── static/ # CSS、JS、图片 └── media/ # 上传的 txt 文件存放目录这个结构里最值得关注的是apps/analysis/utils/这个目录,它把文本处理链路拆成了三个独立文件:预处理、特征提取、相似度计算。这样做的好处是算法逻辑和 Web 视图解耦,你改算法的时候不用动 views.py,换前端的时候也不用碰 utils。对于二次开发来说,这种分层比把所有代码塞进一个 views.py 要友好得多。
2.3 数据库表设计:新闻、操作记录和词向量表怎么存
系统里至少涉及三张核心表。新闻表存标题、正文、发布时间和来源;操作记录表存每次相似度计算的输入摘要、算法类型、结果分数和操作时间;词向量表存关键词及其对应的向量表示,用于加速后续匹配。Django 的 ORM 会把它们映射成模型类,迁移命令一跑,表就自动建好了。
# apps/news/models.py from django.db import models class News(models.Model): title = models.CharField(max_length=200, verbose_name='新闻标题') content = models.TextField(verbose_name='新闻正文') source = models.CharField(max_length=100, blank=True, verbose_name='来源') created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'news' verbose_name = '新闻' class OperationLog(models.Model): input_text = models.TextField(verbose_name='输入文本摘要') algorithm = models.CharField(max_length=50, verbose_name='算法类型') score = models.FloatField(verbose_name='相似度分数') created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'operation_log'这里input_text存的是摘要而不是全文,是为了避免操作记录表膨胀过快。algorithm字段记录用户选了哪种算法,方便后续做统计对比。score用 FloatField 存浮点数,精度够用。如果你要加用户外键,就在 OperationLog 里补一个user = models.ForeignKey(...),然后在视图里把当前登录用户传进去。
2.4 路由与视图的衔接方式
Django 的路由分两层:项目级 urls.py 负责把不同 app 的路径分发下去,app 级 urls.py 负责具体视图函数的映射。文本分析模块通常会有三个入口:文本分析页、文件分析页和结果展示页。
# apps/analysis/urls.py from django.urls import path from . import views urlpatterns = [ path('text/', views.text_analysis, name='text_analysis'), path('file/', views.file_analysis, name='file_analysis'), path('result/<int:log_id>/', views.result_detail, name='result_detail'), ]text_analysis处理表单提交和 GET 请求,file_analysis接收上传的 txt 文件,result_detail根据操作记录 ID 把结果渲染出来。这种设计让结果页可以分享和回看,而不是算完就丢。实际开发中我习惯把结果先落库再跳转,这样用户刷新页面不会重复计算,也方便排查问题。
3. 核心算法链路:从原始文本到相似度分数的完整实现
3.1 文本预处理:清洗、分词、去停用词三步走
预处理是整个链路里最影响最终效果的一环。原始文本里往往混着标点、数字、特殊符号和大量无意义的高频词,如果不处理,向量空间会被噪声撑得很大,相似度分数也会失真。这个项目的预处理模块做了三件事:用正则去掉非文本内容,用 jieba 做中文分词,用停用词表过滤掉“的”“了”“在”这类词。
# apps/analysis/utils/preprocess.py import re import jieba def load_stopwords(path='stopwords.txt'): """加载停用词表,每行一个词""" with open(path, 'r', encoding='utf-8') as f: return set(line.strip() for line in f if line.strip()) STOPWORDS = load_stopwords() def clean_text(text): """去除标点、数字和特殊符号,只保留中英文""" text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z]', ' ', text) return text.strip() def tokenize(text): """中文分词并过滤停用词和单字""" text = clean_text(text) words = jieba.lcut(text) return [w for w in words if w not in STOPWORDS and len(w) > 1]clean_text里的正则[^\u4e00-\u9fa5a-zA-Z]表示保留所有中文字符和英文字母,其余一律替换成空格。tokenize先分词再过滤,len(w) > 1是为了去掉单个字,因为单字在相似度计算里区分度太低,留着反而增加维度。停用词表可以自己维护,也可以用现成的中文停用词表,放在项目根目录下即可。如果你的文本里英文占比高,可以把 jieba 换成 nltk 的 word_tokenize,逻辑一样。
3.2 特征提取:词袋模型加 TF-IDF 加权
分词完成后,下一步是把词列表变成向量。这个项目用的是词袋模型:先收集所有文本中出现过的词,组成一个词汇表,然后对每段文本,统计词汇表中每个词是否出现或出现多少次。但单纯用 0/1 或词频有个问题——“的”“是”这种词在所有文本里都高频出现,区分度很低。所以项目里对词向量做了 TF-IDF 加权,让稀有词获得更高权重。
# apps/analysis/utils/feature.py import math from collections import Counter def build_vocab(token_lists): """根据所有文本的分词结果构建词汇表""" vocab = set() for tokens in token_lists: vocab.update(tokens) return sorted(vocab) def compute_tf(tokens): """计算词频""" counter = Counter(tokens) total = len(tokens) or 1 return {word: count / total for word, count in counter.items()} def compute_idf(token_lists): """计算逆文档频率""" doc_count = len(token_lists) word_doc = Counter() for tokens in token_lists: for word in set(tokens): word_doc[word] += 1 return {word: math.log(doc_count / (freq + 1)) for word, freq in word_doc.items()} def vectorize(tokens, vocab, idf): """将单段文本转为 TF-IDF 加权向量""" tf = compute_tf(tokens) return [tf.get(word, 0) * idf.get(word, 0) for word in vocab]build_vocab把所有文本的词合并去重后排序,保证每次运行向量维度一致。compute_idf里的+1是平滑项,防止某个词在所有文档中都不出现导致除零。vectorize返回的是一个与词汇表等长的浮点数列表,这就是后续计算余弦相似度的输入。实际项目中如果文本量很大,词汇表可能上万维,这时候可以考虑用 sklearn 的 TfidfVectorizer 替代手写实现,但手写版的好处是逻辑透明,改起来没有黑盒。
3.3 相似度计算:余弦相似度为主,多算法可切换
拿到两个 TF-IDF 向量之后,余弦相似度就是算它们夹角的余弦值。公式很简单:点积除以模长乘积。值越接近 1 表示越相似,接近 0 表示越不相关。项目里还实现了欧几里得距离、曼哈顿距离和 Jaccard 相似度,用户可以在前端下拉框里切换。
# apps/analysis/utils/similarity.py import math def cosine_similarity(vec_a, vec_b): """余弦相似度:点积 / (模长乘积)""" dot = sum(a * b for a, b in zip(vec_a, vec_b)) norm_a = math.sqrt(sum(a * a for a in vec_a)) norm_b = math.sqrt(sum(b * b for b in vec_b)) if norm_a == 0 or norm_b == 0: return 0.0 return dot / (norm_a * norm_b) def euclidean_distance(vec_a, vec_b): """欧几里得距离,越小越相似""" return math.sqrt(sum((a - b) ** 2 for a, b in zip(vec_a, vec_b))) def jaccard_similarity(tokens_a, tokens_b): """Jaccard 相似度:交集 / 并集""" set_a, set_b = set(tokens_a), set(tokens_b) if not set_a or not set_b: return 0.0 return len(set_a & set_b) / len(set_a | set_b)cosine_similarity里对模长为零的情况做了保护,避免空文本导致除零异常。euclidean_distance返回的是距离而不是相似度,前端展示时需要说明“值越小越相似”。jaccard_similarity直接基于分词后的集合计算,不经过向量化,适合短文本快速比对。这几种算法各有适用场景:余弦适合长文本,Jaccard 适合短句,欧几里得距离对向量绝对差异敏感。我一般会在结果页把多个算法的分数并排展示,让用户自己判断。
3.4 视图层怎么串起整条链路
算法写完之后,需要在 Django 视图里把它们串起来。用户提交表单后,视图先取到两段文本,分别预处理、合并构建词汇表、计算 IDF、向量化,最后调用相似度函数,把结果存库并渲染到模板。
# apps/analysis/views.py from django.shortcuts import render, redirect from .forms import TextSimilarityForm from .models import OperationLog from .utils.preprocess import tokenize from .utils.feature import build_vocab, compute_idf, vectorize from .utils.similarity import cosine_similarity def text_analysis(request): form = TextSimilarityForm() if request.method == 'POST': form = TextSimilarityForm(request.POST) if form.is_valid(): text_a = form.cleaned_data['text_a'] text_b = form.cleaned_data['text_b'] tokens_a, tokens_b = tokenize(text_a), tokenize(text_b) vocab = build_vocab([tokens_a, tokens_b]) idf = compute_idf([tokens_a, tokens_b]) vec_a = vectorize(tokens_a, vocab, idf) vec_b = vectorize(tokens_b, vocab, idf) score = cosine_similarity(vec_a, vec_b) log = OperationLog.objects.create( input_text=text_a[:100], algorithm='cosine', score=round(score, 4) ) return redirect('result_detail', log_id=log.id) return render(request, 'analysis/text.html', {'form': form})这段代码里cleaned_data是 Django 表单验证通过后的干净数据,直接拿来用不用担心注入问题。build_vocab和compute_idf都传入两段文本的分词结果,保证向量维度对齐。OperationLog.objects.create把输入摘要、算法名和分数落库,然后重定向到结果页。这种“POST 后重定向”的模式能防止用户刷新页面时重复提交,是 Django 里的标准做法。
4. 避坑与常见问题:跑不起来时先查这几处
4.1 jieba 分词结果为空或全是单字
现象是输入一段中文后,相似度始终为 0 或极低。原因通常是停用词表把大部分词都过滤掉了,或者len(w) > 1这个条件太严格,而测试文本又恰好以单字词为主。解决方法是先打印tokenize的返回结果,确认分词是否正常;如果停用词表过大,可以适当精简,或者把len(w) > 1改成len(w) >= 1再观察效果。
4.2 MySQL 连接报错 Access denied 或 Unknown database
现象是python manage.py migrate时直接抛异常。原因一般是 settings.py 里的数据库用户名、密码或库名和本地不一致。解决方法是先在 MySQL 里手动建库CREATE DATABASE text_similarity DEFAULT CHARSET=utf8mb4;,然后核对 settings.py 的 DATABASES 配置,确保 USER、PASSWORD、NAME 三项和实际环境一致。如果用的是 pymysql,还要确认init.py 里的伪装代码已经加上。
4.3 上传 txt 文件后读取乱码
现象是文件分析页显示的结果和原文对不上,或者直接报 UnicodeDecodeError。原因是 txt 文件可能用了 GBK 编码,而代码里默认用 utf-8 读取。解决方法是在打开文件时加编码探测,或者统一用open(path, 'r', encoding='utf-8', errors='ignore')先忽略无法解码的字符。更稳妥的做法是用 chardet 库检测编码后再读取。
4.4 相似度分数偏高,不同文本都接近 1
现象是随便输入两段不相关的文本,余弦相似度也在 0.8 以上。原因通常是词汇表太小,或者 IDF 计算时文档数量太少导致权重区分度不够。解决方法是增加对比文本的数量,让 IDF 更有意义;或者检查预处理是否把太多词过滤掉了,导致向量维度极低。另外,如果两段文本都很短,余弦相似度本身就容易偏高,这时候可以改用 Jaccard 相似度做辅助判断。
4.5 前端页面样式丢失或按钮无响应
现象是页面能打开但 Bootstrap 样式没生效,或者点击“开始计算”没反应。原因是 Django 的静态文件配置有问题,或者 CSRF token 没正确传递。解决方法是确认 settings.py 里 STATIC_URL 和 STATICFILES_DIRS 配置正确,模板里用{% load static %}引入 CSS。表单里必须加{% csrf_token %},否则 POST 请求会被 Django 拦截返回 403。
5. 二次开发与改造:把这套系统变成你自己的项目
拿到这份源码之后,最直接的改造方向是换算法。项目里已经预留了余弦、欧几里得、曼哈顿和 Jaccard 四种方法,你可以再加一个基于 Word2Vec 的语义相似度:用 gensim 训练词向量,对每段文本的词向量取平均得到句向量,再算余弦。改动点集中在utils/feature.py和utils/similarity.py,视图层只需要多传一个算法参数。
第二个方向是加批量比对。现在的系统一次只能比两段文本,如果你要做新闻去重,就需要把新来的新闻和库里所有新闻逐一比对。实现方式是在views.py里加一个循环,遍历 News 表,把每条新闻的正文和输入文本做相似度计算,最后按分数排序返回 Top N。注意这里要做性能优化,可以先把所有新闻的向量预先算好存到词向量表里,避免每次请求都重新计算。
第三个方向是换前端。项目现在用的是 Django 模板加 Bootstrap,如果你更习惯前后端分离,可以把算法层保留,视图层改成 DRF 接口,前端用 Vue 或 React 重写。接口设计很简单:一个 POST 接口接收两段文本和算法类型,返回 JSON 格式的分数和耗时。这样前端可以做成实时计算,用户边输入边看分数变化。
验证改造是否成功,我一般会准备一组基准文本:两段完全相同的、两段完全不相关的、两段部分重叠的。每次改完算法都跑一遍这三组,看分数是否符合预期。如果相同文本的分数不是 1,不相关文本的分数不是接近 0,那就说明预处理或向量化环节出了问题。
注意:改算法时不要动
OperationLog的表结构,否则历史记录会对不上。如果确实需要加字段,用 Django 的 migration 命令生成迁移文件,不要手动改数据库。
从那以后我每次拿到这类 NLP 项目,都强制先跑一遍基准文本验证链路,再动任何代码。希望帮到你。
本文还有配套的精品资源,点击获取