news 2026/10/17 8:40:35

中国天气网天气数据采集实战:从页面解析到结构化数据集

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
中国天气网天气数据采集实战:从页面解析到结构化数据集

简介:这份资源围绕“中国天气网获取天气数据”这一典型开发任务展开,面向具备一定C#基础、希望在VS2008环境下练习网络请求与数据解析的开发者。内容涉及API接口调用、HTTP请求与响应处理、JSON/XML解析、API密钥注册管理,以及Windows Forms界面数据显示等核心知识点,适合作为气象类小工具或课程设计的参考案例。资源包共34个文件,以cs源码、config配置、dll与exe程序、resx资源及sln解决方案为主,另有少量缓存与项目设置文件,整体约259KB,结构完整,可直接在VS2008中打开运行。目前已有2311人学习下载。通过这份资料,读者可以对照完整工程理解HttpWebRequest发起请求、JavaScriptSerializer或XDocument解析返回数据的实现方式,并借鉴错误处理与界面展示的写法,快速搭建自己的天气数据获取程序。

1. 中国天气网获取天气数据:从页面结构到可用数据集的落地路径

做气象相关项目的开发者大概都经历过这个场景:需要一个城市的逐日天气数据做分析或建模,第一反应是找开放 API,结果要么额度不够,要么字段残缺,要么稳定性堪忧。这时候中国天气网就成了一个绕不开的备选——它页面信息全、覆盖城市广、更新及时,但官方并没有提供面向普通开发者的公开数据接口。于是「获取天气数据」这件事,就变成了一个需要自己动手拆页面、理字段、做清洗的工程活。

这份资源要解决的,正是从中国天气网页面中稳定提取结构化天气数据的问题。它适合两类人:一类是需要批量采集历史与实时天气数据做研究或产品原型的开发者,另一类是正在学习网页数据提取、想找一个真实且结构清晰的练手项目的同学。核心难点不在「能不能抓到」,而在「抓得稳不稳、字段全不全、后续能不能直接用」。

2. 拆解页面结构:天气数据到底藏在哪一层

2.1 先分清三类页面,别一上来就写代码

中国天气网的页面并不是铁板一块,不同入口对应的数据粒度和加载方式差别很大。我一般会先把目标页面分成三类来看:

第一类是城市天气详情页,通常展示当前实况加上未来几天预报,字段包括温度、湿度、风力、空气质量等。这类页面结构相对规整,是采集的主力目标。

第二类是城市列表或省份索引页,用来拿到城市名称和对应的页面地址。这一步是批量采集的前提,很多新手直接跳过,结果只能手动一个个城市去抓。

第三类是历史天气或统计类页面,数据往往按月份或年份组织,表格化程度高,但入口较深,需要先定位到具体城市再逐层进入。

分清楚这三类之后,采集流程就清晰了:先从索引页拿到城市清单,再逐个进入详情页提取字段,最后按统一结构落库。常见做法是先用少量城市跑通全流程,再放大到全量。

2.2 用浏览器开发者工具定位真实数据节点

打开目标城市页面后,按 F12 进入开发者工具,重点看 Elements 面板。不要急着看 Network,因为很多天气字段是直接写在 HTML 里的,并不需要额外请求。

定位时有个小技巧:在页面上选中某个温度数字,右键选择「检查」,工具会直接跳到对应的 DOM 节点。顺着这个节点往上找,通常能找到一个包含多个天气字段的父容器。把这个容器的 class 或 id 记下来,它就是后续解析的锚点。

需要注意,页面里可能存在多个相似容器,比如「今天」「明天」「后天」各有一块结构相同的区域。这时候不能只靠 class 抓第一个,要结合索引或日期字段做区分。我一般会先把父容器列表全部取出来,再逐个解析内部字段,避免漏掉某一天的数据。

2.3 请求头与编码:两个最容易翻车的地方

直接用一个裸请求去访问,大概率会拿到不完整的内容或者乱码。原因通常有两个:一是缺少必要的请求头,二是编码没处理对。

