基于Python Flask与ECharts的天气数据爬取可视化系统实战
2026/9/4 4:13:02 网站建设 项目流程

简介:本资源是一套基于Python与Flask开发的山东省气象数据全链路分析系统,面向气象爱好者、数据分析初学者及高校地理信息/环境科学相关专业学生,解决实时天气抓取、历史数据存储与多维度可视化呈现的实际需求。系统完整实现从网页爬虫获取山东省各市实时与历史气象数据(温度、湿度、风速、降水等),经清洗入库后,通过地图热力图、时间序列折线图、气象要素柱状图等交互式图表进行直观展示,兼顾科研分析与大众化信息解读。压缩包共140个文件(3.26MB),含35个CSV气象数据集、31张可视化结果JPG图、19个前端JS交互脚本、12个PNG图标及10个CSS样式文件,配合Bootstrap、Font Awesome等主流前端库构建响应式界面;另有4个核心Python爬虫与Flask后端模块,结构清晰、注释完备,便于二次开发与教学复现。目前已有120人学习下载,提供开箱即用的本地部署方案与完整前后端代码,是理解Web+数据可视化项目落地的典型实践案例。

1. 项目概述与核心价值

最近在做一个挺有意思的玩意儿,一个专门针对山东省天气数据的爬取和可视化分析系统。起因很简单,我之前想查查老家山东某个小县城过去几年的天气变化趋势,发现要么是商业气象网站数据不全,要么是API调用次数有限制,要么就是数据格式不友好,没法按自己的想法做深度分析。这让我觉得,如果能自己动手,把数据抓下来、存起来、再按需可视化,那不就自由多了?于是,这个基于Python和Flask的“山东省天气数据爬取及可视化分析系统”就诞生了。

这个系统本质上是一个Web应用,它干三件核心的事:第一,从网络上自动、持续地抓取山东省各地市的实时和历史天气数据;第二,把这些结构化和非结构化的气象数据,规规矩矩地存到数据库里,方便后续查询和分析;第三,也是最有意思的部分,就是通过一个友好的网页界面,用地图、折线图、柱状图、热力图等多种形式,把温度、湿度、风速、降水这些气象要素直观地展示出来,让你一眼就能看出天气的时空分布规律和变化趋势。

它适合谁呢?如果你是Python初学者,想找一个有完整链路(爬虫->数据处理->Web开发->数据可视化)的真实项目来练手,这个项目再合适不过了,涵盖了从后端到前端的多个核心技能点。如果你是对气象数据分析感兴趣的数据爱好者、学生,或者需要本地化气象数据支持的小型研究或业务团队,这个系统提供了一个可定制、可扩展的基础框架。当然,前提是你得对Python有点基础,知道怎么配环境、装库。

2. 系统整体架构与技术选型

2.1 核心架构设计思路

整个系统的设计遵循了典型的数据流水线思维,也就是“数据采集 -> 数据存储 -> 数据处理 -> 数据展示”这条主线。但和简单的脚本不同,我们需要把它工程化、服务化,这就是引入Flask框架的原因。

我的架构分为三个相对独立的模块,通过数据库和Web服务器进行耦合:

  1. 数据爬取模块:这是一个可以独立运行的脚本或定时任务。它负责访问目标天气数据源,解析网页或调用API,将获取到的山东省17个地市(济南、青岛、烟台等)的天气数据清洗成结构化格式。
  2. 数据存储与处理模块:核心是数据库。爬取到的数据会被持久化存储在这里。我选择的是关系型数据库,因为它结构清晰,便于做复杂查询(比如“查询济南市2023年夏季所有日平均温度高于30度的日期”)。同时,这里也包含一些简单的数据预处理逻辑,比如单位换算、异常值处理。
  3. Web可视化展示模块:这是Flask的“主战场”。它负责提供网页界面,接收用户的查询请求(比如选择城市、时间范围、气象要素),从数据库中取出相应的数据,然后利用前端的图表库生成可视化图表,最后渲染成HTML页面返回给用户。

