news 2026/8/13 13:41:35

Python爬虫实战:从中国天气网自动获取并可视化天气预报数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python爬虫实战:从中国天气网自动获取并可视化天气预报数据

1. 项目概述:从网页到数据与图表的自动化旅程

最近在做一个数据分析的小项目,需要一些城市的历史天气数据来做趋势分析。手动去网站一天天查显然不现实,于是很自然地就想到了用Python写个爬虫,把中国天气网上的预报数据自动抓下来。这个需求听起来简单,但真做起来,从模拟请求、解析HTML、处理数据到最终可视化,每一步都有不少细节需要注意。今天就把我这次“天气预报数据爬取与分析”的完整实现过程,包括踩过的坑和总结的经验,详细分享一下。无论你是刚接触Python爬虫的新手,想找个完整的实战项目练手,还是已经有一定基础,想了解如何更稳健地处理一个完整的“数据获取-处理-展示”流程,相信这篇内容都能给你带来直接的参考价值。我们的目标很明确:写一个脚本,输入城市名,它就能自动登录中国天气网,抓取该城市未来7天或15天的天气预报,把数据规整地存进CSV文件,同时用折线图直观展示温度变化趋势。

2. 核心思路与工具选型:为什么是它们?

在动手写代码之前,先花点时间聊聊整体的设计思路和为什么选择这些工具。一个健壮的爬虫项目,前期规划比盲目编码更重要。

2.1 目标网站分析与请求策略

中国天气网(www.weather.com.cn)是一个结构相对规整的网站。我们需要的天气预报数据通常以静态或动态加载的方式呈现在城市详情页。经过分析,我发现其数据获取主要有两种方式:一是直接解析HTML页面,二是查找页面中可能存在的JSON数据接口。对于天气预报这种更新不极端频繁、且对普通用户免费开放的数据,网站一般不会设置非常复杂的反爬机制,但基本的请求头伪装和频率控制仍是必须的。

我选择使用requests库来发送HTTP请求,这是Python生态中最简单易用的HTTP库。关键在于模拟一个真实浏览器的请求。这意味着我们需要构造完整的请求头(User-Agent, Referer等),让服务器认为这是一个正常的用户访问,而不是脚本。对于可能存在的动态加载数据,需要用到浏览器的开发者工具(F12)中的“网络”(Network)选项卡,观察页面加载过程中发出的XHR或Fetch请求,直接找到返回JSON数据的API地址,这往往比解析整个HTML更高效、更稳定。

2.2 数据解析方案:HTML与JSON之争

拿到网页响应后,下一步是从中提取出我们需要的信息:日期、天气状况、最高温、最低温、风力风向等。这里有两个主流选择:

  1. HTML解析:使用BeautifulSouplxml。这种方法直观,适合页面结构清晰、数据直接嵌入在HTML标签中的情况。你需要通过查看网页源代码,找到包裹目标数据的特定标签和CSS选择器路径。
  2. JSON解析:如果找到了数据接口,那么返回的数据通常是结构化的JSON格式,直接使用Python内置的json库解析即可。这种方式得到的数据非常干净,无需处理杂乱的HTML标签,是首选方案。

在实际操作中,我优先尝试寻找JSON接口。通过分析中国天气网的城市页面,我发现其天气预报数据正是通过一个后台接口异步加载的,这让我们省去了大量解析HTML的麻烦。

2.3 数据存储与可视化工具

提取出的数据需要持久化存储。CSV(逗号分隔值)格式是轻量级结构化数据的绝佳选择,它可以用Excel直接打开,也容易被Pandas等数据分析库读取。Python内置的csv模块就足以完成读写操作。

对于可视化,Matplotlib是Python绘图领域的事实标准,功能强大且灵活。虽然其默认样式可能不那么“时尚”,但通过简单的参数调整,完全可以生成清晰、专业的图表。我们用它来绘制未来几天最高温和最低温的折线图,直观展示温度变化趋势。

注意:在开始编写爬虫前,请务必阅读目标网站的robots.txt文件(通常在网站根目录,如www.weather.com.cn/robots.txt)和使用条款。本项目的目的是为了个人学习与技术交流,演示自动化数据收集的合法、合理方式。在实际操作中,必须控制请求频率,避免对目标网站服务器造成压力,严禁将数据用于商业用途或侵犯他人权益。