请求头里最关键的是 User-Agent,很多站点会据此判断是否为正常浏览器访问。建议至少带上 User-Agent 和 Accept-Language,模拟一个常见的浏览器环境。不要用默认的 python-requests 标识,那基本等于告诉对方「我是脚本」。

编码方面,中国天气网部分页面使用 UTF-8,但也有页面是 GBK。判断方法很简单:看响应内容里 meta 标签声明的 charset,或者直接看响应头里的 Content-Type。如果拿到的中文是乱码,先检查这一步,而不是急着改解析逻辑。

import requests headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36", "Accept-Language": "zh-CN,zh;q=0.9", } url = "https://www.weather.com.cn/weather/101010100.shtml" resp = requests.get(url, headers=headers, timeout=10) # 先看响应头里的编码声明,再决定用哪种方式解码 resp.encoding = resp.apparent_encoding html = resp.text print(html[:500])

这段代码做了三件事:构造一个接近真实浏览器的请求头、发起带超时控制的请求、根据响应内容推断编码。timeout参数建议不要省,否则遇到慢响应会一直挂住。apparent_encoding是一种兜底推断,实际使用时如果已知页面编码,直接指定会更稳。

3. 解析与结构化:把 HTML 变成能用的字段

3.1 用解析库提取核心天气字段

拿到 HTML 之后,下一步是把它变成结构化数据。常用的解析库有 BeautifulSoup 和 lxml,前者上手快,后者速度快。对于天气页面这种结构相对固定的场景,两者都够用。

解析的核心思路是:先定位到包含天气信息的父容器,再在容器内部按字段逐个提取。不要一上来就用全局搜索,那样容易抓到导航栏或广告里的干扰内容。

from bs4 import BeautifulSoup soup = BeautifulSoup(html, "html.parser") # 定位未来几天预报的列表容器 day_list = soup.select("ul.t.clearfix li") results = [] for li in day_list: # 日期和星期 date_tag = li.find("h1") # 天气现象 weather_tag = li.find("p", class_="wea") # 温度范围 temp_tag = li.find("p", class_="tem") # 风力 wind_tag = li.find("p", class_="win") if not (date_tag and weather_tag and temp_tag): continue item = { "date": date_tag.get_text(strip=True), "weather": weather_tag.get_text(strip=True), "temp": temp_tag.get_text(strip=True).replace("\n", ""), "wind": wind_tag.get_text(strip=True) if wind_tag else "", } results.append(item) for r in results: print(r)

这段代码的关键点在于:用select定位列表容器,再在循环里逐字段提取。strip=True去掉多余空白,replace("\n", "")处理温度字段里常见的换行。if not (...)那行是防御性判断,避免某个字段缺失时直接报错。

参数方面,html.parser是 Python 内置解析器,不需要额外安装,速度一般但足够用。如果数据量大,可以换成lxml,需要先安装对应库。选择器ul.t.clearfix li是根据页面实际结构写的,不同页面可能不同,需要按 2.2 的方法重新定位。

3.2 字段清洗:温度、风力、日期都要单独处理

原始提取出来的字段往往带着多余字符,直接入库会很难用。常见需要清洗的有三类:

温度字段通常形如「-3/6℃」或「6℃」,需要拆成最低温和最高温两个数值。风力字段可能是「3-4级」或「微风」,需要统一成可比较的格式。日期字段可能只有「今天」「明天」这种相对表述,需要结合采集时间换算成具体日期。

import re from datetime import datetime, timedelta def parse_temp(temp_str): """把温度字符串拆成最低温和最高温""" nums = re.findall(r"-?\d+", temp_str) if len(nums) == 2: return int(nums[0]), int(nums[1]) elif len(nums) == 1: return int(nums[0]), int(nums[0]) return None, None def parse_date(date_str, base_date=None): """把相对日期转成具体日期""" base = base_date or datetime.now() mapping = {"今天": 0, "明天": 1, "后天": 2} for key, offset in mapping.items(): if key in date_str: return (base + timedelta(days=offset)).strftime("%Y-%m-%d") # 如果是「X日」这种格式,按当前月份补全 m = re.search(r"(\d+)日", date_str) if m: day = int(m.group(1)) return base.replace(day=day).strftime("%Y-%m-%d") return date_str # 对前面提取的结果做清洗 for r in results: low, high = parse_temp(r["temp"]) r["temp_low"] = low r["temp_high"] = high r["date_full"] = parse_date(r["date"]) print(results[0])