这种模块化设计的好处是解耦。爬虫挂了不影响网站访问;你想换一种图表库,也只需要改动前端部分;甚至未来想增加新的数据源,也只需要扩展爬虫模块。

2.2 关键技术栈选型与理由

  • 后端框架:Flask为什么选Flask而不是Django?对于这个数据展示型的项目,Django那套全功能的“全家桶”(自带Admin、ORM、用户认证等)显得有些重了。我们的核心需求是轻量、灵活、快速搭建RESTful API来提供数据。Flask的微框架特性正好契合,它没有强制的项目结构,用多少功能就引入多少扩展,学习曲线平缓,能让我更专注于业务逻辑本身。比如,我需要提供JSON格式的天气数据给前端图表库,用Flask的jsonify和路由功能几行代码就能搞定。

  • 爬虫库:Requests + BeautifulSoup4 / PyQueryRequests库是HTTP请求的“瑞士军刀”,简单易用,配合BeautifulSoup4或PyQuery进行HTML解析,足以应对大多数静态天气网站。如果目标网站数据是通过JavaScript动态加载的,那么可能需要引入Selenium或Playwright。考虑到天气数据通常更新不频繁且结构相对固定,静态解析在大多数情况下是更高效稳定的选择。

  • 数据存储:SQLite / MySQL项目初期或者个人学习使用,SQLite是绝佳选择。它是一个文件数据库,无需安装和配置独立的数据库服务,用Python标准库sqlite3就能直接操作,极大简化了部署复杂度。当数据量增大(比如存储了十年以上的逐日数据),或者需要多人协作时,可以平滑迁移到MySQLPostgreSQL。我建议初期用SQLite快速原型验证,后期再根据需求升级。

  • 数据可视化:ECharts / Plotly这是前端的核心。ECharts是百度开源的一个强大的图表库,文档丰富,社区活跃,地图支持(尤其是对中国省份城市的支持)非常好,这对于展示山东省内各地市的气象数据分布至关重要。它的热力图、折线图、柱状图等都能满足需求。Plotly也是一个优秀的选择,它的交互性更强,并且同时支持在Python后端生成静态图表和在前端动态交互。在这个项目中,我选择让Flask后端只提供纯净的JSON数据,由前端JavaScript调用ECharts库来渲染图表,这样前后端分离更彻底,用户体验也更流畅。

  • 前端框架:简单的HTML/CSS/JavaScript + Bootstrap为了快速搭建一个整洁美观的界面,我引入了Bootstrap前端框架。它提供了现成的网格系统、按钮、表单和导航栏样式,让我们不用在CSS上花费太多精力。页面布局主要用Bootstrap的栅格系统来划分区域,比如左侧放城市选择面板,中间主区域展示地图和图表。

注意:技术选型没有绝对的对错,只有是否适合当前场景。这里的选择是基于“快速实现、易于学习、满足核心功能”的原则。如果你的项目对并发性能要求极高,可能需要考虑异步框架(如FastAPI)和更强大的数据库优化。

3. 核心模块实现细节拆解

3.1 天气数据爬取模块的实现

爬虫是这个系统的数据源头,它的稳定性和准确性直接决定了后续所有分析的价值。我的目标是爬取山东省每个地级市的实时天气(温度、湿度、风向风速、天气状况)和历史数据(每日最高/最低温、降水量等)。

第一步:目标网站分析与数据源选择首先得找到可靠、免费且可持续访问的数据源。国内有一些提供天气数据的网站和API。在选择时,我主要考虑几点:1) 数据是否包含山东省所有地市;2) 历史数据是否可获取;3) 网站是否有反爬机制(如频繁访问限制、验证码);4) 数据更新频率。经过一番搜寻和测试,我选定了一个结构清晰、反爬相对宽松的公益性气象数据网站作为主要数据源。切记,爬取数据一定要遵守网站的robots.txt协议,控制请求频率,避免对对方服务器造成压力。

第二步:编写爬虫脚本核心代码结构如下:

