# 基于Python的简易天气查询系统的设计与实现
## 1. 系统架构设计
一个完整的天气查询系统通常包含数据获取、数据处理、用户界面和功能扩展四个核心模块。以下是系统的整体架构设计:
| 模块名称 | 核心功能 | 技术实现 |
|---------|---------|---------|
| 数据获取模块 | 从网络获取天气数据 | requests + BeautifulSoup / API调用 |
| 数据处理模块 | 解析和清洗数据 | json解析、正则表达式、pandas |
| 用户界面模块 | 提供用户交互界面 | tkinter / PyQt5 |
| 功能扩展模块 | 数据可视化、语音播报等 | matplotlib、pyttsx3 |
## 2. 核心功能实现
### 2.1 数据获取方式
系统支持两种主要的数据获取方式:网络爬虫和API接口调用。
**方式一:网络爬虫获取数据**
```python
import requests
from bs4 import BeautifulSoup
import re
def get_weather_by_crawler(city, date):
"""
通过爬虫获取历史天气数据
"""
try:
# 构造目标URL
base_url = f"https://lishi.tianqi.com/{city}/{date}.html"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
# 发送请求
response = requests.get(base_url, headers=headers)
response.encoding = 'utf-8'
if response.status_code == 200:
soup = BeautifulSoup(response.text, 'html.parser')
# 解析天气数据
weather_data = []
table = soup.find('div', class_='tqtongji2')
if table:
ul_list = table.find_all('ul')[1:] # 跳过表头
for ul in ul_list:
li_list = ul.find_all('li')
if len(li_list) >= 4:
date = li_list[0].text.strip()
high_temp = li_list[1].text.strip()
low_temp = li_list[2].text.strip()
weather = li_list[3].text.strip()
weather_data.append({
'日期': date,
'最高温': high_temp,
'最低温': low_temp,
'天气': weather
})
return weather_data
else:
return None
except Exception as e:
print(f"爬虫获取数据失败: {e}")
return None
```
**方式二:API接口获取数据**
```python
import requests
import json
def get_weather_by_api(city_code):
"""
通过API接口获取实时天气数据
"""
try:
# 使用公开的天气API
api_url = f"http://t.weather.itboy.net/api/weather/city/{city_code}"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(api_url, headers=headers)
if response.status_code == 200:
data = response.json()
# 解析JSON数据
if data['status'] == 200:
weather_info = {
'城市': data['cityInfo']['city'],
'日期': data['data']['forecast'][0]['ymd'],
'天气': data['data']['forecast'][0]['type'],
'最高温': data['data']['forecast'][0]['high'],
'最低温': data['data']['forecast'][0]['low'],
'风向': data['data']['forecast'][0]['fx'],
'风力': data['data']['forecast'][0]['fl'],
'空气质量': data['data']['quality']
}
return weather_info
else:
return None
else:
return None
except Exception as e:
print(f"API获取数据失败: {e}")
return None
```
### 2.2 数据处理与解析
```python
import pandas as pd
import re
class WeatherDataProcessor:
"""
天气数据处理类
"""
def __init__(self):
self.weather_data = []
def clean_temperature(self, temp_str):
"""
清洗温度数据,提取数字
"""
if temp_str:
# 使用正则表达式提取温度数字
match = re.search(r'(\d+)', temp_str)
if match:
return int(match.group(1))
return None
def process_weather_data(self, raw_data):
"""
处理原始天气数据
"""
processed_data = []
for item in raw_data:
cleaned_item = {
'日期': item.get('日期', ''),
'最高温': self.clean_temperature(item.get('最高温', '')),
'最低温': self.clean_temperature(item.get('最低温', '')),
'天气': item.get('天气', ''),
'城市': item.get('城市', '')
}
processed_data.append(cleaned_item)
return processed_data
def to_dataframe(self, processed_data):
"""
转换为pandas DataFrame
"""
df = pd.DataFrame(processed_data)
return df
```
### 2.3 用户界面设计
使用tkinter创建图形用户界面:
```python
import tkinter as tk
from tkinter import ttk, messagebox
import threading
class WeatherApp:
"""
天气查询应用主界面
"""
def __init__(self, root):
self.root = root
self.root.title("简易天气查询系统")
self.root.geometry("600x400")
self.setup_ui()
def setup_ui(self):
"""
设置用户界面
"""
# 主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 输入区域
input_frame = ttk.LabelFrame(main_frame, text="查询条件", padding="10")
input_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=5)
# 城市输入
ttk.Label(input_frame, text="城市:").grid(row=0, column=0, sticky=tk.W)
self.city_var = tk.StringVar()
self.city_entry = ttk.Entry(input_frame, textvariable=self.city_var, width=20)
self.city_entry.grid(row=0, column=1, padx=5)
# 日期输入
ttk.Label(input_frame, text="日期(YYYYMM):").grid(row=0, column=2, sticky=tk.W)
self.date_var = tk.StringVar()
self.date_entry = ttk.Entry(input_frame, textvariable=self.date_var, width=15)
self.date_entry.grid(row=0, column=3, padx=5)
# 查询按钮
self.query_btn = ttk.Button(input_frame, text="查询天气", command=self.query_weather)
self.query_btn.grid(row=0, column=4, padx=10)
# 结果显示区域
result_frame = ttk.LabelFrame(main_frame, text="天气信息", padding="10")
result_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
# 创建文本框显示结果
self.result_text = tk.Text(result_frame, height=15, width=70)
scrollbar = ttk.Scrollbar(result_frame, orient="vertical", command=self.result_text.yview)
self.result_text.configure(yscrollcommand=scrollbar.set)
self.result_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
# 配置权重
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(1, weight=1)
result_frame.columnconfigure(0, weight=1)
result_frame.rowconfigure(0, weight=1)
def query_weather(self):
"""
查询天气数据
"""
city = self.city_var.get().strip()
date = self.date_var.get().strip()
if not city:
messagebox.showerror("错误", "请输入城市名称!")
return
# 禁用查询按钮,防止重复查询
self.query_btn.config(state='disabled')
# 在新线程中执行查询,避免界面卡顿
thread = threading.Thread(target=self._execute_query, args=(city, date))
thread.daemon = True
thread.start()
def _execute_query(self, city, date):
"""
执行查询操作
"""
try:
# 清空结果显示
self.result_text.delete(1.0, tk.END)
# 显示查询中提示
self.result_text.insert(tk.END, "正在查询天气数据,请稍候...\n")
# 获取天气数据
if date:
# 查询历史天气
weather_data = get_weather_by_crawler(city, date)
else:
# 查询实时天气(需要城市代码映射)
city_code = self.get_city_code(city)
if city_code:
weather_data = [get_weather_by_api(city_code)]
else:
weather_data = None
# 在主线程中更新界面
self.root.after(0, self._update_result, weather_data)
except Exception as e:
self.root.after(0, self._show_error, str(e))
def _update_result(self, weather_data):
"""
更新查询结果
"""
# 重新启用查询按钮
self.query_btn.config(state='normal')
# 清空结果显示
self.result_text.delete(1.0, tk.END)
if weather_data:
if isinstance(weather_data, list):
# 显示多条数据
for data in weather_data:
self._display_single_weather(data)
else:
# 显示单条数据
self._display_single_weather(weather_data)
else:
self.result_text.insert(tk.END, "未找到相关天气数据!")
def _display_single_weather(self, data):
"""
显示单条天气数据
"""
display_text = f"""
城市: {data.get('城市', '未知')}
日期: {data.get('日期', '未知')}
天气: {data.get('天气', '未知')}
最高温度: {data.get('最高温', '未知')}
最低温度: {data.get('最低温', '未知')}
风向风力: {data.get('风向', '未知')} {data.get('风力', '未知')}
空气质量: {data.get('空气质量', '未知')}
{"-" * 40}
"""
self.result_text.insert(tk.END, display_text)
def _show_error(self, error_msg):
"""
显示错误信息
"""
self.query_btn.config(state='normal')
self.result_text.delete(1.0, tk.END)
self.result_text.insert(tk.END, f"查询失败: {error_msg}")
def get_city_code(self, city_name):
"""
获取城市代码(简化版)
"""
city_code_map = {
'北京': '101010100',
'上海': '101020100',
'广州': '101280101',
'深圳': '101280601',
'杭州': '101210101'
}
return city_code_map.get(city_name)
# 启动应用
if __name__ == "__main__":
root = tk.Tk()
app = WeatherApp(root)
root.mainloop()
```
## 3. 系统特色功能
### 3.1 数据可视化
```python
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
def visualize_weather_data(weather_df):
"""
可视化天气数据
"""
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 创建图表
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))
# 温度趋势图
if '最高温' in weather_df.columns and '最低温' in weather_df.columns:
ax1.plot(weather_df['日期'], weather_df['最高温'], 'r-', label='最高温', marker='o')
ax1.plot(weather_df['日期'], weather_df['最低温'], 'b-', label='最低温', marker='s')
ax1.set_title('温度变化趋势')
ax1.set_ylabel('温度(℃)')
ax1.legend()
ax1.grid(True)
# 天气类型统计
if '天气' in weather_df.columns:
weather_counts = weather_df['天气'].value_counts()
ax2.pie(weather_counts.values, labels=weather_counts.index, autopct='%1.1f%%')
ax2.set_title('天气类型分布')
plt.tight_layout()
plt.show()
```
### 3.2 语音播报功能
```python
import pyttsx3
class WeatherVoice:
"""
天气语音播报类
"""
def __init__(self):
self.engine = pyttsx3.init()
# 设置语速
self.engine.setProperty('rate', 150)
# 设置音量
self.engine.setProperty('volume', 0.8)
def speak_weather(self, weather_data):
"""
语音播报天气
"""
if weather_data:
text = f"""
今天{weather_data.get('城市', '')}的天气情况:
天气{weather_data.get('天气', '')},
最高温度{weather_data.get('最高温', '')}度,
最低温度{weather_data.get('最低温', '')}度,
{weather_data.get('风向', '')}风{weather_data.get('风力', '')}。
"""
self.engine.say(text)
self.engine.runAndWait()
```
## 4. 系统部署与打包
### 4.1 使用PyInstaller打包
```bash
# 安装PyInstaller
pip install pyinstaller
# 打包应用
pyinstaller --onefile --windowed weather_app.py
```
### 4.2 配置文件管理
```python
import json
import os
class ConfigManager:
"""
配置文件管理类
"""
def __init__(self, config_file='config.json'):
self.config_file = config_file
self.config = self.load_config()
def load_config(self):
"""
加载配置文件
"""
default_config = {
'api_settings': {
'timeout': 10,
'retry_times': 3
},
'ui_settings': {
'window_width': 600,
'window_height': 400
},
'cities': {
'北京': '101010100',
'上海': '101020100'
}
}
if os.path.exists(self.config_file):
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
return json.load(f)
except:
return default_config
else:
return default_config
def save_config(self):
"""
保存配置文件
"""
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.config, f, ensure_ascii=False, indent=4)
```
## 5. 系统优化建议
1. **数据缓存**:对频繁查询的数据进行缓存,减少网络请求 [ref_1]
2. **错误处理**:完善网络异常、数据解析失败等情况的处理机制 [ref_5]
3. **用户体验**:添加加载动画、搜索历史等功能提升用户体验 [ref_4]
4. **多数据源**:集成多个天气数据源,提高数据的准确性和可用性 [ref_6]
5. **扩展功能**:可添加天气预警、生活指数等增值功能 [ref_2]
该系统通过模块化设计,实现了天气数据的高效获取、处理和展示,具有良好的可扩展性和用户体验。开发者可以根据实际需求选择不同的技术方案进行实现和优化。