3. 实战环境搭建与核心代码实现

思路清晰后,我们开始动手搭建环境并编写核心代码。我会分步骤详细说明,并提供可直接运行的代码片段。

3.1 环境准备与依赖安装

首先,确保你的Python环境(建议使用Python 3.7及以上版本)已经就绪。然后,通过pip安装我们所需的第三方库。打开你的终端或命令提示符,执行以下命令:

pip install requests beautifulsoup4 matplotlib pandas
  • requests:用于发送HTTP请求。
  • beautifulsoup4:备用,用于HTML解析(虽然本项目主要用JSON接口,但作为通用技能,安装以备不时之需)。
  • matplotlib:用于数据可视化绘图。
  • pandas:非必须,但它是处理表格数据的利器,这里我们可以用它来优雅地创建DataFrame并保存为CSV,比直接用csv模块更简洁。

如果你在安装matplotlib时遇到问题,特别是在Windows 32位系统上,可能会因为依赖的兼容性问题导致失败(如网络热词中提到的错误)。一个通用的解决方法是使用清华大学等国内镜像源加速安装,或者考虑使用更轻量的altairplotly库作为替代。对于绝大多数64位系统,直接安装通常很顺利。

3.2 核心爬取逻辑:寻找数据接口并解析

这是最关键的步骤。我们以“北京”为例。

  1. 打开浏览器开发者工具:访问中国天气网北京页面(例如http://www.weather.com.cn/weather/101010100.shtml),按F12打开开发者工具,切换到“网络”(Network)选项卡,并勾选“保留日志”(Preserve log)。
  2. 刷新页面:刷新页面,观察“网络”选项卡中加载的所有资源。
  3. 寻找数据接口:在资源列表中,寻找类型为XHRFetch的请求,其响应内容可能包含“7d”或“15d”等关键字。经过查找,我发现了一个类似http://www.weather.com.cn/weather/101010100.shtml的请求,但其响应是HTML。进一步查找,发现了一个更关键的接口,其URL模式通常包含城市代码和“weather”信息。实际上,中国天气网的数据接口地址可能需要更仔细地探查。一种更可靠的方法是直接搜索“7天”或“15天”在响应中的关键词。这里我采用一种经过验证的、更直接的方法:实际上,对于7天预报,数据直接嵌在HTML中一个id为“7d”的<script>标签里,是一个JavaScript变量。我们可以通过正则表达式提取出其中的JSON字符串。

基于以上分析,我们编写爬取函数:

import requests import re import json from datetime import datetime def get_weather_data(city_code=‘101010100’, days=7): """ 获取指定城市代码的天气预报数据 :param city_code: 城市代码,如北京是101010100 :param days: 获取天数,7或15 :return: 包含天气预报数据的字典列表 """ # 构造URL(这里以7天预报页面为例,15天预报页面结构可能不同) url = f‘http://www.weather.com.cn/weather/{city_code}.shtml’ # 构造请求头,模拟浏览器访问 headers = { ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36’, ‘Referer’: ‘http://www.weather.com.cn/’, } try: response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() # 检查请求是否成功 response.encoding = ‘utf-8’ # 设置编码,防止中文乱码 html_text = response.text except requests.exceptions.RequestException as e: print(f“网络请求失败: {e}”) return [] # 关键步骤:使用正则表达式提取HTML中<script>标签内的JSON数据 # 观察页面源码,发现7天数据在 id=“7d” 的 script 标签内,格式如:var hour3data={...} # 我们使用更通用的模式来匹配 pattern = r‘var\s+7d\s*=\s*(\{.*?\});’ match = re.search(pattern, html_text, re.S) # re.S 使 . 匹配包括换行符在内的所有字符 if not match: print(“未在页面中找到7天天气预报数据。”) # 可以尝试其他匹配模式或解析HTML return [] json_str = match.group(1) try: # 将JavaScript对象格式的字符串转换为Python字典 # 注意:JS对象中的键可能没有引号,需要先简单处理 # 这里假设提取出的字符串已经是合法的JSON,实际情况可能需要更复杂的清洗 # 一个更健壮的方法是使用 `json5` 库来解析非标准JSON # 为简化,我们假设匹配到的部分是标准JSON weather_dict = json.loads(json_str) except json.JSONDecodeError as e: print(f“JSON解析失败: {e}”) print(f“原始字符串片段: {json_str[:200]}...”) return [] # 解析数据,提取我们需要的信息 # 实际数据结构需要根据提取出的json_str具体分析,这里假设一个通用结构 # 例如,数据可能在 weather_dict[‘7d’] 或某个键下 forecast_data = [] # 这里需要根据实际解析出的 weather_dict 结构来编写提取逻辑 # 以下是一个假设性的示例流程,你需要根据实际数据结构调整: if ‘weather’ in weather_dict and ‘forecast’ in weather_dict[‘weather’]: forecast_list = weather_dict[‘weather’][‘forecast’] for item in forecast_list[:days]: # 只取指定天数 daily_info = { ‘date’: item.get(‘date’, ‘’), ‘day_weather’: item.get(‘dayWeather’, ‘’), ‘night_weather’: item.get(‘nightWeather’, ‘’), ‘high_temp’: item.get(‘highTemp’, ‘’).replace(‘℃’, ‘’), ‘low_temp’: item.get(‘lowTemp’, ‘’).replace(‘℃’, ‘’), ‘day_wind’: item.get(‘dayWind’, ‘’), ‘night_wind’: item.get(‘nightWind’, ‘’), } forecast_data.append(daily_info) else: # 如果结构不符,尝试其他路径或打印结构以便调试 print(“未找到预期的数据结构,实际结构可能是:”) print(json.dumps(weather_dict, indent=2, ensure_ascii=False)[:500]) return forecast_data # 测试函数 if __name__ == ‘__main__’: data = get_weather_data(‘101010100’, 7) for d in data: print(d)

重要提示:上面的代码中,正则表达式pattern和后续的数据提取逻辑forecast_list假设性的。中国天气网的实际页面结构可能会发生变化,<script>标签内的变量名和数据结构需要你根据当时访问的页面源代码实时分析确定。这是爬虫开发中最核心也最需要灵活应对的部分。你需要打开目标页面的源代码(Ctrl+U),搜索“7d”或“weather”等关键词,找到真正包含数据的JavaScript变量,然后调整正则表达式和json.loads()后的字典键路径。

3.3 数据存储:优雅地写入CSV文件

拿到结构化的数据列表后,我们使用pandas库来保存为CSV文件,这比用csv.writer一行行写要方便得多。

import pandas as pd def save_to_csv(data, filename=‘weather_forecast.csv’): """ 将天气预报数据保存到CSV文件 :param data: 字典列表,即get_weather_data函数的返回值 :param filename: 保存的文件名 """ if not data: print(“没有数据可保存。”) return # 将字典列表转换为pandas DataFrame df = pd.DataFrame(data) # 保存到CSV文件,index=False表示不保存行索引,encoding=‘utf-8-sig’确保Excel打开中文不乱码 df.to_csv(filename, index=False, encoding=‘utf-8-sig’) print(f“数据已成功保存到 {filename}”) # 打印前几行预览 print(df.head())

使用utf-8-sig编码是一个非常重要的细节。utf-8-sig会在文件开头添加一个BOM(字节顺序标记),这样当用Windows系统自带的记事本或Excel打开时,能自动识别编码,避免中文显示为乱码。如果你确定只在Python或特定环境下使用,用utf-8也可以。

3.4 数据可视化:用Matplotlib绘制温度趋势图

数据存好了,我们再用图表直观地看一下温度变化。用Matplotlib绘制最高温和最低温的折线图。

import matplotlib.pyplot as plt import matplotlib # 设置中文字体,防止图表中文乱码(根据你的系统调整字体路径或名称) matplotlib.rcParams[‘font.sans-serif’] = [‘SimHei’, ‘Microsoft YaHei’, ‘DejaVu Sans’] # 指定默认字体 matplotlib.rcParams[‘axes.unicode_minus’] = False # 解决负号‘-’显示为方块的问题 def plot_temperature_trend(data, city_name=‘北京’): """ 绘制最高温和最低温趋势折线图 :param data: 字典列表,包含‘date’, ‘high_temp’, ‘low_temp’键 :param city_name: 城市名称,用于图表标题 """ if not data: print(“没有数据可绘制图表。”) return # 准备数据 dates = [d[‘date’] for d in data] # 注意:提取的温度可能是字符串,需要转换为数值。可能包含‘高温’、‘低温’等文字,需要清洗。 high_temps = [] low_temps = [] for d in data: ht = d[‘high_temp’] lt = d[‘low_temp’] # 简单的清洗,提取数字部分。实际情况可能更复杂。 try: # 假设字符串如“28℃”或“28” ht_num = int(re.sub(r‘[^\d-]’, ‘’, ht)) if ht else None lt_num = int(re.sub(r‘[^\d-]’, ‘’, lt)) if lt else None except ValueError: ht_num, lt_num = None, None high_temps.append(ht_num) low_temps.append(lt_num) # 创建图表 plt.figure(figsize=(12, 6)) plt.plot(dates, high_temps, marker=‘o’, label=‘最高温’, color=‘#FF6B6B’, linewidth=2) plt.plot(dates, low_temps, marker=‘s’, label=‘最低温’, color=‘#4ECDC4’, linewidth=2) # 填充最高温和最低温之间的区域,增加视觉效果 plt.fill_between(dates, high_temps, low_temps, color=‘#C7F0DB’, alpha=0.3) # 添加标题和标签 plt.title(f‘{city_name}未来{len(dates)}天温度趋势预报’, fontsize=16, fontweight=‘bold’) plt.xlabel(‘日期’, fontsize=12) plt.ylabel(‘温度 (℃)’, fontsize=12) plt.xticks(rotation=45) # 旋转日期标签,防止重叠 plt.legend() plt.grid(True, linestyle=‘--’, alpha=0.6) plt.tight_layout() # 自动调整子图参数,使之填充整个图像区域 # 保存图片 plt.savefig(f‘{city_name}_temperature_trend.png’, dpi=300, bbox_inches=‘tight’) print(f“温度趋势图已保存为 {city_name}_temperature_trend.png”) # 显示图片(如果在Jupyter Notebook或支持GUI的环境中) # plt.show()

这段代码做了几件重要的事:1) 设置了中文字体,这是Matplotlib显示中文的前提;2) 从数据中清洗并提取出数值型的温度;3) 绘制了带标记点的双折线图,并用颜色填充了区域,使图表更美观;4) 添加了网格、标签、标题等元素;5) 将图表保存为高分辨率PNG图片。

