☰
Python携程旅游数据分析大屏系统:从爬虫到Django与机器学习全链路实践
2026/10/7 17:09:20 网站建设 项目流程

很多人私信问我,像“Python 携程旅游数据分析大屏系统”这种题到底能不能选、做完后答辩有没有东西可讲。我直接说结论:这个题目不是一个单一功能,它是一条非常完整的工程链路——Selenium 负责取数,Django 负责后端服务,数据分析和大屏负责结果呈现,机器学习和大模型 agent 负责把“死数据”盘活成可以交互的东西。真要拆开了看,它适合那些愿意多花两周时间去打磨综合能力的人,也正因为它模块多,所以容易做出答辩亮点。

我接触过不少同类型的选题,包括景区客流分析、旅游推荐系统、酒店价格预测之类的。但这个题目最大的价值在于:它把近几年的趋势性技术几乎全覆盖了。你不需要在每个方向上都做到顶尖,只要有一个能用代码跑通、能说清楚原理的子项目,就已经超过大部分只会写 CRUD 的毕业设计了。

1. 题目里每个关键词背后是一套完整的交付物

1.1 先别急着写代码,把题目拆成模块图

大多数同学拿到这种炫技型题目,第一反应就是去装环境、复制代码,结果搞了三天连数据都没跑通。我的习惯是先拿张纸,把标题里的关键词逐个圈出来,对应到具体的交付物。

“Python”是整个项目的基础语言。“Django 框架”意味着你要交付一个能开起来、有页面、有后台的项目,而不是一个孤立的 Python 脚本。它决定了你的工程结构、路由、数据库模型和 API 设计。“Selenium 爬虫”是数据来源,重点解决动态页面取数。“大数据”在毕设里通常不意味着海量数据,而是超出普通 Excel 处理能力的结构化数据,需要你用批量清洗、多表关联、聚合计算来处理。“数据分析”对应图表和指标看板。“机器学习”则需要至少有一个可量化的模型,比如客流量预测或者价格区间分类。“大模型 agent”是最容易包装的亮点,它让你能用自然语言向系统提问,比如“帮我看看 10 月 1 号杭州游客最多的是哪个景区”,系统自动调度后端分析函数生成答案。

这样拆完,你就会发现,表面上是一个题目,实际是四个子项目:爬虫采集子系统、Django 服务子系统、可视化分析子系统、AI 问答子系统。答辩时你随便拿起哪一个都能讲十分钟。

1.2 工作量评估:哪些是核心、哪些是包装

我还想多说一句工作量分配。如果只看难度,爬虫部分其实是最容易“卡死”的,因为网页结构经常变,Cookie 和登录态处理也比较麻烦。但是能不能展示效果,反而看的是数据清洗和大屏设计。数据不干净,图再好看也经不起问。而大模型 agent 是典型的包装型模块,它的价值在于“用起来很新”,实际代码量不一定多,却能让整个系统的可演示性上一个档次。

所以你心里要有个底:把爬虫和数据管道当核心去啃,把大模型 agent 当创新点来包装,其余模块能稳定跑通、页面不崩,就已经是一份不错的毕设工程。

2. Selenium 抓取携程数据:动态页面可用,但不能硬扫

2.1 为什么用 Selenium 而不是 requests

携程页面如果直接用 requests 拿,大概率拿不到想要的列表数据。原因很简单,页面里的酒店卡片、景点评分、销量信息都是通过 JavaScript 异步加载的,初始 HTML 里并没有这些节点。requests 拿到的只是一个空壳,你得花大量时间去逆向接口、拼参数、处理签名,毕业设计阶段没必要这么折磨自己。

Selenium 的思路是直接控制浏览器,让页面自己加载完,再去读取渲染出来的文本和属性。它在模拟真实用户访问方面很有效,代码也直观。基础代码大概长这样:

from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def get_driver(): options = webdriver.ChromeOptions() options.add_argument("--headless=new") options.add_argument("--disable-blink-features=AutomationControlled") options.add_argument( "user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" ) return webdriver.Chrome(options=options) def scrape_attractions(city_url: str): driver = get_driver() try: driver.get(city_url) wait = WebDriverWait(driver, 15) wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".attraction-item"))) items = driver.find_elements(By.CSS_SELECTOR, ".attraction-item") for item in items: name = item.find_element(By.CSS_SELECTOR, ".name").text score = item.find_element(By.CSS_SELECTOR, ".score").text comment_count = item.find_element(By.CSS_SELECTOR, ".comment-count").text print(name, score, comment_count) finally: driver.quit()

这段代码有两个关键点。一是WebDriverWait显式等待,二是在 finally 里退出浏览器。很多同学写爬虫只写主流程,忘了关浏览器,跑几次内存就爆了。

2.2 采数字段的落库设计与频率控制

