旅游推荐数据分析可视化:Python项目从数据清洗到可视化大屏

发布时间:2026/10/12 14:31:05
旅游推荐数据分析可视化:Python项目从数据清洗到可视化大屏 简介面向计算机相关专业毕业生和需要项目实战练习的Python学习者这份旅游推荐数据分析可视化项目基于Python和Django框架使用MySQL存储数据并采用协同过滤算法实现个性化推荐适合作为毕业设计、课程设计或期末大作业。资源包共422个文件体积约9.36MB除27个Python源码文件与18个HTML页面外还包含174个JavaScript脚本、59个CSS样式、数据库脚本、JSON配置及图片素材前后端模块划分明确。目前共有一百三十五人学习下载。项目实现了用户端与管理员端完整功能用户可注册登录、管理个人信息、浏览景区、评分收藏并获取推荐结果管理员可维护景区信息、景区分类以及用户资料支持添加、修改和删除等操作。压缩包内附部署说明和数据库文件下载后可直接运行调试源码注释完整便于二次开发与论文答辩。1. 旅游推荐数据分析可视化为什么先于算法你得先把数据“看明白”很多人把旅游推荐系统当成算法问题以为模型换得越高级推荐就越准。我做过几个旅游数据分析可视化项目后的体会恰好相反效果差多数时候不是模型不行而是数据从一开始就没被“看明白”。价格带严重失衡、旺季流量全挤在头部景点、评分稀疏到用户之间几乎无交集这些问题靠一行统计量看不出来画成图却一眼露馅。这个 python 数据分析项目要解决的正是这一环把景点、订单、评分、浏览日志清洗干净再通过数据可视化把热度、价格、季节性、推荐命中情况全部摊开让推荐逻辑可解释、可调、可交付。适合手里有旅游数据、想把数据分析与可视化用到推荐场景的从业者也适合拿它当第一个完整数据分析项目来练手的学习者。2. 先把数据立住旅游推荐数据集的特征工程与预处理旅游推荐和电商推荐最大的差别在于“标的物”的复杂度。电商推的是标准化商品旅游推的是“目的地时间预算同行人”的组合任何一个维度不对推荐就是无效的。所以这类项目的第一步不是建模而是把数据结构化到能支撑推荐的程度。常见的做法是把原始数据拆成三张表用户表、POI 表、行为表。下面的字段设计是一套基础模板足够跑通整个数据分析与可视化流程。表关键字段说明用户表user_id, age, city, travel_budget, prefer_typeprefer_type 决定初始推荐倾向POI 表poi_id, name, city, category, price, avg_score, score_cnt, lon, lat价格、评分、坐标、类别行为表user_id, poi_id, action_type, action_time, spend_amountaction_type 区分浏览/收藏/下单2.1 字段设计与数据清洗别让脏数据先污染推荐结果拿到数据之后先别急着算相关性第一件事是清洗。旅游数据里最常见的脏数据有三类经纬度为 0 或明显超出城市范围的 POI 记录评分和价格字段出现负值或超常值行为表里同一用户对同一 POI 在几秒内重复点击。前两类直接影响地图点位和价格分布第三类直接影响热度排序的公平性都要在特征工程之前处理掉。import pandas as pd poi pd.read_csv(poi.csv) behavior pd.read_csv(behavior.csv) # 1. 经纬度基础校验境内 POI 的经度约在 73~135纬度约在 18~54 poi poi[(poi[lon].between(73, 135)) (poi[lat].between(18, 54))] # 2. 价格字段去负值和离群值用分位数截断而不是硬编码上限 low, high poi[price].quantile([0.01, 0.99]) poi poi[(poi[price] low) (poi[price] high)] # 3. 行为表去重同一用户对同一 POI 在 10 秒内的重复点击算一次 behavior[action_time] pd.to_datetime(behavior[action_time]) behavior behavior.sort_values([user_id, poi_id, action_time]) behavior[dup_flag] ( behavior.groupby([user_id, poi_id])[action_time].diff().dt.total_seconds() 10 ) behavior behavior[~behavior[dup_flag]].drop(columns[dup_flag])逻辑上第一步用经纬度范围过滤掉脏点位这个范围是“保守校验”不会误删正常数据第二步用 0.01 和 0.99 分位数截断价格避免被少数标价异常的野数据带偏价格带区间第三步的 diff 是看同组内相邻两次行为的时间差小于 10 秒判定为重复点击。参数里的 10 秒不是固定标准真实业务日志可以放宽到 30 秒活动期间流量建议缩到 5 秒避免把真实兴趣误删。2.2 用户-物品矩阵与三类核心特征清洗完之后下一步是构建推荐用的特征。旅游推荐里最常用的是“用户-物品矩阵内容特征”的组合行为表转成矩阵POI 表转成特征向量。矩阵里的值不建议直接用点击次数因为头部景点天然流量大会导致热度型推荐越推越窄。常见做法是加权下单权重 5收藏权重 2浏览权重 1再按用户历史行为总量做归一化。# 行为加权与矩阵构建 weight_map {order: 5, fav: 2, view: 1} behavior[weight] behavior[action_type].map(weight_map) matrix ( behavior.groupby([user_id, poi_id])[weight] .sum() .unstack(fill_value0) ) # 用户行为总量归一化削弱高频用户的权重主导 matrix matrix.div(matrix.sum(axis1), axis0) # 热度特征加权行为数 评分置信度威尔逊区间下界 from math import sqrt def wilson_lower(n, p, z1.96): if n 0: return 0.0 denom 1 z * z / n center p z * z / (2 * n) return max(0.0, (center - z * sqrt(p * (1 - p) / n z * z / (4 * n * n)))) / denom poi[hot_score] poi.apply( lambda r: wilson_lower(r[score_cnt], r[avg_score] / 5.0), axis1 )这段代码里矩阵归一化是容易被忽略的一步。如果不除以行和行为量大的用户会在协同过滤里压制低频用户推荐结果基本被“爱逛的人”带偏。威尔逊区间下界是一个实用的统计技巧它用“评分人数”和“平均分”共同计算热度一个 9.8 分但只有 3 人评的景点热度会被压到不如 9.2 分但 3000 人评的景点。这个思想贯穿整个项目——推荐不能只看均值要看置信度。2.3 切分训练集与评估集时间维度别忽略最后一个预处理步骤是切分数据。旅游推荐最怕“用未来预测过去”如果训练集和评估集随机切分模型会看到用户下单之后的行为评估分数虚高。正确做法是按时间切分行为表按 action_time 排序前 80% 做训练后 20% 做评估。更严格一点按“用户第一次行为之后的时间窗口”切避免新用户的评估被老用户的训练数据污染。behavior behavior.sort_values(action_time) cut_time behavior[action_time].quantile(0.8) train behavior[behavior[action_time] cut_time] eval_set behavior[behavior[action_time] cut_time] # 记录每个用户最早行为时间用于评估时过滤训练期已推荐过的内容 first_seen train.groupby(user_id)[action_time].min().rename(first_seen) eval_set eval_set.join(first_seen, onuser_id)关键参数是 0.8 这个切分点它不是凭感觉定的。先看行为时间分布如果 80% 分位正好落在旺季切换的边界就要挪到 0.85 或 0.75避开“一个旺季的数据被拦腰截断”的尴尬。切完之后一定要检查评估集里用户数占比太少说明训练期数据覆盖过广评估结果会非常不稳定。这一步是后续所有可视化评估的地基地基歪了后面画出来的命中率曲线再漂亮也是自欺欺人。3. 用Python把旅游推荐结果变成可视化从matplotlib到可视化大屏数据准备好之后才进入标题里最重的“可视化”环节。我的习惯是分两层做第一层用 matplotlib 做探索性可视化目的是自己看懂数据、发现脏数据和分布问题第二层用 pyecharts 做展示型可视化把推荐结果和业务结论拼成可视化大屏交付给非技术同事或客户。工具选型不是随意的——matplotlib 适合快速出图、精细控制细节pyecharts 基于 ECharts交互效果好图表类型丰富而且直接输出 HTML部署时不需要前端参与这对一个以 python 数据分析为主的源码项目来说是最省事的方案。3.1 先做三张基础图热度、价格带与季节性第一张是目的地热度 Top20回答“哪些地方在抢流量”。第二张是价格带分布回答“推荐逻辑是不是被低价商品带偏了”。第三张是季节性折线把 POI 的季节属性或月度订单量画出来回答“这个推荐在冬季是不是明显失效”。这三张图是这类项目里最常被业务方追问的图也是评估推荐系统健康状况的底图。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 中文字体部署到 Linux 再换 plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 3, figsize(18, 5)) # 图1热度 Top20hot_score 来自上一章的特征计算 top20 poi.nlargest(20, hot_score) axes[0].barh(top20[name], top20[hot_score]) axes[0].set_title(目的地热度 Top20) # 图2价格带分布 axes[1].hist(poi[price], bins20, color#4C72B0) axes[1].set_title(POI 价格带分布) # 图3月度行为量趋势 monthly behavior.set_index(action_time).resample(M)[weight].sum() axes[2].plot(monthly.index, monthly.values, markero) axes[2].set_title(月度行为量趋势) plt.tight_layout() plt.savefig(output/figures/explore_basic.png, dpi150)三张图放一起看才能真正发现数据问题热度 Top20 里如果超过一半是同一个城市的景点说明推荐范围已经严重收窄价格带直方图如果呈现明显双峰说明业务里存在“低价引流品”和“高价品质品”两个客群推荐策略需要分开月度趋势如果冬季出现断崖而数据里又没有滑雪、温泉这类冬季景点那就不是模型问题是供给缺口。这三张图是后面所有推荐策略调整的参照系每次调完参数都要回来重新看一眼。3.2 用pyecharts拼出可视化大屏的基本套路探索性可视化自己看展示型可视化给别人看。pyecharts 拼大屏的核心套路不复杂每个图表是独立的 Chart 对象最后用 Page 或 Grid 拼到同一个 HTML 里。我一般会写一个 charts.py把每个图的构建函数单独拆出来避免一个文件里堆几百行后面改样式时无从下手。from pyecharts import options as opts from pyecharts.charts import Bar, Pie, Page def make_hot_bar(): data poi.nlargest(20, hot_score)[[name, hot_score]].values.tolist() return ( Bar() .add_xaxis([d[0] for d in data]) .add_yaxis(热度, [d[1] for d in data]) .set_global_opts(title_optsopts.TitleOpts(titlePOI 热度 Top20)) ) def make_city_pie(): data poi.groupby(city)[hot_score].sum().nlargest(10) return ( Pie() .add(, [list(z) for z in zip(data.index, data.values)]) .set_global_opts(title_optsopts.TitleOpts(title城市热度占比)) ) page Page(layoutPage.SimplePageLayout) page.add(make_hot_bar(), make_city_pie()) page.render(output/dashboard.html)Page.SimplePageLayout 是 pyecharts 2.x 里快速拼大屏的布局方式图表按顺序从上到下排布。如果要做一行两列的布局可以换 Grid 或 Page 加自定义 CSS。注意每个图表构建函数里都返回独立的 Chart 对象而不是在函数内部 render这样方便测试、也方便只重绘某一张图。大屏文件是单个 HTML所有图表逻辑打包在里面部署时后端只需要提供一个静态页面不依赖前端框架。3.3 推荐效果评估可视化命中率、覆盖率和可解释性推荐系统不能只看离线指标还要把指标画出来让业务方看。我通常画三样命中率曲线、覆盖率柱状图、推荐理由词云。命中率按评估集里的用户算给每个用户推荐 Top10 列表看评估期内实际下单或收藏的内容有没有落在这个列表里按用户行为量分桶画曲线能看出系统对轻度用户和重度用户的服务差异。覆盖率统计推荐列表里出现过的 POI 占全部 POI 的比例判断推荐是否总在推那 5% 的热门景点。def recall_at_k(recommended, ground_truth, k10): hits 0 total 0 for user, truth in ground_truth.items(): if not truth: continue rec recommended.get(user, [])[:k] hits len(set(rec) set(truth)) total len(set(truth)) return hits / total if total else 0 def coverage(recommended, all_poi_ids): rec_ids set() for rec_list in recommended.values(): rec_ids.update(rec_list) return len(rec_ids all_poi_ids) / len(all_poi_ids)参数 k 是推荐列表长度。业务方关心“用户愿不愿意点开”k 取 5 更贴近首页首屏关心“这次推荐有没有带火更多目的地”k 取 20 更合适。命中率在旅游推荐里通常不会太高因为用户决策周期长、干扰因素多单体酒店和景点类推荐能到 15% 已经是不错的结果不要拿电商标准来要求旅游场景。覆盖率则要看供给策略如果目标是让冷门优质景点被看到覆盖率目标可以定到 50% 以上同时接受命中率下降如果目标是纯转化覆盖率掉到 10% 以下就要警惕了。4. 源码结构与部署说明让项目能交付而不是只能本地跑很多开源项目死在“本地能跑部署就废”这一步。这个标题里标了“源码部署说明”说明它瞄准的不只是一个分析脚本而是一个可以被别人拉下来、按文档跑起来的完整交付物。这类项目我一般按“数据层-特征层-推荐层-可视化层”四层组织目录每层有清晰的入口和出口部署说明里写清楚哪些文件是自动生成的、哪些是必须手动改的。如果没有这一层组织代码里的绝对路径、硬编码参数、中文编码问题会在换机器时一次性爆出来。4.1 目录结构与模块边界我习惯的项目骨架长这样travel_recommend/ ├── data/ # 原始数据和中间数据 │ ├── raw/ # 上游导出的 csv只读 │ └── processed/ # 清洗后的 parquet / csv ├── src/ │ ├── preprocess.py # 数据清洗与特征工程 │ ├── recommend.py # 推荐逻辑热度/协同过滤/内容 │ ├── evaluate.py # 命中率、覆盖率评估 │ └── charts.py # 所有可视化图表构建函数 ├── output/ │ ├── figures/ # matplotlib 静态图 │ └── dashboard.html # pyecharts 大屏产物 ├── config.yaml # 数据路径、参数、切分比例 ├── requirements.txt └── README.md这个结构里有几个边界值得说明。data/raw 目录里的文件是只读的任何清洗脚本都不允许修改原始文件这样才能保证重复运行时结果一致。src 里的四个模块是单向依赖preprocess 不 import 其他模块recommend 只依赖 processed 数据evaluate 和 charts 都依赖 recommend 的输出。如果哪天推荐逻辑要替换成深度学习模型只需要重写 recommend.py 和它产出的数据格式可视化代码一行都不用动。output 目录是每次运行重新生成的README 里写清楚这一点避免使用者误改生成文件后排查半天。4.2 配置与数据接口分离换数据不换代码项目在本地能跑是一回事换一套数据还能跑是另一回事。我踩过的最大坑就是路径和参数写死在脚本里在你自己机器上没问题交给别人之后他第一件事就是问你“data 路径在哪改”。所以配置项应该集中到一个文件里代码里只出现配置键名。# config.yaml data: raw_dir: data/raw processed_dir: data/processed poi_file: poi.csv behavior_file: behavior.csv split: train_ratio: 0.8 min_user_actions: 3 recommend: top_k: 10 weight_map: {order: 5, fav: 2, view: 1} visual: lang: zh# src/config_loader.py import yaml with open(config.yaml, r, encodingutf-8) as f: CONFIG yaml.safe_load(f) def get(key_path, defaultNone): cur CONFIG for part in key_path.split(.): if not isinstance(cur, dict) or part not in cur: return default cur cur[part] return curconfig_loader 用“点号路径”读取配置比如 get(recommend.top_k)比直接读字典少很多样板代码。注意 yaml.safe_load 不要换成 yaml.loadyaml.load 在无 safe 参数时可能执行任意对象构造安全上有风险。配置分离的价值在部署时体现得最明显服务器上只改 config.yaml 里的 raw_dir 和 output_dir不用动任何 Python 代码数据文件命名变了也只改配置不改逻辑。这个习惯养成了后面做任何数据分析项目都会省很多事。4.3 部署说明的四个必写项部署说明不是把 README 复制一遍而是要让一个没跑过这个项目的人在干净环境里用最少的命令跑通。我一般会在部署说明里固定写四块内容环境要求、运行顺序、关键参数解释、常见异常对照。环境要求精确到 Python 版本和操作系统差异比如 pyecharts 在 Linux 无头环境下渲染不需要浏览器但 matplotlib 保存中文图片需要单独安装中文字体。运行顺序要写到命令粒度而且必须是“自上而下复制粘贴就能跑”的顺序。python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install -r requirements.txt python src/preprocess.py python src/recommend.py python src/evaluate.py python src/charts.pyrequirements.txt 里要固定大版本比如 pandas2.0,3.0不要只写 pandas。只写包名会导致一年后别人部署时拉到不兼容的版本然后回来问你“为什么我这个报错你没写”。部署说明里还要写一句“哪些步骤可以安全重复执行”preprocess、recommend、evaluate、charts 都应该是幂等的跑两遍结果一致这样使用者在改参数时可以反复重跑而不心虚。常见异常原因处理ModuleNotFoundError: pyecharts未安装或版本过旧pip install pyecharts2.0图片中文乱码系统缺中文字体安装字体并在 matplotlib 注册图表空白静态资源路径不对将 JS 资源置于 static 目录并用相对路径最后加一个常见异常表把“ModuleNotFoundError”“中文乱码”“图表空白”这三个高频问题写进去部署体验会好很多。这一步是项目从“我能跑”变成“你也能跑”的关键分水岭。5. 避坑指南旅游推荐数据分析可视化项目的5个常见翻车点5.1 地图偏移经纬度画出来和底图对不上现象用 pyecharts 的 Map 画景点分布点位整体偏移有的直接漂到海面上。原因最常见的是坐标系不一致。POI 数据源里混用了 WGS-84 和 GCJ-02 两种坐标而 pyecharts 里的地图对坐标有要求。还有一个容易忽略的点同一条数据里不同来源的景点经纬度精度不一致有的精确到小数点后 6 位有的只有小数点后 3 位画出来后点位会叠成一团。解决统一坐标系用现成转换库不要自己写公式。转换脚本做成 preprocess 的一环在数据清洗阶段就把坐标统一而不是在绘图前临时转换。精度字段要在清洗时检查低于小数点后 3 位的记录建议标出来要么从其他源补全要么在图上用圆点标记而不是精确坐标展示。处理完这一步地图可视化才谈得上点位可信。5.2 中文乱码字体缺失不是玄学现象matplotlib 保存的图里中文全变成方块pyecharts 输出 HTML 在自己电脑上正常发给别人打开乱码。原因matplotlib 的默认字体不含中文字符Linux 服务器上更是经常一个中文字体都没装。pyecharts 的 HTML 在别人机器上乱码是因为图表里用了自定义字体但没有把字体文件或加载地址写清楚。解决matplotlib 在代码开头显式指定中文字体不要每次现查字体名Linux 服务器上要提前安装中文字体包并在 matplotlib 里重新注册。pyecharts 的字体问题常见做法是直接用默认字体或者把字体文件放进项目目录、在 HTML 里引用相对路径。这个坑是“一次配置处处受益”配好了整个项目不会再犯。5.3 pyecharts 渲染卡死数据量一大就白屏现象图表数据从几千条加到几万条打开 HTML 页面转圈最后浏览器崩溃或白屏。原因pyecharts 把全部数据序列化成 JavaScript 对象塞进 HTML数据量大之后页面初次渲染占满主线程。特别是地图类图表每个点位都要参与投影计算数据量一上来就卡。解决展示型图表只放聚合结果不放明细。Top20 就是 20 条城市聚合就是几十条需要看明细时用下钻机制先在页面放城市维度的柱状图点击城市再加载该城市的 POI 列表。聚合逻辑放在 preprocess 或 recommend 阶段完成charts 阶段只负责画图。如果产品上实在要展示全量点位用 effectScatter 做散点图层并打开采样渲染参数点位超过 5000 个时视觉差异不大但渲染压力会小很多。5.4 推荐结果“好看不中用”流行度偏差与冷启动现象离线评估命中率很不错看起来推荐得很准实际上全靠热门景点撑起来。新上线的冷门优质景点一个都没被推荐出去。原因热度加权太重行为矩阵里热门景点天然占主导协同过滤结果被头部景点绑架。评估集里如果也全是热门景点的行为命中率会被虚高。这是典型的“数据可视化看着漂亮业务效果却没起来”。解决热度分和个性化得分分开计算推荐时按比例融合最终得分 0.7 * 个性化分 0.3 * 热度分而不是让热度直接参与排序。评估时把推荐列表里的 POI 分成头部、腰部、长尾三档分别画命中率一眼能看出系统有没有在给长尾景点机会。冷启动的新 POI 在特征工程阶段要单独打标记用内容特征分类、价格、季节和它所在区域的相似 POI 做热度补全而不是让它直接沉底。5.5 部署到服务器图表空白静态资源路径问题现象本地渲染 HTML 一切正常传到服务器后用浏览器打开全是空白控制台报 404。原因pyecharts 渲染出的 HTML 默认引用了本地或 CDN 上的 ECharts 静态资源。本地打开能看到是因为资源文件在你磁盘上传到服务器后如果服务器不放这些资源或者代码里用的是绝对磁盘路径浏览器自然加载不到。解决部署时把 ECharts 的 JS 文件放到项目 static 目录下HTML 里改成相对路径引用。更省事的方案是把所有图表统一渲染成一个 HTMLPage 方式同时把 render 时的资源目录参数指到服务器上真实存在的目录。部署说明里要写清楚这一步很多项目就是在这里因为路径问题“本地好好的一部署就废”。6. 往前一步把推荐的bad case可视化反向清洗数据数据前面几章讲的是把项目跑通、把坑填平最后分享一个真正让推荐效果“上台阶”的实用技巧不做模型优化而是把推荐结果里的 bad case 可视化反向去找数据问题。这个技巧在好几个数据分析项目里比调参收益更大。具体做法分三步。第一步从评估集里找出推荐失败的用户推荐 Top10 全部没有命中或者用户实际选择了推荐列表之外的 POI把这些案例单独导出。第二步把每个 bad case 的用户画像和推荐列表画成对比图用户预算、常去的城市、偏好类型以及推荐列表里 POI 的价格带、城市、分类一眼就能看出推荐逻辑哪里失配。第三步按失配模式分组是预算错配推荐列表全是中高价位但用户是价格敏感型还是地域错配推荐了跨越多省的景点而用户明明只在周边活动。有个项目里我遇到过典型案例一版推荐命中率一直卡在 12% 上不去调了权重、换了算法都没有起色。把 bad case 画出来之后发现所有失败案例里用户最近一个月的浏览记录都包含大量“亲子”关键词但 POI 表里的分类字段只有“景点”“酒店”“餐饮”三个大类“亲子”这个维度在被清洗时丢掉了。后来在特征工程里增加了一个“亲子友好度”的派生特征命中率直接跳到 21%。这个问题的根因根本不在模型就在数据特征缺失。这个技巧的另一个价值是让推荐系统变得可解释。业务方问你“为什么推这个”时你能直接用可视化图回答看这个用户的预算和这个 POI 的价格带是匹配的而这个失败案例是因为当时不知道他带着孩子出行。我把这个“bad case 可视化”的脚本长期留在项目里每次数据更新后都跑一遍因为数据是会变的用户的兴趣迁移、新品类的加入都会让旧的特征工程失效。可视化不只是交付物它也是数据质量的监测工具。希望这个习惯对你也有用希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询