ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Django文本相似度系统:可部署、可替换算法的工程化实践

Django文本相似度系统:可部署、可替换算法的工程化实践

简介:本资源是一份面向本科毕业设计与自然语言处理初学者的Python文本相似度计算系统完整方案,聚焦信息检索、推荐系统等实际应用场景。文档详细阐述了系统设计思路、技术选型(Python+Django+MySQL)、核心模块(文本清洗、NLTK/spaCy分词、TF-IDF关键词提取、余弦相似度计算及可视化展示)与实验验证过程,并附有标准论文格式的绪论、可行性分析、开发技术说明等章节,可直接用于开题、答辩与代码复现参考。资源为单个749KB的Word文档(.docx),涵盖摘要、中英文关键词、目录、各章技术细节及参考文献,结构规范、内容翔实。目前已有138人学习下载,适合需要快速掌握NLP基础算法落地、构建课程设计或毕设系统的Python学习者,尤其利于理解从理论公式(如余弦相似度)到工程实现(Django界面+数据库存储)的完整链路。

1. 这不是个“毕设摆设”:一个能真跑、能改、能塞进你项目里的文本相似度系统

上周帮某高校实验室搭一个课程作业平台,学生交上来的“基于 Python 的文本相似度计算系统”源码包,解压后第一眼看到manage.py和requirements.txt,我就知道——这回不用重写了。它没用 Flask 硬凑路由,没把 TF-IDF 写成三行 for 循环就交差,也没把 MySQL 表设计成text1 TEXT, text2 TEXT, similarity FLOAT这种反模式。它真用了 Django 的 Model 层建了TextDocument和SimilarityRecord,预处理链路里 jieba 分词后接了自定义停用词过滤器,余弦计算前做了 L2 归一化,连前端的相似度雷达图都是用 Chart.js 动态渲染的。更关键的是,它没在论文里吹“支持 BERT”,但feature_extractor.py里留了if model_type == 'word2vec'的钩子——说明作者真跑过 Word2Vec,只是毕业答辩前删了权重加载逻辑保稳定。如果你正卡在“怎么把相似度模块嵌进自己那个新闻聚合后台”“怎么让客服工单系统自动聚类重复问题”,或者单纯想拿个能本地跑通、有数据库、带 Web 界面、算法可替换的文本相似度脚手架,这个项目就是你该下载解压、pip install -r requirements.txt、然后python manage.py runserver看见首页的那个。

它不解决“千亿级语料实时去重”这种工业级问题,但能稳稳扛住日均 500+ 条新闻摘要比对、支撑起一个 20 人团队的内部知识库检索;它没用 PyTorch 写 Attention,但把 TF-IDF 向量化、稀疏矩阵存储、余弦批量计算这些真实项目里天天踩的坑全趟过一遍;它的价值不在“多高大上”,而在“改三行代码就能换算法,加两个字段就能存历史记录,删掉admin/目录就能当纯 API 服务用”。别被标题里“毕业设计”四个字劝退——这玩意儿的工程完成度,比很多开源小工具都扎实。


2. 技术栈拆解:为什么是 Django + MySQL + jieba,而不是 Flask + SQLite + re.sub?

2.1 Web 框架选型:Django 不是“杀鸡用牛刀”,而是省下 200 行胶水代码

很多人看到“文本相似度”第一反应是写个 Python 脚本,输入两段文字,print 出来一个 0.73。但现实场景里,用户要的是:

  • 在浏览器里粘贴两段新闻稿,点按钮,1 秒内看到带颜色深浅的相似度热力图;
  • 历史记录能查、能导出 Excel;
  • 运维同事能用systemctl restart textsim一键重启,不用管 virtualenv 路径。

Django 在这里不是炫技,是用成熟轮子堵住所有运维和协作的缝。看settings.py里的关键配置:

# settings.py 片段 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'similarity', # 核心业务 App ] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'text_similarity_db', 'USER': 'simuser', 'PASSWORD': 'simpass123', # 生产环境务必用环境变量! 'HOST': 'localhost', 'PORT': '3306', 'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", } } } # 静态文件托管(关键!) STATIC_URL = '/static/' STATICFILES_DIRS = [BASE_DIR / "static"] STATIC_ROOT = BASE_DIR / "collected_static" # collectstatic 输出目录

提示:STATIC_ROOT是部署时python manage.py collectstatic的输出目标,Django 开发服务器会自动从STATICFILES_DIRS读取,但 Nginx/Gunicorn 生产环境必须走STATIC_ROOT。漏配这个,前端 CSS/JS 全 404,你会在 Chrome 控制台看到满屏红色报错——这是新手最常翻车的第一步。

Django Admin 自带的 CRUD 界面,直接让你能手动增删停用词表、查看历史计算记录、甚至给用户分角色(比如“审核员”能看到所有相似度结果,“普通用户”只能查自己的)。similarity/admin.py里就三行:

# similarity/admin.py from django.contrib import admin from .models import TextDocument, SimilarityRecord, StopWord @admin.register(TextDocument) class TextDocumentAdmin(admin.ModelAdmin): list_display = ['title', 'content_preview', 'created_at'] search_fields = ['title', 'content'] @admin.register(SimilarityRecord) class SimilarityRecordAdmin(admin.ModelAdmin): list_display = ['doc1_title', 'doc2_title', 'similarity_score', 'created_at'] list_filter = ['created_at'] search_fields = ['doc1__title', 'doc2__title'] @admin.register(StopWord) class StopWordAdmin(admin.ModelAdmin): list_display = ['word', 'is_active'] list_filter = ['is_active']

list_display里content_preview是个自定义方法,避免长文本撑爆 Admin 页面:

# models.py 中 TextDocument 模型的方法 def content_preview(self): return self.content[:100] + "..." if len(self.content) > 100 else self.content content_preview.short_description = '内容预览'

对比 Flask:你要自己写/admin/documents路由、自己渲染 Jinja2 模板、自己处理分页和搜索 SQL、自己加权限校验……而 Django Admin 一行@admin.register就搞定。这不是偷懒,是把精力留给核心算法。

2.2 数据库设计:MySQL 不为“高并发”,而为“可追溯”和“可扩展”

论文里说“用 MySQL 存文本”,但实际models.py的设计暴露了真实意图:

# similarity/models.py from django.db import models from django.contrib.auth.models import User class StopWord(models.Model): word = models.CharField(max_length=50, unique=True, db_index=True) is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.word class TextDocument(models.Model): title = models.CharField(max_length=200, blank=True) content = models.TextField() cleaned_content = models.TextField(blank=True) # 预处理后的文本,供向量化用 user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.title or f"文档 {self.id}" class SimilarityRecord(models.Model): doc1 = models.ForeignKey(TextDocument, on_delete=models.CASCADE, related_name='similarity_as_doc1') doc2 = models.ForeignKey(TextDocument, on_delete=models.CASCADE, related_name='similarity_as_doc2') similarity_score = models.FloatField() # 0.0 ~ 1.0 algorithm_used = models.CharField(max_length=50, default='cosine_tfidf') # 记录用的算法 created_at = models.DateTimeField(auto_now_add=True) class Meta: # 防止重复计算同一对文档 unique_together = ('doc1', 'doc2') ordering = ['-created_at'] def __str__(self): return f"{self.doc1.title} ↔ {self.doc2.title} = {self.similarity_score:.3f}"

三个表的设计逻辑很清晰:

  • StopWord:可动态管理的停用词表,不是写死在代码里的 list。上线后运营同学发现“的”“了”“吧”在特定领域不该过滤?Admin 里点两下就生效,不用发版。
  • TextDocument:分离原始文本和清洗后文本。content是用户粘贴的原文(含标点、空格、乱码),cleaned_content是 jieba 分词前的标准输入(已去 HTML 标签、统一空白符、转小写)。这样既保留溯源,又保证向量计算一致性。
  • SimilarityRecord:强制唯一约束 + 算法标记。unique_together避免反复计算同一对文档浪费资源;algorithm_used字段为后续 A/B 测试埋点——比如你想对比 TF-IDF 和 Word2Vec 效果,直接查SELECT * FROM similarity_similarityrecord WHERE algorithm_used = 'word2vec'就能拉出全部数据。