4. 完整脚本组装与主流程控制

现在,我们把所有功能模块组装起来,形成一个完整的、可交互的脚本。

import requests import re import json import pandas as pd import matplotlib.pyplot as plt import matplotlib from datetime import datetime # 设置中文字体 matplotlib.rcParams[‘font.sans-serif’] = [‘SimHei’, ‘Microsoft YaHei’] matplotlib.rcParams[‘axes.unicode_minus’] = False # 这里插入上面定义好的三个函数:get_weather_data, save_to_csv, plot_temperature_trend # (为了节省篇幅,此处省略函数具体代码,实际使用时需粘贴过来) def main(): print(“=== 天气预报数据爬取与分析工具 ===”) # 城市代码映射字典,可以预先准备一些常用城市 city_dict = { ‘北京’: ‘101010100’, ‘上海’: ‘101020100’, ‘广州’: ‘101280101’, ‘深圳’: ‘101280601’, ‘杭州’: ‘101210101’, # 更多城市代码可以从中国天气网查找或通过其他接口获取 } city_name = input(“请输入城市名(如 北京): “).strip() if city_name not in city_dict: print(f“未找到城市 {city_name} 的代码,将使用默认北京代码。”) city_code = ‘101010100’ city_name_for_display = ‘北京’ else: city_code = city_dict[city_name] city_name_for_display = city_name try: days = int(input(“请输入要获取的天数 (7 或 15): “).strip()) if days not in [7, 15]: print(“输入天数无效,将默认获取7天数据。”) days = 7 except ValueError: print(“输入格式错误,将默认获取7天数据。”) days = 7 print(f“正在获取 {city_name_for_display} 未来{days}天天气预报...”) weather_data = get_weather_data(city_code, days) if weather_data: print(f“成功获取到 {len(weather_data)} 条数据。”) # 保存为CSV csv_filename = f‘{city_name_for_display}_weather_forecast.csv’ save_to_csv(weather_data, csv_filename) # 绘制温度趋势图 plot_temperature_trend(weather_data, city_name_for_display) print(“\n所有任务已完成!”) else: print(“未能获取到有效数据,请检查网络连接或城市代码是否正确。”) if __name__ == ‘__main__’: main()