parse_temp用正则抓出所有数字,根据数量判断是单值还是范围。parse_date先处理相对日期,再用正则兜底处理「X日」格式。这两个函数都不复杂,但能省掉后续大量手工整理的时间。实际使用时,建议把清洗逻辑单独放一个模块,方便复用和测试。

3.3 批量采集:城市清单与循环控制

单城市采集跑通后,下一步是批量。批量采集的核心是拿到城市清单,然后循环调用前面的采集逻辑。

城市清单可以从省份索引页提取,也可以自己维护一份常用城市列表。前者更全,后者更可控。我一般会先用索引页生成一份城市-URL 映射表,存成 CSV,后续采集直接读表,避免每次重复解析索引页。

import csv import time def load_cities(path="cities.csv"): """从 CSV 读取城市清单""" cities = [] with open(path, "r", encoding="utf-8") as f: reader = csv.DictReader(f) for row in reader: cities.append(row) return cities def crawl_all(cities, delay=1.5): """批量采集,带间隔控制""" all_data = [] for city in cities: try: html = fetch(city["url"]) # 复用前面的请求逻辑 data = parse_weather(html) # 复用前面的解析逻辑 for item in data: item["city"] = city["name"] all_data.extend(data) print(f"完成:{city['name']},{len(data)} 条") except Exception as e: print(f"失败:{city['name']},原因:{e}") time.sleep(delay) # 控制请求频率 return all_data

delay参数是批量采集里最重要的一个。设得太短容易触发限制,设得太长效率低。常见做法是 1 到 2 秒,具体看目标站点的响应情况。try/except保证单个城市失败不影响整体流程,失败信息打印出来方便后续补采。

4. 避坑与排查:采集天气数据时最容易踩的五个坑

4.1 现象:返回内容里没有天气数据,只有一段脚本

原因:页面数据是通过后续请求加载的,直接抓初始 HTML 拿不到。解决:打开 Network 面板,筛选 XHR 或 Fetch 请求,找到真正返回数据的接口,直接请求该接口。注意接口可能需要特定的 Referer 或参数。

4.2 现象:中文全部变成乱码

原因:编码判断错误,常见于 GBK 页面被当成 UTF-8 处理。解决:先看响应头 Content-Type,再看页面 meta 声明。如果两者不一致,以实际内容为准,手动指定resp.encoding = "gbk"或"utf-8"。

4.3 现象:前几天能跑,后面全部超时或返回空

原因:请求频率过高被限制,或者请求头被识别。解决:加大请求间隔,补充 Referer、Accept 等请求头,必要时轮换 User-Agent。不要用多线程猛冲,天气数据不是抢购,稳定比速度重要。

4.4 现象:温度字段解析出来是空值

原因:页面结构变了,或者选择器写得太死。解决:重新用开发者工具定位,检查 class 名是否变化。建议把选择器集中配置,方便统一修改,而不是散落在代码各处。

4.5 现象:日期字段对不上,今天的数据标成了昨天

原因:相对日期换算时用了 UTC 时间,而页面用的是本地时间。解决:统一用本地时间做基准,或者在换算时加上时区偏移。这个坑很隐蔽,数据量小的时候不容易发现。

5. 从采集到可用:数据校验与增量更新的一点经验

采集跑通只是第一步,真正让数据能用起来,还得做校验和更新。我一般会在入库前加一层校验:温度范围是否合理、日期是否连续、城市字段是否为空。任何一条不满足,就标记出来单独处理,而不是直接混进数据集。

增量更新方面,天气数据的特点是每天都会变,但历史数据不变。所以策略很简单:历史部分只采一次,当天和未来几天的数据每天定时刷新。刷新时按城市加日期做去重,避免重复入库。

