
简介这是一份基于Python的旅游网站数据分析及可视化期末大作业源码包面向数据分析、爬虫与可视化方向的高校学生也适合需要参考完整项目流程的初学者。包内以36个HTML可视化页面、3个Python脚本、3个Jupyter Notebook和8个CSS样式文件为主体配套CSV数据、图片及说明文档覆盖从数据处理、景点热度分析到多维度图表展示的完整链路。项目围绕2020年五一全国热门旅游景点、各省景区等级、门票销售等真实数据展开包含地图、漏斗图、水球图、词云图等多种交互式可视化模板适合用于课程设计或期末答辩演示。资源共74个文件压缩包大小13.62MB目录结构清晰包含模板页与静态资源分类。目前已有616人学习下载项目经过严格调试确保可运行评价分达95分以上对希望快速上手旅游数据分析可视化实战的读者很有参考价值。1. 这份旅游数据分析大作业真正值钱的是把整条链路串起来了做数据分析课程设计的人很多但能把「爬来的数据、清洗脚本、可视化图表、Web展示」完整串成一条链路的大作业不多。这份基于Python的旅游网站数据分析及可视化项目评审分能到95分以上恰恰不是赢在图表有多花哨而是它把数据分析和可视化从Notebook里的草稿一路做到了Flask网页里可点、可看、可交互的成品。包里既有去哪儿2020年五一旅游数据CSV也有三个Python脚本、两个Notebook、几十个HTML图表和一套Bootstrap前端模板。适合正在做期末大作业、需要参照完整流程的人也适合想抄一份「能跑、能讲、能答辩」的实战源码的人——它解决的核心问题很简单拿到一份旅游数据怎么把它变成一整套能展示的分析作品。2. 项目结构与图表生成先把每个文件对应到它该干的活拆这个包的时候第一件事不是打开Notebook看代码而是把整个文件树的角色分清楚。因为很多同学的期末大作业代码和数据都堆在同一个目录里跑起来全靠运气而这份资源最大的优点就是分工明确app.py负责启动整个项目function.py专门处理数据读取和统计逻辑picture.py专职生成各种图表最后统一交付成Flask能渲染的HTML页面。2.1 文件清单里那些英文名拆开对应哪些交付物app.py # Flask 主入口路由和页面渲染都在这 function.py # 数据统计函数被 app.py 和 picture.py 共用 picture.py # 批量生成 Pyecharts 图表的脚本 旅游出行数据分析.ipynb # 分析过程的 Notebook适合答辩时展示思路 飞猪城市热门景点门票数据分析-checkpoint.ipynb # 飞猪数据的补充分析 去哪儿2020年五一旅游数据.csv # 核心数据源后续所有图表都源自这张表 templates/ # HTML 模板目录Flask 默认从这里找页面 index.html # 首页展示项目说明和分析概览 rate.html # 景点评价率水球图页面 zone.html # 地区维度分析页面 sightseeing.html # 热门景点页面 project.html # 项目介绍页 static/ # Bootstrap、jQuery 等静态资源 *.html # 大量预先渲染好的图表页如各省评分、漏斗图、词云图等这套结构对应的是一个非常标准的做法把「数据读取」和「图表绘制」分开Flask 只负责路由和把图表HTML塞进页面模板。function.py里的函数读CSV、做聚合统计picture.py把统计结果转成水球图、条形图、扇形图、词云图、漏斗图最后所有图表以独立HTML或内嵌片段的形式交给templates/下的模板去渲染。这样你在答辩时就能很清楚地讲哪一段是数据清洗哪一段是可视化配置哪一段是Web框架整合。2.2 从 CSV 到统计结果function.py 这一层做了什么数据源是「去哪儿2020年五一旅游数据.csv」从文件名能看出这是2020年五一假期全国热门旅游目的地的景点数据包含景点名称、所在省份、所属地区、星级、评分、评价数量、门票价格这类字段。function.py里最核心的工作就是把这张表读进来按省份、按星级、按价位区间做聚合。常见做法是先用pandas.read_csv读入再做列名规整和缺失值处理因为原始CSV经常会出现「省份」列带空格、「评分」列是字符串带百分号这类问题。import pandas as pd def load_tourism_data(csv_path去哪儿2020年五一旅游数据.csv): df pd.read_csv(csv_path, encodingutf-8-sig) # 列名规整去掉首尾空格统一成下划线命名 df.columns [col.strip().replace( , _) for col in df.columns] # 评分列常见格式是 4.8分需要转成浮点数 if 评分 in df.columns: df[评分] ( df[评分].astype(str).str.replace(分, ).astype(float) ) return df def count_by_province(df): 统计各省景点数量返回 DataFrame return df.groupby(省份).size().reset_index(name景点数量)这段代码有几个常见的坑要说明第一CSV的编码大概率是带BOM的utf-8-sig直接用utf-8读会在第一列列名上出现\ufeff后面所有按列名操作全翻车第二评分列如果混入「暂无评分」这类文本astype(float)之前必须做过滤或填充第三groupby(省份)之前要先把省份列的空值清掉否则会出现一个「空省份」的统计组。你拿到这份资源后如果发现某个图表的省份数据对不上十有八九是CSV原始编码或者列名规整这一步和你本机环境不一致导致的。2.3 picture.py 批量出图水球图、漏斗图、词云图背后的配置逻辑picture.py是这份资源里最值得抄的部分因为它展示了一个很实用的工程习惯用一个脚本批量产出几十张图而不是在Notebook里一张一张手动跑。2020年五一全国热门景点类型词云图、各省景点评价率水球图、最受小朋友们喜爱的十大景点、不同价位区间消费情况、各省旅游景点门票销售情况……这些图如果靠手写配置工作量非常大但抽象成函数后就变成「传入DataFrame和省份名返回一个渲染好的HTML文件」。from pyecharts.charts import Liquid, WordCloud, Funnel, Bar from pyecharts import options as opts def draw_liquid_chart(rate: float, output_html: str): 绘制景点评价率水球图 rate: 0~1 之间的评价覆盖率 output_html: 输出文件路径 liquid ( Liquid() .add(评价率, [rate], center[50%, 50%]) .set_global_opts(title_optsopts.TitleOpts(title景点评价率)) ) liquid.render(output_html)这段代码背后的逻辑和参数值得多说几句水球图是用来表达单个比率的比如某省景点的评价率所以传入的是一个0到1的浮点数center控制水球在画布中的位置默认在正中间如果模板里还叠加了别的图表标题位置参数就要跟着挪。picture.py里所有生成图表的函数都遵循同一个套路——接收统计结果、创建图表对象、调用render输出HTML。这样app.py和Notebook只要调函数就能复用同一套图表配置是最值得保留的工程习惯。3. 核心数据处理脚本数据分析链路中的指标口径与参数陷阱这一章往下钻一层讨论「指标怎么算」的问题。旅游网站数据分析看着是画图实际上一大半工作量在指标口径上。评价率怎么定义门票价格区间怎么划分景点星级怎么聚合这些口径不同出来的图和结论完全不一样。这份资源里的function.py和picture.py已经把口径定义好了但你要在答辩时讲清楚为什么这么定就必须知道每个指标背后的计算细节。3.1 景点评分和评价率的统计口径决定水球图的数值对不对广东省-景点评价率水球图.html这类文件核心指标是景点评价率。看命名能推断出口径某个省份有评价记录的景点数量除以该省景点总数。也就是说如果广东省有50个景点其中40个有用户评价评价率就是80%。这个指标比单纯的「平均评分」更能反映一个省份旅游产品的成熟度也是这份作业在数据分析上拿高分的原因——它不只堆图表还在图表之间讲了逻辑。def eval_rate_by_province(df, province): 计算指定省份的景点评价率 口径有评价记录的景点数 / 该省景点总数 prov_df df[df[省份] province] total len(prov_df) if total 0: return 0.0 has_eval prov_df[评价数].fillna(0) 0 return round(has_eval.sum() / total, 4)这里有一个非常隐蔽的坑评价数如果是0算不算「有评价记录」按这份作业的口径严格大于0才算等于0不算。但部分爬虫数据里缺失值会被写成空字符串或者NaN直接用fillna(0)会把缺失和真实0混在一起。我的习惯是先看df[评价数].isna().sum()占整体比例缺失超过5%就考虑删除而不是填充否则水球图的数值会整体偏高。你在答辩现场被问到「为什么这个省评价率这么低」时能说清楚这个口径细节几乎就是送分题。3.2 价位区间消费分析区间划分方式影响结论方向不同价位区间消费情况.html做的是门票价格的区间统计。常见的划分做法有两种一种是按实际分布等频切分用pandas.qcut保证每个区间样本量接近另一种是按业务含义切分比如0到50元、50到100元、100元以上。这份作业从图表命名看使用的是固定区间切分因为图表的解读方向是「哪些价位的景点最受欢迎」固定区间更好向老师解释。bins [0, 50, 100, 200, 500] labels [0-50元, 50-100元, 100-200元, 200-500元] df[价位区间] pd.cut(df[门票价格], binsbins, labelslabels, rightFalse) price_group df.groupby(价位区间, observedFalse).size()pd.cut的两个参数最容易出错rightFalse表示左闭右开即0到50元包含0不包含50如果漏了这个参数默认是左开右闭同一条数据会被划到不同的区间里去。另一个是observedFalse这在pandas 2.0以上版本里必须显式声明否则groupby分类变量时会报警告甚至报错。你在本机跑这份代码如果遇到FutureWarning多半就是这个问题。从分析结论角度看从这张图能看出2020年五一期间50元以下景区占主导这符合疫情期间周边游、短途游为主的大背景。3.3 省份与地区维度的聚合华北、华东、华南这些分区从哪来这份作业里有一组「某某地区景点不同星级数量.html」的图把全国分成了华北、华东、华南、华中、东北、西南、西北、港澳台八个区域。这个分区不是随便写的而是景点数据里自带的属性还是脚本里维护的一张映射表决定了呈现场景。从项目HTML文件名看是有「港澳台地区」这个独立分类的说明数据源本身就存在这个维度。处理这类数据的常见做法是准备一张映射表字段把省份映射到地区。region_map { 北京: 华北, 天津: 华北, 河北: 华北, 山西: 华北, 内蒙古: 华北, 上海: 华东, 江苏: 华东, 浙江: 华东, 山东: 华东, 安徽: 华东, 福建: 华东, 江西: 华东, 广东: 华南, 广西: 华南, 海南: 华南, 河南: 华中, 湖北: 华中, 湖南: 华中, 黑龙江: 东北, 吉林: 东北, 辽宁: 东北, 重庆: 西南, 四川: 西南, 贵州: 西南, 云南: 西南, 西藏: 西南, 陕西: 西北, 甘肃: 西北, 青海: 西北, 宁夏: 西北, 新疆: 西北, 台湾: 港澳台, 香港: 港澳台, 澳门: 港澳台, } def add_region_column(df): df[地区] df[省份].map(region_map) return df这段映射表代码看起来简单但要注意三点第一直辖市北京、上海、重庆和省份同样映射不能漏第二海南在数据源里虽然属于华南地区但在旅游场景下经常被单独拎出来分析这份作业里既有「华南地区景点不同星级数量.html」又有「海南省-景点评价率水球图.html」说明做图时做了双重处理第三map的结果如果出现NaN说明CSV里有你映射表没覆盖到的省份名比如「广西壮族自治区」这种全称必须先做省份名的别名归一化。你在改成自己数据的时候最容易翻车的就是这一层映射关系。4. 用 Flask 串起整套可视化app.py 的路由与模板渲染逻辑数据分析做完图表生成好最后怎么把它们变成「能演示的网站」是这份大作业的另一半价值。app.py用Flask起了本地Web服务把templates/下的页面串成一套可持续访问的站点。你双击某个HTML能看图是一回事能通过app.py启动后从首页点进去逐层浏览是另一回事后者在答辩演示时观感完全不同。4.1 Flask 应用的最小骨架路由、渲染和模板传参from flask import Flask, render_template import function as fn app Flask(__name__) app.route(/) def index(): return render_template(index.html) app.route(/rate) def rate(): df fn.load_tourism_data() result fn.eval_rate_by_province(df, 广东) return render_template(rate.html, rateresult) app.route(/zone) def zone(): df fn.load_tourism_data() region_df fn.add_region_column(df) summary region_df.groupby(地区).size().to_dict() return render_template(zone.html, zone_datasummary) if __name__ __main__: app.run(debugTrue, port5000)这段代码的逻辑是每个路由函数读取CSV、调用function.py里的统计函数、把结果传给render_template指定的HTML模板。index.html是纯展示页不需要传数据rate.html和zone.html需要从Python侧接收计算好的指标再通过模板里的ECharts脚本渲染成水球图和地区分布图。debugTrue在开发阶段很有用改代码不用重启服务但到了答辩演示那几天建议改成debugFalse否则浏览器报错时会直接暴露项目路径和部分源码逻辑。port5000是Flask默认端口如果你本机的5000端口被其他程序占了启动会报Address already in use换个端口就行app.run(debugFalse, port8080)。这里有个细节值得注意每次请求路由时都重新load_tourism_data()读一次CSV在数据量小几千行时是无所谓的但如果你后续换成了几十万行的数据就要把读取结果缓存到模块级变量里避免每次请求都做一次全量IO。4.2 templates 目录下五个页面的分工templates里的五个HTMLindex.html、project.html、sightseeing.html、rate.html、zone.html构成了一个完整的演示叙事首页讲项目背景、项目页讲技术栈和分析思路、景点页展示十大热门景点排名、评价率页用水球图对比各省、地区页用地图和条形图展示区域分布。你答辩时按这个顺序点过去老师跟着你的叙事走比你自己在十几个HTML之间来回切换要有说服力得多。这份资源在「演示动线」上是真下过功夫的这也是它拿高分的原因之一。4.3 static 目录与独立 HTML 的关系为什么有些图直接双击就能看包根目录下那一堆HTML像各省景点等级数量对比.html、2020年五一期间全国最受欢迎的十大景点-漏斗图.html、wordcloud_diamond.html是已经渲染好的成品图表双击就能在浏览器里看。它们的角色是「静态交付物」不需要Flask参与而templates/下带模板语法模板变量和继承的页面必须由Flask渲染。这两类文件容易混实际上用途不同前者拿来直接看、导出、贴到PPT里后者拿来整合成网站。你如果只想快速预览所有图表效果直接双击看成品HTML效率更高如果要改数据重新生成图表再去跑picture.py。5. 避坑指南复现这份旅游分析项目最容易翻车的五个地方拿到任何源码包第一件事不是补环境是「预测哪里会炸」。这份包我在复现过程中遇到过的坑按概率从高到低排基本集中在下面五类。5.1 Pyecharts 版本不符导致水球图白屏现象picture.py生成的HTML在浏览器打开图表区域是空白但页面其他元素正常。原因Pyecharts 新旧版本地图注册和图表初始化的接口变化较大。这个包能在本机跑通大概率是基于特定Pyecharts版本写好的你本机装的是新版Map的add接口变成了add_mapline或需要显式register_map。解决先看requirements.txt或代码里的导入语句确定脚本写的版本区间没给锁版本就把代码里的from pyecharts.charts import Map移到脚本最前面试着导入导入不报错就换Liquid这类没版本争议的图表先排查排除法定位到具体哪个图表挂了再针对性地改接口调用。5.2 Python 版本与 .pyc 缓存不匹配现象运行picture.py时报语法错误或__pycache__里的缓存文件反复干扰运行结果。原因包里保留了picture.cpython-310.pyc和function.cpython-310.pyc说明这个项目大概率是在CPython 3.10环境下开发和运行的。你本机如果用的是3.8或3.9脚本本身没问题但import机制对缓存文件敏感时会出现你改了代码、跑起来的却是旧逻辑的诡异现象。解决删掉整个__pycache__目录让Python在你自己版本下重新生成缓存这是最省心的做法。另外建议新建虚拟环境而不是直接用基础环境能减少很多依赖冲突。5.3 templates 路径写错导致 Flask 找不到页面现象访问http://127.0.0.1:5000/rate时报TemplateNotFound。原因Flask 默认从项目根目录下的templates文件夹找模板如果你把app.py挪到了子目录或者启动Flask的工作目录不对模板路径就会失效。解决确保你在项目根目录下启动python app.py而不是在IDE里乱开终端。检查templates文件夹就在app.py同级的目录里不要把HTML直接放到根目录。5.4 CSV 编码问题导致中文乱码和省份名错位现象图表里省份名变成乱码或者水球图显示省份不存在。原因2020年的爬虫CSV列名带BOM很正常有些列里还混入了\r换行残留。解决读CSV用参数encodingutf-8-sig而不是utf-8拿到DataFrame后先打印df.head()看原始数据。做省份映射前先df[省份].unique()输出所有省份名确认其中有几个带空格先str.strip()再走映射表否则「江苏」和「江苏 」会被当成两个省份。5.5 ECharts 与 Pyecharts 生成的 HTML 模板互相冲突现象一张页面里同时放了Pyecharts生成的图和手写的ECharts脚本结果只有一个图能显示。原因Pyecharts生成的HTML自带完整的ECharts脚本引用如果你的模板又手动引入了一份ECharts两套脚本初始化同一个div冲突。解决检查页面里是不是多次引用了echarts.min.js保留一份把图表容器的id错开遇到实在查不出来的情况直接开浏览器开发者工具看Console报错脚本冲突会在控制台明确报Cannot read properties of undefined之类的信息。6. 把2020年五一数据换成你自己的数据三步替换与验证这份资源能不能用于你手头的新课题关键看数据替换成本。按我的经验只要CSV的列名结构不变替换只需要三步。第一步把去哪儿2020年五一旅游数据.csv替换成你自己的CSV但列名必须和脚本里读到的保持一致。评分、评价数、门票价格这些核心字段名如果不一样就去function.py里把列名映射改掉。df pd.read_csv(你的数据.csv, encodingutf-8-sig) df df.rename(columns{ 景区名: 景点名称, 地市: 城市, 评分值: 评分, })第二步改function.py里的区域映射表。如果你的数据覆盖的省份不同照着region_map的格式增删条目。第三步重新跑picture.py生成图表再把app.py重启逐页点一遍确认数据传到了模板里。从那以后我每次拿到任何一份大作业源码都强制走一遍「删缓存 → 跑脚本 → 查模板 → 验路由」四步流程再做改动这习惯帮我省掉了至少一半的玄学报错。希望帮到你。本文还有配套的精品资源点击获取