ARTICLE DETAIL

资讯详情

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

Python智慧气象监测平台:Flask+爬虫+ECharts毕业设计全流程解析

Python智慧气象监测平台:Flask+爬虫+ECharts毕业设计全流程解析 每年到这个阶段总有一批计算机相关专业的学生被毕业设计折腾得焦头烂额。尤其选 Python 方向的人特别多但很多人的选题要么只做爬虫要么只做可视化很难把一个项目串成一条完整的技术链路。我自己前几年带过一个“Python智慧气象监测与数据管理平台”的毕业设计项目正好是 Flask 做后端、爬虫采数据、pandas 做分析、ECharts 做可视化整套流程走完该踩的坑基本都踩了一遍。这篇就把这个项目的完整拆解写出来从爬虫设计到后端接口再到可视化大屏和答辩准备全部摊开来讲。如果你正在纠结毕业设计选题或者已经定了类似“气象数据分析平台”方向但不知道从哪里下手这篇文章可以直接当参考框架。我尽量把每一步的“为什么这么做”和“怎么做”都说清楚这样你不仅能把系统写出来答辩时也能讲得明白。1. 项目整体设计与技术选型1.1 毕业设计选题背景与核心需求拆解智慧气象监测这个题目能火不是因为名字好听而是它天然覆盖了计算机专业毕业设计最喜欢考察的几个点网络爬虫、数据存储、后端开发、数据分析、可视化展示。这五个词拆开各自都能做一个小作业合在一起就是一个标准的信息管理系统难度可控工作量充足评委也好理解。我接手时先跟学生确认了系统要满足的核心功能大致分成四块第一块是数据采集也就是用爬虫定时抓取指定城市的气象数据包括实时温度、湿度、风速、风向、空气质量、天气现象这些字段。第二块是数据管理抓下来的原始数据要清洗、去重、入库同时提供查询、筛选、导出等基本操作。第三块是数据分析比如计算某段时间的平均气温、温度变化趋势、各类天气出现次数这部分用 pandas 处理比直接写 SQL 方便得多。第四块是可视化展示把分析结果用图表的形式呈现在网页上包括温度曲线、湿度分布、风向玫瑰图、城市天气对比等。这个需求拆解看起来很基础但它是整篇论文的骨架。我见过很多学生一上来就写代码最后文档写得一团乱核心原因就是没在开始时把需求和模块边界划清楚。建议你动手前先花半天时间把这几块列出来每个模块对应什么技术选型、什么输入输出后面写起来会顺畅很多。1.2 为什么选择 Flask 爬虫 ECharts 这套组合技术选型是毕业设计最容易纠结的环节学生经常问“用 Django 行不行”“前端要不要上 Vue”。我的建议很直接如果你是做毕业设计不是做商业项目Flask 是最稳的选择。原因主要有三点。第一Flask 轻量一个 app.py 就能把后端跑起来路由写法和 Python 函数几乎一一对应调试方便。Django 功能全但它自带 ORM、Admin 后台、中间件体系学习成本高写毕业设计项目时很多功能根本用不上。第二Flask 的扩展生态足够覆盖这个项目的所有需求我们后面要用到的 SQLAlchemy、APScheduler、CORS 都有成熟扩展不需要自己造轮子。第三答辩时解释起来容易面试官问“你为什么用 Flask”你可以从轻量、灵活、适合快速迭代三个角度回答比说“看教程选的”体面得多。爬虫模块我选了 requests BeautifulSoup 的组合没有用 Scrapy。Scrapy 适合大规模分布式抓取用到这个项目里属于大炮打蚊子而且 Scrapy 的 Twisted 异步框架调试起来比较绕学生自己写很容易卡住。requests 配合 time.sleep 控制频率BeautifulSoup 解析 HTML逻辑直观出了问题也好排查。等到系统数据量真的大了再往 Scrapy 迁移也不难。可视化端我们用了 ECharts主要是因为它对动态数据的支持好图表类型全而且中文文档友好。虽然现在很多教程吹 DataV、AntV但 ECharts 在校园项目里的普及度和稳定性明显更高。前端布局用 Bootstrap jQuery 就够了不需要额外引入 Vue 或 React否则还要考虑接口跨域、组件生命周期工作量会蹭蹭涨。2. 气象数据获取爬虫模块的设计与实现2.1 数据源分析与爬虫方案选择做爬虫第一步想的不是代码怎么写而是数据从哪里来。毕业设计的气象数据源通常有三个选择公开 API、第三方气象网站、自己造数据。这三个方案各有坑。公开 API 看着最省事但很多免费气象 API 都有访问次数限制有些还需要注册申请密钥审批流程卡得比较久。而且接口返回的字段是固定的如果你想要更丰富的数据比如逐小时风向免费接口未必覆盖得了。第三方气象网站的数据相对全实时性强但反爬策略各不一样有的要处理 Cookie有的页面结构经常变这会直接拖慢你的开发进度。自己造数据最不推荐评委一眼就能看出来你的数据是编的因为你造不出真实的温度波动规律。我最后选的是某个公开天气预报页面作为数据源理由很朴素页面结构稳定、字段完整、访问不需要模拟登录、动态加载内容不多。当然我不能保证这个页面永远不变所以我特别强调一点选数据源之后第一件事是把页面保存到本地写一个解析函数固定住字段提取逻辑。这样就算线上页面改版你已经有一份“历史快照”可以用来开发和测试。2.2 核心爬虫代码实现与字段设计爬虫模块的代码不宜写得过复杂核心是“请求页面、解析内容、结构化存储”三步。我写的爬虫类结构大致是这样import requests from bs4 import BeautifulSoup import pandas as pd import time import random class WeatherSpider: def __init__(self): self.session requests.Session() self.session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }) self.base_url https://example-weather-site.com/city/beijing def fetch_page(self): resp self.session.get(self.base_url, timeout10) resp.encoding utf-8 return resp.text def parse_page(self, html): soup BeautifulSoup(html, html.parser) row {} row[city] 北京 row[temperature] soup.select_one(.temp).text.strip() row[humidity] soup.select_one(.humidity).text.strip() row[wind_direction] soup.select_one(.wind-dir).text.strip() row[wind_power] soup.select_one(.wind-power).text.strip() row[weather] soup.select_one(.weather-desc).text.strip() row[collect_time] time.strftime(%Y-%m-%d %H:%M:%S) return row def run(self): html self.fetch_page() item self.parse_page(html) return item这段代码的关键不是选择器写得有多花哨而是它体现了一个完整爬虫的基本规范和字段设计思路。每一个字段对应页面上一个确定的位置解析之后统一生成字典结构方便后续转 DataFrame 入库。这里要提醒一句实际网页的 CSS 类名很可能和我示例不一样你需要用浏览器的开发者工具检查元素找到自己需要的字段。这是爬虫开发最花时间的地方也是最能体现动手能力的环节。字段设计方面我建议至少包含城市、温度、湿度、风向、风力、天气现象、采集时间这七个字段。不要省也不要贪多。省了后面数据分析素材不够加太多字段比如 PM2.5 每个城市都有的话也行但解析成本会上升而且历史数据未必完整。2.3 爬虫稳定运行的关键细节爬虫跑一次不叫成功连续跑一个月不出问题才算稳定。我在这个项目里花了最多时间调试的不是解析逻辑而是一些很快就会被忽略的细节。第一个是请求频率控制。真实气象网站的服务器扛不住高频访问如果你每秒钟请求一次用不了几分钟 IP 就可能被限制后面再爬全是 403。我在代码里加了一个随机延时间隔设成 3 到 6 秒既不会让数据采集太慢也不至于触发反爬。不要小看这句 time.sleep(random.uniform(3, 6))它被写在所有爬虫教程里但能坚持用到的人不多。第二个是超时和重试。网络请求永远可能失败这和你代码质量无关。建议给 requests.get 设置 timeout 参数并配合一个简单的重试机制def fetch_page_with_retry(self, retries3): for i in range(retries): try: return self.fetch_page() except requests.RequestException as e: print(f第 {i1} 次请求失败{e}) time.sleep(2 * (i 1)) return None重试间隔用递增方式可以避免在服务端还没有恢复正常时反复打请求。超时时间我设的是 10 秒这个值根据你数据源的响应速度调整太短容易误判失败太长会让定时任务堆积。第三个是定时任务的幂等性。我们后面会用 APScheduler 每隔一小时自动采集一次数据但如果某次采集因为网络原因跑了十分钟下一轮任务又启动了就可能重复采集。解决办法是在采集前检查数据库里是否已有同一时刻、同一城市的记录有就跳过。这个逻辑虽然简单却能让你的系统从“能跑”变成“稳定跑”。3. 数据分析与后端接口设计3.1 数据清洗与统计分析思路爬虫拿到的是原始数据里面可能会有空值、重复值、异常值直接拿来展示会被评委问住。我处理项目时按“先清洗、后分析、再汇总”的顺序做了一套标准流程。先用 pandas 把数据库中的数据读成 DataFrameimport pandas as pd df pd.read_sql(SELECT * FROM weather_data, engine) df[temperature] pd.to_numeric(df[temperature], errorscoerce) df[humidity] pd.to_numeric(df[humidity], errorscoerce) df df.drop_duplicates(subset[city, collect_time], keeplast) df df.dropna(subset[temperature, humidity])这里有两个容易被问到的点。第一个是温度转数值时用 errorscoerce因为爬下来的原始字段可能是字符串甚至带单位直接转 numeric 会报错用 coerce 会自动把解析不了的变成 NaN之后再统一丢弃。第二个是去重的 subset 指定了 city 和 collect_time意思是同一个城市同一时刻只保留一条记录这是爬虫重复运行后的兜底保障。清洗完之后就是统计指标。这个项目里我做了几组基础分析温度统计按小时和按天分别计算平均温度、最高温和最低温湿度统计计算平均湿度以及最高最低湿度天气现象频次统计比如一个月里晴天、多云、下雨各出现几天风向频率统计把八个方向的风向出现次数算出来后面用来画风玫瑰图。这些统计用 pandas 的 groupby 和 agg 几行就能解决daily_temp df.groupby(df[collect_time].dt.date)[temperature].agg([mean, max, min]) weather_count df[weather].value_counts() wind_dir_count df[wind_direction].value_counts()统计完成后把结果转成 JSON 格式返回给前端这一步看似基础但它是“数据分析”和“数据展示”之间的桥梁。如果这步含糊前端拿到的数据格式会乱七八糟图表画不出来。3.2 Flask 后端接口设计与数据库建模后端我用 Flask SQLAlchemy数据库用的 SQLite。有人问为什么不用 MySQL我的答案是SQLite 文件即数据库部署简单不用担心环境配置问题而且对单机项目的数据量完全够用。如果你后续想换 MySQLSQLAlchemy 的 ORM 层可以让切换成本降到最低。数据库表结构我设计得比较克制一张表搞定from flask_sqlalchemy import SQLAlchemy db SQLAlchemy() class WeatherRecord(db.Model): __tablename__ weather_data id db.Column(db.Integer, primary_keyTrue) city db.Column(db.String(50), nullableFalse) temperature db.Column(db.Float) humidity db.Column(db.Float) wind_direction db.Column(db.String(20)) wind_power db.Column(db.String(20)) weather db.Column(db.String(50)) collect_time db.Column(db.DateTime, nullableFalse)字段类型上温度和湿度用 Float风向和天气现象用 String采集时间用 DateTime。注意 collect_time 要建索引因为后续查询基本都是按时间范围和城市过滤的没有索引的数据量一大查询就会明显变慢。接口设计我分了三个主要路由思路很清晰app.route(/api/weather/realtime) def realtime_weather(): city request.args.get(city, 北京) record WeatherRecord.query.filter_by(citycity).order_by(WeatherRecord.collect_time.desc()).first() if not record: return jsonify({code: 1, msg: 暂无数据} return jsonify({code: 0, data: record.to_dict()}) app.route(/api/weather/history) def history_weather(): city request.args.get(city, 北京) days request.args.get(days, default7, typeint) records WeatherRecord.query.filter( WeatherRecord.city city, WeatherRecord.collect_time datetime.now() - timedelta(daysdays) ).order_by(WeatherRecord.collect_time).all() return jsonify({code: 0, data: [r.to_dict() for r in records]}) app.route(/api/weather/stats) def weather_stats(): city request.args.get(city, 北京) stats calculate_stats(city) return jsonify({code: 0, data: stats})这三个接口分别对应“当前实况”“历史曲线”“统计分析”三个功能模块前端页面不管怎么布局数据来源都是这几条路由。我特意在返回格式里统一加了 code 字段0 表示成功1 表示失败这样前端可以统一做异常提示不会因为个别接口报错而白屏。3.3 接口性能优化与异常处理毕业设计的并发量不会高但性能优化这个点一定要提因为答辩时这是加分项。我做了两件小事成本很低但效果明显。第一是给高频查询加缓存。realtime 接口每次刷新都要查询数据库其实热点数据也就最新几条。我用简单的字典加时间戳做了个内存缓存缓存时间 60 秒超过缓存时间才重新查库_cache {time: 0, data: None} def get_realtime_with_cache(): if time.time() - _cache[time] 60: return _cache[data] data query_realtime() _cache[time] time.time() _cache[data] data return data第二是处理常见的请求异常。比如前端传了一个不存在的城市名或者 days 参数非法如果没有拦截就会直接 500。我写了一个简单的错误捕获装饰器把所有接口的异常统一包装成 JSON 返回from functools import wraps def api_exception_handler(func): wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: app.logger.error(str(e)) return jsonify({code: 1, msg: 服务器内部错误}), 500 return wrapper这段代码的核心作用不是修复逻辑而是告诉评委你的系统考虑了非正常情况。实际开发中后端接口出问题不可怕可怕的是问题变成一堆堆的堆栈信息直接甩到前端页面上。有了这个统一异常处理页面至少能友好地提示用户“系统繁忙请稍后再试”。4. 可视化大屏与前端展示实现4.1 图表选型与页面布局设计可视化是这个项目最直观的展示窗口也是很多评委第一眼看到的东西。页面做得好不好看直接影响他们对整个系统完成度的判断。我建议布局采用“上中下”的大屏风格顶部放标题和你自己的学号信息中间区域放实时数据卡片下面放各类图表。具体图表选型方面温度变化用折线图因为它能清楚展示一段时间内的波动趋势湿度分布用柱状图按小时或按天统计天气现象频次用饼图直观展示各类天气占比风向用风玫瑰图ECharts 自带 polar 坐标系可以模拟实现空气质量如果字段充足可以用仪表盘展示当前等级。我做了一个简单的对照表方便你快速决定图表怎么配图表类型适合展示的数据ECharts 实现方法折线图逐小时/逐日温度变化line 系列柱状图各城市温度对比、湿度分布bar 系列饼图各类天气出现次数占比pie 系列雷达图多城市多指标对比radar 坐标系仪表盘当前空气质量等级gauge 系列页面布局上不要堆太多图表挑三到四个核心图表展示就够了。我见过有的学生做了八个图结果每个图都只有一点点数据页面显得很空反而暴露了数据量不足的问题。精做三个图比敷衍做八个图效果好得多。4.2 前后端数据联调与动态刷新前端我用的是 Bootstrap 布局 ECharts 图表数据加载用 jQuery 的 ajax。这一套组合虽然老但胜在稳定而且网上资料多遇到问题容易找到解决方案。实时数据卡片的刷新逻辑很简单function loadRealtime() { $.ajax({ url: /api/weather/realtime, method: GET, data: { city: currentCity }, dataType: json, success: function (res) { if (res.code 0) { $(#temp).text(res.data.temperature °C); $(#humidity).text(res.data.humidity %); $(#weather).text(res.data.weather); } } }); }这段代码的核心是 dataType 指定 json保证响应数据被自动解析成 JS 对象。如果你发现页面数据一直加载不出来先打开浏览器开发者工具的 Network 面板看接口有没有返回 200响应体里的 JSON 结构是不是预期的。这是排查前后端联调问题的第一步。温度曲线图的动态刷新稍微复杂一点因为要先把历史数据数组传进去再调用 setOption 更新function loadHistory() { $.ajax({ url: /api/weather/history, method: GET, data: { city: currentCity, days: 7 }, success: function (res) { if (res.code ! 0) return; let times res.data.map(item item.collect_time); let temps res.data.map(item item.temperature); tempChart.setOption({ xAxis: { data: times }, series: [{ data: temps }] }); } }); }这里我发现一个初学者容易出问题的地方如果直接给 setOption 传完整 option 覆盖图表的图例、网格、坐标轴配置都会被重置导致图表闪烁。正确做法是只传需要变更的 data 部分也就是执行增量更新。如果你在项目中碰到图表不断重绘、动画反复播放大概率就是这个原因。定时刷新我用的是 setInterval每 60 秒调一次 loadRealtime 和 loadHistory。这个间隔要和后端缓存时间保持一致或稍大一点否则你刷新的频率再高拿到的还是缓存数据白白增加请求次数。4.3 可视化组件踩坑记录前端这块看着不复杂但实际操作中我踩过几个比较典型的坑写出来帮你节省时间。第一个是 ECharts 图表在页面加载时宽度为 0。如果图表容器放在一个初始隐藏的 Tab 或弹窗里或者样式加载顺序有问题图表初始化时容器宽度是 0画出来的图就特别窄或者压根不显示。解决办法是在初始化前调用 window.onresize 事件或者干脆在 Tab 切换时手动调用 chart.resize()。第二个是颜色配置。ECharts 默认主题的颜色用在毕业设计项目里会显得偏玩具感我建议在初始化时统一配置一个偏科技蓝的色板比如 #409EFF、#36CFC9、#FFA940 这几个颜色整体视觉效果立刻不一样。具体的颜色搭配可以根据学校答辩的展示环境调整但尽量避开高饱和红绿搭配看久了容易视觉疲劳。第三个是数据为空时的占位提示。如果你刚搭好环境数据库里还没有足够的历史数据图表区域会显示空白评委可能误以为系统有问题。我在页面加载时判断 data 数组长度如果为空就显示“暂无数据等待采集任务运行”文案虽然简单但给人的印象完全不一样。5. 系统部署与答辩准备要点5.1 本地环境准备与项目启动步骤这个项目跑起来对环境的要求其实不高但每年都有不少同学在环境配置上翻车。我把完整的启动步骤整理在这里你按顺序操作基本不会出大问题。第一步安装 Python 环境。建议用 Python 3.8 或 3.10 版本不要太新因为部分依赖库对新版 Python 的支持可能有延迟。安装时记得勾选“Add Python to PATH”把 Python 加入系统环境变量这一步很多人会漏掉漏掉之后在命令行里敲 python 就会提示找不到命令。第二步进入项目目录创建虚拟环境并激活。虚拟环境的好处是隔离依赖不会影响你本地其他 Python 项目python -m venv venv # Windows venv\Scripts\activate # Linux / macOS source venv/bin/activate第三步安装依赖包。我建议把依赖写进 requirements.txt内容大致如下pip install flask pip install flask-sqlalchemy pip install requests pip install beautifulsoup4 pip install pandas pip install apache-scheduler实际安装时直接执行 pip install -r requirements.txt 就行。这里特别提醒pandas 和 flask-sqlalchemy 很容易出现版本冲突如果你遇到安装后导入报错先检查版本号。我项目里最终用的是 pandas 2.0.3、flask 2.3.2、flask-sqlalchemy 3.0.5 的组合兼容性比较稳定。第四步初始化数据库。第一次运行项目前需要在 REPL 或启动脚本里创建数据库表python from app import db db.create_all()如果这一步提示没有表格创建成功的日志别慌去项目目录下看有没有生成 weather.db 文件。有这个文件就说明建库成功。建完库后退出 REPL启动主程序python app.py看到 “Running on http://127.0.0.1:5000” 的输出打开浏览器访问这个地址就可以了。5.2 常见问题排查与修复方案这个项目跑起来之后最容易碰到的问题我整理成了一张表你在开发或答辩准备时可以直接对着查现象可能原因解决办法爬虫请求返回 403请求头缺失或访问频率过高补全 User-Agent加大延时页面显示但图表不出来接口返回的 JSON 结构与前端预期不一致用开发者工具查看 Network 响应数据库没有数据定时任务没启动或爬虫异常先手动跑一次爬虫脚本测试温度显示为 null数据清洗时温度字段转换失败检查原始字段是否含中文单位图表全部挤在一起容器宽度没设或 chart.resize 未调用给容器加固定高度和宽度resizeFlask 启动提示端口被占用5000 端口被其他程序占用修改 app.run(port5001)中文乱码HTML 编码或数据库编码问题爬虫设置 resp.encoding数据库使用 UTF-8还有一个比较隐蔽的问题如果你在爬虫脚本里直接 print 中文在 Windows 命令行下可能会报 UnicodeEncodeError。这个不会影响项目运行但会干扰你调试。解决办法是在脚本开头加一行import sys sys.stdout.reconfigure(encodingutf-8)这些小问题看似琐碎但每一个都是我实际调试过程中真实遇到过的。把这些记下来写进论文的“系统测试与问题分析”章节反而能成为你工作量的证明材料。5.3 答辩现场的演示与讲解建议答辩这件事很多学生把它当成“背稿子”其实评委更想听的是你做项目时的思考过程。根据我带项目的经验演示环节按下面这个顺序讲最容易拿分。先打开系统主页面展示整体界面布局用半分钟讲清楚系统有哪些功能模块。然后现场手动触发一次爬虫采集等十几秒后刷新页面让评委看到新数据出现在实时卡片上这一步能直观证明爬虫模块是真实工作的。接下来切换到历史查询页面选择最近七天的数据展示温度曲线和天气饼图这里可以顺便提一句你做了哪些数据清洗和统计。最后打开数据库文件或管理工具展示数据表里的记录数量和字段结构验证数据确实被持久化存储了。讲解的过程中有一个加分技巧主动暴露一个“小问题”再当场修复。比如你可以说“如果现在网络断开会怎么样”然后演示系统的异常处理机制展示页面如何友好提示错误。评委会觉得你对系统做过的思考远不止代码本身。还有一个细节容易被忽略多准备一份“环境备用方案”。答辩现场的电脑很可能没有 Python 环境如果要求现场演示你可以在答辩前把系统打包成 exe 或者用 Docker 镜像确保对方电脑上也能快速运行。就算最终用不上备而无患。最后再分享一个实用的经验这个项目做完之后不要急着把代码拷进论文附录就结束花一天时间把你踩过的坑整理成文档。这份文档不仅是你写“总结与展望”章节的素材也是你后续面试时最能体现技术深度的底气。气象监测平台只是壳里面那套“爬虫采集—数据清洗—统计分析—可视化展示”的完整流程才是你真正收获的东西。
返回列表