ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python天气数据爬取与可视化系统设计实战指南

Python天气数据爬取与可视化系统设计实战指南 1. 项目概述不少同学一听到“毕业设计”四个字就开始头大尤其是计算机专业的既要写代码又要写论文还担心答辩被问倒。我最近帮一个学弟把“Python天气数据爬取及可视化展示系统”这个选题从零到一完整落地整个过程走下来最大的感触是这个题选得挺聪明它踩中了毕业设计最讨喜的几个点——技术栈主流、数据源好获取、视觉效果好、工作量可控而且答辩时每一个模块都能“有话说”。这个选题到底是什么说白了就是用 Python 写爬虫去采集天气数据把数据存下来再用 Flask 搭一个 Web 系统把数据以图表、卡片、大屏的形式展示出来。它覆盖了爬虫、数据分析、可视化、Web 开发四条线每一块都是 Python 就业市场的热门技能。也正因为覆盖面广它特别适合作为计算机专业、软件工程、数据科学相关方向的毕业设计选题——既能体现编程能力又能展示数据处理的完整思维链。这套系统适合谁参考如果你是正在为毕业设计选题发愁的应届生或者想做 Python 全栈项目来充实简历的初学者又或者想在简历上凑一个“数据采集→处理→可视化”完整项目经历的人这篇博文都能帮你少踩很多坑。我会把整个系统的架构设计、技术选型、核心代码、避坑指南全部拆开来讲照着一步步做一个能跑、能展示、能答辩的完整项目就能落出来。2. 项目整体设计与技术选型2.1 核心模块拆解整个天气数据可视化系统不管标题怎么包装拆开来看本质上是四个模块串联成一条流水线数据采集层、数据存储层、后端服务层、前端展示层。数据采集层干的是“把天气数据拿回来”这件事核心工具是 Python 的 requests 库加解析库。数据存储层负责把采集到的数据落库常见选择是 SQLite 或 MySQL。后端服务层用 Flask 搭一个轻量 Web 应用对外提供接口对内操作数据库。前端展示层负责把数据变成人看得懂的图表和界面用 HTML、CSS、JavaScript 加 ECharts 这套组合就能做出不输大厂可视化平台的效果。这四层各管一段彼此通过接口连接逻辑非常清晰。论文里架构图画起来好看答辩时按层讲解也很有条理这是这个选题的第一个优势。2.2 为什么是 Python Flask 而不是其他组合很多人会纠结Java 的 Spring Boot 也可以做Node.js 的 Express 也行为什么偏偏选 Python FlaskPython 在这件事里有三个不可替代的优势。第一爬虫和数据分析生态最成熟requests、BeautifulSoup、pandas 都是开箱即用的利器写起来比 Java 短一半还多。第二Python 的数据处理能力直接碾压其他语言用 pandas 做数据清洗、聚合统计几行代码就搞定换 Java 要写一大堆样板代码。第三Python 是目前数据科学领域的事实标准用 Python 做毕业设计简历上写“技术栈Python Flask ECharts”HR 一看就知道你走的是数据方向岗位匹配度更高。Flask 对比 Django胜在轻量和灵活。Django 是全家桶ORM、Admin 后台、认证系统全给你配好但正因如此很多逻辑被框架藏起来了答辩时老师问“你这个路由是怎么匹配的”“请求生命周期是怎样的”你可能答不上来。Flask 只有最核心的路由和模板引擎其他东西都是自己拼装你对整个请求流程一清二楚被问深了也心里有底。对于毕业设计而言代码透明度和可解释性比开发效率更重要——因为老师看的就是你对每个细节的掌握程度。2.3 数据源选型API 接口优先网站爬虫兜底这是整个项目里最容易踩坑的地方也是决定项目成败的关键环节。我见过太多人一上来就写 requests 直接爬中国天气网、天气后报这类网站结果要么反爬机制太强被封 IP要么页面结构变了抓取代码瞬间报废。我的建议是优先用正规气象数据 API爬虫只作为补充手段。国内可用的天气 API 有好几个比如和风天气、高德天气接口、心知天气、国家气象信息中心的数据接口。以高德天气接口为例注册一个开发者账号申请 Key就可以通过简单的 GET 请求获取全国上千个城市的天气数据。免费额度对毕设项目来说完全够用。选择 API 有四个好处数据字段规范、返回格式统一大多是 JSON、无需处理复杂的 HTML 结构、代码量大幅减少。更重要的是答辩时老师问“你的数据从哪来”你可以说“通过公开的气象 API 接口进行数据采集”这比“我爬了某某网站”在合规性上妥当得多。那爬虫技术还要不要用要用但要选对场景。比如你想采集历史 30 天的天气数据做趋势分析而 API 免费版只给实时数据和三天预报这时候就需要用爬虫去补充历史数据。或者你想抓某个特定网站的气象科普文章、PM2.5 历史排行等 API 不提供的字段爬虫就有用武之地了。这话我说得直白一点如果你是零基础起步用 Python 爬虫直接抓 HTML 页面解析逻辑从零手写少说也要折腾两三天还容易被网站改动坑一道。而对接 API半小时就能跑通数据获取这一步。所以我的建议是第一步用 API 把整个系统流程跑通项目能交差了学有余力再去写爬虫做补充两条腿走路最稳。3. 天气数据采集模块的实现3.1 申请 API 接入把数据拿到手我用的是高德开放平台的地理编码和天气查询接口免费、稳定、文档清晰。注册账号后在控制台创建应用获得一个 Key就能开始调用。接口调用非常直接使用 requests 库几行代码即可完成import requests # 高德天气查询接口 # citycode 是城市编码例如北京是110000 url https://restapi.amap.com/v3/weather/weatherInfo params { key: 你的高德Key, city: 110000, extensions: all # all 预报数据 实况数据 } response requests.get(url, paramsparams, timeout10) data response.json() print(data)需要特别说明一下extensions这个参数。设置成all时接口返回的数据既包含今天的实况天气温度、湿度、风力、风向也包含未来三天的天气预报。如果只写base返回的是简化版实况数据字段少很多可视化的时候会捉襟见肘。建议直接拉全量后面存库、分析、展示都从容。返回的 JSON 里主要字段包括城市名、实况温度、体感温度、天气现象晴、多云、小雨等、相对湿度、风力等级、风向、未来几天的最高/最低温度等。这些字段足以支撑后续的可视化图表了。3.2 爬虫补充历史数据requests BeautifulSoup如果你确实需要爬虫这块来体现“爬虫”关键词我推荐一个相对好抓的目标——天气后报网站。它的每日天气页面结构比较简单没有太强的反爬机制适合做教学案例。核心思路是拼接目标月份的 URL → 请求页面拿到 HTML → 用 BeautifulSoup 解析表格 → 提取日期、天气现象、温度、风力这些字段存入列表。import requests from bs4 import BeautifulSoup import pandas as pd def fetch_history_weather(url): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } resp requests.get(url, headersheaders, timeout10) resp.encoding resp.apparent_encoding # 关键自动识别网页编码 soup BeautifulSoup(resp.text, html.parser) # 天气表格在 class 为 table 的标签里 table soup.find(table) rows table.find_all(tr) data_list [] for row in rows[1:]: # 跳过表头行 cells row.find_all(td) if len(cells) 4: item { date: cells[0].text.strip(), weather: cells[1].text.strip(), temperature: cells[2].text.strip(), wind: cells[3].text.strip() } data_list.append(item) return data_list # 示例北京 2024 年 5 月的历史天气 history_data fetch_history_weather(http://www.tianqihoubao.com/lishi/beijing/month/202405.html) print(len(history_data))这里有个特别容易坑新手的点网页编码。国内不少网站的页面是 GBK 或 GB2312 编码如果用resp.encoding utf-8去解码解析出来的文本全是乱码。正确的做法是用resp.apparent_encoding让 requests 自动从响应内容里识别真实编码或者直接指定resp.encoding gbk。这个小坑不死不伤但很影响体验属于那种“百度五分钟学会、实际上手搞半小时”的典型问题。3.3 数据解析与字段筛选的细节无论从 API 拿到的 JSON 还是从网页抓下来的表格数据都需要做字段整理和格式统一。我自己的习惯是一进程序就把它转成 pandas DataFrame后面所有清洗和补全操作都用 pandas 搞定。import pandas as pd # 假设 data_list 是前面 API 或爬虫返回的字典列表 df pd.DataFrame(data_list) # 字段标准化只保留后续可视化用得到的列 df df[[city, temperature, humidity, weather, wind_direction, wind_power]] # 类型强转温度转为数值型处理脏数据 df[temperature] pd.to_numeric(df[temperature], errorscoerce) # 删除缺失值 df df.dropna() print(df.info())errorscoerce这个参数能把无法转换的乱数据直接变成 NaN而不是抛异常中断程序。这在处理真实世界的数据时太实用了——你永远猜不到网页里某个温度字段会出现什么奇奇怪怪的值比如“微风转晴”这种混进温度列的文本或者空字符串、单位符号等杂物。先用coerce兜底再用dropna清场数据质量就有基础保障了。4. 数据存储与数据库设计4.1 表结构怎么设计才合理数据拿到手了下一步就是落库。我这里用的是 SQLite主要因为它零配置、单文件、Python 内置支持适合毕业设计这种规模的项目。如果老师明确要求用 MySQL也完全可以在不改业务逻辑的前提下整体替换——只要把 Flask 的数据库连接串改一下SQLAlchemy ORM 会帮你屏蔽底层差异。天气数据表的设计不需要太复杂但字段规划要清晰。我设计了一张weather_data表核心字段如下字段名类型说明idINTEGER 主键自增主键cityVARCHAR(20)城市名称dateDATE数据日期temp_maxFLOAT当天最高温度temp_minFLOAT当天最低温度humidityINTEGER相对湿度百分比weather_typeVARCHAR(30)天气现象晴、多云等wind_directionVARCHAR(10)风向wind_powerVARCHAR(10)风力等级create_timeDATETIME记录写入时间这里有个设计心得create_time这个字段很容易被忽略但实际上它很有价值。采集任务不是跑一次就完事可能需要每天定时更新。有了这个字段你可以很方便地知道每条记录是什么时候入库的排查数据更新的问题一目了然。而且答辩时一提“我设计了数据的时间戳策略”在数据库设计这个层面就显得有思考深度了。4.2 SQLAlchemy 操作数据库的优雅姿势用 Flask SQLAlchemy 来操作数据库代码写起来非常舒服不需要手写原生 SQL。定义模型类一个类就是一张表from flask_sqlalchemy import SQLAlchemy from datetime import datetime db SQLAlchemy() class WeatherData(db.Model): __tablename__ weather_data id db.Column(db.Integer, primary_keyTrue) city db.Column(db.String(20), nullableFalse) date db.Column(db.Date, nullableFalse) temp_max db.Column(db.Float) temp_min db.Column(db.Float) humidity db.Column(db.Integer) weather_type db.Column(db.String(30)) wind_direction db.Column(db.String(10)) wind_power db.Column(db.String(10)) create_time db.Column(db.DateTime, defaultdatetime.now)存储数据时把 pandas DataFrame 转成 ORM 对象再批量入库def save_to_db(df): records [] for _, row in df.iterrows(): record WeatherData( cityrow[city], daterow[date], temp_maxrow[temp_max], temp_minrow[temp_min], humidityrow[humidity], weather_typerow[weather_type], wind_directionrow[wind_direction], wind_powerrow[wind_power] ) records.append(record) db.session.add_all(records) db.session.commit()写数据的时候要注意一个性能问题如果用db.session.add()一条一条加加 10 万条记录会非常慢因为每次都要走一次会话状态检查。用add_all()批量添加、最后集中commit()效率能提升好几倍。这是正规项目里才会注意到的优化点写在答辩 PPT 里绝对是加分项。4.3 数据清洗与去重的正确姿势天气数据有一个非常典型的麻烦重复采集。比如你定时任务每天跑一次同一个城市同一天的数据可能被插入好几回如果不去重后面做趋势分析、算平均温度出来的结果全是错的。去重的思路有两种。第一种是入库前查重插入前查询数据库里是否已经存在同城市同日期的记录存在就跳过或更新。第二种是建唯一约束在city和date两个字段上建联合唯一索引数据库层面保证不重复插入时捕获IntegrityError异常处理冲突。# 建表时添加唯一约束 __table_args__ ( db.UniqueConstraint(city, date, nameuq_city_date), )我推荐第二种数据库约束比业务逻辑判断可靠得多。在分布式任务多进程并发的场景下业务层的先查再插存在竞态条件可能两边同时查到“不存在”然后同时插入照样重复。而唯一约束是在数据库层面做原子判断从根上杜绝这个问题。这个细节如果在答辩时讲出来老师会明显觉得你考虑问题比较周全不是那种“能跑就行”的水平。5. Flask 后端与可视化展示5.1 Flask 项目的基础骨架搭建Flask 项目的目录结构建议从一开始就规划好不要所有文件堆在一个 app.py 里。那种写法做个小 demo 没问题但毕设代码一长自己都看不懂。我习惯用下面这个结构weather_project/ ├── app.py # 应用入口 ├── models.py # 数据库模型 ├── spider/ │ ├── __init__.py │ ├── api_fetcher.py # API 数据采集 │ └── crawler.py # 爬虫数据采集 ├── views/ │ ├── __init__.py │ ├── main_view.py # 主页面路由 │ └── api_view.py # JSON 接口路由 ├── templates/ # HTML 模板 ├── static/ │ ├── css/ │ └── js/ └── requirements.txt # 依赖列表app.py 里完成 Flask 初始化、数据库绑定、蓝图注册from flask import Flask from models import db from views.main_view import main_bp from views.api_view import api_bp def create_app(): app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] sqlite:///weather.db app.config[SQLALCHEMY_TRACK_MODIFICATIONS] False db.init_app(app) app.register_blueprint(main_bp) app.register_blueprint(api_bp) return app app create_app() if __name__ __main__: app.run(debugTrue, host0.0.0.0, port5000)模块化拆分是加分项。答辩时如果老师问“你的项目有多少代码量”你回“两千行分了几个模块”比“全部写在一个文件里”听起来靠谱太多。同时这也是在模拟真实项目的工程规范对以后工作也有帮助。5.2 设计好 API 接口前后端彻底解耦这个项目里的数据展示页面强烈建议用前后端分离的方式写——后端只提供 JSON 数据接口前端通过 JavaScript 异步获取数据再渲染图表。这样做的好处非常直接后端不用管页面长什么样前端不管数据怎么存两边各自改起来互不打扰。一个典型的 API 接口写在蓝图里from flask import Blueprint, jsonify, request from models import WeatherData from datetime import datetime api_bp Blueprint(api, __name__) api_bp.route(/api/weather/history) def weather_history(): city request.args.get(city, 北京) days int(request.args.get(days, 30)) # 查询该城市最近 N 天的数据 records WeatherData.query.filter( WeatherData.city city ).order_by(WeatherData.date.desc()).limit(days).all() data [{ date: str(r.date), temp_max: r.temp_max, temp_min: r.temp_min, weather_type: r.weather_type } for r in records] return jsonify({code: 0, data: data})这个接口设计有几个细节比较重要。第一用参数控制返回天数前端可以灵活切换“最近7天”“最近30天”等维度不用写死。第二返回 JSON 的同时有一个code字段这是约定俗成的状态码规范前端判断code 0再渲染数据异常情况不会导致界面崩溃。第三接口按资源命名URL 语义清晰这是 ROUTE 设计能力的体现。把时间格式化成字符串再返回是因为 JSON 本身不支持日期类型如果不处理Flask 的默认序列化器会直接报错。这个坑不深但凡是做过前后端分离的人基本都踩过。5.3 前端可视化ECharts 是当前最优解可视化部分的选型我强烈推荐 ECharts没有之一。国内做数据可视化ECharts 基本上是事实标准官方文档全中文示例丰富到离谱百度系团队出品社区极其活跃。对比 D3.jsECharts 的写代码成本低太多——D3.js 一条折线图可能得写几十行底层 SVG 代码ECharts 一个option对象就搞定。在 HTML 页面引入 ECharts用 CDN 的方式最简单script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script然后准备一个带尺寸的容器div idtempChart stylewidth: 600px; height: 400px;/div写一个折线图展示近 30 天温度变化fetch(/api/weather/history?city北京days30) .then(res res.json()) .then(res { if (res.code ! 0) return; const dates res.data.map(item item.date); const tempMax res.data.map(item item.temp_max); const tempMin res.data.map(item item.temp_min); const chart echarts.init(document.getElementById(tempChart)); chart.setOption({ title: { text: 北京近30天温度变化, left: center }, tooltip: { trigger: axis }, legend: { data: [最高温, 最低温], top: 30 }, xAxis: { type: category, data: dates }, yAxis: { type: value, name: 温度(℃) }, series: [ { name: 最高温, type: line, data: tempMax, smooth: true, lineStyle: { width: 3 } }, { name: 最低温, type: line, data: tempMin, smooth: true, lineStyle: { width: 3 } } ] }); });这套代码跑起来一个带 tooltip 悬浮提示、图例切换、曲线平滑显示的温度趋势图就直接呈现在页面上了。视觉效果已经比很多外包公司做的后台管理系统还好看答辩演示时翻到这里基本稳了。除了折线图我建议再多做两三个图表丰富展示维度。比如饼图展示近 30 天各类天气现象的占比晴、多云、雨各占多少天。柱状图展示各城市的月平均温度横向对比直观看出南北温度差异。表格展示最新实况数据的明细包括湿度、风力、风向。图表并不是越多越好关键是每个图表能回答一个具体问题。比如“北京最近一个月热不热”用折线图“这段时间天气以什么为主”用饼图“不同城市温差多大”用柱状图。每一个图表都有独立的意义这在答辩讲项目价值时非常加分。5.4 一个可视化大屏的雏形布局如果想让项目再上一个台阶可以在一张页面上把多个图表组合成可视化大屏的效果。布局逻辑是一张大容器用 CSS Grid 或 Flexbox 分成若干区域顶部放标题中间并排放折线图和柱状图底部放饼图和数据表格卡片。最后加一层深色背景用setInterval定时刷新数据接口就能做出类似监控大屏的效果。.dashboard { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 80px 300px 300px 200px; gap: 16px; width: 100%; min-height: 100vh; background: #0f1423; color: #fff; }大屏项目历来是可视化方向的重头戏不仅在毕业设计中效果炸裂写成简历里的作品链接投递数据产品岗、前端可视化岗的通过率也会高不少。而且技术难度其实不大就是把单张图表拼起来再加一点 CSS 审美非常值得花半天时间做出来。6. 常见问题与实战避坑6.1 爬虫与 API 采集中最容易翻车的几个点采集端的问题我总结了几个高频翻车现场每个都是真实踩过的坑。字符编码乱码这个是新手重灾区。requests 库默认用resp.text时会根据响应头猜测编码但很多网站响应头不标编码或者标了却不对。解决办法是显式指定resp.encoding utf-8或resp.encoding gbk或者用resp.apparent_encoding自动检测哪个能用用哪个。请求频率过高被服务商限流。不管是调 API 还是爬网页频繁请求都会触发服务端的反爬机制返回 403 或封 IP。解决方式有两个第一个是君子协议——在两次请求之间加time.sleep(1)做间隔尊重对方服务器的负载第二个是如果必须高频请求加随机User-Agent和代理池但这种方法就不建议在毕业设计里用了合规风险太高。解析出来的字段有空值、类型不匹配。网页表格里某个单元格数据缺失解析出来的就是空字符串入库时数据库字段类型对不上就会报错。这里的关键是做好防御式编程每个字段在入库前从pandas.read到入库都有两道防护先用pd.to_numeric(errorscoerce)把脏数据转成 NaN再统一fillna填充默认值。6.2 Flask 运行时暴露的常见坑调试模式和线上模式混淆。很多新手在开发时开着debugTrue部署到服务器时忘了关。这不仅是安全问题更会在并发访问时出现“Thrdding”模式的奇怪报错。建议开发用 debug部署时强制debugFalse。SQLAlchemy 报Table xxx doesnt exist。这种情况多半是数据表还没创建运行db.create_all()就完事了。但也有人明明执行了create_all还是报错——这个坑通常是因为模型类没有在入口文件里被 import 过Flask 扫描不到模型类就越过建表了。在入口文件里显式 import 一下模型类就能解决。JSON 序列化失败。Flask 的jsonify不能直接序列化datetime类型所以前面说过查询结果必须先转成字符串再返回。还有一个隐藏雷区是Decimal类型SQLite 不会遇到但如果换到 MySQL 且字段是 decimal 类型同样的报错又会出现。所以最好的习惯是在序列化前统一做数据转换不要依赖框架默认行为。6.3 数据可视化图表的性能问题和显示细节当数据量变大比如一次性查 2000 个点的数据去渲染折线图ECharts 也会有明显的卡顿感。优化手段有几个方面第一是前端只渲染必要的数据比如后端就只返回聚合后的结果不要全量吐出去第二是使用 ECharts 的dataZoom组件让用户自己缩放查看区间初始只渲染一部分数据第三是把曲线加symbol: none去掉圆点标记渲染压力会小很多。显示方面还有一个高频细节坐标轴标签太多导致重叠。日期一多X 轴的刻度标签就会挤成一团完全没法看。解决办法是加axisLabel: { interval: auto }让 ECharts 自动抽稀或者设置旋转角度rotate: 45。这个细节虽然小但是直接决定图表整体观感值得在调色时多花几分钟。6.4 答辩现场的避坑经验论文和代码都写完之后答辩前的准备同样决定最终分数。我从实际经验里总结了几条答辩注意事项第一一定要自己完整跑通演示流程至少三遍。我见过有的学弟在答辩现场打开浏览器发现数据库没有初始化页面直接报 404。演示前把数据库文件、Flask 启动命令、浏览器访问地址全部检查一遍最好写在自己的演示脚本上防止紧张忘掉。第二准备 3 到 5 个“老师必问”问题的标准答案包括但不限于你的数据怎么保证真实性、爬虫被反爬了怎么办、为什么选 Flask 不选 Django、图表用的是什么库、数据更新的策略是什么。这些问题提前写好标准答案背熟答辩时对答如流印象分直接拉满。第三主动暴露项目的不足并提出改进方向。老师问“你这个项目有什么可以优化的地方”时不要愣住说“我觉得可以加 Redis 做缓存、用 Celery 做定时任务调度、再用 WebSocket 做实时推送”。这些话一出来答辩委员会基本会认为你对技术有清晰的全局认知分数自然高。7. 项目扩展方向与个人体会系统做完之后我建议在基础版本上再做两个小小的扩展投入时间不多但收效很大。一个是增加数据对比分析功能。在 Flask 后端里加一个接口支持选择多个城市把它们的温度曲线放在同一个图表里对比。这样一套系统就从“单城市天气查看器”升级成了“多城市气象对比分析平台”项目名字立马显得有深度了。另一个是做一个数据导出功能。页面上放一个下载按钮点击后从数据库读取数据用 pandas 导出成 CSV 或 Excel 文件前端用浏览器下载到本地。这个功能实现也就二三十行代码但把“数据导出”这个环节补上了系统的数据链路才是完整的——采集、存储、分析、展示、导出闭环了。我在带学弟做这个项目的过程中最有感触的一点是毕业设计的本质不在炫技而在于完整地跑通一件有价值的事情。天气数据可视化这个题目技术栈主流但不过度复杂爬虫、Flask、ECharts 三样东西都是市场上真实需求的技能点认认真真做下来论文有的写简历有的放答辩有的讲。如果你正为选题发愁或者已经在卡在某个环节不妨照着这篇博文的思路把整个流程走一遍我相信你做完之后对 Python 全栈开发的理解会比学校的任何一门课都有用得多。
返回列表