import requests from bs4 import BeautifulSoup import pandas as pd import time import sqlite3 # 1. 定义目标城市列表(山东省17地市) shandong_cities = [‘济南‘, ‘青岛‘, ‘淄博‘, ...] # 2. 模拟请求头,避免被识别为简单爬虫 headers = { ‘User-Agent‘: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...‘ } def fetch_realtime_weather(city_name): """抓取单个城市的实时天气""" # 构建对应城市的URL,这里需要根据实际网站结构分析 url = f‘http://example-weather-site.com/{city_name}‘ try: resp = requests.get(url, headers=headers, timeout=10) resp.encoding = ‘utf-8‘ # 根据网站编码调整 soup = BeautifulSoup(resp.text, ‘html.parser‘) # 3. 解析页面,提取关键数据 # 这部分代码高度依赖目标网站的HTML结构,需要仔细分析其标签和class temp = soup.find(‘span‘, class_=‘temp‘).text # 例如温度 humidity = soup.find(‘div‘, id=‘humidity‘).text # 湿度 # ... 提取其他要素 weather_data = { ‘city‘: city_name, ‘temperature‘: temp, ‘humidity‘: humidity, ‘wind_speed‘: wind_speed, ‘update_time‘: ‘2023-10-27 14:00‘, # 实际应从页面解析或使用当前时间 ‘data_time‘: ‘2023-10-27 14:00‘ # 数据对应的时间点 } return weather_data except Exception as e: print(f“抓取{city_name}数据失败: {e}“) return None def save_to_db(data_dict, db_path=‘weather.db‘): """将数据保存到SQLite数据库""" conn = sqlite3.connect(db_path) cursor = conn.cursor() # 确保表存在,这里以实时数据表为例 cursor.execute(‘‘‘CREATE TABLE IF NOT EXISTS realtime_weather (id INTEGER PRIMARY KEY AUTOINCREMENT, city TEXT, temperature REAL, humidity REAL, wind_speed REAL, update_time TIMESTAMP, data_time TIMESTAMP)‘‘‘) # 插入数据 cursor.execute(‘‘‘INSERT INTO realtime_weather (city, temperature, humidity, wind_speed, update_time, data_time) VALUES (?, ?, ?, ?, ?, ?)‘‘‘, (data_dict[‘city‘], data_dict[‘temperature‘], data_dict[‘humidity‘], data_dict[‘wind_speed‘], data_dict[‘update_time‘], data_dict[‘data_time‘])) conn.commit() conn.close() # 4. 主循环:遍历所有城市,抓取并保存 for city in shandong_cities: print(f‘正在抓取{city}的天气...‘) data = fetch_realtime_weather(city) if data: save_to_db(data) # 礼貌性延时,避免请求过快 time.sleep(2) print(‘所有城市实时天气数据抓取完成!‘)

第三步:历史数据抓取策略历史数据通常以“日期”为维度进行查询。这就需要构造一个日期循环,比如从2023-01-01循环到今天,每天请求一次历史数据页面。URL模式通常是base_url + city + date。这里的关键点是异常处理增量抓取。每次抓取前,先查询数据库是否已存在该城市该日期的数据,如果存在则跳过,避免重复劳动和浪费资源。同时,网络请求和页面结构可能变动,必须用try...except包裹核心抓取逻辑,并做好日志记录。

第四步:定时任务与自动化为了让数据保持更新,需要让爬虫定时运行。在Linux服务器上,最直接的方式是使用Crontab。你可以设置一个定时任务,比如每小时运行一次实时数据爬虫,每天凌晨运行一次历史数据更新爬虫。

# 编辑crontab crontab -e # 添加以下行,表示每小时的第5分钟运行一次爬虫脚本 5 * * * * /usr/bin/python3 /path/to/your/realtime_spider.py >> /path/to/spider.log 2>&1

在Windows上,可以使用计划任务功能实现类似效果。更工程化的做法是使用APScheduler这样的Python库,在Flask应用内部实现定时任务。

3.2 数据存储与数据库设计

数据库设计的好坏,直接影响数据查询的效率和后续分析的便利性。我设计了两张核心表:

