
简介一份基于Python的天气预测与可视化毕业设计项目配套源码、数据库、视频演示与文档说明主要面向Python学习者、毕业设计学生以及需要快速搭建完整项目的开发者。压缩包共155个文件约88.36MB包含Python后端代码、前端网页与样式、数据配置与数据库脚本以及演示视频和说明文档覆盖网页、脚本、样式、JSON、SQL、视频等常见类型目录结构清晰便于按模块查阅。已有330人学习/下载。项目通过解析天气接口获取实时数据结合常用请求、数据处理与可视化库完成分析预测并可基于历史数据或简单机器学习模型给出预测结论前端集成现成UI组件框架支持地点输入、日期选择与趋势查看以图表清楚呈现实时天气、温度变化及预测结果整体覆盖数据采集、清洗分析到交互展示的完整流程。这套资源既可作为毕业设计直接参考也是Python Web与数据可视化练手的好素材能帮助读者快速理解天气数据获取、分析展示的全链路思路节省从零搭建项目的时间。1. 从「天气App不好用」说起为什么自己动手做一套天气预测与可视化系统做开发这几年我越来越觉得现成的天气App有一个根本问题它们给你看的是「编辑精选」后的信息而不是你真正需要的数据。比如你做户外活动策划想看过去两周的逐小时温度曲线做农业项目需要对比不同区域的降雨概率这些需求在商业App里要么没有要么藏在三级菜单里。所以当我想做一套自己的天气预测与可视化系统时核心思路很明确——用 Python 把 OpenWeatherMap 的原始数据拉下来存进本地数据库再用 Pandas 做清洗和特征提取最后用 Matplotlib 和 Flask 呈现成自己能控制的图表和页面。这套项目的技术栈很干净没有复杂的框架依赖却能覆盖 requests、SQLite、Pandas、Matplotlib 到 Flask 的完整数据链路特别适合作为毕业设计展示完整的工程能力也适合想深入理解数据流转的开发者参考。下面我把整个拆解过程写出来每一步都有能直接复现的命令和代码。2. 数据采集层用 requests 把 OpenWeatherMap 的实时数据拿下来2.1 为什么选 OpenWeatherMap 而不是其他气象接口做天气数据采集第一步是选数据源。国内的和风天气、心知天气接口文档都很友好但它们的免费额度对毕业设计来说够用对频繁开发调试却不够宽松尤其是每分钟请求次数限制得很紧。我最后选 OpenWeatherMap 是因为它免费版就能提供实时天气、逐小时预测和 5 天预报而且返回的 JSON 结构非常规整适合教学演示和二次封装。另一个选它的理由是免费层 API Key 的申请流程非常简单——注册邮箱就能拿到不需要企业资质审核。对于课程设计或者毕业设计来说这意味着从注册到拿到第一份数据可以控制在 20 分钟以内不会因为审批流程卡住进度。如果你做的是国内城市级项目而且对数据源有合规要求也可以换成和风天气但下面这套采集逻辑——requests 请求、参数拼接、响应解析、异常兜底——是完全通用的只需要改 API 地址和字段名。2.2 请求参数怎么设城市、单位、语言与超时重试采集代码最核心的部分是请求参数的构造。我一般会单独写一个weather_client.py把所有跟外部接口打交道的逻辑收拢在这个文件里方便后面替换数据源或者修改参数。import requests import time from datetime import datetime class WeatherClient: def __init__(self, api_key, timeout5, retry_times3): self.base_url https://api.openweathermap.org/data/2.5/weather self.api_key api_key self.timeout timeout # 单次请求超时时间秒 self.retry_times retry_times # 失败重试次数 def fetch_current_weather(self, city_name): payload { q: city_name, # 城市名支持 Beijing,CN 带国家码 appid: self.api_key, units: metric, # 返回摄氏温度设为 imperial 则返回华氏 lang: zh_cn # 返回中文天气描述如“多云”“小雨” } for attempt in range(self.retry_times): try: resp requests.get(self.base_url, paramspayload, timeoutself.timeout) resp.raise_for_status() # 4xx/5xx 状态码直接抛异常 data resp.json() return self._normalize(data) except requests.exceptions.Timeout: print(f[{datetime.now()}] 请求超时第 {attempt 1} 次重试) time.sleep(2 * (attempt 1)) # 指数退避2s, 4s, 6s except requests.exceptions.HTTPError as e: # 常见错误码401 Key 无效404 城市不存在429 限流 print(fHTTP错误: {e}) if e.response.status_code 404: raise ValueError(f城市 {city_name} 不存在请检查拼写) time.sleep(10) # 限流或 Key 问题等久一点再试 raise RuntimeError(f请求 {city_name} 失败已重试 {self.retry_times} 次) def _normalize(self, raw_data): return { city: raw_data[name], temp: raw_data[main][temp], humidity: raw_data[main][humidity], pressure: raw_data[main][pressure], weather: raw_data[weather][0][description], wind_speed: raw_data[wind][speed], timestamp: int(time.time()) }这里有几个参数值得单独说明。unitsmetric是我强烈建议加上去的不写这个参数时 OpenWeatherMap 默认返回开尔文温度很多新手第一次拿到数据会发现温度是 300 多就是因为没设单位。langzh_cn控制天气描述的语言如果你后续要做图表标题用中文会更直观。timeout参数一定要设否则 requests 默认无限等待在校园网环境里很容易卡死整个采集脚本。重试机制里我做了 2 倍递增的退避策略第一次失败等 2 秒第二次等 4 秒这样既不会在瞬时抖动时误判也不会因为高频重试被封 Key。2.3 响应结构解析与异常兜底拿到 JSON 之后不能直接往数据库里塞必经一步是字段抽取。OpenWeatherMap 的响应结构比较深例如实时天气的完整路径是data[main][temp]而不是data[temp]。_normalize方法在这里起到的作用是把嵌套的 JSON 拍平成一条扁平的记录同时过滤掉我们不需要的字段比如经纬度、时区偏移量、云量百分比等。异常兜底方面除了代码里的Timeout和HTTPError还有一个常见坑是KeyError——有时候接口返回的数据里没有main字段比如城市名拼错时返回的是{ cod: 404, message: city not found }。所以我在项目里会再加一层判断if main not in raw_data: raise ValueError(f接口返回异常: {raw_data.get(message, 未知错误)})这部分做好之后数据采集层就稳定了。下一步要考虑的是采集到的数据不能只留在内存里要有一个可长期累积的存储方案。3. 数据仓库与特征工程从 JSON 到 SQLite 再到 Pandas3.1 为什么要自建 SQLite 存储而不过度依赖 API如果你只是做完一个页面就交差可以直接在 Flask 路由里写requests.get实时拉数据。但真实场景不是这样免费 API 有调用次数上限而且你需要历史数据来画趋势图和做预测模型。所以我在系统里加了一个 SQLite 存储层。选 SQLite 而不是 MySQL 的原因很直接——毕业设计和课程设计阶段没有服务器运维需求SQLite 单文件、零配置、支持标准 SQLPython 标准库直接import sqlite3就能用不需要额外装驱动。等数据量到了几百万条再平滑迁移到 PostgreSQL 也不迟而且 SQL 语句基本不用改。为了更好地衔接后续的 Pandas 分析和可视化我建议在存储层就把时间字段和城市字段做成规范格式不要存乱七八糟的字符串。下面是建表语句和入库逻辑。3.2 表结构设计与入库脚本-- schema.sql CREATE TABLE IF NOT EXISTS weather_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, city TEXT NOT NULL, -- 城市名如 Beijing temp REAL NOT NULL, -- 温度摄氏度 humidity INTEGER, -- 相对湿度% pressure INTEGER, -- 气压hPa weather TEXT, -- 天气描述如 多云 wind_speed REAL, -- 风速m/s record_time INTEGER NOT NULL -- Unix 时间戳秒级 ); CREATE INDEX IF NOT EXISTS idx_city_time ON weather_data(city, record_time);建索引是很多人容易忽略的一步。后续做时序分析时要反复按城市和时间范围过滤数据没有索引的话数据量稍大就会明显变慢。idx_city_time这个联合索引把两个高频查询字段都覆盖了。入库脚本沿着上一章的采集流程往后接在_normalize拿到干净字典之后写入 SQLiteimport sqlite3 def save_to_db(record, db_pathweather.db): conn sqlite3.connect(db_path) cursor conn.cursor() cursor.execute( INSERT INTO weather_data (city, temp, humidity, pressure, weather, wind_speed, record_time) VALUES (:city, :temp, :humidity, :pressure, :weather, :wind_speed, :timestamp) , record ) conn.commit() conn.close()这段逻辑里用了:city这种命名占位符而不是 Python 的%格式化这样做有两点作用第一sqlite3 自带参数转义功能可以避免 SQL 注入问题第二字典直接传参不用再维护一个位置对应的元组数组减少字段顺序出错的可能。另一个值得注意的细节是conn.commit()的位置——写完一条就提交虽然吞吐量不高但胜在稳妥毕竟采集脚本中间崩溃的概率远大于单条写入的性能差距。3.3 用 Pandas 做特征提取均值、趋势、缺失值处理数据落库之后分析和特征提取的工作就交给 Pandas。这一层要做的事情是把原始的逐条观测数据聚合成我们画图时的输入比如日平均温度、温度标准差、逐周趋势线等。import pandas as pd import sqlite3 def load_weather_df(db_pathweather.db, cityBeijing, days7): conn sqlite3.connect(db_path) query SELECT city, temp, humidity, record_time FROM weather_data WHERE city ? AND record_time ? ORDER BY record_time start_ts int(pd.Timestamp.now().timestamp()) - days * 86400 df pd.read_sql_query(query, conn, params(city, start_ts)) conn.close() df[datetime] pd.to_datetime(df[record_time], units) df.set_index(datetime, inplaceTrue) return df def daily_aggregation(df): daily df.resample(D).agg({ temp: [mean, max, min], humidity: mean }) daily.columns [temp_mean, temp_max, temp_min, humidity_mean] return daily.dropna()pd.read_sql_query可以直接把 SQL 查询结果转为 DataFrame省去手动遍历游标的步骤。这里有个细节params(city, start_ts)传参和 sqlite3 原生写法保持一致注意必须是元组不能是简单字符串。resample(D)是按自然日聚合同时计算均值、最大值、最小值这些统计量后面做可视化的时候正好对应温度曲线的高低点展示。缺失值处理也是特征工程里躲不开的问题——比如某天接口限流没有采到数据resample之后那一天的记录就是 NaN直接画图会在曲线上留下一个断点。我通常的做法是先用上一章数据库里的最近一条记录回填实在没有就去拉一次历史记录补齐。Pandas 里最简单的回填方式是daily daily.ffill() # 用上一个有效值填充缺失如果缺失值跨度超过三天ffill就没有意义了这时我会在图上直接标注「数据缺失区间」而不是硬画一条平滑曲线骗自己。分析层的原则就是不造假数据宁可让用户看到缺口也不能推断出不存在的趋势。4. 可视化层Matplotlib 静态图 Flask 页面展示4.1 图表设计温度曲线、降雨概率柱状、风向玫瑰数据准备好了之后下一步是把它们变成人能直接看懂的图。可视化这部分我在系统里做了三种图表各自的适用场景和代码细节我拆开说。第一种是温度曲线图展示近 7 天或近 30 天的温度变化趋势。虽然称为曲线图但我通常把最高温和最低温用两条线画出来中间用fill_between填充浅色区域这样一眼就能看出温差范围比单条平均温度线信息量丰富得多。import matplotlib.pyplot as plt import matplotlib.dates as mdates plt.rcParams[font.sans-serif] [SimHei] # 解决中文乱码 plt.rcParams[axes.unicode_minus] False # 解决负号显示为方块 fig, ax plt.subplots(figsize(10, 4)) ax.plot(daily.index, daily[temp_max], label最高温, color#d9534f, linewidth1.8) ax.plot(daily.index, daily[temp_min], label最低温, color#337ab7, linewidth1.8) ax.fill_between(daily.index, daily[temp_min], daily[temp_max], alpha0.15, colorgray) ax.xaxis.set_major_formatter(mdates.DateFormatter(%m-%d)) ax.set_xlabel(日期) ax.set_ylabel(温度 (°C)) ax.set_title(f{city} 近 {days} 天温度变化) ax.legend() ax.grid(True, linestyle--, alpha0.4) plt.xticks(rotation45) plt.tight_layout() plt.savefig(fstatic/plots/{city}_temp.png, dpi120) plt.close()这套配置里有几个容易踩坑的地方。中文字体必须设置SimHei或Microsoft YaHei否则 Matplotlib 默认字体不支持中文横轴标签在 Linux 服务器上会显示成一堆方块。axes.unicode_minus也一样如果不设成False负温度显示出来是乱码。savefig之后必须plt.close()否则每跑一次就新增一个 figure 对象在 Flask 里跑时间长了会内存泄漏——这个问题在开发环境不容易察觉部署后跑几天就会把内存吃满。第二种是降雨概率柱状图适合近 12 小时或近 3 天的逐段降水概率展示。柱状图的逻辑简单但有一个设计原则要注意如果所有小时段的降雨概率都是 0图表会变成一条水平线这时我会在图上额外标注「近 48 小时无降水」让看的人不用盯着坐标轴猜到底有没有雨。4.2 动态画像图表刷新策略与缓存处理图表不能每次都重新生成那样很耗资源。做毕业设计时老师问得最多的一个问题就是「你如何保证数据实时性」这里我建议把「实时」拆成两个层面页面访问时是实时的但底层的图表文件是按频率刷新的。也就是说采集脚本定时把新数据写入 SQLite 并重新生成图表文件页面加载时只读已生成好的 PNG。这样把重活挪到后台前端响应飞快还能避免高并发场景下 Matplotlib 线程不安全的问题。from flask import Flask, render_template from apscheduler.schedulers.background import BackgroundScheduler app Flask(__name__) def scheduled_job(): # 拉取天气 - 存库 - 生成图表 cities [Beijing, Shanghai, Guangzhou] for city in cities: client WeatherClient(api_key) record client.fetch_current_weather(city) save_to_db(record) generate_all_plots(cities) # 封装所有画图逻辑 scheduler BackgroundScheduler() scheduler.add_job(scheduled_job, interval, hours1) # 每小时整点拉取 scheduler.start()这里选 APScheduler 而不是自己写 while 循环是因为它自带了 misfire 处理机制——如果某个任务因为网络卡顿超过预定时间它可以自动补跑或者跳过不会像time.sleep那样越跑越偏。hours1是综合考虑后的结果免费 API 的调用额度一般是每分钟 60 次每小时拉一次远在限额内而且一小时内的天气变化对可视化展示来说并不明显没必要每分钟刷新。页面端用 Flask 的路由渲染模板图表文件路径作为变量传给 HTMLapp.route(/) def index(): return render_template(index.html, cityBeijing, temp_plot/static/plots/Beijing_temp.png, rain_plot/static/plots/Beijing_rain.png)4.3 页面渲染Flask 路由与数据库联动模板层面我用了从开源后台模板中拆下来的 UI 框架比如layui.css、font-awesome.css、layuimini.css这一类现成的样式资源。这里我一般不做页面静态化会保留一个简单的刷新按钮让用户主动触发最新数据。每次点击刷新时Flask 这边不重新生成图表而是返回当前数据库里最新的记录并在页面上标注「数据更新于 HH:MM」这样用户能直观地看到系统在正常工作。页面布局上我推荐一个「总览卡 明细表」的结构第一行放城市、温度、湿度、风速、天气描述这几张信息卡片第二行放温度曲线图第三行放降雨概率柱状图再加一个最近 7 天记录表格。表格数据直接从数据库翻出来不用走 Matplotlib效率高很多app.route(/api/weather/city) def api_weather(city): conn sqlite3.connect(weather.db) df pd.read_sql_query(SELECT * FROM weather_data WHERE city ? ORDER BY record_time DESC LIMIT 10, conn, params(city,)) conn.close() return df.to_json(orientrecords, force_asciiFalse)orientrecords会输出 JSON 数组格式和前端fetch之后直接JSON.parse完美对应。force_asciiFalse让中文城市名和天气描述在 JSON 响应里不被转义成\uXXXX否则前端还要多做一步解码而且排查问题时看着很费劲。5. 进阶用线性回归自建温度预测模型并给出量化验证这一章我在原项目基础上再往前走一步把「天气预测」从口号变成可运行的代码。不用机器学习框架就用最朴素的线性回归来预测明天的最高温度让老师或面试官看到你不只会画图还理解预测的本质。这里用到的库只有scikit-learn相比 TensorFlow 或 PyTorch它的依赖更轻安装也更省心。预测的思路用过去 7 天的最高温和最低温作为特征预测明天的最高温。训练数据从 SQLite 里按时间顺序取出最近 60 天记录随机分割出 80% 做训练集、20% 做验证集。关键代码我拆成特征构造和模型训练两段。from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error import numpy as np def build_features(df): X, y [], [] temps df[temp].values for i in range(7, len(temps)): X.append(temps[i-7:i]) # 前 7 天的温度序列 y.append(temps[i]) # 第 8 天的温度 return np.array(X), np.array(y) X, y build_features(df) # df 从 load_weather_df 得到 split int(len(X) * 0.8) model LinearRegression() model.fit(X[:split], y[:split])build_features里做的事情本质上是把时间序列转成监督学习格式每条样本的输入是连续 7 天的温度输出是第 8 天的温度。这种方式叫滑动窗口在时间序列预测里是基础但管用的特征构造手段。窗口大小定为 7 是为了匹配自然周的周期——温度的预测上一周前的温度比一个月前的温度信息量更大。模型训练好之后用验证集算误差y_pred model.predict(X[split:]) mae mean_absolute_error(y[split:], y_pred) print(f验证集平均绝对误差: {mae:.2f} °C)平均绝对误差在 2°C 以内说明模型可用超过 3°C 就说明特征不够或者数据量太小。我实际跑下来发现夏天预测误差一般比冬天小因为冬季温度受冷空气活动影响单靠历史温度序列确实难以准确捕捉。这个结论本身就是你好的一段分析素材——说明你不只会调库还理解模型在什么场景下失效。如果你想把准确率再往上推一档一个不用改模型的做法是增加特征维度把湿度、气压、风速也加进X序列让每个时刻的特征从一个数变成四个数。def build_multi_feature(df): feature_cols [temp, humidity, pressure, wind_speed] X, y [], [] values df[feature_cols].values for i in range(7, len(values)): X.append(values[i-7:i].flatten()) # 展平成 7*428 维向量 y.append(values[i, 0]) # 只预测温度 return np.array(X), np.array(y)flatten()的作用是把 7 天乘 4 个特征的两维数组展平成 28 个元素的向量让LinearRegression能正常工作这一步在一些旧教程里经常漏掉漏掉后会直接报维度相关错误。验证完模型之后还需要把预测结果接回原来的可视化系统——我一般会在 Flask 的/api/weather/city接口里增加一个tomorrow_temp字段前端模板里加一张「明日温度预报」卡片把模型的预测值和当前实测温度并列展示。卡片下方放一行小字「基于历史数据的线性回归预测误差通常在 ±2°C」这行注释我建议保留它展示了你对模型边界的认知比单纯写「预测准确」可信得多。整个系统的闭环到这里就完成了你可以尝试连续运行一周验证模型预测误差的稳定性。本文还有配套的精品资源点击获取