
简介本资源是一个基于Flask框架的Python天气预测与可视化项目源码包面向计算机专业本科生、毕业设计学生及Web开发初学者旨在提供一套可运行、易理解的气象数据处理与前端展示完整实践方案。项目整合了天气数据获取、后端逻辑处理与动态图表渲染能力覆盖从API调用、CSV数据解析到ECharts可视化及响应式页面部署全流程适合作为课程设计或毕设参考。压缩包共138个文件含4个核心Python脚本含Flask主应用与数据处理模块、35个CSV气象数据样本、31张JPG/SVG图表素材、19个JS交互脚本及12个CSS样式文件整体体积仅3.26MB轻量易部署。目前已有179人学习下载代码经本地编译验证可直接运行评审分达95分以上附带BootstrapFont Awesome前端组件库与响应式布局支持目录结构清晰模块职责分明便于二次开发与功能扩展。1. 用 Flask 搭一个能查天气、画图表、还能本地跑通的 Python 小系统你不需要部署到云服务器也不必调用收费 API——这个项目用 Python 写Flask 做 Web 框架前端靠原生 HTML Chart.js 渲染折线图和温度分布后端用 requests 调用免费开放的 Open-Meteo 天气接口无密钥、不限频、支持全球经纬度查询。它不是“爬网页天气”而是直连气象模型数据源也不是“画个静态图就完事”而是用户输入城市名或坐标实时请求未来 7 天逐小时气温、降水概率、风速并自动生成带交互缩放的多维度可视化面板。适合刚学完 Flask 路由和模板渲染、想练手真实数据流的新手也适合需要快速验证天气分析逻辑、不希望被 API 配额卡住的中级开发者。整个流程从pip install flask requests开始到浏览器打开http://127.0.0.1:5000看见动态天气图全程可控制在 20 分钟内完成。2. 为什么选 Open-Meteo 而不是高德/和风Flask 如何组织三层数据流2.1 免密钥、免注册、响应快Open-Meteo 是当前最适配教学与轻量部署的天气数据源国内常见天气 API如高德、和风虽有中文文档但强制要求注册、绑定手机号、申请 Key且免费额度常限于每日 1000 次调用对本地调试极不友好。Open-Meteohttps://open-meteo.com是欧洲开源气象服务提供基于 ERA5 和 GFS 模型的全球预报数据完全免费、无需认证、支持 CORS、返回标准 JSON且单 IP 每秒可发 10 请求。其/forecast接口接受latitude、longitude、hourly参数直接返回未来 7 天每小时的temperature_2m、precipitation_probability、windspeed_10m等字段结构干净无广告字段、无冗余嵌套。对比发现调用一次北京39.9042, 116.4074的 168 小时数据Open-Meteo 平均耗时 320ms而某国产 API 在未触发限流时也需 680ms 以上且返回中夹杂status、server_time、data.city等非核心字段解析成本更高。提示Open-Meteo 不提供城市名自动转坐标功能所以本项目需内置简易地理编码缓存如用geopy离线查一次存 CSV但为降低依赖我们采用「前端输入经纬度 后端校验范围」策略避免引入额外网络请求。2.2 Flask 的三层职责划分路由接收 → 服务层封装 → 模板注入图表数据一个健壮的 Flask 天气项目不能把所有逻辑塞进app.route函数里。我们按关注点分离为三层路由层app.py只做参数校验、HTTP 状态码返回、跳转控制。例如检查lat是否在 -90~90 之间lon是否在 -180~180 之间非法则重定向至错误页服务层services/weather_service.py封装get_forecast_data(lat, lon)方法内部处理 requests 调用、异常捕获超时、400 错误、数据清洗将 hourly 字段转为 Pandas DataFrame补全缺失时间戳表现层templates/index.html用 Jinja2 注入chart_data字典其中labels是时间数组datasets是多个{label: 温度, data: [...], borderColor: #36A2EB}对象供 Chart.js 渲染。这种分层让代码可测试你可以单独运行weather_service.py中的函数验证数据获取逻辑而不必启动 Flask 服务也能在test_weather_service.py中 mock requests 响应覆盖网络失败场景。2.2.1 路由层最小可行代码校验、转发、错误兜底# app.py from flask import Flask, render_template, request, redirect, url_for import services.weather_service as ws app Flask(__name__) app.route(/, methods[GET, POST]) def index(): if request.method POST: try: lat float(request.form.get(lat)) lon float(request.form.get(lon)) if not (-90 lat 90): raise ValueError(纬度必须在 -90 到 90 之间) if not (-180 lon 180): raise ValueError(经度必须在 -180 到 180 之间) forecast ws.get_forecast_data(lat, lon) return render_template(index.html, chart_dataforecast, latlat, lonlon) except (ValueError, TypeError) as e: return render_template(error.html, messagestr(e)) except Exception as e: return render_template(error.html, message数据获取失败请检查网络) return render_template(index.html)这段代码做了三件事① 强制转换并范围校验输入② 调用服务层获取结构化图表数据③ 将数据传给模板。注意它不处理 HTTP 状态码细节如 429因为那是服务层该捕获的也不拼接 URL 或解析 JSON那是weather_service.py的责任。2.2.2 服务层核心逻辑requests 调用 数据规整 异常映射# services/weather_service.py import requests import pandas as pd from datetime import datetime, timedelta def get_forecast_data(lat: float, lon: float) - dict: 调用 Open-Meteo 获取未来 7 天逐小时天气数据返回 Chart.js 可用格式 返回结构{ labels: [2024-05-01 00:00, ...], datasets: [ {label: 温度(℃), data: [12.3, 11.8, ...], borderColor: #36A2EB}, {label: 降水概率(%), data: [10, 5, ...], borderColor: #FF6384} ] } base_url https://api.open-meteo.com/v1/forecast params { latitude: lat, longitude: lon, hourly: temperature_2m,precipitation_probability,windspeed_10m, forecast_days: 7, timezone: auto } try: resp requests.get(base_url, paramsparams, timeout10) resp.raise_for_status() # 抛出 4xx/5xx 异常 data resp.json() # 提取 hourly 数据并转为 DataFrame hourly data.get(hourly, {}) times hourly.get(time, []) temp hourly.get(temperature_2m, []) precip hourly.get(precipitation_probability, []) wind hourly.get(windspeed_10m, []) # 构建 Chart.js 所需结构 labels [t[:16] for t in times] # 截取 YYYY-MM-DD HH:MM datasets [ { label: 温度(℃), data: [round(x, 1) for x in temp], borderColor: #36A2EB, fill: False }, { label: 降水概率(%), data: precip, borderColor: #FF6384, fill: False }, { label: 风速(m/s), data: [round(x, 1) for x in wind], borderColor: #4BC0C0, fill: False } ] return {labels: labels, datasets: datasets} except requests.exceptions.Timeout: raise Exception(请求超时请检查网络连接) except requests.exceptions.ConnectionError: raise Exception(无法连接到天气服务请稍后重试) except requests.exceptions.HTTPError as e: if resp.status_code 400: raise Exception(坐标参数错误请确认经纬度格式) else: raise Exception(f天气服务异常{resp.status_code}) except KeyError as e: raise Exception(天气数据格式异常请联系开发者)关键点说明timeout10防止请求挂起比默认无穷等待更安全resp.raise_for_status()统一抛出 HTTP 错误后续用except分类处理labels截取前 16 位是为适配 Chart.js 时间轴显示密度避免“2024-05-01T00:00”过长挤占空间round(x, 1)对浮点数做精度控制防止图表上出现12.300000000000001这类显示 bug所有异常都转为带业务语义的Exception便于路由层统一渲染错误页。异常类型触发条件用户看到的提示requests.exceptions.Timeout请求超过 10 秒“请求超时请检查网络连接”requests.exceptions.ConnectionErrorDNS 失败或目标不可达“无法连接到天气服务请稍后重试”HTTP 400 错误lat超出范围或非数字“坐标参数错误请确认经纬度格式”KeyErrorOpen-Meteo 接口变更导致字段缺失“天气数据格式异常请联系开发者”3. 前端怎么把 Python 字典变成可交互图表Chart.js 初始化与 Flask 模板联动3.1 Chart.js v4 的 CDN 引入与基础配置避免版本兼容陷阱很多教程用 Chart.js v2 或 v3但 v42023 年发布已移除全局Chart.defaults配置方式改用plugins和scales显式声明。本项目使用 v4.4.1当前稳定版通过 CDN 引入不走 npm 构建降低新手环境门槛!-- templates/base.html -- script srchttps://cdn.jsdelivr.net/npm/chart.js4.4.1/dist/chart.umd.min.js/script注意chart.umd.min.js是 UMD 模块兼容script标签直接加载若用chart.esm.min.js则需typemodule对 Flask 模板不友好。v4 的options.plugins.legend默认开启但options.scales.x.title需手动启用否则 X 轴无时间标签。3.2 Jinja2 模板如何安全注入 Python 数据到 JavaScript 变量Flask 默认对{{ variable }}做 HTML 转义但 Chart.js 需要原始 JSON 字符串。错误写法var data {{ chart_data|tojson }};会因|tojson过滤器输出带引号字符串导致 JS 解析失败。正确做法是用|safe标记信任内容并确保chart_data是字典而非字符串!-- templates/index.html -- script // 安全注入tojson 生成合法 JSONsafe 防止 HTML 转义 const chartData {{ chart_data|tojson|safe }}; /script|tojson过滤器会把 Python 字典转为标准 JSON如{labels: [a,b], datasets: [...]}|safe告诉 Jinja2 不要再把双引号转成quot;。这是 Flask 官方推荐的数据传递方式比json.dumps()手动序列化更可靠。3.2.1 Chart.js 初始化代码时间轴、多 Y 轴、悬停提示定制!-- templates/index.html -- canvas idweatherChart width800 height400/canvas script const ctx document.getElementById(weatherChart).getContext(2d); const myChart new Chart(ctx, { type: line, data: chartData, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: top, }, title: { display: true, text: 天气预报${{{ lat|float }}°N, {{{ lon|float }}°E}, font: { size: 16 } }, tooltip: { mode: index, intersect: false, callbacks: { label: function(context) { return ${context.dataset.label}: ${context.parsed.y}; } } } }, scales: { x: { type: time, time: { unit: hour, displayFormats: { hour: MMM d HH:mm } }, title: { display: true, text: 时间 } }, y: { beginAtZero: false, title: { display: true, text: 数值 } } } } }); /script参数说明type: line固定为折线图比柱状图更适合连续时间序列scales.x.type: time启用时间轴自动识别2024-05-01T00:00格式displayFormats.hour控制 X 轴标签显示为“5月1日 00:00”plugins.tooltip.callbacks.label自定义悬停文字避免默认显示y: 12.3这种不友好格式title.text中的{{{ lat|float }}°N使用 Jinja2 表达式插值|float确保小数点后位数一致避免39.904200000000004。注意scales.x.time.unit: hour必须与 Open-Meteo 返回的时间粒度匹配它确实是每小时一条若误设为day图表会压缩成 7 个点丢失逐小时细节。3.3 响应式布局与移动端适配CSS 控制 canvas 宽度Chart.js 的responsive: true仅控制 canvas 自身缩放不解决父容器宽度问题。需在 CSS 中明确.chart-container最大宽度并用max-width: 100%保证小屏下不溢出!-- templates/index.html -- div classchart-container canvas idweatherChart/canvas /div style .chart-container { max-width: 100%; margin: 0 auto; } #weatherChart { max-width: 100%; height: auto; } /style实测在 iPhone 14 上width800的 canvas 会被压缩至 390px 宽但线条粗细、字体大小仍清晰可读若删掉max-width: 100%canvas 会横向滚动破坏体验。4. 本地运行全流程从安装依赖到浏览器验证含三个必调参数4.1 五步启动命令零配置、纯 Python、PyCharm/VSCode 均适用整个项目不依赖数据库、不需 Nginx只需 Python 3.8 和 pip。以下命令在任意目录执行建议新建weather-flask文件夹# 1. 创建虚拟环境推荐避免包冲突 python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # 2. 安装核心依赖 pip install flask requests # 3. 创建项目结构 mkdir -p templates static touch app.py touch services/__init__.py services/weather_service.py # 4. 复制上面的 app.py 和 weather_service.py 代码 # 此处省略粘贴实际操作中复制即可 # 5. 启动开发服务器 flask --app app --debug run执行第 5 步后终端输出* Running on http://127.0.0.1:5000打开浏览器访问即可见首页。--debug启用热重载修改 Python 文件后自动重启无需手动 CtrlC。提示若提示FLASK_APP is not set请先运行export FLASK_APPappLinux/macOS或set FLASK_APPappWindows再执行flask run。4.2 三个影响可用性的关键参数及其调试方法参数位置默认值修改建议调试方法timeoutinrequests.get()weather_service.py10若公司内网限制出站请求可调至15在weather_service.py中临时加print(Fetched in, resp.elapsed.total_seconds())forecast_daysin API paramsweather_service.py7改为1可加速调试减少返回数据量查看浏览器 Network 面板过滤forecast请求观察 Response Size 是否从 12KB 降至 2KBscales.x.time.unitin Chart.jsindex.htmlhour若数据是每日汇总需改为day打开浏览器 Console输入myChart.config.scales.x.time.unit查看当前值验证是否生效的最快方式修改forecast_days1后刷新页面X 轴应只显示 24 个时间点00:00 到 23:00而非 168 个。若仍显示 168 点说明代码未重新加载——检查是否忘了--debug或浏览器缓存了旧 JS可强制CtrlF5。4.2.1 用 curl 模拟请求绕过 Flask 直接验证 Open-Meteo 接口当页面空白或报错时优先排除网络层问题。在终端执行curl -s https://api.open-meteo.com/v1/forecast?latitude39.9042longitude116.4074hourlytemperature_2mforecast_days1 | head -n 20正常响应应包含hourly:{time:[2024-05-01T00:00,2024-05-01T01:00,...],temperature_2m:[12.3,11.8,...]}。若返回 HTML 页面如 Cloudflare 错误说明网络被拦截若返回{error:...}说明参数格式错误。4.3 常见报错与定位路径从终端日志到浏览器控制台终端报ImportError: No module named services原因services文件夹缺少__init__.py。解决touch services/__init__.py即使为空文件Python 也会将其识别为包。浏览器空白Console 报Uncaught ReferenceError: Chart is not defined原因Chart.js CDN 加载失败。解决打开 Network 面板筛选chart.umd.min.js看 Status 是否为200若为404检查 CDN 地址是否拼错若为blocked说明公司网络屏蔽了 jsdelivr。图表显示但数据全为 0X 轴是乱码时间原因chartData.labels传入的是[2024-05-01T00:00,...]但 Chart.js v4 要求时间轴数据必须是 Date 对象或 ISO 字符串。解决在weather_service.py中保持字符串格式不变v4 已支持检查scales.x.type是否误写为category。输入坐标后页面卡住Network 面板显示 pending原因Open-Meteo 请求超时但timeout参数未生效。解决在weather_service.py的requests.get前加print(Calling API with, params)确认参数已构造再检查timeout10是否写在正确位置必须是requests.get(..., timeout10)而非requests.get(...).timeout10。5. 进阶技巧添加城市搜索框、缓存机制、导出 CSV 功能5.1 用 geopy 实现城市名→坐标的离线映射不调用网络 APIOpen-Meteo 不支持城市名查询但用户更习惯输“北京”而非“39.9042,116.4074”。我们用geopy的Nominatim需联网或更轻量的geocoder支持离线 CSV。为避免增加外部依赖本项目采用预置 CSV 缓存方案# utils/city_cache.py import csv from typing import Optional, Tuple CITY_DB data/cities.csv # 格式city,latitude,longitude def city_to_coords(city_name: str) - Optional[Tuple[float, float]]: 从本地 CSV 查找城市坐标不发起网络请求 try: with open(CITY_DB, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: if row[city].lower() city_name.lower().strip(): return float(row[latitude]), float(row[longitude]) except FileNotFoundError: pass return None预置data/cities.csv示例UTF-8 编码city,latitude,longitude 北京,39.9042,116.4074 上海,31.2304,121.4737 广州,23.1291,113.2644 深圳,22.5431,114.0579在app.py中扩展 POST 处理逻辑# app.py 片段 from utils.city_cache import city_to_coords app.route(/, methods[GET, POST]) def index(): if request.method POST: city request.form.get(city) if city: coords city_to_coords(city) if coords: lat, lon coords forecast ws.get_forecast_data(lat, lon) return render_template(index.html, chart_dataforecast, latlat, lonlon, citycity) else: return render_template(error.html, messagef未找到城市{city}) # ... 其余 lat/lon 处理逻辑这样用户既可输“北京”也可输经纬度前端表单加两个 input 并用 JS 控制显隐即可。5.2 用 Flask-Caching 实现内存级响应缓存降低重复请求压力同一坐标频繁刷新会反复调用 Open-Meteo虽免费但非最佳实践。Flask-Caching支持内存、Redis 等后端本项目用简单内存缓存pip install Flask-Caching# app.py from flask_caching import Cache cache Cache(app, config{CACHE_TYPE: SimpleCache}) cache.cached(timeout300, key_prefixlambda: request.form.get(lat) _ request.form.get(lon)) def cached_forecast(lat, lon): return ws.get_forecast_data(lat, lon) app.route(/, methods[GET, POST]) def index(): if request.method POST: lat float(request.form.get(lat)) lon float(request.form.get(lon)) forecast cached_forecast(lat, lon) # 自动缓存 5 分钟 # ...key_prefix用经纬度拼接确保不同坐标不共享缓存timeout300即 5 分钟后自动失效平衡新鲜度与性能。5.3 添加 CSV 导出按钮后端生成、前端触发下载用户常需把预测数据存档。Flask 可以直接返回 CSV 响应# app.py from io import StringIO import csv app.route(/export-csv) def export_csv(): lat float(request.args.get(lat)) lon float(request.args.get(lon)) data ws.get_forecast_data(lat, lon) output StringIO() writer csv.writer(output) # 写入表头 writer.writerow([时间, 温度(℃), 降水概率(%), 风速(m/s)]) # 写入数据行 for i, label in enumerate(data[labels]): row [label] for dataset in data[datasets]: row.append(dataset[data][i] if i len(dataset[data]) else ) writer.writerow(row) output.seek(0) return app.response_class( output.getvalue(), mimetypetext/csv, headers{Content-Disposition: fattachment;filenameweather_{lat}_{lon}.csv} )前端加一个按钮a href{{ url_for(export_csv, latlat, lonlon) }} classbtn btn-secondary i classbi bi-download/i 导出 CSV /a点击后浏览器自动下载weather_39.9042_116.4074.csv内容为标准逗号分隔Excel 可直接打开。此功能不依赖前端 JS 库纯 Flask 实现稳定可靠。注意url_for(export_csv, ...)生成的 URL 包含查询参数若lat或lon含小数点Flask 会自动编码为%2E不影响后端解析。本文还有配套的精品资源点击获取