1. 实时天气数据表 (realtime_weather)这张表存储最新的、或按小时抓取的天气快照。

CREATE TABLE realtime_weather ( id INTEGER PRIMARY KEY AUTOINCREMENT, city VARCHAR(20) NOT NULL, -- 城市名 temperature FLOAT, -- 温度(摄氏度) humidity FLOAT, -- 湿度(百分比) wind_speed FLOAT, -- 风速(米/秒) wind_direction VARCHAR(10), -- 风向 weather_condition VARCHAR(20), -- 天气状况(晴、多云、雨等) pressure FLOAT, -- 气压(百帕) visibility FLOAT, -- 能见度(公里) aqi INTEGER, -- 空气质量指数(如有) data_time TIMESTAMP NOT NULL, -- 数据所代表的时间(如2023-10-27 14:00:00) update_time TIMESTAMP DEFAULT (datetime(‘now‘, ‘localtime‘)) -- 数据入库时间 ); -- 为经常查询的字段创建索引,大幅提升查询速度 CREATE INDEX idx_city ON realtime_weather (city); CREATE INDEX idx_data_time ON realtime_weather (data_time);

2. 历史天气数据表 (history_weather)这张表存储每日的统计型数据,如日最高/最低温、降水量等。

CREATE TABLE history_weather ( id INTEGER PRIMARY KEY AUTOINCREMENT, city VARCHAR(20) NOT NULL, date DATE NOT NULL, -- 日期 max_temp FLOAT, -- 日最高温 min_temp FLOAT, -- 日最低温 avg_temp FLOAT, -- 日平均温 precipitation FLOAT, -- 降水量(毫米) sunshine_hours FLOAT, -- 日照时长(小时) avg_humidity FLOAT, -- 平均湿度 dominant_wind_direction VARCHAR(10), -- 主导风向 UNIQUE(city, date) -- 联合唯一约束,防止同一城市同一天数据重复 ); CREATE INDEX idx_city_date ON history_weather (city, date);

实操心得UNIQUE(city, date)这个约束非常重要。它能确保爬虫脚本在意外重复运行时,数据库不会插入重复数据,要么忽略,要么更新(使用INSERT OR REPLACEON CONFLICT子句)。另外,为citydate/data_time字段建立索引,对于按城市和时间范围查询的性能提升是巨大的,当数据量达到几十万条时,有索引和没索引的查询速度可能是天壤之别。

3.3 Flask后端API设计与实现

Flask后端的作用是充当数据库和前端页面之间的桥梁,提供数据接口。我们不需要复杂的页面渲染,主要提供RESTful API。

项目结构

weather_visualization_system/ ├── app.py # Flask应用主入口 ├── config.py # 配置文件(数据库路径、密钥等) ├── models.py # 数据库模型定义(可使用SQLAlchemy ORM) ├── spider/ # 爬虫模块目录 │ ├── __init__.py │ ├── realtime_spider.py │ └── history_spider.py ├── static/ # 静态文件(CSS, JS, 图片) │ ├── css/ │ └── js/ ├── templates/ # 网页模板(HTML) │ └── index.html └── weather.db # SQLite数据库文件

核心API路由示例 (app.py)