注意:cleaned_content字段是关键设计。很多初学者把清洗逻辑写在视图里,每次请求都re.sub(r'[^\w\s]', '', text)一遍,CPU 白白烧。这里提前存好,向量化时直接vectorizer.fit_transform([doc.cleaned_content]),性能提升立竿见影。

2.3 文本处理链路:jieba 是起点,不是终点

论文里写“用 jieba 分词”,但preprocessor.py的真实实现是条流水线:

# similarity/utils/preprocessor.py import re import jieba from similarity.models import StopWord def clean_text(text): """基础清洗:去HTML、去多余空白、转小写""" # 去HTML标签 text = re.sub(r'<[^>]+>', ' ', text) # 去连续空白符(包括换行、制表符) text = re.sub(r'\s+', ' ', text).strip() # 中文标点转空格(便于后续分词) text = re.sub(r'[,。!?;:“”‘’()【】《》、]', ' ', text) return text.lower() def get_stopwords(): """动态获取活跃停用词列表""" return set(StopWord.objects.filter(is_active=True).values_list('word', flat=True)) def segment_and_filter(text): """分词 + 停用词过滤 + 单字过滤""" stopwords = get_stopwords() words = jieba.lcut(text) # 过滤:停用词、单字(除非是数字或英文)、纯标点 filtered_words = [ w.strip() for w in words if w.strip() and w.strip() not in stopwords and (len(w.strip()) > 1 or w.strip().isalnum()) ] return filtered_words def preprocess_full(text): """完整预处理流程""" cleaned = clean_text(text) segmented = segment_and_filter(cleaned) return ' '.join(segmented) # 返回空格连接的字符串,供TF-IDF用

这个设计的精妙在于:

  • get_stopwords()每次调用都查数据库,但加了@cached_property(实际代码里有)缓存到请求生命周期,避免 N+1 查询;
  • segment_and_filter()里len(w.strip()) > 1过滤单字,是因为中文里“我”“你”“他”这类代词在相似度计算中噪声极大,但保留“3G”“AI”这种缩写;
  • preprocess_full()返回的是' '连接的字符串,直接喂给TfidfVectorizer,不用再拼接。

对比网上那些“jieba.lcut(text); [w for w in words if w not in stoplist]”的脚本,这里每一步都有明确目的,且可单独测试、可开关、可替换。比如你想换成 spaCy,只改segment_and_filter里 jieba 的调用就行,其他模块完全不受影响。


3. 核心模块实现:从文本到相似度分数的完整链路

3.1 特征提取模块:TF-IDF 向量化不是调包,是理解稀疏矩阵

系统没用 Word2Vec 或 BERT(论文里提了但没实现),而是选择了工程落地性最强的 TF-IDF + 余弦相似度。feature_extractor.py的核心是TfidfVectorizer的定制化使用:

# similarity/utils/feature_extractor.py from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity import numpy as np from similarity.models import TextDocument class TextVectorizer: def __init__(self, max_features=10000, ngram_range=(1, 1)): """ 初始化向量器 :param max_features: 词汇表最大词数,防内存爆炸 :param ngram_range: (1,1) 为词袋,(1,2) 为加入二元词组 """ self.vectorizer = TfidfVectorizer( max_features=max_features, ngram_range=ngram_range, lowercase=False, # 中文不转小写 token_pattern=r'(?u)\b\w+\b', # 匹配中文字符和英文单词 stop_words=None, # 停用词在预处理阶段已过滤,这里不重复 sublinear_tf=True, # 使用 sublinear 缩放 TF,缓解高频词主导 norm='l2' # L2 归一化,确保余弦相似度计算正确 ) self.is_fitted = False self.vocabulary_ = None def fit(self, documents): """拟合向量器,构建词汇表""" # documents 是预处理后的字符串列表,如 ['人工智能 发展 快', '机器学习 算法 复杂'] self.vocabulary_ = self.vectorizer.fit(documents) self.is_fitted = True return self def transform(self, documents): """将文档列表转为 TF-IDF 矩阵""" if not self.is_fitted: raise ValueError("Vectorizer not fitted. Call fit() first.") return self.vectorizer.transform(documents) def get_feature_names_out(self): """获取词汇表""" return self.vectorizer.get_feature_names_out() # 全局单例,避免重复初始化 vectorizer_instance = TextVectorizer(max_features=5000)

