
简介一套基于Python的景区数据分析与可视化期末大作业源码主要面向高等院校Python课程设计、数据分析方向的学生以及希望学习景区数据采集与可视化的小白开发者。项目涵盖数据爬取、清洗、分析与可视化完整流程可有效解决从数据获取到图表呈现的课程设计要求。压缩包共26个文件包含9个Python脚本、9个HTML可视化页面、4个XML工程配置、2张PNG结果图及使用说明等整体大小2.35MB结构清晰便于对照学习。其中Python脚本覆盖爬虫采集、美食数据处理、词云生成、地图绘制等功能HTML页面展示了箱线图、饼图、漏斗图等交互式图表。该项目为个人大作业评审分达95分以上已经严格调试可直接运行还配套使用说明和结果图片便于快速复现与二次开发。已有1048人学习下载对于期末答辩或课程设计具有较高的参考价值。1. 景区大作业项目的模块拆分与运行路径这份基于 Python 的景区数据分析与可视化项目文件结构里藏着一条完整的数据流链路spider.py负责从公开旅游平台采集景区基础信息clear.py做清洗标准化food.py抓取景区周边美食数据最后china.py、jiangsu.py、view.py等绘图脚本把处理好的数据落成 ECharts 模板和 Pyecharts 图表。整套项目拿来做期末大作业或者课程设计能同时覆盖爬虫、数据处理、可视化三个考核点评审分能到 95 分以上说明代码的健壮性和输出成果的完整度都经过了验证。你拿到压缩包先别急着跑建议按spider.py → clear.py → china.py → view.py的顺序读代码这样能快速理解每个文件在整个分析流程里的位置。项目里 cpie.html、box.html、funnel.html 这些已经是生成好的可视化结果即便暂时不想碰爬虫也能直接打开看分析效果。2. 爬虫与数据解析景区基础信息的采集全流程2.1 数据源选型与反爬策略景区数据的采集对象一般是携程、同程、去哪儿这类 OTA 平台的公开榜单页或 POI 搜索接口。常见做法是用requests库直接 GET 页面再用BeautifulSoup配合lxml解析 HTML 结构。这个项目里的spider.py走的就是这条路它需要抓取的核心字段包括景区名称、所在省份、景区等级5A/4A/3A、用户评分、门票价格、地理位置经纬度。注意这类平台通常有简单的反爬机制必须设置合理的User-Agent和请求间隔。import requests from bs4 import BeautifulSoup import json import time headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } url https://example.com/scenic/list response requests.get(url, headersheaders, timeout10) soup BeautifulSoup(response.text, lxml) items soup.select(div.scenic-item) data [] for item in items: name item.select_one(h3.name).text.strip() score item.select_one(span.score).text.strip() level item.select_one(span.level).text.strip() price item.select_one(span.price).text.strip() data.append({ name: name, score: score, level: level, price: price }) with open(scenic_raw.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) time.sleep(2)这段代码的逻辑是先构造带浏览器的请求头然后请求列表页用 CSS 选择器定位每一条景区数据的 DOM 节点提取后写入 JSON 文件。选择器里的h3.name、span.score这些需要根据实际网站的 HTML 结构调整不同平台类名差异很大。请求间隔time.sleep(2)是必须要有的直接决定你能否在连续翻页时不被封 IP。如果发现返回的页面验证码可以在headers里补上Cookie字段先手动登录一次从浏览器开发者工具里复制。2.2 经纬度补全与地理编码项目里view.py和地图类图表需要经纬度坐标但平台列表页通常不直接提供。常见的做法是调用高德地图或百度地图的地理编码 API把景区名称转成经纬度。免费版 QPS 限制是每秒 3 次需要控制调用频率。值得注意的是部分平台详情页会直接吐经纬度可以优先尝试解析详情页的 JavaScript 变量省下 API 配额。import requests import time AMAP_KEY your_amap_web_service_key def get_lng_lat(name, city): params { key: AMAP_KEY, address: name, city: city, output: JSON } try: resp requests.get( https://restapi.amap.com/v3/geocode/geo, paramsparams, timeout5 ) geocode resp.json().get(geocodes) if geocode: location geocode[0].get(location, ) lng, lat location.split(,) return float(lng), float(lat) except Exception as e: print(f[ERROR] {name}: {e}) return None, None这里geocodes返回的是标准地理位置数组location字段是经度,纬度字符串需要 split 后转 float。注意高德 API 返回的坐标是 GCJ-02 坐标系如果后续地图组件用的是 WGS-84 坐标比如一些英文地图库会有几十到几百米的偏移要再走一遍坐标转换函数。大作业评审阶段展示的散点如果稍微偏离真实位置一般不会扣分但坐标系不一致导致的图表偏移要心里有数。2.3 美食数据采集与关联存储food.py和parsefood.py承担的是景区周边美食数据的获取和解析。这里的思路通常是先从spider.py拿到的景区列表里取景点名称再作为关键字去美食平台搜索周边餐饮提取店名、人均消费、评分、菜系分类。这个模块的分析角度是景区热度与周边餐饮消费的关系能增加项目分析维度的丰富度。parsefood.py的职责是解析嵌套 JSON 结构。美食平台接口返回的数据往往是多层嵌套比如result.data.poi_list[].ext_info里才有完整信息直接json.loads后靠多层下标取值容易越界。建议写一个独立的解析函数对缺失字段用dict.get配合默认值兜底。整个爬虫部分需要注意必要时把爬取结果存成 Excel 中间文件方便在clear.py出了问题时不至于重跑全量采集。3. 数据清洗与标准化从脏数据到可分析 DataFrame3.1 缺省值与字段格式的统一处理爬下来的数据大概率不能直接用。评分的缺失、价格的免费字样、经纬度的字符串类型、等级字段的空值这些都要在clear.py里统一处理。数据清洗是整套项目拿到高分的核心评审老师主要看你对真实脏数据的处理能力。import pandas as pd df pd.read_json(scenic_raw.json) # 价格字段清洗统一转为数值型免费记为 0 def parse_price(val): if isinstance(val, str): if 免费 in val: return 0.0 val val.replace(¥, ).replace(元, ).strip() try: return float(val) except ValueError: return None return val df[price] df[price].apply(parse_price) # 评分超过 5 分的视为异常值置为空后做均值填充 df.loc[df[score] 0, score] None df.loc[df[score] 5, score] None df[score] df[score].fillna(df[score].mean()).round(2) # 省份字段去掉省/市后缀便于地图匹配 df[province] df[province].str.replace(省, ).replace(市, )清洗的逻辑要把握几个原则第一能根据业务规则修正的不要直接删行比如免费转 0暂无转 None第二无法修正的缺失值用均值或中位数填充避免图表出现大段空白第三用于地图匹配的字段要统一格式省、市后缀会影响 Pyecharts 地图的 nameMap 匹配导致数据无法映射到对应区域。价格字段处理这里有些数据是成人票 80 元字符串里混杂非数值内容正则表达式re.sub(r[^\d.], , val)是更稳的做法。3.2 归一化处理与透视表聚合评分、价格、热度这三个字段量纲不同做多指标对比前需要归一化。例如把评分从 [0, 5] 映射到 [0, 100] 区间或者用最小-最大缩放把价格映射到 01。归一化之后的字段放在同一个量纲下才适合放进雷达图或者做综合评分排名。这个小步骤经常被忽略却直接影响可视化结果的可读性。df[score_norm] df[score] / 5 * 100 df[price_norm] (df[price] - df[price].min()) / \ (df[price].max() - df[price].min()) df[hot_norm] (df[heat] - df[heat].min()) / \ (df[heat].max() - df[heat].min()) # 综合热度权重分配后排序 df[composite] df[score_norm] * 0.4 \ df[price_norm] * 0.3 \ df[hot_norm] * 0.3这段代码里score_norm的算法是线性缩放适合分布均匀的数据。如果发现评分分布集中在 45 分区间用df[score].rank(pctTrue)做百分位排名比线性缩放更能拉开差距。权重 0.4/0.3/0.3 是按质量优先、兼顾性价比和热度的业务逻辑设定的你可以根据分析主题调整成 0.5/0.2/0.3。最后生成的composite字段能直接作为柱状图排序依据评审时讲清楚权重设计逻辑比单纯贴图得分更高。3.3 多表拼接与地理字段匹配清洗后的景区数据和美食数据要拼接才能做关联分析常见的做法是基于景区名称字段做左连接。但由于两个平台命名不一致西湖可能在景区表里叫西湖风景名胜区在美食表里叫西湖景区。如果直接 merge大部分行会匹配不上。我一般会在连接前做一步关键字段的归一化去掉括号备注、去掉风景名胜区景区这类后缀词再合并。如果仍然存在不匹配就需要维护一个手工映射字典把常见别名映射到主键名。df_scenic[match_key] df_scenic[name].str.replace( 风景名胜区|景区|公园, , regexTrue ) df_food[match_key] df_food[poi_name].str.replace( 风景名胜区|景区|公园, , regexTrue ) df_merged pd.merge( df_scenic, df_food, onmatch_key, howleft, suffixes(_scenic, _food) )regexTrue是 pandas 的字符串替换开关配合|符号可以一次匹配多个词。合并后的结果用suffixes参数区分两张表里同名字段比如score_scenic和score_food如果重名不设置 suffixespandas 会自动加_x、_y可读性会差一点。还有一点容易被忽略howleft保留了左边表的全部记录如果美食表里同一个景区对应多家餐饮连接结果会产生多行冗余此时如果只需要做聚合分析可以先groupby(match_key).agg({price: mean, score: mean})再 merge数据量小很多。4. Pyecharts 地图与组合图表的可视化实现4.1 地图热力图的区域映射逻辑china.py是整套可视化里最有冲击力的模块。它把各省的景区数量、平均评分、平均门票价格映射到中国地图上用 Geo 或 Map 类型呈现。Pyecharts 地图的一个关键点是区域名称的匹配省级地图要求数据里的省份名称与地图内部注册的名称完全一致比如广西要写成广西壮族自治区才能正确映射。from pyecharts import options as opts from pyecharts.charts import Map # province_df: 含 province 和 count 两列 province_data [ [row[province], int(row[count])] for _, row in province_df.iterrows() ] map_chart ( Map() .add( series_name景区数量, data_pairprovince_data, maptypechina, is_map_symbol_showFalse, label_optsopts.LabelOpts(is_showFalse), ) .set_global_opts( title_optsopts.TitleOpts(title全国景区数量分布), visualmap_optsopts.VisualMapOpts( min_0, max_int(province_df[count].max()), is_piecewiseTrue, range_color[#e0f3f8, #abd9e9, #74add1, #4575b4] ), ) ) map_chart.render(china_map.html)data_pair接收的是[[区域名, 数值]]结构maptypechina指定使用中国地图。visualmap_opts里is_piecewise设为 True 会显示图例分段方便用户看区间range_color是从浅到深的渐变色系代表景区数量的递增。如果你的省份数据里包含香港澳门maptypechina默认是包含的但要确认 Pyecharts 版本的地图包版本兼容。另外地图文件是离线加载的Pyecharts 从 v1.x 开始地图体积较大运行前要确认pyecharts和pyecharts_snapshot的版本配套常见问题是老代码用from pyecharts import Map导入报错新版要改成from pyecharts.charts import Map这是期末答辩时最容易翻车的地方。4.2 箱线图与漏斗图的业务解读box.html和funnel.html代表两种不同维度的分析视角。箱线图展示的是不同省份景区评分的分布形态能看到中位数、四分位距和离群点用来回答哪个省份的景区品质更稳定这个问题。from pyecharts.charts import Boxplot province_list [安徽, 江苏, 浙江, 江西] score_lists [] for prov in province_list: subset df[df[province] prov][score].tolist() score_lists.append(subset) box Boxplot() box.add_xaxis(province_list) box.add_yaxis( 景区评分, box.prepare_data(score_lists), tooltip_optsopts.TooltipOpts(triggeritem) ) box.render(score_box.html)prepare_data是 Boxplot 内置的静态方法负责把原始分数列表转换成箱线图五要素。如果某个省份的样本量太少少于 5 个画出来的箱线图会缺失须线这时可以适当合并相邻省份。漏斗图更适合做旅游决策链路的转化分析比如从浏览景区页 → 查看门票 → 购买门票 → 完成游玩四个环节的人数递减用Funnel的sort_descending控制数据排序方式gap参数控制漏斗块之间的间隔间隔太大会让图形脱节评审时视觉效果不好。4.3 词云与饼图的高频词提取cloud.py和pie.html侧重文本分析和占比展示。词云的数据源是景区描述文本或评论关键词核心处理在 jieba 分词和高频词统计那一层。注意分词前要加载自定义词典把飞来峰云海这类景区专有名词预先加入jieba.add_word()里否则会被切成飞来峰。饼图在展示景区等级占比5A、4A、3A时非常直观。这里有个技巧如果一个等级占比过低比如 3A 景区只占 3%饼图标签会重叠可以设置label_optsopts.LabelOpts(formatter{b}: {d}%)让标签只显示名称和百分比不显示具体数值。{b}是系列名称{c}是数值{d}是百分比占位符这个 formatter 的写法会直接影响图表的可读性。5. 期末答辩前的验证方法与扩展技巧5.1 地图地名匹配的验证对照答辩演示前优先检查地图类图表是否有数据漏显示。Pyecharts 地图对地名匹配非常严格最常见的坑是数据里写安徽而地图组件注册的是安徽省。可以跑一个快速脚本验证差异从 Pyecharts 内部取出地图的注册名称和你的数据做差集。from pyecharts.datasets import register_url # 检查数据中的省份是否都能匹配到地图名称 import pyecharts.datasets as ds def check_name_consistency(data_provinces, map_namechina): registed ds.FILENAMES.get(map_name) # 实际使用时需要遍历地图内部的 name_map 做对照 mismatch [p for p in data_provinces if p 省 not in registed and p ! 北京] print(f不匹配项{mismatch})更实际的办法是打开生成的 HTML 文件用浏览器开发者工具看 console 里是否有data is not defined或对应地区的 value 为 null。如果某一省份空白直接打开源码搜索省份名看 JSON 数据里 key 和地图注册名是否一致。最常见的修正方式是做一个翻译字典把安徽映射成安徽省、内蒙古映射成内蒙古自治区这个映射表写死成本很低但能避免评审现场地图白一块的尴尬。5.2 静态图表的本地部署与端口服务render.html和其他 HTML 图表是纯静态页面用浏览器直接打开file://协议就能运行但如果涉及异步加载外部 JS 资源部分浏览器会拦截跨域请求导致图表不显示。建议用 Python 自带模块起一个本地服务cd /path/to/project python -m http.server 8080然后浏览器访问http://localhost:8080/render.html。http.server会监听当前目录为根路径注意所有 HTML 需要放在同一目录下否则路径引用会 404。如果你要临时把图表嵌入 Jupyter Notebook用chart.render_notebook()替代chart.render()可以直接在单元格里输出交互图表这个技巧在做实验记录时特别实用。render.html如果是手动拼接多个图表的综合页面需要确认每个子图表的容器div的id与初始化脚本里的getElementById一一对应常见的白屏问题都出在这里。5.3 从期末大作业到简历项目的三个扩展方向这个项目拿 95 分说明完成度已经很高但如果你想让它在简历上更有竞争力可以考虑三个低成本扩展。第一加入时间维度把爬虫改成支持定时增量采集记录景区热度随时间的变化用折线图展示节假日效应这能体现你理解数据采集的持续性而非一次性脚本。第二增加一个简单的推荐逻辑基于综合评分和价格归一化值用df.nlargest(10, composite)输出 Top10 推荐景区列表并在页面上展示推荐理由文本。第三把清洗后的数据导出成 CSV 并用 Pandas 做一次描述性统计输出到summary.txt文件这能在答辩时直接展示你对数据分布的掌握。需要注意扩展时尽量保持原有文件结构不变新增模块单独命名避免破坏已经调试好的主流程。本文还有配套的精品资源点击获取