from flask import Flask, render_template, jsonify, request import sqlite3 from datetime import datetime, timedelta app = Flask(__name__) app.config.from_pyfile(‘config.py‘) # 从配置文件加载配置 def get_db_connection(): """获取数据库连接(简单示例,生产环境建议用连接池)""" conn = sqlite3.connect(app.config[‘DATABASE‘]) # 设置返回字典格式的游标,方便处理 conn.row_factory = sqlite3.Row return conn @app.route(‘/‘) def index(): """首页,渲染主可视化页面""" return render_template(‘index.html‘) @app.route(‘/api/realtime‘) def get_realtime_data(): """API:获取所有城市的最新实时天气数据""" conn = get_db_connection() # 获取每个城市最新的一条记录 cursor = conn.execute(‘‘‘ SELECT city, temperature, humidity, wind_speed, weather_condition, data_time FROM realtime_weather WHERE data_time = (SELECT MAX(data_time) FROM realtime_weather WHERE city = rw.city) GROUP BY city ORDER BY city ‘‘‘) # 另一种更高效的写法是使用窗口函数,但SQLite版本需支持 rows = cursor.fetchall() conn.close() # 将结果转换为字典列表 data = [dict(row) for row in rows] return jsonify({‘code‘: 0, ‘msg‘: ‘success‘, ‘data‘: data}) @app.route(‘/api/history‘) def get_history_data(): """API:获取指定城市、时间范围的历史数据""" city = request.args.get(‘city‘, ‘济南‘) start_date = request.args.get(‘start‘, (datetime.now() - timedelta(days=30)).strftime(‘%Y-%m-%d‘)) end_date = request.args.get(‘end‘, datetime.now().strftime(‘%Y-%m-%d‘)) conn = get_db_connection() cursor = conn.execute(‘‘‘ SELECT date, avg_temp, max_temp, min_temp, precipitation FROM history_weather WHERE city = ? AND date BETWEEN ? AND ? ORDER BY date ASC ‘‘‘, (city, start_date, end_date)) rows = cursor.fetchall() conn.close() dates = [row[‘date‘] for row in rows] avg_temps = [row[‘avg_temp‘] for row in rows] # ... 组织其他数据 chart_data = { ‘dates‘: dates, ‘avg_temps‘: avg_temps, ‘precipitations‘: [row[‘precipitation‘] for row in rows] } return jsonify({‘code‘: 0, ‘data‘: chart_data}) if __name__ == ‘__main__‘: app.run(debug=True) # 生产环境务必关闭debug模式

这个后端非常简洁。/api/realtime接口用于在地图上展示全省实时天气状况;/api/history接口用于绘制某个城市的温度、降水变化折线图。前端通过Ajax调用这些接口获取JSON数据。

3.4 前端可视化界面与ECharts集成

前端页面 (templates/index.html) 是用户交互的界面。我使用Bootstrap进行快速布局,并引入ECharts库进行绘图。

页面布局要点

  1. 导航栏:显示系统标题,可能包含数据更新时间等信息。
  2. 侧边栏控制面板:使用Bootstrap的col-md-3col-lg-2。这里放置:
    • 城市选择下拉菜单(单选或多选)。
    • 时间范围选择器(用于历史数据查询)。
    • 气象要素选择复选框(温度、湿度、降水、风速)。
    • 图表类型切换按钮(地图/折线图/柱状图/热力图)。
    • 一个“查询/更新”按钮。
  3. 主内容区:使用Bootstrap的col-md-9col-lg-10。这里是一个大的div容器,用于承载ECharts图表。
  4. 多个图表容器:可以设计选项卡(Tabs)来切换显示不同的图表,比如一个Tab放全省实时地图,一个Tab放单城市历史趋势图。

集成ECharts的核心JavaScript逻辑

  1. 在HTML中引入ECharts JS文件。
  2. 为每个图表容器div初始化一个ECharts实例。
  3. 编写函数,通过Fetch API或jQuery的$.ajax调用我们写好的Flask API (/api/realtime,/api/history)。
  4. 将API返回的JSON数据,按照ECharts要求的格式进行转换。
  5. 调用ECharts实例的setOption方法,渲染图表。

示例:绘制山东省实时温度地图