def validate(item): """基础校验,返回 True 或 False""" if item["temp_low"] is None or item["temp_high"] is None: return False if item["temp_low"] > item["temp_high"]: return False if item["temp_low"] < -60 or item["temp_high"] > 60: return False if not item["city"]: return False return True # 入库前过滤 clean_data = [item for item in all_data if validate(item)] print(f"原始 {len(all_data)} 条,校验后 {len(clean_data)} 条")

校验逻辑不复杂,但能挡掉大部分明显异常。温度上下限根据实际地理范围调整,-60到60是一个比较宽松的兜底。去重可以用「城市+日期」作为唯一键,写入时做冲突处理。

还有一个习惯:每次采集完,把原始 HTML 和解析结果各存一份。原始 HTML 用于事后排查,解析结果用于直接使用。存储成本不高,但出问题时能省很多事。从那以后我每次做页面采集,都会强制走一遍「先存原始、再解析、后校验」的流程,宁可多占点空间,也不想到时候没有后悔药。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 8:40:28

RPA+AI智能流程自动化落地指南:从流程梳理到实施避坑

1. 从"流程自动化"到"智能化跨越"&#xff0c;这个题到底在解什么先说结论&#xff1a;企业数字化转型喊了这么多年&#xff0c;真正卡住的从来不是技术&#xff0c;而是流程本身。RPA解决的是"手"的问题&#xff0c;AI解决的是"脑"的…

作者头像 李华
网站建设 2026/10/17 8:40:12

三个GitHub开源项目实战:容器资源隔离、环境一致性与数据可视化

1. 三个开源项目的整体定位与选型逻辑逛 GitHub 这件事&#xff0c;我坚持了快八年。早期是漫无目的地刷 Trending&#xff0c;看到 star 涨得猛就 clone 下来跑一跑&#xff0c;结果硬盘里堆了一堆“下次一定看”的仓库。后来慢慢学乖了&#xff0c;开始带着问题去找项目——手…

作者头像 李华
网站建设 2026/10/17 8:40:11

OCR结构化实战:表格识别、版面分析与手写识别全解析

这两年做文档数字化的朋友应该都有同感&#xff1a;OCR 单看文字识别率已经很能打了&#xff0c;可一旦丢过来一份带表格的扫描合同、两栏排版的论文 PDF、或者一张手写备注的报销单&#xff0c;问题就全冒出来了——表格线乱断、单元格错位、多栏内容读成一路、手写汉字直接变…

作者头像 李华
网站建设 2026/10/17 8:39:34

Qt集成FFmpeg实现RTSP播放:从拉流到QImage显示的完整工程实践

简介&#xff1a;这是一份基于Qt与FFmpeg的RTSP播放器演示工程&#xff0c;面向刚接触C音视频开发的初学者&#xff0c;完整展示了在Qt界面中拉取RTSP流、解码视频帧并渲染画面的过程&#xff0c;可直接迁移到远程监控、在线直播等实时视频场景。压缩包共16个文件&#xff0c;核…

作者头像 李华
网站建设 2026/10/17 8:39:12

力扣刷题进阶指南:从零到三百题的规划、复盘与实战心法

我到现在还记得第一次在力扣上提交失败的画面&#xff1a;红色的“解答错误”&#xff0c;测试用例卡在第47个&#xff0c;我盯着屏幕看了五分钟才看懂它到底在测什么。那之后我在力扣上刷了三年&#xff0c;前前后后写了一千多道题&#xff08;其中很多是反复二刷三刷的&#…

作者头像 李华
网站建设 2026/10/17 8:38:54

Spring中Cookie与Session会话管理实战:从原理到分布式方案

我做 Spring 开发也踩过不少跟登录状态相关的大坑&#xff0c;刚开始总是把 Cookie 和 Session 搞混&#xff1a;明明是同一个东西&#xff0c;换了个说法就不知道后端到底存了什么。后来在一次排查“接口偶发 401”的问题时&#xff0c;才彻底把这两兄弟的关系理顺。这个话题也…

作者头像 李华