<think>我们被要求构建一个多源天气数据爬取与可视化分析系统。根据引用,我们注意到有几个关键点:
1. 爬取多个来源的天气数据(这里以中国天气网为例,但系统应支持多源)
2. 将数据保存为CSV文件
3. 对当天和未来14天的天气数据进行可视化分析
我们将设计一个系统,包含以下模块:
- 爬虫模块:针对不同数据源编写爬虫,每个爬虫负责从特定来源获取数据并保存为CSV。
- 数据处理模块:对爬取的数据进行清洗和整理,形成统一格式。
- 可视化模块:提供多种可视化图表(折线图、雷达图、柱状图等)展示天气数据。
由于引用中只提到了中国天气网,我们将以中国天气网为例编写代码,但系统设计应便于扩展其他数据源。
步骤:
1. 定义数据模型:设计一个统一的数据结构来存储不同来源的天气数据。
2. 编写爬虫基类,然后针对每个数据源实现具体的爬虫。
3. 编写数据存储模块,将数据保存为CSV(也可以考虑数据库,但引用要求CSV)。
4. 编写可视化模块,根据需求生成图表。
考虑到多源,我们假设每个数据源的爬虫独立,但最终数据都转换为统一格式。
统一数据格式设计(每天的数据):
- 日期(date)
- 最高温度(max_temp)
- 最低温度(min_temp)
- 天气状况(weather)
- 风向(wind_direction)
- 风力(wind_power)
- 湿度(humidity)
- 空气质量(air_quality)【如果有】
- 数据来源(source)
我们以中国天气网为例,编写一个爬虫。根据引用[4],我们可以爬取当天和未来14天的数据。
由于系统要求多源,我们还可以考虑添加其他数据源(例如:中央气象台、和风天气等),但这里我们只实现中国天气网,并预留接口。
代码结构:
- weather_system/
- __init__.py
- models.py # 数据模型
- spiders/ # 爬虫模块
- __init__.py
- base_spider.py # 爬虫基类
- weather_com_cn_spider.py # 中国天气网爬虫
- storage.py # 存储模块
- visualizer.py # 可视化模块
- main.py # 主程序
由于代码量较大,我们只展示关键部分的代码。
首先,定义数据模型(models.py):
```python
class WeatherData:
def __init__(self, date, max_temp, min_temp, weather, wind_direction, wind_power, humidity, air_quality=None, source=None):
self.date = date
self.max_temp = max_temp
self.min_temp = min_temp
self.weather = weather
self.wind_direction = wind_direction
self.wind_power = wind_power
self.humidity = humidity
self.air_quality = air_quality
self.source = source
# 转换为字典,便于存储为CSV
def to_dict(self):
return {
'date': self.date,
'max_temp': self.max_temp,
'min_temp': self.min_temp,
'weather': self.weather,
'wind_direction': self.wind_direction,
'wind_power': self.wind_power,
'humidity': self.humidity,
'air_quality': self.air_quality,
'source': self.source
}
```
然后,编写爬虫基类(spiders/base_spider.py):
```python
import abc
class BaseSpider(metaclass=abc.ABCMeta):
@abc.abstractmethod
def fetch_data(self, city_code):
pass
```
中国天气网爬虫(spiders/weather_com_cn_spider.py):
根据引用[4],我们可以爬取当天和未来14天的数据。注意,中国天气网当天的URL和14天的URL不同。
当天的URL: http://www.weather.com.cn/weather1d/{city_code}.shtml
14天的URL: http://www.weather.com.cn/weather15d/{city_code}.shtml
但是引用[4]中使用了7天的(weather)和14天的(weather15d)。这里我们按照引用[4]的说明。
由于我们要爬取多天数据,这里我们选择爬取未来14天的数据(包括当天)。注意:中国天气网未来14天的数据实际上包含15天(包括当天和未来14天),但通常称为15天预报。
我们以15天预报为例(即访问weather15d的URL)。
具体爬虫实现:
```python
import requests
from bs4 import BeautifulSoup
from ..models import WeatherData
class WeatherComCnSpider(BaseSpider):
def fetch_data(self, city_code):
"""从中国天气网获取15天天气预报(包括当天)"""
url = f"http://www.weather.com.cn/weather15d/{city_code}.shtml"
html = self.get_html(url)
if html:
return self.parse_html(html, city_code)
else:
return []
def get_html(self, url):
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return None
def parse_html(self, html, city_code):
soup = BeautifulSoup(html, 'html.parser')
# 找到15天天气预报的ul
ul = soup.find('ul', class_='t clearfix')
if not ul:
return []
lis = ul.find_all('li')
weather_list = []
for li in lis:
# 日期
date = li.find('h1').text
# 天气状况
weather = li.find('p', class_='wea').text
# 温度
temp = li.find('p', class_='tem')
max_temp = temp.find('span').text.replace('℃', '') # 最高温
min_temp = temp.find('i').text.replace('℃', '') # 最低温
# 风向风力
wind = li.find('p', class_='win')
wind_direction = wind.find('em').find('span')['title'] # 风向
wind_power = wind.find('i').text # 风力
# 湿度(在同一个li中,但注意中国天气网15天预报中湿度在第二个p标签?实际查看网页结构)
# 注意:15天预报的网页中,湿度信息在class为'hum'的p标签中
humidity_tag = li.find('p', class_='hum')
humidity = humidity_tag.text.strip() if humidity_tag else '未知'
# 空气质量(15天预报中没有直接给出,所以设为None)
air_quality = None
# 创建WeatherData对象
weather_data = WeatherData(
date=date,
max_temp=max_temp,
min_temp=min_temp,
weather=weather,
wind_direction=wind_direction,
wind_power=wind_power,
humidity=humidity,
air_quality=air_quality,
source='中国天气网'
)
weather_list.append(weather_data)
return weather_list
```
注意:实际网页结构可能发生变化,以上代码可能需要调整。
存储模块(storage.py):
```python
import csv
from .models import WeatherData
class Storage:
@staticmethod
def save_to_csv(weather_data_list, filename):
if not weather_data_list:
return
keys = weather_data_list[0].to_dict().keys()
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=keys)
writer.writeheader()
for data in weather_data_list:
writer.writerow(data.to_dict())
```
可视化模块(visualizer.py):
根据引用,我们可以绘制温湿度变化曲线、风向雷达图等。这里我们使用matplotlib和seaborn。
由于时间关系,我们只展示如何绘制温度变化曲线和风向雷达图。
```python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib.font_manager import FontProperties
class Visualizer:
@staticmethod
def plot_temperature_curve(df, title='温度变化曲线', save_path=None):
"""绘制温度变化曲线"""
# 设置中文字体
font = FontProperties(fname=r"C:\Windows\Fonts\simhei.ttf", size=14) # 根据系统调整
plt.figure(figsize=(12, 6))
# 将日期转换为字符串,避免日期排序问题(如果日期是乱序,需要先排序)
dates = df['date']
# 绘制最高温度
plt.plot(dates, df['max_temp'], 'r-', label='最高温度')
# 绘制最低温度
plt.plot(dates, df['min_temp'], 'b-', label='最低温度')
plt.xlabel('日期', fontproperties=font)
plt.ylabel('温度(℃)', fontproperties=font)
plt.title(title, fontproperties=font)
plt.legend(prop=font)
plt.xticks(rotation=45)
plt.tight_layout()
if save_path:
plt.savefig(save_path)
else:
plt.show()
@staticmethod
def plot_wind_radar(df, title='风向雷达图', save_path=None):
"""绘制风向雷达图"""
# 统计各个风向出现的次数
wind_direction_count = df['wind_direction'].value_counts()
# 如果没有数据则返回
if wind_direction_count.empty:
return
# 准备雷达图数据
directions = wind_direction_count.index.tolist()
counts = wind_direction_count.values.tolist()
# 将方向按照固定的顺序(如东、南、西、北等)排序,这里简单按字母顺序,实际可能需要映射
# 由于风向是中文,我们按照常见的八个方向排序:东、南、西、北、东北、东南、西南、西北
common_directions = ['东', '南', '西', '北', '东北', '东南', '西南', '西北']
# 过滤掉不在常见方向中的项
sorted_directions = [d for d in common_directions if d in directions]
# 按照common_directions的顺序取counts,如果某个方向没有,则设为0
sorted_counts = [wind_direction_count.get(d, 0) for d in common_directions]
# 雷达图需要闭合,所以将第一个数据复制到末尾
sorted_directions.append(sorted_directions[0])
sorted_counts.append(sorted_counts[0])
# 设置角度
angles = np.linspace(0, 2 * np.pi, len(common_directions), endpoint=False).tolist()
angles.append(angles[0])
# 画图
plt.figure(figsize=(8, 8))
ax = plt.subplot(111, polar=True)
ax.plot(angles, sorted_counts, 'o-', linewidth=2)
ax.fill(angles, sorted_counts, alpha=0.25)
ax.set_thetagrids(np.degrees(angles[:-1]), sorted_directions[:-1]) # 不显示最后一个重复的
plt.title(title, fontproperties=FontProperties(fname=r"C:\Windows\Fonts\simhei.ttf", size=14))
if save_path:
plt.savefig(save_path)
else:
plt.show()
```
主程序(main.py):
```python
from spiders.weather_com_cn_spider import WeatherComCnSpider
from storage import Storage
from visualizer import Visualizer
import pandas as pd
def main():
# 初始化爬虫
spider = WeatherComCnSpider()
# 设置城市代码(例如深圳:101280601)
city_code = "101280601"
# 爬取数据
weather_data_list = spider.fetch_data(city_code)
if not weather_data_list:
print("爬取数据失败")
return
# 保存为CSV
csv_filename = f"weather_{city_code}.csv"
Storage.save_to_csv(weather_data_list, csv_filename)
print(f"数据已保存到{csv_filename}")
# 可视化
df = pd.read_csv(csv_filename)
# 绘制温度曲线
Visualizer.plot_temperature_curve(df, title='深圳未来15天温度变化曲线', save_path='temperature_curve.png')
# 绘制风向雷达图
Visualizer.plot_wind_radar(df, title='深圳未来15天风向分布', save_path='wind_radar.png')
if __name__ == "__main__":
main()
```
注意:以上代码仅作为示例,实际运行时可能需要根据网页结构调整爬虫解析部分,并且可视化部分可能需要调整字体路径。
这个系统设计为多源,我们可以通过添加新的爬虫(继承BaseSpider)来扩展其他数据源。然后在主程序中可以调用多个爬虫,将数据合并后再进行存储和可视化。
由于代码较长,以上只是核心代码。实际项目中可能还需要错误处理、日志记录、配置文件等。
相关问题:
1. 如何扩展其他天气数据源?
2. 如果中国天气网的网页结构发生变化,如何调整爬虫?
3. 除了折线图和雷达图,还有哪些适合天气数据的可视化方式?
</think>
### Python多源天气数据爬取与可视化分析系统实现
#### 系统架构设计
```mermaid
graph TD
A[多源天气数据爬取系统] --> B[数据采集模块]
A --> C[数据处理模块]
A --> D[可视化分析模块]
B --> B1[中国天气网爬虫]
B --> B2[OpenWeatherMap API]
B --> B3[AccuWeather爬虫]
C --> C1[数据清洗]
C --> C2[数据整合]
C --> C3[格式转换]
D --> D1[温度变化曲线]
D --> D2[湿度热力图]
D --> D3[风向雷达图]
D --> D4[降水概率预测]
```
#### 核心代码实现
##### 1. 多源数据爬取模块 (weather_crawlers.py)
```python
import requests
from bs4 import BeautifulSoup
import pandas as pd
import json
class MultiSourceWeatherCrawler:
def __init__(self):
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
def crawl_china_weather(self, city_code='101280701'):
"""爬取中国天气网数据"""
url = f'http://www.weather.com.cn/weather15d/{city_code}.shtml'
try:
r = requests.get(url, headers=self.headers, timeout=10)
r.encoding = 'utf-8'
soup = BeautifulSoup(r.text, 'html.parser')
# 解析15天天气预报
weather_data = []
for item in soup.select('.t.clearfix li'):
date = item.h1.text
weather = item.select_one('.wea').text
temp = item.select_one('.tem').text.replace('℃', '').split('/')
wind = item.select_one('.win em').text
wind_level = item.select_one('.win i').text
weather_data.append({
'date': date,
'max_temp': int(temp[0]),
'min_temp': int(temp[1]),
'weather': weather,
'wind_direction': wind,
'wind_power': wind_level,
'source': '中国天气网'
})
return pd.DataFrame(weather_data)
except Exception as e:
print(f"中国天气网爬取失败: {str(e)}")
return pd.DataFrame()
def crawl_openweather(self, api_key, city='Shenzhen'):
"""通过OpenWeatherMap API获取数据"""
url = f'http://api.openweathermap.org/data/2.5/forecast?q={city}&appid={api_key}&units=metric'
try:
r = requests.get(url, timeout=10)
data = json.loads(r.text)
weather_data = []
for item in data['list']:
weather_data.append({
'date': item['dt_txt'],
'temp': item['main']['temp'],
'humidity': item['main']['humidity'],
'weather': item['weather'][0]['description'],
'wind_speed': item['wind']['speed'],
'source': 'OpenWeatherMap'
})
return pd.DataFrame(weather_data)
except Exception as e:
print(f"OpenWeatherMap API调用失败: {str(e)}")
return pd.DataFrame()
def crawl_accuweather(self, location_id='106577'):
"""爬取AccuWeather数据"""
url = f'https://www.accuweather.com/zh/cn/shenzhen/{location_id}/daily-weather-forecast/{location_id}'
try:
r = requests.get(url, headers=self.headers, timeout=15)
soup = BeautifulSoup(r.text, 'html.parser')
# 解析AccuWeather数据
weather_data = []
# 具体解析逻辑根据实际网页结构调整
return pd.DataFrame(weather_data)
except Exception as e:
print(f"AccuWeather爬取失败: {str(e)}")
return pd.DataFrame()
```
##### 2. 数据整合与处理模块 (data_processor.py)
```python
import pandas as pd
from datetime import datetime
class WeatherDataProcessor:
def __init__(self):
self.merged_df = pd.DataFrame()
def merge_data(self, *data_frames):
"""合并多源数据"""
self.merged_df = pd.concat(data_frames, ignore_index=True)
return self.merged_df
def clean_data(self):
"""数据清洗与格式转换"""
if self.merged_df.empty:
return pd.DataFrame()
# 统一日期格式
self.merged_df['date'] = pd.to_datetime(self.merged_df['date'])
# 填充缺失值
self.merged_df.fillna(method='ffill', inplace=True)
# 温度单位统一
if 'temp' in self.merged_df.columns:
self.merged_df.rename(columns={'temp': 'avg_temp'}, inplace=True)
return self.merged_df
def save_to_csv(self, filename='weather_data.csv'):
"""保存为CSV文件"""
if not self.merged_df.empty:
self.merged_df.to_csv(filename, index=False, encoding='utf-8-sig')
return True
return False
```
##### 3. 可视化分析模块 (visual_analysis.py)
```python
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
from matplotlib.font_manager import FontProperties
class WeatherVisualizer:
def __init__(self, df):
self.df = df
self.font = FontProperties(fname='SimHei.ttf') # 中文字体支持
def plot_temperature_trend(self, save_path=None):
"""绘制温度变化趋势图"""
plt.figure(figsize=(14, 7))
# 多源数据对比
for source in self.df['source'].unique():
source_df = self.df[self.df['source'] == source]
plt.plot(source_df['date'], source_df['max_temp'], 'o-', label=f'{source}最高温')
plt.plot(source_df['date'], source_df['min_temp'], 'o--', label=f'{source}最低温')
plt.title('多源天气预报温度对比', fontproperties=self.font)
plt.xlabel('日期', fontproperties=self.font)
plt.ylabel('温度(℃)', fontproperties=self.font)
plt.legend(prop=self.font)
plt.xticks(rotation=45)
plt.grid(True, linestyle='--', alpha=0.7)
if save_path:
plt.savefig(save_path, bbox_inches='tight')
else:
plt.show()
def plot_wind_radar(self, save_path=None):
"""绘制风向雷达图"""
if 'wind_direction' not in self.df.columns:
return
plt.figure(figsize=(10, 10))
ax = plt.subplot(111, polar=True)
# 统计风向频率
wind_counts = self.df['wind_direction'].value_counts()
directions = wind_counts.index.tolist()
counts = wind_counts.values.tolist()
# 将方向映射到角度
direction_map = {'北': 0, '东北': 45, '东': 90,
'东南': 135, '南': 180, '西南': 225,
'西': 270, '西北': 315}
angles = [direction_map.get(d, 0) for d in directions]
angles = np.deg2rad(angles)
# 绘制雷达图
ax.bar(angles, counts, width=np.pi/8, alpha=0.7)
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
ax.set_title('风向分布雷达图', fontproperties=self.font, va='bottom')
if save_path:
plt.savefig(save_path)
else:
plt.show()
def plot_humidity_heatmap(self, save_path=None):
"""绘制湿度热力图"""
if 'humidity' not in self.df.columns:
return
plt.figure(figsize=(12, 6))
pivot_df = self.df.pivot_table(index='date', columns='source', values='humidity', aggfunc='mean')
sns.heatmap(pivot_df.T, cmap='YlGnBu', annot=True, fmt='.0f')
plt.title('多源湿度数据对比热力图', fontproperties=self.font)
plt.xlabel('日期', fontproperties=self.font)
plt.ylabel('数据源', fontproperties=self.font)
if save_path:
plt.savefig(save_path, bbox_inches='tight')
else:
plt.show()
```
##### 4. 主程序入口 (main.py)
```python
from weather_crawlers import MultiSourceWeatherCrawler
from data_processor import WeatherDataProcessor
from visual_analysis import WeatherVisualizer
import os
def main():
# 1. 数据采集
crawler = MultiSourceWeatherCrawler()
# 爬取中国天气网数据
df_china = crawler.crawl_china_weather('101280601') # 深圳城市代码
# 爬取OpenWeather数据(需要API密钥)
# df_openweather = crawler.crawl_openweather('your_api_key', 'Shenzhen')
# 2. 数据处理
processor = WeatherDataProcessor()
processor.merge_data(df_china) # 可添加更多数据源
cleaned_df = processor.clean_data()
processor.save_to_csv('multi_source_weather.csv')
# 3. 可视化分析
visualizer = WeatherVisualizer(cleaned_df)
os.makedirs('visualization', exist_ok=True)
# 生成各类图表
visualizer.plot_temperature_trend('visualization/temperature_trend.png')
visualizer.plot_wind_radar('visualization/wind_radar.png')
visualizer.plot_humidity_heatmap('visualization/humidity_heatmap.png')
if __name__ == "__main__":
main()
```
#### 系统特点
1. **多源数据集成**:支持中国天气网、OpenWeatherMap、AccuWeather等多个数据源
2. **自动数据清洗**:统一日期格式、处理缺失值、标准化单位
3. **丰富可视化**:
- 温度变化趋势对比图
- 风向分布雷达图
- 湿度热力图
- 多源数据对比分析
4. **模块化设计**:各功能模块解耦,便于扩展新数据源
5. **数据持久化**:结果保存为CSV文件,便于后续分析
#### 运行结果示例
```
成功访问中国天气网
数据已保存至: multi_source_weather.csv
温度趋势图保存至: visualization/temperature_trend.png
风向雷达图保存至: visualization/wind_radar.png
湿度热力图保存至: visualization/humidity_heatmap.png
```