采集只是第一步,更麻烦的是数据模型怎么设计。我的建议是,不要在爬虫里直接写业务表,先把原始数据存到一份临时表或者 json 文件里,清洗后再导入正式表。字段至少包含:城市、景点/酒店名称、评分、评论数、门票价格、坐标经纬度、采集时间。

同时一定要控制采集频率。Selenium 每开一个页面都要加载完整浏览器,代价比 requests 高得多,并发环境也比较容易触发风控。我实际用的策略是:一次只开两个线程、每次访问后随机 sleep 3 到 6 秒、每个页面循环最多重试两次。这样既够用于毕设展示,也不至于给对方服务器造成压力。这也是一个很现实的经验:爬虫不是跑得越快越好,稳定不被限才是本事。

3. Django 后端:把“数据管道”变成可演示的 Web 系统

3.1 数据模型怎么设计,才不会越写越乱

爬虫数据有了,如果一股脑放在 CSV 里,Django 没法做多条件筛选,可视化接口也写得不顺手。我的做法是用 Django ORM 先建好模型,再通过 management command 做数据导入。核心表大概是城市表、景区统计表、酒店价格表、用户访问日志表这几类。

# models.py 简化示例 from django.db import models class TravelCity(models.Model): name = models.CharField(max_length=64, unique=True) province = models.CharField(max_length=64, null=True, blank=True) lat = models.FloatField(null=True, blank=True) lng = models.FloatField(null=True, blank=True) class AttractionStat(models.Model): city = models.ForeignKey(TravelCity, on_delete=models.CASCADE, related_name="stats") name = models.CharField(max_length=128) date = models.DateField() visitor_count = models.IntegerField(default=0) revenue = models.FloatField(default=0.0) avg_rating = models.FloatField(default=0.0) class Meta: indexes = [ models.Index(fields=["city", "date"]), ]

注意我特意加了city + date的联合索引。数据分析系统最大的问题不是数据量有多大,而是后续按城市、按日期聚合时,没有索引的查询会越来越慢,大屏打开一次要好几秒,演示体验很差。

3.2 接口、缓存和定时任务的分工

Django 部分不要做成传统的模板渲染页面,而是要用 DRF 提供 JSON 接口给前端大屏。这样的好处是,大屏可以随时换实现方案,你也可以单独给大模型 agent 复用同一套接口数据。

# serializers.py from rest_framework import serializers from .models import AttractionStat class AttractionStatSerializer(serializers.ModelSerializer): city_name = serializers.CharField(source="city.name", read_only=True) class Meta: model = AttractionStat fields = ["id", "city_name", "name", "date", "visitor_count", "revenue", "avg_rating"]

然后配合 Redis 做数据缓存。大屏轮询时,凌晨的聚合结果可能一小时才变一次,不需要每次都重新查数据库。定时抓取可以用 Celery + Redis,也可以用 Django 自带的crontab或者系统的 cron。如果你不想引入太多组件,我建议先用 Django management command 配合系统计划任务,等答辩前再换成 Celery,写进 PPT 里档次完全不同。

4. 大屏可视化与机器学习模型:数据要“看得见”还要“算得出”

4.1 大屏指标体系与前端接入方式

大屏不是越高大上越好,而是要看数据层次。我常用的三层结构是:顶部全局指标、中间地理分布、底部趋势排行。全局指标放总游客量、总营收、平均评分、活跃城市数;中间用地图和热力图展示地区热度;底部用折线图看时间趋势,用柱状图看城市排行。

前端最简单的接入方式是 Django 模板 + ECharts。你可以直接在页面里写一个 div,然后通过 fetch 请求/api/dashboard/summary/,拿到 JSON 后再 setOption。不要一开始就上 Vue 全家桶,Node 环境、打包、代理这些东西很容易让毕设卡进度。ECharts 本身足够强大,而且社区案例多,改起来快。

4.2 机器学习模型的选型与效果验证

机器学习部分不需要做得很复杂,但要保证“解释得通”。我推荐做两个方向:

  • 客流量预测:用历史日期、星期、节假日、天气特征、上月同期客流量作为输入,预测未来一周的景区游客数。入门可以先用线性回归或 LightGBM,重要的是做训练集和测试集的切分,不能把当年所有数据一股脑塞进去训练。
  • 旅游偏好聚类:对用户行为数据做 KMeans,归成“亲子游”“学生党”“高消费度假”几个类别,再映射到推荐逻辑。

表达式上,预测模型的评估指标用 MAE、RMSE 就够了。答辩时如果被问“为什么用这个模型”,最简单的答法是:先跑基线线性回归,再看梯度提升模型的误差有没有明显下降,如果有,就说明数据里存在非线性关系。这套思路比直接背算法理论更让人信服。

