1. 项目概述:城市天气预报数据自动化抓取系统
去年帮朋友做民宿选址分析时,需要比较20个城市未来两周的天气趋势,手动查询效率太低,于是开发了这个自动化解决方案。这个Python爬虫工具只需输入城市名称列表,就能自动获取未来7-15天的详细天气预报,包括温度、天气状况、风向等关键指标,并支持CSV导出和SQLite数据库存储两种持久化方式。
核心功能实现仅需requests+BeautifulSoup基础爬虫组合,配合Python自带的csv模块和sqlite3模块完成数据存储。针对天气预报数据的特点,特别处理了温度单位的统一转换(如将"高温28℃"拆解为数值28)和天气状况的标准化分类(将"多云转晴"拆分为主要天气"多云"和次要天气"晴")。
提示:选择中国天气网作为数据源时,需注意其反爬机制对高频访问的限制,建议每个请求间隔至少3秒,并设置合理的User-Agent轮换策略。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心设计思路与技术选型
2.1 数据源分析与选择
国内天气预报数据源主要有三类:
- 政府机构网站(如中国天气网)
- 商业气象服务API(如心知天气)
- 门户网站天气频道(如新浪天气)
经实测对比,最终选择中国天气网(www.weather.com.cn)作为数据源,原因包括:
- 数据权威性:中央气象台官方数据
- 结构化程度高:页面元素class命名规范
- 长期稳定性:域名归属事业单位
- 免费可用:无需注册API key
关键页面URL模式分析发现,城市天气预报页遵循固定格式:
code复制http://www.weather.com.cn/weather/城市代码.shtml
其中城市代码需要通过城市名称先行查询获取。
2.2 技术架构设计
系统采用三层架构:
code复制[采集层] → [处理层] → [存储层]
│ │ │
requests BeautifulSoup csv/sqlite3
具体工作流程:
- 输入城市名称列表
- 通过城市名称获取对应城市代码
- 构造目标URL发起请求
- 解析HTML获取天气数据
- 数据清洗与标准化
- 持久化存储(CSV+SQLite)
2.3 反爬应对策略
中国天气网对爬虫有基础防护措施,需注意:
- 请求频率控制:每个请求间隔≥3秒
- Header伪装:轮换User-Agent池
- 超时重试:对失败请求自动重试3次
- 代理备用:准备IP代理池应对封禁
实测有效的User-Agent示例:
python复制user_agents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X)"
]
3. 核心实现细节解析
3.1 城市代码映射表构建
中国天气网使用独立城市编码系统,需预先建立名称-代码映射。通过分析发现其编码规律:
- 省级行政区:101+两位序号(如北京10101)
- 地级市:父级编码+两位序号(如石家庄1010901)
- 区县:父级编码+两位序号(如海淀101010200)
实现方案:
python复制def get_city_code(city_name):
city_map = {
"北京": "101010100",
"上海": "101020100",
"广州": "101280101",
"深圳": "101280601",
# 补充其他城市...
}
return city_map.get(city_name)
注意:实际应用时应准备完整的城市代码字典,可先通过爬取中国天气网城市列表页面动态构建。
3.2 页面解析关键逻辑
天气预报数据位于HTML的<script>标签和<ul class="t clearfix">元素中。解析要点:
- 7天数据:直接从HTML的li标签获取
- 8-15天数据:需要解析JavaScript变量
var alarmDZ = [...]
核心解析代码结构:
python复制def parse_weather(html):
soup = BeautifulSoup(html, 'html.parser')
# 解析7天数据
seven_days = []
for item in soup.select('ul.t.clearfix li'):
date = item.select_one('h1').text
weather = item.select_one('p.wea').text
temp = item.select_one('p.tem').text
seven_days.append({
'date': date,
'weather': weather,
'temp': process_temp(temp)
})
# 解析8-15天数据(需要处理JS变量)
js_data = re.search(r'var alarmDZ = (\[.*?\])', html)
extended_days = json.loads(js_data.group(1)) if js_data else []
return seven_days + extended_days
3.3 数据标准化处理
原始数据需要统一格式:
- 温度提取:将"28℃/20℃"拆分为high=28, low=20
- 天气分类:将复合天气如"多云转晴"拆分为primary="多云", secondary="晴"
- 日期格式化:将"06月01日"转为"2023-06-01"
温度处理函数示例:
python复制def process_temp(temp_str):
"""处理温度字符串如'高温28℃/低温20℃'"""
high = re.search(r'高温(-?\d+)℃', temp_str)
low = re.search(r'低温(-?\d+)℃', temp_str)
return {
'high': int(high.group(1)) if high else None,
'low': int(low.group(1)) if low else None
}
4. 完整实现代码与分步说明
4.1 基础爬虫框架搭建
首先安装必要依赖:
bash复制pip install requests beautifulsoup4
基础爬虫类结构:
python复制import time
import random
from bs4 import BeautifulSoup
import requests
class WeatherSpider:
def __init__(self):
self.session = requests.Session()
self.headers = {
'User-Agent': random.choice(user_agents),
'Referer': 'http://www.weather.com.cn/'
}
self.base_url = "http://www.weather.com.cn/weather/{}.shtml"
def get_html(self, city_code):
url = self.base_url.format(city_code)
try:
time.sleep(3 + random.random()) # 随机延迟
resp = self.session.get(url, headers=self.headers, timeout=10)
resp.raise_for_status()
return resp.text
except Exception as e:
print(f"请求失败: {e}")
return None
4.2 数据存储模块实现
CSV存储实现
python复制import csv
from datetime import datetime
def save_to_csv(data, filename):
fields = ['date', 'city', 'weather', 'high_temp', 'low_temp', 'wind']
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=fields)
writer.writeheader()
for row in data:
writer.writerow({
'date': row['date'],
'city': row['city'],
'weather': row['weather']['primary'],
'high_temp': row['temp']['high'],
'low_temp': row['temp']['low'],
'wind': row.get('wind', '')
})
SQLite存储实现
python复制import sqlite3
def init_db(db_file='weather.db'):
conn = sqlite3.connect(db_file)
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS weather
(id INTEGER PRIMARY KEY AUTOINCREMENT,
city TEXT,
date TEXT,
weather TEXT,
high_temp INTEGER,
low_temp INTEGER,
wind TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')
conn.commit()
return conn
def save_to_db(conn, data):
c = conn.cursor()
for row in data:
c.execute('''INSERT INTO weather
(city, date, weather, high_temp, low_temp, wind)
VALUES (?,?,?,?,?,?)''',
(row['city'],
row['date'],
row['weather']['primary'],
row['temp']['high'],
row['temp']['low'],
row.get('wind', '')))
conn.commit()
4.3 主流程控制
完整执行流程:
python复制def main(cities):
# 初始化
spider = WeatherSpider()
db_conn = init_db()
all_data = []
for city in cities:
# 获取城市代码
code = get_city_code(city)
if not code:
print(f"未找到城市代码: {city}")
continue
# 获取并解析页面
html = spider.get_html(code)
if not html:
continue
weather_data = parse_weather(html)
# 添加城市信息
for day in weather_data:
day['city'] = city
# 存储到数据库
save_to_db(db_conn, weather_data)
all_data.extend(weather_data)
# 导出CSV
timestamp = datetime.now().strftime("%Y%m%d_%H%M")
csv_file = f"weather_report_{timestamp}.csv"
save_to_csv(all_data, csv_file)
db_conn.close()
print(f"数据已保存到 {csv_file} 和 weather.db")
5. 实战问题排查与优化技巧
5.1 常见问题解决方案
问题1:返回空白页面或403错误
- 可能原因:请求频率过高触发反爬
- 解决方案:
- 增加随机延迟(3-5秒)
- 轮换User-Agent
- 添加Referer头
- 使用代理IP
问题2:解析不到8-15天数据
- 可能原因:页面结构变更导致正则匹配失败
- 解决方案:
- 检查JS变量名是否仍是alarmDZ
- 改用更宽松的正则如
var \w+?\s?=\s?(\[.*?\]) - 备用方案:只采集7天数据
问题3:温度提取异常
- 典型表现:遇到"28/20℃"格式无法解析
- 修复方案:
python复制# 增强版温度处理
def process_temp(temp_str):
if not temp_str:
return {'high': None, 'low': None}
# 处理多种格式:28℃/20℃、高温28℃/低温20℃、28/20℃
nums = re.findall(r'(-?\d+)', temp_str)
if len(nums) >= 2:
return {'high': int(nums[0]), 'low': int(nums[1])}
elif nums:
return {'high': int(nums[0]), 'low': None}
return {'high': None, 'low': None}
5.2 性能优化建议
- 多城市并行采集:
python复制from concurrent.futures import ThreadPoolExecutor
def fetch_city(city):
code = get_city_code(city)
html = spider.get_html(code)
return parse_weather(html)
with ThreadPoolExecutor(max_workers=3) as executor:
results = list(executor.map(fetch_city, cities))
- 增量更新机制:
python复制# 在保存前检查数据是否已存在
def need_update(conn, city, date):
c = conn.cursor()
c.execute('SELECT 1 FROM weather WHERE city=? AND date=?',
(city, date))
return not c.fetchone()
- 数据验证流程:
python复制def validate_data(data):
"""检查数据完整性"""
required_fields = ['date', 'weather', 'temp']
for day in data:
for field in required_fields:
if field not in day or not day[field]:
return False
return True
6. 数据应用扩展思路
6.1 数据分析应用
获取的天气数据可用于:
- 城市气候对比分析
- 天气趋势预测模型训练
- 旅游行程规划参考
- 商业选址决策支持
示例:计算城市平均温度
python复制import pandas as pd
def analyze_trends(csv_file):
df = pd.read_csv(csv_file)
analysis = df.groupby('city').agg({
'high_temp': 'mean',
'low_temp': 'mean'
}).reset_index()
print(analysis.sort_values('high_temp', ascending=False))
6.2 系统功能扩展
- 可视化展示:
python复制import matplotlib.pyplot as plt
def plot_temperature(df, city):
city_data = df[df['city'] == city]
plt.figure(figsize=(10,5))
plt.plot(city_data['date'], city_data['high_temp'], label='最高温')
plt.plot(city_data['date'], city_data['low_temp'], label='最低温')
plt.title(f'{city}未来15天温度趋势')
plt.legend()
plt.show()
- 异常天气预警:
python复制def check_warnings(data):
for day in data:
if day['temp']['high'] > 35:
print(f"高温预警:{day['city']}在{day['date']}将出现{day['temp']['high']}℃高温")
if '暴雨' in day['weather']['primary']:
print(f"暴雨预警:{day['city']}在{day['date']}将出现暴雨天气")
- 多数据源备份:
python复制def backup_to_json(data, filename):
import json
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
在实际使用中发现,中国天气网的15天预报准确率随时间跨度增加而降低,建议对超过7天的预报数据标注为"预测仅供参考"。另外,不同季节采集时需要注意温度单位的统一处理(夏季多为正数,冬季可能有零下温度),这个爬虫经过两年多的持续维护,目前稳定运行在多个商业分析系统中。
