引言
"今天出门要不要带伞?""周末适不适合去露营?""明天要不要给车加防冻液?"------天气数据是普通人日常生活中最高频的查询需求之一,也是数据分析领域最经典的应用场景之一。如果能用爬虫把全国主要城市的天气数据定时采集下来,再用可视化工具呈现在一块大屏上,就能构建一个实时、全面、直观的天气监控系统。
这个系统的价值不仅在于"看天气"本身。对于物流企业来说,全国天气状况直接影响运输路线规划;对于零售行业来说,天气变化与商品销量之间存在强关联;对于活动策划者来说,天气数据是决策的重要输入。而将天气数据与可视化大屏结合,则能让这些信息以最直观的方式呈现------用地图展示全国气温分布,用折线图追踪未来一周的天气趋势,用热力图显示降水概率。
本文将系统介绍如何用Scrapy框架爬取全国天气数据,并基于采集结果构建可视化大屏。文章将涵盖数据源分析、Scrapy爬虫搭建、反爬策略应对、站大爷隧道代理IP的集成,以及可视化大屏的实现方案。
免责声明:本文仅限技术学习与交流。请遵守目标网站的robots.txt协议及相关法律法规,合理控制请求频率,不得将数据用于商业用途。

一、数据源选择与分析
1.1 天气数据源对比
目前主流的天气数据来源有以下几类:
| 数据源 | 特点 | 适用场景 |
|---|---|---|
| 中国天气网 | 官方数据,覆盖全国所有市县,有7天预报 | 爬虫练习、数据分析 |
| 和风天气API | 结构化JSON,数据准确 | 商用项目、高频查询 |
| 心知天气API | 免费额度充足,接口友好 | 个人项目 |
| OpenWeatherMap | 全球覆盖,多语言支持 | 国际项目 |
对于"全国天气可视化大屏"的需求,中国天气网是最适合的数据源。它由官方运营,数据权威,覆盖全国所有地级市和县区,且页面结构相对规整,适合作为Scrapy的实战项目。
1.2 页面结构分析
中国天气网的核心页面结构如下:
城市列表页:按省份展示所有地级市的链接,URL通常形如:
http://www.weather.com.cn/weather/101010100.shtml
其中101010100是城市的天气编码。
城市天气详情页:展示该城市的7天天气预报,包含日期、天气状况、最高/最低温度、风向风力等字段。
城市编码规律:中国天气网的城市编码遵循一定的规律------前5位代表省份和地级市,后4位代表区县。可以通过遍历编码范围来获取所有城市的数据。
1.3 反爬机制分析
中国天气网的反爬强度适中,主要有以下几道防线:
第一道:请求头校验。缺少User-Agent、Referer等请求头的请求会被拒绝或返回异常页面。
第二道:IP频率限制。这是最核心的防线。当同一IP在短时间内请求大量城市页面时,会触发限流,轻则返回403,重则临时封禁IP。全国有300多个地级市,如果每个城市都要请求详情页,总请求量超过300次,单IP在短时间内完成这些请求很容易触发封禁。
第三道:页面编码。中国天气网的页面使用UTF-8编码,但部分页面可能使用GBK,需要正确处理编码问题。
第四道:动态加载。部分页面内容通过JavaScript动态加载,直接请求HTML可能拿不到完整数据。不过天气预报的核心数据通常在静态HTML中,用requests即可获取。
1.4 反爬应对策略
| 反爬手段 | 应对策略 |
|---|---|
| 请求头校验 | 构造完整的浏览器请求头,随机User-Agent |
| IP频率限制 | 站大爷隧道代理自动切换IP |
| 编码问题 | 使用resp.apparent_encoding自动识别 |
| 请求频率 | Scrapy的DOWNLOAD_DELAY + 随机延迟 |
二、Scrapy框架搭建
2.1 为什么选择Scrapy?
相比于requests + BeautifulSoup的组合,Scrapy在天气数据采集场景中有以下优势:
-
异步架构:基于Twisted的异步网络引擎,并发效率远高于同步方案
-
中间件机制:通过Downloader Middleware可以优雅地集成代理、重试、随机延迟
-
管道系统:Item Pipeline可以方便地实现数据清洗、去重、存储
-
调度器:内置的调度器支持去重和优先级管理
-
可扩展性:适合从单机爬虫扩展到分布式爬虫
2.2 项目结构
scrapy startproject weather_spider
cd weather_spider
# 项目结构
weather_spider/
├── scrapy.cfg
└── weather_spider/
├── __init__.py
├── items.py # 数据模型定义
├── middlewares.py # 中间件(代理、重试)
├── pipelines.py # 数据管道(清洗、存储)
├── settings.py # 全局配置
└── spiders/
└── weather.py # 爬虫核心逻辑
2.3 环境准备
pip install scrapy pandas openpyxl
2.4 数据模型定义
在items.py中定义天气数据的字段:
import scrapy
class WeatherItem(scrapy.Item):
"""天气数据模型"""
province = scrapy.Field() # 省份
city = scrapy.Field() # 城市
city_code = scrapy.Field() # 城市编码
date = scrapy.Field() # 日期
weather = scrapy.Field() # 天气状况
temp_high = scrapy.Field() # 最高温度
temp_low = scrapy.Field() # 最低温度
wind = scrapy.Field() # 风向风力
collected_at = scrapy.Field() # 采集时间
三、爬虫核心实现
3.1 城市编码获取
首先需要获取全国所有城市的天气编码。中国天气网的城市编码可以通过其API或页面获取。
# spiders/weather.py
import scrapy
from weather_spider.items import WeatherItem
import re
import time
class WeatherSpider(scrapy.Spider):
name = "weather"
allowed_domains = ["weather.com.cn"]
# 主要城市的天气编码(实际应从城市列表页动态获取)
city_codes = {
"北京": "101010100",
"上海": "101020100",
"广州": "101280101",
"深圳": "101280601",
"杭州": "101210101",
"南京": "101190101",
"成都": "101270101",
"武汉": "101200101",
"西安": "101110101",
"重庆": "101040100",
# ... 更多城市
}
def start_requests(self):
"""生成初始请求"""
for city, code in self.city_codes.items():
url = f"http://www.weather.com.cn/weather/{code}.shtml"
yield scrapy.Request(
url=url,
callback=self.parse_weather,
meta={"city": city, "code": code},
dont_filter=True
)
def parse_weather(self, response):
"""解析天气页面"""
city = response.meta["city"]
code = response.meta["code"]
# 提取7天预报
days = response.css(".t.clearfix li")
for day in days:
item = WeatherItem()
item["city"] = city
item["city_code"] = code
item["date"] = day.css("h1::text").get("").strip()
item["weather"] = day.css(".wea::text").get("").strip()
# 温度解析
temp_text = day.css(".tem::text").getall()
if len(temp_text) >= 2:
item["temp_high"] = temp_text[0].strip()
item["temp_low"] = temp_text[1].strip()
elif len(temp_text) == 1:
item["temp_high"] = temp_text[0].strip()
item["temp_low"] = ""
item["wind"] = day.css(".win span::attr(title)").get("").strip()
item["collected_at"] = time.strftime("%Y-%m-%d %H:%M:%S")
yield item
3.2 解析逻辑要点
CSS选择器 :中国天气网的天气列表使用.t.clearfix li作为每行的容器,内部包含h1(日期)、.wea(天气)、.tem(温度)、.win(风力)等元素。
温度解析:温度通常以"最高温/最低温"的形式呈现,但有时只有单值(如阴天),需要做兼容处理。
编码处理 :Scrapy默认使用UTF-8,如果页面使用GBK,需要在settings.py中配置FEED_EXPORT_ENCODING = 'utf-8-sig'。
四、站大爷隧道代理集成
4.1 为什么天气采集需要代理?
全国有300多个地级市,如果每个城市都需要请求详情页,总请求量超过300次。Scrapy的默认并发是16,如果不在中间件中做延迟控制,这些请求会在极短时间内从同一个IP发出,必然触发服务器的频率限制。
更关键的是,中国天气网对数据中心IP的识别能力较强 。如果使用普通的机房IP,即使频率控制得再好,也可能因为IP段被标记而遭遇封禁。站大爷隧道代理提供了高匿的住宅IP资源,能够有效规避IP层面的识别。
4.2 中间件实现
在middlewares.py中实现隧道代理中间件:
# middlewares.py
import base64
class TunnelProxyMiddleware:
"""站大爷隧道代理中间件"""
def __init__(self, proxy_url, auth_header):
self.proxy_url = proxy_url
self.auth_header = auth_header
@classmethod
def from_crawler(cls, crawler):
proxy_url = crawler.settings.get("TUNNEL_PROXY_URL")
# 认证信息通过Proxy-Authorization头传递
auth = base64.b64encode(
crawler.settings.get("PROXY_AUTH").encode()
).decode()
return cls(proxy_url, f"Basic {auth}")
def process_request(self, request, spider):
request.meta["proxy"] = self.proxy_url
request.headers["Proxy-Authorization"] = self.auth_header
在settings.py中配置:
# settings.py
# 站大爷隧道代理配置
TUNNEL_PROXY_URL = "http://tps.zdaye.com:8080"
PROXY_AUTH = "用户名:密码"
DOWNLOADER_MIDDLEWARES = {
'weather_spider.middlewares.TunnelProxyMiddleware': 543,
}
# 请求延迟控制
DOWNLOAD_DELAY = 2
RANDOMIZE_DOWNLOAD_DELAY = True
CONCURRENT_REQUESTS_PER_DOMAIN = 4
# 重试配置
RETRY_ENABLED = True
RETRY_TIMES = 3
RETRY_HTTP_CODES = [403, 429, 500, 502, 503, 504]
# 编码
FEED_EXPORT_ENCODING = "utf-8-sig"
# User-Agent池
USER_AGENT_LIST = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0",
]
4.3 随机User-Agent中间件
# middlewares.py
import random
class RandomUserAgentMiddleware:
"""随机User-Agent中间件"""
def __init__(self, user_agents):
self.user_agents = user_agents
@classmethod
def from_crawler(cls, crawler):
return cls(crawler.settings.get("USER_AGENT_LIST"))
def process_request(self, request, spider):
request.headers["User-Agent"] = random.choice(self.user_agents)
4.4 站大爷隧道代理的核心优势
站大爷隧道代理在天气数据采集场景中表现突出:
-
自动切换无感知:每次请求自动更换出口IP,无需手动维护IP池
-
覆盖范围广泛:覆盖全国99%地域,支持300+城市IP精准定位
-
高可用性:连续7天24小时压力测试,连接成功率稳定在99.3%
-
高并发支持:分布式采集架构下稳定运行
-
三种鉴权模式:白名单、用户名密码/白名单、用户名密码+白名单
-
灵活切换周期:支持按请求切换IP,也支持自定义切换周期
对于需要精细控制IP更换周期的场景,站大爷隧道代理支持通过period参数自定义:
TUNNEL_PROXY_URL = "http://用户名-period-300:密码@tps.zdaye.com:8080"
五、数据管道与存储
5.1 数据清洗管道
在pipelines.py中实现数据清洗和存储:
# pipelines.py
import sqlite3
from datetime import datetime
class WeatherPipeline:
"""天气数据清洗与存储"""
def __init__(self):
self.conn = sqlite3.connect("weather.db")
self.cursor = self.conn.cursor()
self.cursor.execute("""
CREATE TABLE IF NOT EXISTS weather (
id INTEGER PRIMARY KEY AUTOINCREMENT,
province TEXT,
city TEXT,
city_code TEXT,
date TEXT,
weather TEXT,
temp_high TEXT,
temp_low TEXT,
wind TEXT,
collected_at TEXT
)
""")
self.conn.commit()
def process_item(self, item, spider):
# 清洗温度数据:提取数字
import re
def clean_temp(temp_str):
if not temp_str:
return ""
nums = re.findall(r'-?\d+', str(temp_str))
return nums[0] if nums else ""
item["temp_high"] = clean_temp(item.get("temp_high"))
item["temp_low"] = clean_temp(item.get("temp_low"))
# 存储到SQLite
self.cursor.execute("""
INSERT INTO weather
(province, city, city_code, date, weather, temp_high, temp_low, wind, collected_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
item.get("province", ""),
item.get("city", ""),
item.get("city_code", ""),
item.get("date", ""),
item.get("weather", ""),
item.get("temp_high", ""),
item.get("temp_low", ""),
item.get("wind", ""),
item.get("collected_at", "")
))
self.conn.commit()
return item
def close_spider(self, spider):
self.conn.close()
5.2 运行爬虫
scrapy crawl weather -o weather_data.csv
六、可视化大屏实现
6.1 大屏设计思路
天气可视化大屏通常包含以下模块:
| 模块 | 内容 | 可视化形式 |
|---|---|---|
| 全国气温分布 | 各城市温度 | 地图热力图 |
| 天气状况统计 | 晴/雨/阴/雪占比 | 饼图/环形图 |
| 温度趋势 | 未来7天温度变化 | 折线图 |
| 降水分布 | 各城市降水概率 | 地图散点图 |
| 风力风向 | 各城市风力等级 | 柱状图/仪表盘 |
| 实时数据流 | 最新采集的天气数据 | 滚动列表 |
6.2 使用Pyecharts实现
Pyecharts是Python中生成Echarts图表的库,适合制作可视化大屏。
from pyecharts import options as opts
from pyecharts.charts import Map, Bar, Line, Pie, Page
import pandas as pd
import sqlite3
# 加载数据
conn = sqlite3.connect("weather.db")
df = pd.read_sql_query("SELECT * FROM weather", conn)
conn.close()
# 取最新一批数据
latest_date = df["collected_at"].max()
df_latest = df[df["collected_at"] == latest_date]
# 1. 全国气温分布地图
def create_temp_map():
# 按城市聚合最高温度
city_temp = df_latest.groupby("city")["temp_high"].apply(
lambda x: pd.to_numeric(x, errors="coerce").mean()
).dropna().round(0).to_dict()
map_chart = (
Map()
.add("最高温度(℃)", list(city_temp.items()), "china")
.set_global_opts(
title_opts=opts.TitleOpts(title="全国气温分布"),
visualmap_opts=opts.VisualMapOpts(
min_=-10, max_=40,
range_color=["#50a3ba", "#eac736", "#d94e5d"]
)
)
)
return map_chart
# 2. 天气状况统计
def create_weather_pie():
weather_counts = df_latest["weather"].value_counts().head(8)
pie = (
Pie()
.add("", list(zip(weather_counts.index, weather_counts.values)))
.set_global_opts(title_opts=opts.TitleOpts(title="天气状况分布"))
)
return pie
# 3. 温度趋势折线图
def create_temp_trend():
# 取北京未来7天数据
bj = df[df["city"] == "北京"].head(7)
dates = bj["date"].tolist()
highs = pd.to_numeric(bj["temp_high"], errors="coerce").tolist()
lows = pd.to_numeric(bj["temp_low"], errors="coerce").tolist()
line = (
Line()
.add_xaxis(dates)
.add_yaxis("最高温度", highs, is_smooth=True)
.add_yaxis("最低温度", lows, is_smooth=True)
.set_global_opts(title_opts=opts.TitleOpts(title="北京7天温度趋势"))
)
return line
# 组合大屏
page = Page(layout=Page.DraggablePageLayout)
page.add(create_temp_map(), create_weather_pie(), create_temp_trend())
page.render("weather_dashboard.html")
6.3 大屏效果说明
生成的weather_dashboard.html可以在浏览器中直接打开,包含:
-
地图热力图:用颜色深浅展示全国各城市的温度分布,一目了然
-
天气饼图:展示晴、雨、阴、雪等天气状况的占比
-
温度折线图:追踪北京未来7天的温度变化趋势
如果需要更专业的可视化效果,可以将数据接入DataV 或FineReport等BI工具,制作更精美的大屏。
七、常见问题与避坑指南
7.1 温度数据解析失败怎么办?
中国天气网的温度格式并不统一:有时是"8℃/1℃",有时是"8℃",有时是"-3℃"。建议用正则表达式提取数字:
import re
nums = re.findall(r'-?\d+', temp_str)
7.2 部分城市数据为空怎么办?
-
检查城市编码是否正确
-
部分小城市可能没有独立的天气页面
-
可以先用少量城市测试,确认解析逻辑无误后再扩大范围
7.3 请求被拒绝怎么办?
-
检查是否携带了完整的请求头
-
确认是否使用了隧道代理
-
增加
DOWNLOAD_DELAY,降低请求频率 -
使用站大爷隧道代理的
period参数控制IP更换周期
7.4 数据更新频率如何设置?
天气数据每天更新1-2次即可。建议使用Scrapy的定时任务或系统的Cron定时执行:
# 每天6:00和18:00各执行一次
0 6,18 * * * cd /path/to/weather_spider && scrapy crawl weather
7.5 法律与合规提醒
-
爬取前查看中国天气网的robots.txt及用户协议
-
控制请求频率,避免对目标服务器造成过大压力
-
仅将数据用于学习和研究目的,不得用于商业用途
-
遵守《网络安全法》《数据安全法》等相关法律法规
八、总结
本文从天气数据源分析入手,系统介绍了用Scrapy爬取全国天气数据、构建可视化大屏的完整方案。核心要点可以概括为:
| 环节 | 关键技术 | 产出 |
|---|---|---|
| 数据采集 | Scrapy + CSS选择器 | 全国城市天气数据 |
| IP突破 | 站大爷隧道代理中间件 | 稳定持续采集 |
| 数据清洗 | Item Pipeline + 正则 | 标准化天气数据集 |
| 数据存储 | SQLite | 结构化天气库 |
| 可视化大屏 | Pyecharts + 地图/饼图/折线图 | 天气监控大屏 |
技术选型速览 :推荐"Scrapy + 站大爷隧道代理 + Pyecharts + SQLite"的组合方案。Scrapy提供异步采集框架,隧道代理解决IP频率限制,Pyecharts生成可视化大屏,SQLite存储结构化数据。
天气数据是最贴近日常生活的数据之一。通过合理的采集策略和可视化设计,我们可以将分散在各城市页面上的天气信息整合起来,构建一个直观、实时的天气监控大屏------用地图看全国温度分布,用折线图追踪温度趋势,用饼图了解天气状况占比。这套系统对于物流调度、活动策划、出行决策等场景都具有实际参考价值。
最后需要提醒的是:数据采集行为应当遵循网站规则,控制请求频率以避免对目标服务器造成过载。请遵守相关法律法规,仅将爬虫技术用于学习和研究目的。希望本文能帮助你在Scrapy框架和天气数据可视化的道路上迈出坚实的一步。