关键参数说明:

  • max_features=5000:不是拍脑袋,是根据你的语料规模定的。如果TextDocument.objects.count()是 1000 条,每条平均 200 字,5000 词足够覆盖 95% 的有效词;设太大(如 50000)会导致稀疏矩阵占满内存,transform()直接 OOM。
  • ngram_range=(1,1):默认只用一元词(unigram)。如果你想捕捉“深度学习”“神经网络”这种固定搭配,改成(1,2),但要注意max_features得同步调大,否则二元词被截断。
  • sublinear_tf=True:对词频做1 + log(tf)变换,防止“的”“了”这种超高频词碾压“量子计算”“区块链”等关键术语。
  • norm='l2':必须设置!余弦相似度公式cosθ = (A·B) / (||A|| ||B||)要求向量是单位向量。TfidfVectorizer默认不归一化,cosine_similarity()内部会做,但显式设置更安全。

使用示例(在视图中):

# similarity/views.py from django.shortcuts import render from django.http import JsonResponse from .utils.preprocessor import preprocess_full from .utils.feature_extractor import vectorizer_instance from .models import TextDocument def calculate_similarity(request): if request.method == 'POST': text1 = request.POST.get('text1', '').strip() text2 = request.POST.get('text2', '').strip() if not text1 or not text2: return JsonResponse({'error': '请输入两段文本'}, status=400) # 1. 预处理 processed1 = preprocess_full(text1) processed2 = preprocess_full(text2) # 2. 向量化(注意:fit_transform 仅用于首次,后续用 transform) # 实际生产中,应定期用全量文档 fit,此处简化为每次用这两条 try: # 临时向量化(适合演示) docs = [processed1, processed2] tfidf_matrix = vectorizer_instance.fit(docs).transform(docs) # 3. 计算余弦相似度 similarity_matrix = cosine_similarity(tfidf_matrix) score = float(similarity_matrix[0][1]) # 第一段 vs 第二段 # 4. 保存记录(可选) doc1 = TextDocument.objects.create(content=text1, cleaned_content=processed1) doc2 = TextDocument.objects.create(content=text2, cleaned_content=processed2) SimilarityRecord.objects.create( doc1=doc1, doc2=doc2, similarity_score=score, algorithm_used='cosine_tfidf' ) return JsonResponse({'similarity': round(score, 4)}) except Exception as e: return JsonResponse({'error': f'计算失败: {str(e)}'}, status=500)

避坑 / 常见问题 / 排查

现象 1:cosine_similarity返回值全是 1.0 或 0.0

原因:TfidfVectorizer没做norm='l2',或向量未归一化。TF-IDF 矩阵的行向量模长不为 1,导致A·B和||A|| ||B||比例失衡。
解决:检查vectorizer初始化参数,确保norm='l2';或手动归一化:from sklearn.preprocessing import normalize; tfidf_norm = normalize(tfidf_matrix, norm='l2', axis=1)。

现象 2:transform()报错ValueError: Vocabulary wasn't fitted.

原因:调用了transform()但没先调fit()。TfidfVectorizer是有状态的,必须先fit()构建词汇表,才能transform()新文本。
解决:在项目启动时(如apps.py的ready()方法)用全量历史文档fit()一次,之后所有请求只用transform()。不要每次请求都fit_transform()。

现象 3:计算耗时超过 1 秒,页面卡死

