尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Python旅游推荐数据分析可视化项目:数据清洗、PyECharts与推荐算法

发布时间:2026/9/24 0:59:45

资讯中心
01
ARTICLE

Python旅游推荐数据分析可视化项目:数据清洗、PyECharts与推荐算法

Python旅游推荐数据分析可视化项目:数据清洗、PyECharts与推荐算法
简介基于Python的旅游推荐数据分析可视化毕业设计项目面向计算机相关专业正在准备毕设的学生及需要实战练习的Python学习者也可作为课程设计或期末大作业使用项目采用Django框架结合MySQL数据库与协同过滤算法构建了完整的旅游推荐系统。压缩包共422个文件整体仅9.36MB结构紧凑主要文件类型涵盖174个JavaScript脚本、59个CSS样式表、27个Python源文件、18个HTML页面、25个Map文件及SQL数据库文件前端页面与后端逻辑一目了然可直接部署运行已有82人浏览学习。项目包含完整源码、数据库和部署说明经过严格调试确保可运行用户端实现登录注册、个人信息管理、景区浏览、评分收藏与个性化推荐管理员端支持景区信息增删改查、类型分类管理以及用户信息管理。对于需要参考完整毕设项目或深入学习Django开发、协同过滤推荐算法落地实践的读者都具有较高的参考价值。1. 打开这份毕设源码之前先想清楚“旅游推荐数据分析可视化”要替你回答什么问题大部分毕设题目里带“推荐”“分析”“可视化”三个词的项目打开源码包的那一刻都会让人有点懵数据在哪儿、先跑哪个文件、图表是从哪张表出来的。这份以 Python 为主线的旅游推荐数据分析可视化项目本质是一条完整的数据流水线把旅游相关的公开数据收进来用 Pandas 清洗成结构化的分析表再算出热门目的地、价格分布、评分趋势这类结论最后用图表把结论摆到网页上。它适合两类人一类是正在做选题、想把“数据采集-分析-展示”一次性串起来的在校生另一类是刚入行的数据分析师想拿一份能部署、能演示的 Python 项目练手。先说清楚结论这份源码能给你的不是算法有多深而是一条从头到尾跑得通、答辩讲得明白的链路。2. 为什么用 Python 而不是现成 BI模块划分与可视化选型2.1 从 3000 行代码讲清楚这套毕设到底由哪几个模块组成拿到压缩包先别急着解压运行要建立“模块思维”。一个典型的旅游推荐数据分析可视化项目代码量大多在 2000 到 4000 行之间看着不多但它横跨了六件事数据采集、数据清洗、数据分析、推荐计算、可视化渲染、Web 部署。这六件事在代码里会体现为六个相对独立的目录或文件组否则后面改一个字段就得牵连半套代码。常见做法是把项目拆成这样的结构travel_project/ ├── data/ # 原始数据和清洗后的数据 ├── src/ # 核心代码 │ ├── collect.py # 数据采集 │ ├── clean.py # 数据清洗 │ ├── analyze.py # 分析聚合逻辑 │ ├── recommend.py # 推荐算法实现 │ └── visualize.py # 图表生成 ├── templates/ # 网页模板HTML ├── static/ # 静态资源js/css/图片 ├── requirements.txt # 第三方依赖 └── app.py # Web 入口为什么推荐按这个结构拆因为答辩时老师问得最多的问题不是“你用了什么模型”而是“你的数据从哪来的、处理完长什么样、这个图是怎么画出来的”。如果把所有代码写进一个大文件那你讲不清楚依赖关系拆成模块后每个文件承担一件事每张图都能追溯到一段独立代码这是毕设项目最容易被认可的组织方式。提醒一句压缩包里如果已经有目录结构先用tree或编辑器里的文件树过一遍别直接双击app.py就跑。先搞清楚代码与代码之间怎么调用是import还是通过外部文件传递数据这会直接影响你后续排错的速度。2.2 可视化选型PyECharts 为什么能扛起答辩大屏可视化这一步选型很关键因为“旅游数据分析”这个题目的交付物不是表格而是能展示在网页上的图表。可选方案不少但针对毕设场景我一般会推荐 PyECharts 而不是 Matplotlib 或者更重的 BI 工具。方案上手难度是否需前端基础图表丰富度适合场景Matplotlib低不需要偏科研图表论文插图PyECharts中低几乎不需要高地图/词云/大屏都行网页展示、答辩大屏Flask 模板手写 ECharts中需要高有前端基础的人Streamlit很低不需要中快速验证原型PyECharts 的核心优势是“Python 生成配置浏览器负责渲染”。你不用写一行 JavaScript只需要在 Python 里把Bar()、Map()这类对象配置好最后调用render()生成独立的 HTML 文件或者直接把图表对象传给 Flask 模板渲染。对数据分析方向的毕设来说这等于把最后一道门槛也拆掉了。我在实际项目里最常用的是 PyECharts 2.x配合 Flask 使用。它和 Pandas 的衔接也很自然DataFrame 聚合出的结果通过to_dict()转成 Python 字典再按 ECharts 要求的格式丢进add()方法就行。相比 Node.js 那套可视化方案少装一个环境少踩一轮坑。2.3 拿到源码后的第一个动作按部署说明把项目跑起来这一步很多人会跳过去直接读代码我建议反过来先把项目跑通再回头读代码。跑通意味着环境、依赖、数据文件、入口文件四个要素全部对齐用这个“可运行状态”作为基线后续改任何一行代码都有对比对象。如果你电脑里还没有 Python先按 Python 安装教程把 3.9 或 3.10 装好。版本别追新3.12 在某些老依赖上会有兼容问题后面避坑章会细说。装好之后先建一个独立的虚拟环境再安装依赖# 进入项目根目录 cd travel_project # 创建虚拟环境Windows 和 macOS/Linux 命令略有差异 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # macOS / Linux: source venv/bin/activate # 安装依赖依赖均来自 PyPI注意使用国内镜像加速 pip install -r requirements.txt # 启动 Web 服务 python app.py这套命令的每一步都有明确目的venv是为了把项目的依赖跟你电脑上其他 Python 项目隔离开避免 Flask 版本或 Pandas 版本互相打架pip install -r requirements.txt则是把你配好的依赖清单一次性装齐装完可以用pip list看版本号。启动app.py后终端会显示类似Running on http://127.0.0.1:5000的输出浏览器打开这个地址能看见页面就说明链路没问题。如果这一步就报错先看三样东西Python 版本是否在 3.9 到 3.11 之间、requirements.txt里的依赖是否安装完整、data目录下是否有程序启动时需要读取的数据文件。八成问题都出在这三处。3. 数据才是毕设的命根子旅游数据的采集、清洗与聚合3.1 数据源与采集能用 CSV 和公开接口就不写爬虫旅游类数据分析的数据来源常见有三类已有数据集CSV/Excel、公开数据接口、自己写爬虫。这三者不是并列关系而是优先级关系。很多毕设翻车就翻在一开始去爬携程或去哪儿爬到一半被反爬卡住最后交不上东西。我带你走最稳的路线。数据源优点缺点适合的毕设阶段本地 CSV/Excel由统计机构或开源社区整理干净、可复现、答辩好解释数据可能偏旧主推做核心分析公开数据接口如景区实时天气、公开的城市旅游热度 JSON新、能体现采集能力字段格式要自己摸加分项爬虫网页解析数据量大、展示效果好反爬、封 IP、法律风险不熟悉反爬时不建议注意写爬虫并不是毕设的必选项。如果你只想让推荐与分析有数据可用第一优先级去找公开数据集比如开源平台上的“全国 A 级景区列表”“城市旅游收入统计”。这类 CSV 数据能直接读进 Pandas省掉一半工作量。如果你确实想写爬虫选一个公开的、有 JSON 接口的站点解析难度远低于处理动态渲染的页面。爬取前务必遵守 robots 协议控制请求频率不要把对方服务器打崩。3.2 用 Pandas 做旅游数据的清洗与聚合完整代码与字段说明数据拿到手后十有八九是脏的。原始数据常见的毛病包括城市名不规范“北京”和“北京市”并存、评分列是字符串、部分景点的评论数为空。这时用 Pandas 做清洗我的习惯是四个动作固定走一遍看一眼 → 去重 → 补缺 → 改类型。下面是核心清洗代码也是这类项目的骨架逻辑import pandas as pd # 1. 读取原始 CSV注意指定编码避免中文乱码 df pd.read_csv(data/attractions_raw.csv, encodingutf-8) # 2. 先看结构和缺失情况再动手 print(df.info()) print(df.isnull().sum()) # 3. 去重同一景点在不同来源里出现多次保留第一条 df df.drop_duplicates(subset[city, name], keepfirst) # 4. 城市名归一化把 Province 和 City 字段统一成标准名称 df[city] df[city].str.replace(省|市, , regexTrue) # 5. 评分和价格转成数值出错时填默认值 df[score] pd.to_numeric(df[score], errorscoerce).fillna(0.0) df[price] pd.to_numeric(df[price], errorscoerce).fillna(0.0) # 6. 评论数字段有缺失用中位数填充避免推荐被极端值带偏 df[review_count] pd.to_numeric(df[review_count], errorscoerce) df[review_count] df[review_count].fillna(df[review_count].median()) # 7. 按城市聚合作为后续可视化和推荐的中间表 city_stats df.groupby(city).agg( avg_score(score, mean), avg_price(price, mean), total_reviews(review_count, sum), spot_count(name, count), ).reset_index() print(city_stats.head()) city_stats.to_csv(data/city_stats.csv, indexFalse, encodingutf-8-sig)这段代码里最关键的是第 5 步和第 7 步。errorscoerce表示转换失败时置为NaN再加fillna兜底能防止一条脏数据让整个聚合结果报错groupby(city).agg()则汇总出每个城市的平均评分、平均票价、总评论量和景点数量这四列会直接变成后续可视化图表的输入。编码用utf-8-sig是给 Excel 看的Win 系统的 Excel 默认用 GBK 打开 UTF-8 文件会乱码。3.3 从清洗到推荐候选集为“旅游推荐”铺好数据底座分析和可视化解决的是“看什么透”推荐解决的是“给用户推什么”。但推荐的输入不是原始表而是一张“用户可选项”的候选集。常见做法是用聚合产生的city_stats再做一层筛选比如筛出评分大于 4.0 的城市作为热门目的地候选再按景点的类型标签自然风光、历史古迹、主题公园等做内容分组方便后续用相似度算法计算推荐结果。# 生成推荐候选集 candidate df[df[score] 4.0].copy() candidate candidate[candidate[review_count] candidate[review_count].quantile(0.3)] # 补充一列类型主标签用于内容相似度计算 candidate[main_tag] candidate[tags].str.split(|).str[0] # 输出候选集 candidate.to_csv(data/recommend_candidates.csv, indexFalse, encodingutf-8-sig)这里的quantile(0.3)是过滤掉评论区几乎没人讨论的景点避免冷门数据干扰推荐。main_tag列是推荐算法的“抓手”后面做内容相似度时就是用这列加景点名称拼成文本再计算向量。把候选集单独存成一个 CSV可以让“数据分析”和“推荐计算”解耦你调试推荐算法时根本不需要重新洗一遍数据。4. 从 DataFrame 到答辩大屏PyECharts 可视化全流程4.1 PyECharts 读取聚合结果柱状图与折线图的代码骨架聚合表到手后就可以开始画图了。绝大多数旅游数据分析项目最核心的图是“目的地热度 Top10”柱状图和“月度游客量趋势”折线图。这两张图几乎能回答答辩老师一半的问题你分析了什么、结论是什么。用 PyECharts 画图的代码模式非常固定建对象 → 配参数 → 塞数据 → 渲染。from pyecharts import options as opts from pyecharts.charts import Bar import pandas as pd # 读取上一步生成的聚合结果 city_stats pd.read_csv(data/city_stats.csv, encodingutf-8) # 按总评论量排序取前 10 个城市 top10 city_stats.sort_values(total_reviews, ascendingFalse).head(10) bar ( Bar() .add_xaxis(top10[city].tolist()) .add_yaxis( 评论总量, top10[total_reviews].tolist(), category_gap30%, # 控制柱间距避免柱子过挤 label_optsopts.LabelOpts(positiontop), # 柱顶显示数值 ) .set_global_opts( title_optsopts.TitleOpts(title热门旅游城市 Top10), yaxis_optsopts.AxisOpts(name评论总量), ) ) # 渲染为 HTML 文件后续可以嵌入 Web 页面 bar.render(templates/bar_top10.html)这段代码里的category_gap和label_opts两个参数是答辩时容易被问到的细节。category_gap控制柱状图每根柱子的宽度比例默认是 20%如果城市名称太长可以调到 40% 到 60%不然名字会重叠。label_optsopts.LabelOpts(positiontop)让数值直接显示在柱顶观众不用对着坐标轴猜数字。渲染出来的 HTML 文件双击就能在浏览器打开也可以后续被 Web 页面引用。4.2 地图、词云和热力图三个可直接替换的图表组件柱状图只是开胃菜“旅游推荐数据分析可视化”的视觉重心通常是地图。PyECharts 里的地图组件可以画中国地图、省份地图也能画散点式的旅行热度分布。下面是城市热度地图的典型写法from pyecharts.charts import Map # 城市名和对应的热度值热度可以用评论总量归一化后得到 map_data top10[[city, total_reviews]].values.tolist() map_chart ( Map() .add( series_name热度, data_pairmap_data, maptypechina, # 指定地图类型为全国地图 is_map_symbol_showFalse, # 不显示散点标记让颜色块更清爽 ) .set_global_opts( title_optsopts.TitleOpts(title全国热门目的地分布), visualmap_optsopts.VisualMapOpts(max_map_data[0][1]), # 颜色映射上限 ) ) map_chart.render(templates/map_hot.html)这里有一个非常坑的细节maptypechina在 PyECharts 2.x 下首次渲染需要从联网的静态资源服务器加载地图数据。如果答辩现场的电脑没联网地图会白屏。解决办法有两个一个是提前在联网环境下渲染好 HTML答辩演示时直接用浏览器打开生成好的文件另一个是手动把地图 JS 文件下载到项目的static目录再在页面里通过script标签引入。这个问题几乎每年都有人踩详见第 5 章避坑。如果想让页面内容更丰富还可以加上词云展示高频景点标签。PyECharts 里引入WordCloud组件把景点的tags字段按“|”拆开统计词频再喂给词云即可。词云适合放进看板的角落既能展示数据又能体现你做了文本层面的分析。4.3 看板页面串联用 Flask 渲染一个多图表可视化看板单张图表文件适合本地看但毕设演示讲究“一个页面把事情讲完”。常见做法是用 Flask 做 Web 容器把多张图表嵌进同一个 HTML 页面形成可视化大屏效果。from flask import Flask, render_template app Flask(__name__) app.route(/) def dashboard(): # 把提前生成好的图表 HTML 内容读进来传给模板 bar_html open(templates/bar_top10.html, encodingutf-8).read() map_html open(templates/map_hot.html, encodingutf-8).read() return render_template(dashboard.html, bar_htmlbar_html, map_htmlmap_html) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)对应的dashboard.html里面用{{ bar_html | safe }}输出图表内容。需要说明的是| safe过滤器必不可少否则 Jinja2 会把 HTML 字符转义图表直接显示成源代码。host0.0.0.0是为了让同一局域网内的其他设备也能访问方便答辩时用投影仪那台电脑展示你笔记本上的页面。debugTrue在开发时能热重载但答辩演示时建议改成debugFalse避免发生异常时浏览器弹出调试器界面。5. 部署运行避坑5 个最常见的翻车现场与修复办法5.1 现象pip install 老失败或者装完导入就报错这是解压源码后第一道坎。症状通常是pip install -r requirements.txt执行到一半卡住或者全部装完后运行python app.py报ModuleNotFoundError: No module named xxx。原因基本出在两方面一是网络原因导致依赖包下载超时二是 Python 版本太新某些依赖没有对应版本。有些老项目里写的是pyecharts1.9.1这种版本在 Python 3.12 下会直接编译失败。我的解决套路是先确认 Python 版本在项目要求的范围内再用国内镜像源安装。如果项目没有明确版本要求把两行命令合到一起跑pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple如果某一两个包死活装不上单独装它一个再逐级看错误输出。不要一次性装一长串错误信息会被淹没。5.2 现象本地跑通换台电脑就白屏或报错这属于毕设里的经典玄学在自己电脑上一切正常拷到答辩用的电脑上就崩。最常见的两个坑是绝对路径和 Python 环境。代码里如果写的是df pd.read_csv(D:/毕设/data/xxx.csv)换台电脑路径就不存在了。解决的标准做法是全部改成相对路径代码只认项目根目录下的data文件夹并且把依赖打包成requirements.txt在另一台电脑上重新创建虚拟环境安装。一句话总结血泪经验自己电脑能跑不算数从零到一在新环境跑通才算数。建议答辩前一周找一台没有 Python 的电脑完整走一遍部署说明。5.3 现象地图图表白屏控制台刷出一堆红色报错PyECharts 画地图时白屏九成是地图数据没加载成功。PyECharts 2.x 版本默认从公共 CDN 加载地图 JS如果网络不通地图区域就是空白。另外还有一种情况图表主体出来了但地图部分只有零星散点这是因为你传的城市名和内置地图的名称对不上比如传了“北京市”内置地图里叫“北京”。处理办法分两步。第一步检查网络如果是演示环境没有外网提前把地图 JS 文件下载到本地static目录并在页面里手动引入第二步检查城市名用pd.unique()拉出数据中的全部城市名逐个核对与 PyECharts 地图名称的对应关系不一致就做一层映射改正。5.4 现象中文在图表里变成方块或乱码Matplotlib 用户最容易遇到这个问题PyECharts 因为是浏览器渲染一般没事。但如果你在项目里混用了 Matplotlib 生成论文插图或者 PyECharts 里用了特殊字体中文就可能显示成方块。原因不是数据问题而是系统缺少对应字库。解决方法是给 Matplotlib 指定中文字体import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # Windows 用黑体 plt.rcParams[axes.unicode_minus] False # 修复负号显示在 macOS 上改成[Arial Unicode MS]在 Linux 服务器上可能需要先安装中文字体包再把字体名填进去。遇到中文方块先挨个试这三种配置九成能解决。5.5 现象浏览器打开 127.0.0.1:5000页面转圈然后显示无法访问页面显示“无法访问此网站”但 Flask 终端明明打印了Running on http://127.0.0.1:5000。这个问题里的原因是端口被占用或者浏览器代理设置异常。先换端口试试这是最快的定位方法# 换个不常用的端口重新启动 python app.py --port 8888 # 或者直接改代码里的 app.run(port8888)如果换端口后能访问那就是 5000 端口被别的进程占了在命令行用netstat -ano | findstr 5000Windows或lsof -i :5000macOS/Linux找到占用进程再处理。还有一个小概率情况是浏览器设置了代理跳过本地地址把127.0.0.1加进“不使用代理”的列表里就好。6. 想拿优毕给“可视化”加一点推荐算法的分量6.1 用 TF-IDF 给景点建内容画像让推荐不再只是 TopN 排序很多版本的“旅游推荐”其实就是把热门 TopN 列出来答辩时老师一问“你的推荐和排序有什么区别”答不上来分数就掉了。加一个最简单的基于内容的推荐代码量不大但能显著提升项目深度。思路是把每个景点的“名称 类型标签 所在城市”拼成一段文本用 TF-IDF 转成向量再计算两两之间的余弦相似度。用户看中某个景点系统就推荐向量相似度最高的几个。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity # 读候选集拼接文本特征 df pd.read_csv(data/recommend_candidates.csv, encodingutf-8) df[text] df[name] df[main_tag] df[city] # 计算 TF-IDF 矩阵 vectorizer TfidfVectorizer() tfidf_matrix vectorizer.fit_transform(df[text]) # 计算相似度矩阵 similarity cosine_similarity(tfidf_matrix) # 输入景点名返回 Top5 推荐 def recommend(spot_name, top_n5): idx df[df[name] spot_name].index[0] sim_scores list(enumerate(similarity[idx])) sim_scores sorted(sim_scores, keylambda x: x[1], reverseTrue) sim_scores sim_scores[1: top_n 1] # 去掉自身 return df.iloc[[i for i, _ in sim_scores]][name].tolist() print(recommend(故宫))这里TfidfVectorizer()不需要额外调参直接按默认值就能跑出合理结果。核心指标是输出的similarity矩阵矩阵的第 i 行第 j 列代表第 i 个景点和第 j 个景点的相似度范围在 0 到 1 之间。如果推荐结果看起来不合理先检查df[text]拼出来有没有空值再检查是不是所有景点的标签都没区分度。答辩时把这一步讲清楚比堆十个图表更有说服力。6.2 给可视化加筛选器让看板从“展示”变成“能用”纯展示的大屏在答辩里只能得“完成分”。加一个下拉框允许用户选择只看某个省份或某个价格区间系统就重新筛选数据并更新图表这就带上了交互功能项目直接上一个档次。实现思路不复杂Flask 路由接收前端传来的参数重新过滤数据后重新渲染图表 HTML返回给前端刷新局部区域。app.route(/filter) def filter_view(): # 从 URL 参数里拿筛选条件 region request.args.get(region, 全部) max_price float(request.args.get(max_price, 500)) # 重新读取候选集并过滤 df pd.read_csv(data/recommend_candidates.csv, encodingutf-8) if region ! 全部: df df[df[province] region] df df[df[price] max_price] # 重新生成柱状图 top df.groupby(city)[review_count].sum().nlargest(5) bar Bar().add_xaxis(top.index.tolist()).add_yaxis(评论量, top.tolist()) chart_html bar.render_embed() # 返回可嵌入页面的 HTML 片段 return render_template(partial.html, chart_htmlchart_html)与之前用render()生成独立 HTML 不同这里的render_embed()会把图表转成可直接嵌入页面的 HTML 字符串。前端用一个下拉框触发页面跳转地址变为/filter?region四川max_price300后端就会重新出图。这套模式能让你在不引入任何前端框架的前提下把静态看板升级成可交互的数据分析页面。我自己的习惯是每次改完数据字段先把中间结果打印出来看一眼形状再往下走回车一按输出的 DataFrame 长什么样永远比想象中更诚实。这份源码能不能帮你拿到高分不取决于它自带多少张图而取决于你能不能把它讲成一个“数据怎么来、分析怎么说、推荐怎么算、页面怎么动”的完整故事。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。