ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Django旅游关键词分析系统:可查可导可迭代的B/S服务

Django旅游关键词分析系统:可查可导可迭代的B/S服务 简介本资源是一份面向Python与Web开发初学者的旅游城市关键词分析系统设计文档聚焦于利用Django框架构建B/S架构的旅游信息检索平台解决海量网络信息中旅游目的地核心要素景点、美食、资源丰富度提取与可视化对比难题。文档完整阐述了基于自然语言处理、语料库及知识图谱技术实现关键词挖掘、旅游资源图谱生成与城市级数据对比的全流程方案涵盖绪论、技术选型、系统设计与实现等章节附有中英文摘要及规范目录结构。资源为单个2.21MB的DOCX文档内容详实适合作为课程设计参考、毕业设计范例或DjangoPython实战项目学习素材。目前已有91人学习下载读者可直接获取系统需求分析、技术路线、功能模块划分及图谱展示逻辑等关键设计思路快速掌握旅游领域文本分析与Web可视化落地方法。1. 用 Django 搭建旅游城市关键词分析系统不是做词云而是构建可查、可导、可迭代的 B/S 分析服务你手上有几万条游客评论、攻略标题、小红书笔记或携程点评想快速知道“成都”被高频关联哪些词——是“火锅”“熊猫”还是“夜市”“西安”的核心标签是“兵马俑”“回民街”还是突然冒头的“汉服打卡”这类需求在文旅局、OTA 运营、景区营销团队中每天真实发生。但很多人卡在第一步把原始文本扔进 jieba 分词后就只剩静态词频表无法按城市筛选、无法对比时间趋势、无法导出 Excel 给领导汇报更别说嵌入现有业务系统。本项目用 Python Django MySQL 实现的不是演示 Demo而是一个具备完整请求路由、数据库建模、异步分析任务和响应流式导出能力的 B/S 结构关键词分析服务。它默认支持中文分词、TF-IDF 加权、停用词过滤、城市维度聚合并预留了 LDA 主题模型接口。适合 Python 入门者照着部署也足够让有 Django 开发经验的人直接集成进现有后台。2. 为什么选 Django 而非 Flask 或 FastAPI从 MTV 架构到关键词分析的工程适配2.1 MTV 不是概念游戏Model 层直击旅游数据建模痛点旅游城市关键词分析的核心数据结构远不止“城市名词频次”三列。真实场景中你需要区分数据来源携程/马蜂窝/大众点评、采集时间用于趋势对比、文本类型标题/正文/标签、情感倾向正向/中性/负向还要支持后续扩展如地域层级省→市→区、多语言标识中/英/日。Django 的 Model 层天然适配这种强约束关系# models.py from django.db import models class City(models.Model): name models.CharField(max_length50, uniqueTrue, db_indexTrue) province models.CharField(max_length30, blankTrue) pinyin models.CharField(max_length100, blankTrue) # 用于拼音排序和搜索 class SourcePlatform(models.Model): name models.CharField(max_length50, uniqueTrue) url_pattern models.CharField(max_length200, blankTrue) class RawTextEntry(models.Model): city models.ForeignKey(City, on_deletemodels.CASCADE, related_nametexts) platform models.ForeignKey(SourcePlatform, on_deletemodels.SET_NULL, nullTrue) content models.TextField() publish_date models.DateField() text_type models.CharField(max_length20, choices[(title, 标题), (body, 正文), (tag, 标签)]) sentiment models.CharField(max_length10, choices[(positive, 正向), (neutral, 中性), (negative, 负向)], defaultneutral) class KeywordResult(models.Model): city models.ForeignKey(City, on_deletemodels.CASCADE, related_namekeywords) keyword models.CharField(max_length100, db_indexTrue) tfidf_score models.FloatField() raw_frequency models.IntegerField(default0) analysis_date models.DateTimeField(auto_now_addTrue) source_count models.IntegerField(default0) # 来自多少条原始文本提示db_indexTrue对city和keyword字段加索引是 MySQL 中查询“某城市前 20 关键词”性能的关键。未加索引时10 万条记录下SELECT * FROM keywordresult WHERE city_id3 ORDER BY tfidf_score DESC LIMIT 20可能耗时 800ms加索引后稳定在 12ms 内。这是 Django Model 设计阶段就必须决策的底层优化点。2.2 View 层承载分析逻辑从同步计算到 StreamingHttpResponse 的渐进式交付关键词分析本质是 CPU 密集型任务。若用户点击“分析成都”后端直接调用jieba.lcut()TfidfVectorizer处理 5000 条文本页面会白屏 8 秒以上。Django 的 View 层必须拆解为两阶段第一阶段轻量接收请求校验参数生成任务 ID立即返回202 Accepted和轮询地址第二阶段重载后台 Celery 任务执行分词、去停用词、TF-IDF 计算、结果入库第三阶段流式用户访问/api/keywords/export/?city成都时不生成临时文件而是用StreamingHttpResponse边计算边推送 CSV 流。# views.py from django.http import StreamingHttpResponse import csv from io import StringIO def export_keywords_csv(request): city_name request.GET.get(city) if not city_name: return HttpResponseBadRequest(缺少 city 参数) # 查询关键词结果已预计算并存库 keywords KeywordResult.objects.filter( city__namecity_name ).order_by(-tfidf_score)[:1000] def stream_csv(): output StringIO() writer csv.writer(output) # 写入 CSV 头 writer.writerow([关键词, TF-IDF 得分, 原始频次, 分析日期]) yield output.getvalue().encode(utf-8-sig) output.seek(0) output.truncate(0) # 逐行写入数据 for kw in keywords: writer.writerow([ kw.keyword, f{kw.tfidf_score:.4f}, kw.raw_frequency, kw.analysis_date.strftime(%Y-%m-%d %H:%M) ]) yield output.getvalue().encode(utf-8-sig) output.seek(0) output.truncate(0) response StreamingHttpResponse( stream_csv(), content_typetext/csv; charsetutf-8-sig ) response[Content-Disposition] fattachment; filename{city_name}_关键词分析_{timezone.now().strftime(%Y%m%d)}.csv return response注意content_typetext/csv; charsetutf-8-sig中的utf-8-sig是关键。它在 CSV 文件开头写入 BOMByte Order Mark确保 Excel 在 Windows 下正确识别中文编码。若只写utf-8Excel 打开会显示乱码。这是 DjangoStreamingHttpResponse在实际导出场景中的必调参数。3. MySQL 配置与优化支撑万级城市文本的关键词检索性能3.1 表结构设计与索引策略避免全表扫描的三个硬性要求旅游关键词分析系统对 MySQL 的压力集中在两点一是高频SELECT查询如“查杭州近30天TOP50关键词”二是批量INSERT每日新增数万条原始文本。以下配置是经过压测验证的最小可行方案表名必建索引索引类型说明rawtextentry(city_id, publish_date)联合索引支持按城市时间范围快速拉取原始文本keywordresult(city_id, tfidf_score)联合索引ORDER BY tfidf_score DESC LIMIT 20的加速核心keywordresult(keyword)单列索引支持关键词模糊搜索如WHERE keyword LIKE %火锅%-- 执行建索引命令MySQL 8.0 CREATE INDEX idx_city_publish ON rawtextentry (city_id, publish_date); CREATE INDEX idx_city_tfidf ON keywordresult (city_id, tfidf_score); CREATE INDEX idx_keyword ON keywordresult (keyword);提示idx_city_tfidf索引必须按(city_id, tfidf_score)顺序创建。若反过来建(tfidf_score, city_id)则WHERE city_id5 ORDER BY tfidf_score DESC无法使用该索引仍会触发 filesort。这是 MySQL 索引最易踩的坑之一。3.2 字符集与排序规则中文分词结果入库不乱码的根本保障Django 默认使用utf8mb4字符集但 MySQL 安装时若未显式指定可能沿用旧版utf8实际只支持 3 字节 UTF-8无法存储 emoji 和部分生僻汉字。必须在my.cnf中强制声明[client] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci init_connectSET NAMES utf8mb4 skip-character-set-client-handshake TRUE重启 MySQL 后验证当前库字符集SHOW VARIABLES LIKE character_set%; SHOW CREATE TABLE keywordresult; -- 确认表字符集为 utf8mb4若keywordresult.keyword字段仍是utf8需执行ALTER TABLE keywordresult CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;否则当 jieba 分出“鮟鱇鱼”“髣髴”等含四字节 Unicode 的词时MySQL 会静默截断为“鮟鱇”“髣髴”导致关键词统计失真。4. 关键词分析核心模块实现从分词到 TF-IDF 的可控流水线4.1 中文分词与停用词过滤用 jieba 自定义词典提升旅游领域准确率通用分词器对旅游场景效果差将“宽窄巷子”切为“宽窄/巷子”“九寨沟”切为“九寨/沟”“秦始皇兵马俑”切为“秦始皇/兵马俑”。必须加载旅游专用词典# analysis/utils.py import jieba import jieba.posseg as pseg # 加载自定义词典每行一个词格式词 词性 频次 jieba.load_userdict(analysis/data/tourism_dict.txt) # tourism_dict.txt 示例 # 宽窄巷子 nz 1000 # 九寨沟 ns 2000 # 秦始皇兵马俑 nz 1500 # 大雁塔 ns 1800 def cut_and_filter(text, stop_wordsNone): if stop_words is None: stop_words set([的, 了, 在, 是, 我, 有, 和, 就, 不, 人, 都, 一, 一个, 上, 也, 很, 到, 说, 要, 去, 你, 会, 着, 没有, 看, 好, 自己, 这]) words [] for word, flag in pseg.cut(text): # 仅保留名词ns:地名, nz:其他专有名词, n:普通名词和动词v if flag in [ns, nz, n, v] and word not in stop_words and len(word) 2: words.append(word) return words注意pseg.cut()返回词性标注比lcut()更精准。旅游文本中“打卡”是动词v“网红”是名词n“川西”是地名ns——这些语义信息直接影响后续权重计算。丢弃词性直接lcut()会导致“拍照”“打卡”“种草”等行为词与“雪山”“寺庙”等实体词混同处理。4.2 TF-IDF 计算与结果入库控制内存占用的分块策略一次性将 10 万条文本喂给TfidfVectorizer会触发 OOM。必须分块处理# analysis/tasks.py from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import linear_kernel import numpy as np def calculate_keywords_for_city(city_id, batch_size500): from .models import RawTextEntry, KeywordResult # 分批拉取该城市的原始文本 texts [] offset 0 while True: batch list(RawTextEntry.objects.filter(city_idcity_id).values_list(content, flatTrue)[offset:offsetbatch_size]) if not batch: break texts.extend(batch) offset batch_size # 构建向量化器限定最大特征数防爆内存 vectorizer TfidfVectorizer( max_features10000, # 最多保留 10000 个高频词 min_df2, # 词频低于 2 次的直接丢弃 max_df0.95, # 出现在 95% 文本中的词视为停用词 token_patternr(?u)\b\w\b, # 匹配中文字符 tokenizercut_and_filter # 使用自定义分词函数 ) try: tfidf_matrix vectorizer.fit_transform(texts) except ValueError as e: # 若无有效文本跳过 return # 获取特征词列表 feature_names vectorizer.get_feature_names_out() # 计算每个词的平均 TF-IDF 得分按列求均值 mean_scores np.asarray(tfidf_matrix.mean(axis0)).flatten() # 保存前 500 名关键词 top_indices mean_scores.argsort()[-500:][::-1] for idx in top_indices: keyword feature_names[idx] score mean_scores[idx] # 入库注意此处应使用 bulk_create 批量插入非单条 save KeywordResult.objects.create( city_idcity_id, keywordkeyword, tfidf_scorefloat(score), raw_frequencyint(np.sum(tfidf_matrix[:, idx].toarray() 0)), # 出现文本数 source_countlen(texts) )提示max_features10000是内存与精度的平衡点。实测中10 万条旅游文本经分词后约产生 8 万候选词设为 10000 可覆盖 92% 的 TF-IDF 得分分布同时将TfidfVectorizer内存占用从 2.1GB 降至 380MB。这是在 4GB 内存服务器上稳定运行的关键参数。5. 部署与验证用 Waitress Nginx 实现生产级 B/S 访问5.1 Django 项目配置关闭调试、设置静态资源路径生产环境必须禁用DEBUGTrue否则暴露敏感路径和 SQL 错误详情# settings.py DEBUG False ALLOWED_HOSTS [your-domain.com, 192.168.1.100] # 替换为实际域名或 IP # 静态文件收集CSS/JS/图片 STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, staticfiles) # collectstatic 输出目录 # 媒体文件用户上传的原始文本文件 MEDIA_URL /media/ MEDIA_ROOT os.path.join(BASE_DIR, media)执行静态文件收集python manage.py collectstatic --noinput5.2 Waitress 启动与 Nginx 反向代理解决 Windows 下的长连接问题Windows 环境下Django 自带runserver仅限开发。生产必须用 Waitress微软官方推荐的 Windows WSGI 服务器pip install waitress waitress-serve --host127.0.0.1 --port8000 --threads4 myproject.wsgi:applicationNginx 配置反向代理nginx.confupstream django_app { server 127.0.0.1:8000; } server { listen 80; server_name your-domain.com; location /static/ { alias /path/to/your/staticfiles/; expires 1y; add_header Cache-Control public, immutable; } location /media/ { alias /path/to/your/media/; } location / { proxy_pass http://django_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; # 关键启用长连接避免 StreamingHttpResponse 中断 proxy_http_version 1.1; proxy_set_header Connection ; } }提示proxy_http_version 1.1和proxy_set_header Connection 是StreamingHttpResponse正常工作的前提。若缺失Nginx 会以 HTTP/1.0 协议转发导致流式 CSV 下载在传输中途被切断。这是 Django Nginx 部署中关于流式响应最隐蔽的故障点。5.3 验证关键词分析服务是否就绪三步终端检查法检查数据库连接python manage.py dbshell mysql SELECT COUNT(*) FROM rawtextentry WHERE city_id (SELECT id FROM city WHERE name成都);确认有测试数据。触发一次分析任务python manage.py shell from analysis.tasks import calculate_keywords_for_city calculate_keywords_for_city(1) # 假设成都 city_id1curl 验证 API 响应curl -X GET http://localhost:8000/api/keywords/?city成都 | head -n 10 # 应返回 JSON 格式关键词列表 curl -I http://localhost:8000/api/keywords/export/?city成都 # 应看到 HTTP/1.1 200 OK 和 Content-Disposition 头若第 3 步curl -I返回HTTP/1.1 301 Moved Permanently说明 Nginx 未正确代理需检查location /块是否遗漏proxy_pass。本文还有配套的精品资源点击获取
返回列表