天气数据爬虫:基于Scrapy爬取全国天气,做可视化大屏

引言

"今天出门要不要带伞?""周末适不适合去露营?""明天要不要给车加防冻液?"------天气数据是普通人日常生活中最高频的查询需求之一,也是数据分析领域最经典的应用场景之一。如果能用爬虫把全国主要城市的天气数据定时采集下来,再用可视化工具呈现在一块大屏上,就能构建一个实时、全面、直观的天气监控系统。

这个系统的价值不仅在于"看天气"本身。对于物流企业来说,全国天气状况直接影响运输路线规划;对于零售行业来说,天气变化与商品销量之间存在强关联;对于活动策划者来说,天气数据是决策的重要输入。而将天气数据与可视化大屏结合,则能让这些信息以最直观的方式呈现------用地图展示全国气温分布,用折线图追踪未来一周的天气趋势,用热力图显示降水概率。

本文将系统介绍如何用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框架和天气数据可视化的道路上迈出坚实的一步。

相关推荐
夏虫不可与之言冰1 小时前
使用Flash Download Tool给ESP32烧录固件
c语言·c++·windows·单片机·物联网·iot
晴天的雨.9921 小时前
【C++】 priority_queue知识点总结
开发语言·数据结构·c++
小七在进步1 小时前
C++内存管理:new/delete vs malloc/free深度解析
android·java·c++
别动我齐刘海12 小时前
Linux 系统调试学习1
linux·运维·服务器·c++·学习·机器学习·机器人
雪雪爱冲浪15 小时前
2026 年网络爬虫领域的Puppeteer vs. Playwright
爬虫·网络协议·测试工具·接口隔离原则
jimy117 小时前
C++ 虚函数表:基类和派生类共用一张 vtable 吗?
开发语言·c++
爱吃提升17 小时前
Dev-C++ 5.11 C++断点调试完整教程
开发语言·c++
xlq2232218 小时前
全面复习7
c++
geats人山人海18 小时前
现代c++2.4 虚继承
java·开发语言·c++