<!— 在HTML中 —> <div id=“realtimeMap“ style=“width: 100%; height: 600px;“></div> <script src=“https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js“></script> <script src=“https://cdn.jsdelivr.net/npm/echarts/map/js/china.js“></script> <!— 引入中国地图 —> <script> // 初始化图表 var mapChart = echarts.init(document.getElementById(‘realtimeMap‘)); // 从后端API获取数据 fetch(‘/api/realtime‘) .then(response => response.json()) .then(result => { if (result.code === 0) { var cityData = result.data; // 将数据转换为ECharts地图需要的格式 // 格式: [{name: ‘济南‘, value: [117.000923, 36.675807, 25]}, ...] // 其中value数组为[经度, 纬度, 数值(如温度)] var mapSeriesData = []; // 这里需要一个城市名到经纬度的映射字典 var cityCoords = { ‘济南‘: [117.000923, 36.675807], ‘青岛‘: [120.355173, 36.082982], // ... 其他城市 }; cityData.forEach(item => { var coord = cityCoords[item.city]; if (coord) { mapSeriesData.push({ name: item.city, value: coord.concat([item.temperature]) // 拼接经纬度和温度值 }); } }); // 配置项 var option = { title: { text: ‘山东省实时温度分布‘, left: ‘center‘ }, tooltip: { trigger: ‘item‘, formatter: function(params) { return params.name + ‘: ‘ + params.value[2] + ‘°C‘; } }, visualMap: { min: 0, max: 40, left: ‘left‘, top: ‘bottom‘, text: [‘高温‘, ‘低温‘], calculable: true, inRange: { color: [‘#313695‘, ‘#4575b4‘, ‘#74add1‘, ‘#abd9e9‘, ‘#e0f3f8‘, ‘#ffffbf‘, ‘#fee090‘, ‘#fdae61‘, ‘#f46d43‘, ‘#d73027‘, ‘#a50026‘] } }, series: [{ name: ‘温度‘, type: ‘map‘, map: ‘山东‘, // 需要注册‘山东‘地图,可以从china.js细化或使用geoJSON // 或者使用 ‘china‘ 地图,然后通过regions指定山东区域 roam: false, // 禁止拖动缩放 zoom: 1.2, // 初始缩放级别 label: { show: true }, emphasis: { label: { show: true } }, data: mapSeriesData }] }; // 使用刚指定的配置项和数据显示图表。 mapChart.setOption(option); } }) .catch(error => console.error(‘Error fetching realtime data:‘, error)); // 响应窗口大小变化 window.addEventListener(‘resize‘, function() { mapChart.resize(); }); </script>

注意事项:ECharts自带的中国地图只到省级。如果你想展示山东省内更详细的地市边界,需要额外引入或自己制作山东省的GeoJSON地图数据,并通过echarts.registerMap(‘山东‘, geoJsonData)进行注册。这是让地图可视化更精确的关键一步。

4. 系统部署与优化实践

4.1 本地运行与测试

在开发完成后,首先在本地进行完整测试。

  1. 环境准备:确保安装了所有依赖库。可以使用pip freeze > requirements.txt命令生成依赖列表。
  2. 启动Flask开发服务器:在项目根目录运行python app.py。Flask默认会在http://127.0.0.1:5000启动服务。
  3. 手动运行爬虫:先运行一次爬虫脚本 (python spider/realtime_spider.py),确保有数据存入数据库。
  4. 访问页面:打开浏览器访问http://127.0.0.1:5000,检查页面是否正常加载,图表是否成功渲染,交互功能(如选择城市、时间)是否正常。
  5. 测试API:直接访问http://127.0.0.1:5000/api/realtime,查看返回的JSON数据是否正确。

4.2 生产环境部署(以Linux服务器+Nginx+Gunicorn为例)