原因:fit()过程涉及统计词频、计算 IDF,对大量文本很慢;且TfidfVectorizer默认用 Python 实现,非并行。
解决:

  • 对于实时计算,绝不现场fit,只用预训练好的vectorizer;
  • 用max_features严格限制词汇量;
  • 替换为HashingVectorizer(无状态,无需fit),但会牺牲可解释性(无法查某个词的 IDF 值)。

现象 4:中文分词结果包含大量单字或乱码

原因:jieba.lcut()输入了未清洗的原始文本(含 HTML 标签、特殊符号)。
解决:确保preprocess_full()的clean_text()步骤在segment_and_filter()之前执行,且clean_text()中的正则re.sub(r'[^\w\s]', ' ', text)要覆盖中文标点。

现象 5:SimilarityRecord表里出现大量重复记录

原因:前端没禁用提交按钮,用户狂点“计算”;或unique_together约束未生效(MySQL 引擎不是 InnoDB)。
解决:

  • 前端 JS 加button.disabled = true;
  • Django 视图中用get_or_create()替代create();
  • 检查数据库表引擎:SHOW CREATE TABLE similarity_similarityrecord;确认 ENGINE=InnoDB。

3.2 相似度计算模块:不止余弦,还预留了 Jaccard 和编辑距离

虽然默认用余弦,但similarity/utils/similarity_calculator.py提供了多算法支持:

# similarity/utils/similarity_calculator.py from sklearn.metrics.pairwise import cosine_similarity from sklearn.feature_extraction.text import TfidfVectorizer import numpy as np from difflib import SequenceMatcher from collections import Counter def cosine_similarity_score(vec1, vec2): """余弦相似度(输入:两个 TF-IDF 向量)""" return float(cosine_similarity(vec1.reshape(1, -1), vec2.reshape(1, -1))[0][0]) def jaccard_similarity(text1, text2): """Jaccard 相似度(基于词集合)""" set1 = set(text1.split()) set2 = set(text2.split()) intersection = len(set1.intersection(set2)) union = len(set1.union(set2)) return float(intersection / union) if union > 0 else 0.0 def edit_distance_similarity(text1, text2): """编辑距离相似度(1 - 编辑距离 / 最长长度)""" # 使用 difflib 的 SequenceMatcher,比 Levenshtein 更快 similarity = SequenceMatcher(None, text1, text2).ratio() return float(similarity) # 算法注册表,方便扩展 ALGORITHMS = { 'cosine_tfidf': cosine_similarity_score, 'jaccard': jaccard_similarity, 'edit_distance': edit_distance_similarity, } def calculate_similarity(text1, text2, algorithm='cosine_tfidf'): """统一调用入口""" if algorithm not in ALGORITHMS: raise ValueError(f"Unsupported algorithm: {algorithm}") if algorithm == 'cosine_tfidf': # 需要向量化 from .feature_extractor import vectorizer_instance docs = [text1, text2] try: # 用预训练的 vectorizer(生产环境应全局复用) tfidf_matrix = vectorizer_instance.transform(docs) return ALGORITHMS[algorithm](tfidf_matrix[0], tfidf_matrix[1]) except: # 回退到简单 TF-IDF(适合 demo) vectorizer = TfidfVectorizer(max_features=1000, norm='l2') tfidf_matrix = vectorizer.fit_transform(docs) return ALGORITHMS[algorithm](tfidf_matrix[0], tfidf_matrix[1]) else: # 直接文本计算 return ALGORITHMS[algorithm](text1, text2)

这个设计的好处是:

  • ALGORITHMS字典让算法切换变成字符串参数,前端加个<select>就能切;
  • calculate_similarity()统一入口,业务逻辑不用关心底层是向量还是字符串;
  • edit_distance_similarity()用SequenceMatcher.ratio()而不是Levenshtein.distance(),因为前者是 Python 标准库,无需额外安装,且对中文支持更好(按字符比,不是按字节)。

3.3 可视化模块:不只是画个柱状图,而是让用户“看懂”相似度