import pandas as pd from sklearn.model_selection import train_test_split from sklearn.ensemble import GradientBoostingRegressor from sklearn.metrics import mean_absolute_error df = pd.read_csv("visitor_stats.csv") df["is_weekend"] = df["date"].apply(lambda x: 1 if pd.to_datetime(x).weekday() >= 5 else 0) features = ["month", "day", "is_weekend", "avg_rating", "comment_count"] X = df[features] y = df["visitor_count"] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) model = GradientBoostingRegressor() model.fit(X_train, y_train) y_pred = model.predict(X_test) print("MAE:", mean_absolute_error(y_test, y_pred))

这段代码里我特意用了“month、day、is_weekend”这样的原始特征,因为毕设场景下,评委更容易接受“时间特征影响出行”这个逻辑。如果你一下子端出几十个 embedding 特征,反而容易给自己挖坑。

5. 大模型 agent 在旅游数据分析里的落地方式

5.1 自然语言查询的链路设计

“大模型 + agent”这个组合放在旅游分析系统里,最自然的形态是做一个智能问答助手。用户的输入先经过意图识别,判断他是在问总体趋势、城市对比、还是出行建议,然后 agent 调用对应的数据分析函数,最后把结果组织成一段可读文本。

这个链路不一定要让大模型直接操作数据库,那样既慢又不稳定。我采用的是“工具调用”模式:大模型只负责理解自然语言并把请求拆成 JSON 参数,真正的数据查询交给后端的 Python 函数。比如用户问“十一期间杭州游客量日趋势是什么”,前端把问题发给 Django API,Django 先调用 LLM 做意图识别,得到一个 JSON:

{ "intent": "trend", "city": "杭州", "start_date": "2024-10-01", "end_date": "2024-10-07" }

然后你自己的函数就去数据库查数据,生成图表 URL 和文字摘要一起返回。这种设计的好处很实际:大模型回答错了数据,你可以靠后端兜底纠正;页面加载速度也不会因为调用一次 LLM 就卡死。

5.2 提示词工具化:让大模型和你的数据库真正对接

把提示词写好是这个模块的关键。不要用那种让模型自由发挥的提示词,否则它会编造城市名和数字。我会在 system prompt 里明确告诉它:“你只负责抽取用户问题中的城市、日期、分析意图,不要直接回答数据,缺少参数时返回 unsupported。”

同时,为了控制成本,还可以配置一个本地知识库或者规则匹配。如果用户问的是“哪个月适合去三亚”,你不需要完全依赖 LLM,可以先用关键词照常兜底。大模型 agent 在毕业设计里的定位是“功能亮点”,不是“唯一依赖”,这一点答辩时一定要说得清楚。

6. 从被坑到避坑:整套项目里我反复踩过的六个细节

6.1 环境兼容与浏览器驱动版本

最常见的问题就是 selenium 打开后报“session not created: This version of ChromeDriver only supports Chrome version X”。这不是你写错了,而是 Chrome 自动更新后,和你下载的 ChromeDriver 版本对不上了。解决办法很简单:每次跑之前先检查浏览器版本,再下载对应驱动。建议把驱动放在项目根目录,不要放在系统 PATH 里,这样别人拿到你代码后更容易复现。

6.2 数据量爬到一定程度后的存储瓶颈

爬虫连续跑一周,数据可能从几千条涨到几十万条。这时候你就该意识到,Django 里一点点把数据 fetch 到大屏页面是行不通的。大屏接口要提前做按日期、城市、景点分级的聚合查询,把结果缓存到 Redis 里。数据库层面再给大表做分区或者定期归档,才算配得上题目里的“大数据”标签。

6.3 合理控制爬虫频率与合规边界

无论从工程的“稳定性”还是从“可持续开发”的角度,都不建议用高并发去抓取。我亲身试过把协程调到 50 个,结果跑不到十分钟就被限制访问,后面连续一天都被封 IP,之前采集的数据也来不及落库。后来我把频率降到每秒一次,偶尔再加个等待,反而稳稳跑了三周。这里给后来人一个老实建议:毕设项目的目的永远是学习和演示,代码里最好把单次采集量、请求间隔、失败重试次数全做成配置项,既方便展示,也体现工程素养。

6.4 演示效果的包装技巧

最后说一个能直接影响答辩印象分的经验:大屏页面一定要做几套缓存的演示数据,也就是把爬虫成果固化成本地 JSON。真到答辩那天,网络环境不可控,网页可能加载慢,大模型的调用也可能超时。我当时预留了一个“演示模式”开关,所有图表直接读本地快照,保证任何情况下页面能在两秒内打开。这个做法不花多少代码量,但能帮你规避掉现场翻车的大概率事件。

顺带分享一个我常用的 API 接口参考点:大屏的 summary 接口,最好一次返回多个指标,不要每个图表各调一个接口。前端拿到一次完整 JSON 后,后续的切换和筛选全靠前端自己处理,这样你的后端压力会小得多,页面交互也更顺滑。比如让它一次返回“总量、增量、排行、趋势、地图点位”五个块,前端渲染时分别取字段即可。处理完这套之后,我对这个毕设题目的评价就四个字:值回票价。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询