
很多人私信问我像“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(--headlessnew) options.add_argument(--disable-blink-featuresAutomationControlled) options.add_argument( user-agentMozilla/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(optionsoptions) 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_length64, uniqueTrue) province models.CharField(max_length64, nullTrue, blankTrue) lat models.FloatField(nullTrue, blankTrue) lng models.FloatField(nullTrue, blankTrue) class AttractionStat(models.Model): city models.ForeignKey(TravelCity, on_deletemodels.CASCADE, related_namestats) name models.CharField(max_length128) date models.DateField() visitor_count models.IntegerField(default0) revenue models.FloatField(default0.0) avg_rating models.FloatField(default0.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(sourcecity.name, read_onlyTrue) 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_size0.2, random_state42) 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 APIDjango 先调用 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 后后续的切换和筛选全靠前端自己处理这样你的后端压力会小得多页面交互也更顺滑。比如让它一次返回“总量、增量、排行、趋势、地图点位”五个块前端渲染时分别取字段即可。处理完这套之后我对这个毕设题目的评价就四个字值回票价。