这个main()函数提供了一个简单的命令行交互界面,用户输入城市名和天数,脚本自动完成爬取、存储和可视化的全过程。你可以根据需要扩展city_dict,加入更多城市代码。

5. 常见问题、避坑指南与进阶思考

在实际开发和运行过程中,你几乎一定会遇到下面这些问题。这里我把它们和解决方案整理出来,希望能帮你节省大量调试时间。

5.1 数据抓取失败:请求被拒绝或无返回

  • 问题requests.get()返回状态码不是200(如403、404),或者返回的HTML内容中没有我们需要的数据。
  • 排查与解决
    1. 检查请求头:这是最常见的原因。务必模拟完整的浏览器请求头,至少包含User-AgentRefererUser-Agent可以定期更新为较新的浏览器版本字符串。
    2. 检查URL和参数:确认你构造的URL是正确的。城市代码是否准确?不同天数预报的URL是否不同?有时需要添加查询参数(如?day=7)。
    3. 处理Cookie或Session:某些网站可能需要维持会话。使用requests.Session()对象来保持Cookie across requests。
    4. 应对动态加载:如果数据是页面加载后通过JavaScript异步请求的,你需要找到那个真正的API接口地址,而不是初始的HTML页面URL。在开发者工具的“网络”选项卡中仔细查找XHR/Fetch请求。
    5. 添加延时:过于频繁的请求会触发网站的反爬机制。在循环请求多个城市时,使用time.sleep(random.uniform(1, 3))添加随机延时。

