ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Django与Selenium的智慧文旅大数据驾驶舱设计与实现

基于Django与Selenium的智慧文旅大数据驾驶舱设计与实现 这个题目能火一点都不意外。旅游景点可视化、大数据驾驶舱、大模型、Agent……这几个词叠加在一起几乎把现在计算机专业毕业设计最吃香的关键词全占了。但作为带过多年毕设的人我第一眼看过去最关心的是学生能不能把它真正落地成一个能跑、能演示、能答辩的系统。今天这篇文章就把它拆开揉碎从技术选型到核心代码从避坑到答辩亮点全部讲透。这套系统的本质是一个基于Python Django构建的Web应用用Selenium做旅游网站的数据采集对景点数据进行清洗、统计、分析和可视化最终在Web端呈现出一个“智慧文旅大数据驾驶舱”。如果再往高档里做还能把大模型和Agent接进去实现“问一句话就出旅游分析报告”的效果。适合的人群也很清楚正在准备计算机毕业设计的学生尤其是数据科学、软件工程、大数据方向的同学或者想快速搭一个文旅数据可视化Demo的开发者。它能解决的问题也很实在——把采集、入库、分析、展示这条完整链路走通让你在答辩时不仅有代码还有一套能说清楚的数据故事。1. 项目拆解与技术选型1.1 这个毕业设计到底在做什么先把这个长标题翻译成人话。所谓“旅游景点可视化分析系统”你可以理解成四个核心任务第一从旅游网站上采集数据。景点名称、所在城市、星级、评分、评论数、门票价格这些都是最基础的字段。如果有条件还可以把用户评论也抓下来后面做舆情分析。第二把采集到的数据洗干净存进数据库。这个步骤看起来不起眼但决定后面所有页面数字准不准。第三基于Django开发后端接口把数据按维度统计出来。比如“热门城市TOP10”“景点评分分布”“评论情感倾向”等等。第四在前端用ECharts等图表库把统计结果画成大屏也就是“驾驶舱”的效果。一打开页面大屏上城市热力图、柱状图、饼图、趋势线一目了然。至于“大数据”三个字在这个本科毕设项目里更多是概念层面的表达。你不需要部署Hadoop、Spark把MySQL或PostgreSQL用明白就够了。真正打动评委的是你的数据链路完整分析有逻辑不是非得上分布式集群。1.2 技术选型背后的为什么很多同学会问为什么选Django而不是Flask为什么采集用Selenium而不是requests为什么前端不搞个Vue ECharts这里我一个个说。选Django是因为这个项目要做的模块太多数据库建模、后台管理、API接口、用户登录、图表数据返回。Flask虽然轻巧但很多东西要自己装、自己配对毕设来说平白增加工作量。Django自带ORM、自带Admin后台、自带分页加上rest_framework之后写API简直是躺平式开发。最关键的是Django的项目结构清晰models.py、views.py、urls.py分得明明白白写进毕业论文里也好阐述。选Selenium也不是说requests不能用。真实旅游网站现在基本都是JS动态渲染你用requests拿到的往往是空壳HTML根本抓不到景点数据。Selenium等于直接开了一个真实浏览器页面怎么加载数据就怎么出现在DOM里。它慢是慢了点但胜在稳定、好理解。而且答辩时你可以录一段“浏览器自动滚动、点击、抓数据”的演示视频视觉效果直接拉满。至于大模型和Agent这两个东西不是必需模块但是重要的加分项。我建议放在系统里的定位是基于已有数据做智能问答和路线推荐。比如用户问“我想去杭州玩三天预算2000推荐哪些景点”系统调用大模型接口再结合数据库里的景点评分和门票价格生成回答。这样一来“智慧文旅”这个帽子才算真正戴得住。2. 核心模块设计与实现思路2.1 数据采集模块Selenium实战这部分是整个系统的“水源”能不能持续稳定地拿到数据全看Selenium脚本写得怎么样。我见过太多人一上来就对着携程、去哪儿硬刚结果不是被封IP就是被验证码卡死。更靠谱的路线是“先易后难”先用一个访问压力小的目标网站比如本地的文旅平台、景区官网跑通了再决定要不要挑战大平台。以采集“某旅游网站景点列表页”为例核心流程大概是打开列表页指定城市、指定页码。 等待页面关键元素出现比如景点卡片区域。 用XPath或CSS选择器提取每个卡片的景点名、评分、评论数、地址、门票链接。 如果是详情页则点击进入提取更细的字段比如营业时间、优待政策、用户评论。 循环翻页直到全部抓完。 写成代码大概是这个样子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 import time driver webdriver.Chrome() driver.get(https://example-travel-site.com/attractions) # 等待景点卡片加载 wait WebDriverWait(driver, 10) cards wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, div.attraction-card))) for card in cards: name card.find_element(By.CSS_SELECTOR, h3.name).text rating card.find_element(By.CSS_SELECTOR, span.rating).text comments card.find_element(By.CSS_SELECTOR, span.comment-num).text print(name, rating, comments) driver.quit()这里有个非常关键的点元素等待。网站的数据是异步加载的如果刚打开页面就去找元素十有八九找不到。所以我习惯用WebDriverWait配合expected_conditions显式等待目标元素出现而不是用time.sleep()硬睡。后者不稳定而且答辩时一旦网络卡顿演示就会很尴尬。另一个要点是“滚动加载”。很多旅游网站是下滑式加载更多不是传统翻页。这时候需要模拟浏览器滚动先把页面滚到底部触发加载接口再等新的卡片出现循环往复。具体做法是driver.execute_script(window.scrollTo(0, document.body.scrollHeight);) time.sleep(2)如果页面有“查看更多”按钮还可以配合WebDriverWait点击。总之采集中间过程中每过一步都要检查“页面是不是已经按预期发生了变化”这个习惯能帮你省掉大量排查问题的时间。2.2 数据清洗与建模采集回来的数据肯定不能直接用。你会发现评分里混着“暂无评分”门票价格写着“免费”或“待定”评论数里有“1.2万”这种中文缩写。这些都需要在入库前清洗掉。我一般先用Pandas做一轮预处理。通常的操作包括把评论数里的“万”转成数字比如“1.2万”变12000把价格字段统一成数值类型免费就是0待定就填NULL把城市字段统一成标准名称避免“杭州”和“杭州市”混在一起去除重复景点同一景点可能在不同页面出现多次。清洗完的数据会整理成DataFrame然后通过Django的ORM批量导入数据库。这里有一个建议不要用pandas.DataFrame.iterrows()一条条插性能太差。直接利用bulk_create批量写入几千条数据几秒内就能搞定。对应的Django模型可以这样设计from django.db import models class ScenicSpot(models.Model): name models.CharField(max_length200, verbose_name景点名称) city models.CharField(max_length50, db_indexTrue, verbose_name城市) rating models.FloatField(nullTrue, verbose_name评分) comment_count models.IntegerField(default0, verbose_name评论数) ticket_price models.FloatField(nullTrue, verbose_name门票价格) address models.CharField(max_length300, blankTrue, verbose_name地址) description models.TextField(blankTrue, verbose_name简介) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table t_scenic_spot verbose_name 景点数据这里给city和rating加上索引非常有用后面做热力统计、按城市筛选时会明显感觉到查询速度的差异。另外建议把description字段留出来后续如果要做大模型问答它就是天然的语料。2.3 Django后端与REST API数据入库之后下一步就是向后端要各种统计结果。Django REST framework写这一层非常方便。你需要做的无非是创建序列化器、写ViewSet或APIView、配置URL路由。比如我们要一个“城市景点数量TOP10”的接口。直接用ORM的annotate统计from django.db.models import Count from rest_framework.decorators import api_view from rest_framework.response import Response from .models import ScenicSpot api_view([GET]) def city_top10(request): data ( ScenicSpot.objects .values(city) .annotate(countCount(id)) .order_by(-count)[:10] ) return Response(list(data))前端拿到这个接口返回的JSON直接塞给ECharts的柱状图一个“城市热门榜”就出来了。当然这只是最简单的统计。你还可以做评分分布、价格区间分布、评论数随时间变化的趋势等等。需要说明的是在实际驾驶舱大屏中这些统计接口往往要频繁被调用所以我建议在Redis里把热点接口的结果缓存几分钟避免每次刷新大屏都去查一遍全表尤其当数据量到几万条以后这个优化是肉眼可见的。接口设计上尽量保持字段名语义清晰。比如统一用“city”“count”“avg_rating”“total_comments”这样前端对接的人或者你自己不会一头雾水。为了毕设答辩的完整性建议给接口加上简单的接口文档Django REST framework自带的交互式文档就够用了不需要额外装Swagger。2.4 可视化大屏设计驾驶舱最直观的呈现就是一大块屏幕上面铺满各种图表。前端的实现方式我推荐直接用一个HTML页面引入ECharts而不是非得上Vue。毕设的核心是讲清楚数据链路前端框架选得越简单你越不容易被前端代码卡住。大屏的整体布局可以这样规划顶部系统标题 当前时间 核心指标总景点数、覆盖城市数、总评论数、平均评分。左侧城市热门榜柱状图 景点评分分布饼图。中间一个大的地图热点图标注各城市景点密度这个最能体现“驾驶舱”的感觉。右侧门票价格区间分布直方图 最新景区评论列表。底部近30天新增景点趋势折线图。每一个图表都用ECharts的option配置生成数据则来自前面写的API接口。比如地图热力图需要各省市景点数量前端通过fetch请求后端接口拿到数据后再用setOption更新图表的series。这里有一点提醒中国地图需要额外注册GeoJSON数据ECharts新版本里已经不内置地图了。如果你不想折腾地图可以用散点图按城市经纬度绘制效果也说得过去。除了图表大屏上最容易被忽略的是“自动刷新”和“首屏加载体验”。我建议让页面每5分钟自动请求一次数据接口体现出“实时驾驶舱”的动态感。加载时可以先展示骨架屏或加载动画避免白屏时间太长。3. 实操过程从零构建核心功能3.1 环境准备与项目骨架开始写代码之前先把环境理清。我推荐使用Python 3.10或3.11Django使用4.x版本搭配Python官方的venv创建虚拟环境。Selenium需要安装Chrome浏览器和对应版本的ChromeDriver这里最稳妥的方式是用webdriver-manager库来自动管理驱动版本省去手动找驱动的麻烦。项目依赖一次性装齐pip install django djangorestframework pandas selenium webdriver-manager requests pymysql redis openai langchain装好之后用django-admin命令创建项目django-admin startproject travel_dashboard cd travel_dashboard python manage.py startapp analysis python manage.py startapp collector这里我会新建两个appanalysis负责数据分析和APIcollector负责爬虫和清洗。这样的分工在写论文时非常有利于分层描述。记得在INSTALLED_APPS里把rest_framework和两个app加进去再配置好数据库连接。很多人会在这儿直接使用Django默认的SQLite但考虑到要在答辩时展示“数据量越大越需要优化”的思考我更建议一开始就配置MySQL或PostgreSQL。3.2 Selenium采集脚本实现采集脚本我建议独立放在collector/management/commands目录下这样可以像Django原生命令一样执行python manage.py crawl_scenic为什么这么做因为爬虫不是只跑一次你可能需要每天定时更新数据。用Django自定义命令封装之后配合系统定时任务或者Celery Beat就能自动执行。实现上把抓取逻辑放在Command类的handle方法里任务开始前打印日志每抓一个城市或一个景点就记录进度任务结束后统计本次新增和更新数量。这样跑起来心里有数答辩时也能拿出“系统每日自动采集数据”的运维截图。采集过程中还需要处理登录态和Cookie。如果目标网站部分接口需要登录可以用Selenium打开浏览器手动扫码登录一次然后把Cookie序列化保存到文件后续采集脚本直接加载Cookie减少重复登录。这种方式简单粗暴但对一般毕设项目完全够用。另外要特别提到Selenium的“网页左右滑动可见”这类操作。很多旅游网站的详情页会在屏幕外挂载大量数据如果你想抓取某些懒加载截图需要模拟左右滚动让卡片进入可见区域。这时可以使用ActionChainsfrom selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys # 模拟横向滚动 ActionChains(driver).key_down(Keys.CONTROL).send_keys(Keys.END).perform()不过说实话这类操作只有在极特殊的页面里才用得上。大部分情况下收集详细页数据的路径还是直接访问URL而不是靠滚动加载。3.3 数据接口与驾驶舱对接后端API写好后前端大屏的编写就相对机械了。我习惯先把静态HTML放Django的static目录里然后在模板中用一个div容器放置ECharts实例用fetch调用接口拿数据填充。核心流程非常简单页面加载完成后请求“爬虫状态”接口展示采集任务总览。请求“统计汇总”接口刷新顶部核心指标卡片。请求“城市排名”接口更新柱状图。请求“评分分布”接口更新饼图。这里我踩过一个坑在开发阶段直接用8080端口访问前端页面Django后端跑在8000端口跨域请求会被浏览器拦截。解决办法有两种要么后端加django-cors-headers要么干脆把前端页面交给Django渲染让静态页面和后端接口同域。我建议用后者少一个依赖不说答辩部署也更简单。3.4 大模型Agent集成进阶如果你想让项目显得更有“含金量”可以在驾驶舱里加入一个智能助手窗口。用户输入任何关于旅游的问题系统通过LLM生成回答并且在回答中引用数据库里真实存在的统计数据。举个实际例子用户问“杭州哪些景点评分最高”系统内部的执行链路其实是Agent先把问题理解成一个查询任务然后从数据库查询评分最高的5个景点再把这些数据拼接成提示词调用大模型生成一段自然语言回答“根据实时数据杭州评分最高的5个景点是西湖4.9分、灵隐寺4.8分……”这个方案的实现我推荐用LangChain的Agent机制或者直接用OpenAI Function Calling方式调用API。为了节省成本也可以在本地部署一套小的深度学习模型做文本分类但毕设阶段没必要直接调付费API更省时间但要注意Key别泄露到前端。具体用LangChain自带的“创建SQL查询Agent”功能时也可以让大模型直接读取数据库Schema自动生成SQL查询。这是可行的因为你的数据表结构很简单字段就那几个。不过为了稳定和降低答辩风险我更推荐“先查库再润色”的固定流程Agent工具负责查出结构化结果LLM只负责把结果转成口语化回答。这样可以保证每次回答都有真实数据支撑不会出现幻觉。4. 常见问题排查与避坑手册4.1 Selenium采集六大坑说句实话Selenium写起来容易跑得稳很难。我总结了过去带学生做爬虫项目时最常见的六个问题每一个都能让人卡一整天。第一是反爬检测。很多网站会检测WebDriver特征比如navigator.webdriver属性。一般应对方法是使用js修改该属性或采用undetected_chromedriver。但在毕设阶段我更建议的策略是降低请求频率设置随机延时尽量减少触发反爬的几率。第二是元素定位失败。元素ID是动态生成的每次刷新都变这时候要优先用相对XPath或者CSS选择器比如用“包含文本”的方式定位按钮driver.find_element(By.XPATH, //button[contains(text(),加载更多)])第三是等待时间不够。页面加载慢、图片多时固定sleep两秒根本不够。替换成WebDriverWait显式等待后自动适应快慢稳定很多。第四是浏览器内存爆掉。脚本长时间跑自动打开的页面越来越多内存占用直线上升。解决方法是定期清理页面缓存或者干脆每隔N次爬取重启一次浏览器。第五是数据不一致。同一景点在不同URL里可能出现多次去重需要按照景点名加城市组合的唯一键。第六是页面改版后脚本全部失效。这是无解的问题只能在写脚本时尽量抽象出公共函数把选择器集中在配置里方便改版时修复。4.2 Django性能与并发问题驾驶舱大屏页面往往需要同时展示十几个图表如果每个图表都查一次数据库接口响应时间会变得很难看。我在实际开发中会用Redis缓存统计结果以“统计接口名查询条件”作为Key缓存5到10分钟。这样就算大屏页面被频繁刷新后端压力也非常小。另外当景点数据量到几万条以后ORM里尽量避免把没有索引的字段用于筛选和排序。比如经常按评分排序就给rating字段加索引经常按城市过滤就给city加索引。这是最简单的性能优化却常常被忽略。还有一个隐藏问题是你可能用到Mysql的group by统计数据量一大就会出现慢查询。解决方式是定期把汇总结果表生成快照大屏直接读快照而不是实时汇总。这个思路放在论文里也很好写叫做“结果集冗余”。4.3 大模型接入的注意事项大模型接入看着新鲜其实风险点不少。首先是成本控制如果做智能助手时用户反复提问API费用可能超预期。最好在代码里增加一个“用户每日提问次数限制”或者只让少数测试账号可以调用大模型接口。其次是模型幻觉问题。如果不把数据库查询结果约束给模型模型可能自己编造“杭州有个景点叫XX”这时候你的系统答非所问答辩现场会很尴尬。所以务必使用我前面提到的“先查库再润色”流程并且在返回给前端时标注数据来源。最后是Key管理。严禁把API Key硬编码在前端JS里或者直接暴露在请求URL中。放在Django后端的环境变量或配置文件中由后端统一代发请求。4.4 排错速查表为了方便快速排查我把常见问题做成一张速查表。遇到某种现象直接看对应解决方案能节省不少时间。现象可能原因解决方案Selenium找不到元素元素未加载完成改用WebDriverWait显式等待页面数据为空页面被验证码拦截手动扫码并转换Cookie或降低采集频率数据导入报错字段类型不匹配先用Pandas处理成统一格式接口返回慢无索引 / 全表扫描增加索引或缓存统计结果大屏空白跨域问题用Django直接渲染前端页面大模型答非所问未约束模型输出先查库得到结构化结果再用LLM润色爬虫被检测WebDriver特征明显使用undetected_chromedriver或请求伪装数据库乱码字符集不统一建库时设置utf8mb45. 经验总结与扩展建议5.1 毕设答辩亮点要提前设计这个项目能讲的故事非常多但你要提前想好哪几个亮点最能打动评委。我个人的建议是抓三条主线。第一是“全链路数据流转”。从Selenium采集到Pandas清洗到MySQL存储再到Django接口和前端可视化整个过程没有任何断点。这条链路本身就是一个很完整的毕设故事。第二是“驾驶舱大屏的交互感”。强调大屏不是死页月而是会自动刷新会响应用户的筛选操作甚至能配合大模型助手进行自然语言交互。有交互感评委的参与度就高了。第三是“智能Agent的可解释性”。如果做了大模型回答要能展示日志“用户提问 - Agent识别意图 - 查景区评分表 - 获得结构化结果 - 生成回答”。这种可解释流程远比黑盒生成要加分。5.2 后续还能扩展什么这个项目完全可以不是“一次性毕设”。如果以后还想继续优化有几个方向值得投入。可以加上“实时爬虫调度模块”用Celery和Redis把采集任务定时化、队列化让系统真正变成一个能持续运营的数据平台。可以加入“舆情分析模块”对评论数据做情感分析展示游客正面和负面评价占比。情感分析既可以用SnowNLP这类现成库也可以接入大模型批量生成情感标签。还可以把数据源从单一网站扩展到多个平台设计一个“数据源管理”页面集中展示不同渠道的采集任务和数据质量。这样“文旅大数据驾驶舱”这个名字就更有底气了。最后聊点自己的体会。做这种项目最容易被卡住的地方往往不是技术难点而是你以为代码写完就万事大吉结果发现前面采集的数据格式乱七八糟或者数据库设计没留扩展余地。所以我的建议是动手写代码之前先把数据结构画清楚把采集字段列成表格把每个图表对应哪个接口列成清单。你要是把这些前置工作做扎实了写代码本身就是搬砖而已。这个项目最出彩的地方恰恰在于你看上去“什么都会一点”——会爬虫、会Web开发、会数据可视化、会大模型应用这本身就是计算机专业毕业生最理想的综合能力画像。
返回列表