Flask自带的开发服务器性能弱且不安全,绝不能用于生产环境。生产部署通常需要WSGI服务器+反向代理服务器的组合。

  1. 安装Gunicornpip install gunicorn
  2. 使用Gunicorn启动应用
    # 在项目根目录下运行,-w 指定worker进程数,通常为CPU核心数*2+1 gunicorn -w 4 -b 0.0.0.0:8000 app:app
    此时应用运行在8000端口。
  3. 配置Nginx作为反向代理
    • 安装Nginx:sudo apt install nginx
    • 编辑Nginx站点配置文件,例如/etc/nginx/sites-available/weather
    server { listen 80; server_name your_domain.com; # 你的域名或服务器IP location / { # 将请求转发给运行在8000端口的Gunicorn proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 可选的静态文件服务,如果静态文件量大,可由Nginx直接处理,效率更高 location /static { alias /path/to/your/weather_visualization_system/static; expires 30d; } }
    • 创建软链接并重启Nginx:
    sudo ln -s /etc/nginx/sites-available/weather /etc/nginx/sites-enabled/ sudo nginx -t # 测试配置 sudo systemctl restart nginx
  4. 使用进程管理工具(如Supervisor):确保Gunicorn进程在后台稳定运行,崩溃后自动重启。
    ; /etc/supervisor/conf.d/weather.conf [program:weather_app] command=/path/to/your/venv/bin/gunicorn -w 4 -b 127.0.0.1:8000 app:app directory=/path/to/your/weather_visualization_system user=your_username autostart=true autorestart=true stderr_logfile=/var/log/weather/err.log stdout_logfile=/var/log/weather/out.log
    然后启动Supervisor:sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl start weather_app

4.3 性能优化与功能扩展思路

当系统跑起来后,可以从以下几个方向进行优化和增强:

  1. 数据库优化

    • 索引:如前所述,在city,date,data_time字段上建立索引。
    • 查询优化:避免SELECT *,只查询需要的字段。对于复杂的历史趋势查询,可以考虑对数据进行预聚合,比如提前计算好“月平均温度”存入另一张表,用空间换时间。
    • 连接池:在生产环境中,使用如DBUtilsSQLAlchemy自带的连接池管理数据库连接,避免频繁创建和销毁连接的开销。
  2. 前端优化

    • 图表按需加载:初始只加载核心图表(如地图),当用户点击具体城市或切换Tab时,再通过Ajax加载对应的详细图表数据。
    • 数据缓存:对于实时性要求不高的历史数据查询结果,可以在前端或后端(如使用Redis)进行短时间缓存,减少数据库压力。
    • 防抖与节流:对频繁触发的事件(如时间选择器的变化),使用防抖(Debounce)或节流(Throttle)技术,避免短时间内向服务器发送大量重复请求。
  3. 功能扩展

    • 多数据源融合:除了爬取网站,可以尝试接入一些免费的公共气象API(注意调用限制),将多个来源的数据进行对比和融合,提高数据的准确性和可靠性。
    • 预警功能:在后端设置规则(如“连续3天最高温>35度”或“单日降水量>50mm”),当爬取到的新数据触发规则时,通过邮件、微信机器人等方式发送预警通知。
    • 移动端适配:利用Bootstrap的响应式特性,使页面在手机和平板上也能良好显示。
    • 用户交互增强:增加图表联动,比如点击地图上的城市,右侧自动显示该城市的详细历史趋势图。

5. 常见问题与排查技巧实录

在实际开发和部署过程中,我踩过不少坑,这里总结几个典型问题和解决方法。

5.1 爬虫模块常见问题

问题1:爬虫运行一段时间后突然报错ConnectionErrorTimeout

  • 原因:目标网站对频繁访问的IP进行了临时封锁或限速。
  • 解决
    1. 增加延时:在请求之间加入随机延时,模拟人类操作。time.sleep(random.uniform(1, 3))
    2. 使用代理IP池:如果数据量巨大,可以考虑使用付费或免费的代理IP,轮流使用不同IP发起请求。
    3. 设置User-Agent轮换:准备一个User-Agent列表,每次请求随机选择一个。
    4. 处理HTTP状态码:在代码中检查响应状态码,如果是403/429等,说明可能被反爬,应暂停一段时间或更换策略。
    5. 使用更“友好”的库:对于动态加载的网站,可以尝试使用requests-htmlSelenium,但速度会慢很多。

问题2:页面结构变化导致数据解析失败。

  • 原因:天气网站改版,HTML标签或class名称变了。
  • 解决
    1. 健壮的解析逻辑:不要依赖过于具体的标签路径。多用find_all配合文本内容过滤,或使用更灵活的CSS选择器。
    2. 异常捕获与日志:将解析代码包裹在try...except中,一旦解析失败,记录详细的错误信息和当时的HTML片段(可保存到文件),便于后续分析。
    3. 定期检查:将爬虫作为定时任务运行,并监控其日志。一旦发现连续解析失败,及时通知维护者。

5.2 Flask后端与数据库问题

问题3:前端请求API返回500错误,查看Flask日志发现数据库被锁 (sqlite3.OperationalError: database is locked)。

  • 原因:SQLite在同一时间只允许一个写入操作。当爬虫脚本正在写入数据(特别是大量历史数据插入)的同时,Flask应用尝试查询数据库,就可能发生锁冲突。
  • 解决
    1. 读写分离:这是根本解决方法。将数据库迁移到MySQL或PostgreSQL,它们能更好地处理并发。
    2. 优化爬虫写入:对于SQLite,爬虫脚本应使用批量插入 (executemany) 而不是逐条插入,并尽快提交事务,减少锁持有时间。
    3. 重试机制:在Flask的数据库查询函数中,加入简单的重试逻辑,遇到database is locked错误时等待一小段时间再重试。
    4. 错峰运行:安排爬虫在访问低谷期(如凌晨)运行。

问题4:ECharts地图不显示或显示不完整。

  • 原因: a. GeoJSON地图数据未正确注册或路径错误。 b. 地图容器div的宽度或高度为0。 c. 数据格式不符合ECharts地图series的要求。
  • 排查
    1. 打开浏览器开发者工具(F12),查看“网络(Network)”选项卡,确认地图JS文件和GeoJSON数据文件是否成功加载(状态码200)。
    2. 查看“控制台(Console)”是否有JavaScript报错。
    3. 检查ECharts初始化代码,确保DOM元素ID正确,并且在该元素已经加载到页面后再执行初始化(通常把JS代码放在</body>标签前,或使用$(document).ready())。
    4. 仔细核对series.data的格式,确保是[{name: ‘城市‘, value: [经度, 纬度, 数值]}, ...]

5.3 部署与性能问题

问题5:服务器内存占用越来越高,最终导致应用崩溃。

  • 原因:可能是内存泄漏。常见于: a. Flask应用配置了debug=True在生产环境运行。 b. 全局变量不当引用导致对象无法被垃圾回收。 c. 数据库连接未正确关闭。
  • 解决
    1. 生产环境务必关闭Debug模式:设置app.run(debug=False),并通过环境变量或配置文件管理。
    2. 使用连接池管理数据库连接:确保每次查询后连接被正确放回池中。
    3. 使用专业工具监控:用top,htopps命令监控进程内存使用情况。使用gunicorn--max-requests--max-requests-jitter参数,让worker进程在处理一定数量的请求后重启,可以释放积累的内存碎片。
    4. 代码审查:检查是否有全局列表、字典在无限增长。

问题6:图表加载速度慢,特别是历史数据查询时间范围很长时。

  • 原因:一次性从数据库查询出数万条记录,传输到前端,前端JS再处理渲染,耗时很长。
  • 解决
    1. 后端分页或聚合:不要一次性返回所有数据。修改/api/history接口,支持分页参数(page,size),或者在前端选择时间范围时,提供“按日/按月/按年”聚合的选项,后端直接返回聚合后的数据(如月平均值),数据量会大大减少。
    2. 前端数据懒加载:对于折线图,可以采用“滚动加载”或“缩放加载”的模式,初始只加载最近一年的数据,当用户放大查看某段细节时,再动态加载该时间段的高精度数据。
    3. 启用数据库查询缓存:对于不常变动的历史数据,可以在应用层(如使用Flask-Caching扩展配合Redis)或数据库层设置查询缓存。

这个项目从构思到实现,再到一步步优化,让我对Python全栈开发的各个环节——从数据获取、持久化、后端服务到前端展示——有了更扎实的实践理解。最大的体会是,一个看似简单的“数据可视化”需求,背后串联起来的技术点非常丰富,任何一个环节的细节处理不当,都可能影响最终的用户体验。比如,爬虫的稳定性决定了数据的“质”,数据库的设计影响了查询的“速”,而前端图表的选择和配置则决定了信息的“形”。把这三者流畅地结合起来,才是这个项目真正的价值所在。如果你正在学习Python,不妨也找一个自己感兴趣领域的数据,动手搭建一个这样的系统,过程中遇到的每一个问题,都是最好的学习材料。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询