templates/similarity/result.html里的可视化不是装饰,是降低理解门槛的关键:

<!-- templates/similarity/result.html --> <div class="row"> <div class="col-md-6"> <h4>文本 1</h4> <p class="text-muted">{{ text1|linebreaks }}</p> </div> <div class="col-md-6"> <h4>文本 2</h4> <p class="text-muted">{{ text2|linebreaks }}</p> </div> </div> <div class="row mt-4"> <div class="col-md-8"> <h4>相似度得分:{{ similarity|floatformat:4 }}</h4> <!-- 雷达图:展示关键词重叠度 --> <canvas id="radarChart" width="400" height="400"></canvas> </div> <div class="col-md-4"> <h4>算法详情</h4> <ul class="list-group"> <li class="list-group-item">算法:<strong>{{ algorithm_used }}</strong></li> <li class="list-group-item">计算耗时:<strong>{{ calc_time }} ms</strong></li> <li class="list-group-item">共提取 <strong>{{ keyword_count }}</strong> 个关键词</li> </ul> </div> </div> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script> const ctx = document.getElementById('radarChart').getContext('2d'); const radarChart = new Chart(ctx, { type: 'radar', data: { labels: {{ common_keywords|safe }}, // 如 ["人工智能", "发展", "技术"] datasets: [{ label: '文本1关键词', data: {{ keywords1_counts|safe }}, // [1, 2, 0] backgroundColor: 'rgba(54, 162, 235, 0.2)', borderColor: 'rgb(54, 162, 235)', pointBackgroundColor: 'rgb(54, 162, 235)', }, { label: '文本2关键词', data: {{ keywords2_counts|safe }}, backgroundColor: 'rgba(255, 99, 132, 0.2)', borderColor: 'rgb(255, 99, 132)', pointBackgroundColor: 'rgb(255, 99, 132)', }] }, options: { scales: { r: { angleLines: { display: true }, grid: { circular: true }, pointLabels: { font: { size: 12 } }, ticks: { beginAtZero: true, max: 5 } } } } }); </script>

后端视图传递的数据:

# views.py 中 calculate_similarity 视图的返回部分 from collections import Counter import jieba def calculate_similarity(request): # ... 前面的逻辑 ... # 计算关键词重叠(用于雷达图) words1 = jieba.lcut(processed1) words2 = jieba.lcut(processed2) counter1 = Counter(words1) counter2 = Counter(words2) common_keys = list(set(words1) & set(words2)) # 取前 5 个常见词(避免雷达图太密) top_common = sorted(common_keys, key=lambda x: counter1[x] + counter2[x], reverse=True)[:5] context = { 'text1': text1, 'text2': text2, 'similarity': round(score, 4), 'algorithm_used': 'cosine_tfidf', 'calc_time': int((time.time() - start_time) * 1000), 'common_keywords': json.dumps(top_common), 'keywords1_counts': json.dumps([counter1[w] for w in top_common]), 'keywords2_counts': json.dumps([counter2[w] for w in top_common]), 'keyword_count': len(set(words1) | set(words2)), } return render(request, 'similarity/result.html', context)

这个雷达图的价值在于:用户一眼看到“哦,两段话都在讲‘人工智能’和‘发展’,但‘技术’这个词在文本1里出现了2次,文本2里没出现”,比单纯一个 0.68 的数字更有说服力。这才是“可视化”的意义——把算法黑匣子打开一条缝,让用户信服结果。


4. 运行与部署:从python manage.py runserver到 Nginx + Gunicorn

4.1 本地运行:三步启动,绕过所有玄学错误

别被requirements.txt里一堆包吓到,真正核心依赖就 5 个:

# requirements.txt 关键行 Django==4.2.7 mysqlclient==2.2.4 scikit-learn==1.3.0 jieba==0.42.1 numpy==1.24.3

血泪经验:mysqlclient安装失败是 Windows 用户最高频翻车点。别用pip install mysqlclient硬刚,直接去 Christoph Gohlke 的非官方 wheel 库 下载对应 Python 版本和系统架构的.whl文件,然后pip install xxx.whl。Mac 用户用brew install mysql-client再pip install mysqlclient;Linux 用户sudo apt-get install python3-dev default-libmysqlclient-dev build-essential后再 pip。

启动步骤(Windows/Mac/Linux 通用):

  1. 创建并激活虚拟环境(绝对必要!):

    python -m venv venv source venv/bin/activate # Linux/Mac # venv\Scripts\activate # Windows
  2. 安装依赖并迁移数据库:

    pip install -r requirements.txt # 创建 MySQL 数据库(登录 MySQL 后执行) CREATE DATABASE text_similarity_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 修改 settings.py 中的 DATABASES 配置(用户名密码) python manage.py makemigrations python manage.py migrate python manage.py createsuperuser # 创建管理员账号
  3. 收集静态文件并启动:

    python manage.py collectstatic --noinput python manage.py runserver

    打开http://127.0.0.1:8000/admin/登录,进StopWord表添加几个中文停用词(如“的”、“了”、“在”、“和”),再访问http://127.0.0.1:8000/就能用了。

4.2 生产部署:Nginx + Gunicorn,告别开发服务器

runserver只能本地调试,生产必须换。gunicorn.conf.py配置是关键:

# gunicorn.conf.py import multiprocessing # 绑定地址 bind = "127.0.0.1:8000" bind_address = "127.0.0.1:8000" port = "8000" # Unix socket(推荐,比 TCP 更快) # bind = "/run/gunicorn.sock" # chmod = "644" # 工作进程 workers = multiprocessing.cpu_count() * 2 + 1 worker_class = "sync" worker_connections = 1000 timeout = 30 keepalive = 2 # 日志 accesslog = "/var/log/gunicorn_access.log" errorlog = "/var/log/gunicorn_error.log" loglevel = "info" capture_output = True # 进程控制 pidfile = "/var/run/gunicorn.pid" daemon = True user = "www-data" group = "www-data" umask = 0o007

Nginx 配置 (/etc/nginx/sites-available/textsim):

upstream textsim_app { server 127.0.0.1:8000; } server { listen 80; server_name your-domain.com; location /static/ { alias /path/to/your/project/collected_static/; expires 1y; add_header Cache-Control "public, immutable"; } location / { proxy_pass http://textsim_app; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_redirect off; } }

启用服务:

# 启动 Gunicorn gunicorn --config gunicorn.conf.py similarity.wsgi:application # 启用 Nginx sudo systemctl enable nginx sudo systemctl restart nginx

避坑 / 常见问题 / 排查

现象 1:Nginx 502 Bad Gateway

原因:Gunicorn 没起来,或proxy_pass地址不对(比如 Gunicorn 绑定unix:/run/gunicorn.sock,但 Nginx 配置里写http://127.0.0.1:8000)。
解决:sudo journalctl -u gunicorn -f查看 Gunicorn 日志;确认gunicorn.conf.py的bind和 Nginx 的proxy_pass一致。

现象 2:CSS/JS 404,页面丑得没法看

原因:collectstatic没执行,或 Nginx 的alias路径指向错误目录。
解决:ls -l /path/to/collected_static/确认文件存在;检查 Nginx 配置中alias结尾是否有/(必须有!)。

现象 3:MySQL 连接超时,后台报OperationalError: (2013, 'Lost connection to MySQL server during query')

原因:MySQL 默认wait_timeout=28800(8小时),但 Gunicorn worker 长时间空闲后连接失效。
解决:在settings.py的DATABASES配置里加'OPTIONS': {'init_command': "SET SESSION wait_timeout=28800;"},或更彻底地用django-db-geventpool。

现象 4:上传大文本(>1MB)时 413 Request Entity Too Large

原因:Nginx 默认client_max_body_size=1m。
解决:在 Nginxserver块里加client_max_body_size 10m;。

现象 5:中文显示为?????

**

本文还有配套的精品资源,点击获取

返回列表