5.2 数据解析错误:正则匹配失败或JSON解析出错

  • 问题:正则表达式没匹配到内容,或者json.loads()失败。
  • 排查与解决
    1. 验证正则表达式:将抓取到的html_text保存到一个临时文件,用文本编辑器打开,仔细核对你要匹配的文本模式。正则表达式中的空格、换行符(用\s*re.S标志处理)都可能影响匹配。
    2. 处理非标准JSON:网页中JavaScript变量可能不是严格合法的JSON(如键名无双引号,末尾有分号)。可以尝试使用json5库(pip install json5)来解析,或者先进行字符串清洗(如用ast.literal_eval但要小心安全)。
    3. 打印调试:在解析前,打印出你提取到的json_str的前几百个字符,确认其结构。使用json.dumps(parsed_dict, indent=2, ensure_ascii=False)来美观地打印解析后的字典,理解其层级结构。

5.3 中文乱码问题

  • 问题:保存的CSV文件用Excel打开中文是乱码,或者Matplotlib图表标题、标签中文显示为方框。
  • 解决
    • CSV乱码:使用df.to_csv(..., encoding=‘utf-8-sig’)utf-8-sig编码会添加BOM,完美兼容Windows Excel。
    • Matplotlib乱码:确保在执行任何绘图操作前,正确设置了中文字体。如代码中所示,需要指定系统已安装的中文字体名称。如果‘SimHei’(黑体)不可用,可以尝试‘Microsoft YaHei’(微软雅黑)或‘KaiTi’(楷体)。你也可以指定字体文件的绝对路径。

5.4 Matplotlib图表显示或保存问题

  • 问题:在服务器或无GUI环境下运行脚本时,plt.show()会报错,或者保存的图片是空白。
  • 解决
    1. 无GUI环境:在导入matplotlib后,添加matplotlib.use(‘Agg’)。这告诉Matplotlib使用一个不依赖显示器的后端(Agg),专门用于生成图片文件。这样plt.savefig()可以正常工作,但plt.show()无效。
    2. 图片空白:确保在plt.savefig()之前已经创建了图形并绘制了内容。并且,plt.savefig()要在plt.show()之前调用,因为show()在某些环境下会清空图形。使用plt.tight_layout()可以避免标签被截断。

5.5 项目进阶与扩展思路

这个基础项目完成后,你可以从多个方向进行扩展,把它变成一个更强大、更实用的工具:

  1. 增加城市代码自动查询:目前需要手动维护城市代码字典。可以写一个函数,根据输入的城市名,去中国天气网或其他接口查询对应的城市代码。
  2. 支持历史天气数据爬取:中国天气网也提供历史天气查询。分析其历史数据页面的请求方式,扩展脚本以爬取过去几个月或几年的数据,用于更长期的分析。
  3. 构建图形用户界面(GUI):使用tkinterPyQtstreamlit库为脚本制作一个简单的桌面或Web界面,让非技术用户也能方便使用。
  4. 定时自动运行与邮件通知:将脚本部署到服务器,使用cron(Linux)或任务计划程序(Windows)定时运行(如每天早晨8点),并将生成的CSV和图片通过邮件发送给自己,制作一个个性化的天气简报服务。
  5. 数据持久化与简单分析:将爬取的数据不仅存入CSV,也存入SQLite或MySQL数据库。然后使用Pandas进行简单的统计分析,比如计算平均温度、统计晴天/雨天的频率等,并生成更丰富的图表(如饼图、柱状图)。
  6. 增强异常处理与日志记录:为网络请求、数据解析等关键步骤添加更完善的try...except块,并将运行状态、错误信息写入日志文件,便于后期维护和排查问题。

爬虫项目的核心魅力在于“让机器自动完成重复的网页信息收集工作”。在这个过程中,你对网络协议、数据结构和特定网站架构的理解会不断加深。每一次成功抓取和数据规整,都是对问题解决能力的一次锻炼。希望这个详细的爬取天气预报数据的项目,能成为你探索Python自动化世界的一块坚实垫脚石。如果在实现过程中遇到任何问题,回顾一下“常见问题”部分,或者多利用打印语句调试,耐心分析网页源代码,你总能找到解决方案。

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

UVa 1018 Building Bridges

题目描述 New Altonville\texttt{New Altonville}New Altonville 市议会计划建造一个桥梁系统&#xff0c;连接市中心的所有建筑&#xff0c;以便人们可以在不走到户外的情况下从一栋建筑走到另一栋建筑。你需要编写一个程序来帮助确定最佳的桥梁配置。 New Altonville\texttt{…

作者头像 李华
网站建设 2026/8/13 13:40:37

职业院校学工一体化平台采购选型实用指南

✅作者简介&#xff1a;合肥自友科技 &#x1f4cc;核心产品&#xff1a;智慧校园平台(包括教工管理、学工管理、教务管理、考务管理、后勤管理、德育管理、资产管理、公寓管理、实习管理、就业管理、离校管理、科研平台、档案管理、学生平台等26个子平台) 。公司所有人员均有多…

作者头像 李华
网站建设 2026/8/13 13:38:59

Montserrat字体免费商用完整指南:开源几何无衬线字体的快速入门

Montserrat字体免费商用完整指南&#xff1a;开源几何无衬线字体的快速入门 【免费下载链接】Montserrat 项目地址: https://gitcode.com/gh_mirrors/mo/Montserrat 深夜十一点&#xff0c;你还在为一条甲方消息发愁。"感觉缺了点高级感"——这句话你已经听了…

作者头像 李华
网站建设 2026/8/13 13:37:52

HPM6750高性能MCU时钟系统:从心跳到性能优化的全面解析

1. 从“心跳”说起&#xff1a;为什么时钟是芯片的命脉拿到一块像HPM6750这样的高性能MCU&#xff0c;很多开发者第一反应是翻看外设手册&#xff0c;琢磨着怎么驱动GPIO、配置UART、玩转PWM。这没错&#xff0c;但往往忽略了最底层、也最关键的一环——时钟系统。你可以把时钟…

作者头像 李华