尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

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

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之争拿到网页响应后下一步是从中提取出我们需要的信息日期、天气状况、最高温、最低温、风力风向等。这里有两个主流选择HTML解析使用BeautifulSoup或lxml。这种方法直观适合页面结构清晰、数据直接嵌入在HTML标签中的情况。你需要通过查看网页源代码找到包裹目标数据的特定标签和CSS选择器路径。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 pandasrequests用于发送HTTP请求。beautifulsoup4备用用于HTML解析虽然本项目主要用JSON接口但作为通用技能安装以备不时之需。matplotlib用于数据可视化绘图。pandas非必须但它是处理表格数据的利器这里我们可以用它来优雅地创建DataFrame并保存为CSV比直接用csv模块更简洁。如果你在安装matplotlib时遇到问题特别是在Windows 32位系统上可能会因为依赖的兼容性问题导致失败如网络热词中提到的错误。一个通用的解决方法是使用清华大学等国内镜像源加速安装或者考虑使用更轻量的altair或plotly库作为替代。对于绝大多数64位系统直接安装通常很顺利。3.2 核心爬取逻辑寻找数据接口并解析这是最关键的步骤。我们以“北京”为例。打开浏览器开发者工具访问中国天气网北京页面例如http://www.weather.com.cn/weather/101010100.shtml按F12打开开发者工具切换到“网络”(Network)选项卡并勾选“保留日志”(Preserve log)。刷新页面刷新页面观察“网络”选项卡中加载的所有资源。寻找数据接口在资源列表中寻找类型为XHR或Fetch的请求其响应内容可能包含“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’, days7): 获取指定城市代码的天气预报数据 :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, headersheaders, timeout10) 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\s7d\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, indent2, ensure_asciiFalse)[:500]) return forecast_data # 测试函数 if __name__ ‘__main__’: data get_weather_data(‘101010100’, 7) for d in data: print(d)重要提示上面的代码中正则表达式pattern和后续的数据提取逻辑forecast_list是假设性的。中国天气网的实际页面结构可能会发生变化script标签内的变量名和数据结构需要你根据当时访问的页面源代码实时分析确定。这是爬虫开发中最核心也最需要灵活应对的部分。你需要打开目标页面的源代码CtrlU搜索“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文件indexFalse表示不保存行索引encoding‘utf-8-sig’确保Excel打开中文不乱码 df.to_csv(filename, indexFalse, 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’, linewidth2) plt.plot(dates, low_temps, marker‘s’, label‘最低温’, color‘#4ECDC4’, linewidth2) # 填充最高温和最低温之间的区域增加视觉效果 plt.fill_between(dates, high_temps, low_temps, color‘#C7F0DB’, alpha0.3) # 添加标题和标签 plt.title(f‘{city_name}未来{len(dates)}天温度趋势预报’, fontsize16, fontweight‘bold’) plt.xlabel(‘日期’, fontsize12) plt.ylabel(‘温度 (℃)’, fontsize12) plt.xticks(rotation45) # 旋转日期标签防止重叠 plt.legend() plt.grid(True, linestyle‘--’, alpha0.6) plt.tight_layout() # 自动调整子图参数使之填充整个图像区域 # 保存图片 plt.savefig(f‘{city_name}_temperature_trend.png’, dpi300, 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内容中没有我们需要的数据。排查与解决检查请求头这是最常见的原因。务必模拟完整的浏览器请求头至少包含User-Agent和Referer。User-Agent可以定期更新为较新的浏览器版本字符串。检查URL和参数确认你构造的URL是正确的。城市代码是否准确不同天数预报的URL是否不同有时需要添加查询参数如?day7。处理Cookie或Session某些网站可能需要维持会话。使用requests.Session()对象来保持Cookie across requests。应对动态加载如果数据是页面加载后通过JavaScript异步请求的你需要找到那个真正的API接口地址而不是初始的HTML页面URL。在开发者工具的“网络”选项卡中仔细查找XHR/Fetch请求。添加延时过于频繁的请求会触发网站的反爬机制。在循环请求多个城市时使用time.sleep(random.uniform(1, 3))添加随机延时。5.2 数据解析错误正则匹配失败或JSON解析出错问题正则表达式没匹配到内容或者json.loads()失败。排查与解决验证正则表达式将抓取到的html_text保存到一个临时文件用文本编辑器打开仔细核对你要匹配的文本模式。正则表达式中的空格、换行符用\s*或re.S标志处理都可能影响匹配。处理非标准JSON网页中JavaScript变量可能不是严格合法的JSON如键名无双引号末尾有分号。可以尝试使用json5库pip install json5来解析或者先进行字符串清洗如用ast.literal_eval但要小心安全。打印调试在解析前打印出你提取到的json_str的前几百个字符确认其结构。使用json.dumps(parsed_dict, indent2, ensure_asciiFalse)来美观地打印解析后的字典理解其层级结构。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()会报错或者保存的图片是空白。解决无GUI环境在导入matplotlib后添加matplotlib.use(‘Agg’)。这告诉Matplotlib使用一个不依赖显示器的后端Agg专门用于生成图片文件。这样plt.savefig()可以正常工作但plt.show()无效。图片空白确保在plt.savefig()之前已经创建了图形并绘制了内容。并且plt.savefig()要在plt.show()之前调用因为show()在某些环境下会清空图形。使用plt.tight_layout()可以避免标签被截断。5.5 项目进阶与扩展思路这个基础项目完成后你可以从多个方向进行扩展把它变成一个更强大、更实用的工具增加城市代码自动查询目前需要手动维护城市代码字典。可以写一个函数根据输入的城市名去中国天气网或其他接口查询对应的城市代码。支持历史天气数据爬取中国天气网也提供历史天气查询。分析其历史数据页面的请求方式扩展脚本以爬取过去几个月或几年的数据用于更长期的分析。构建图形用户界面GUI使用tkinter、PyQt或streamlit库为脚本制作一个简单的桌面或Web界面让非技术用户也能方便使用。定时自动运行与邮件通知将脚本部署到服务器使用cronLinux或任务计划程序Windows定时运行如每天早晨8点并将生成的CSV和图片通过邮件发送给自己制作一个个性化的天气简报服务。数据持久化与简单分析将爬取的数据不仅存入CSV也存入SQLite或MySQL数据库。然后使用Pandas进行简单的统计分析比如计算平均温度、统计晴天/雨天的频率等并生成更丰富的图表如饼图、柱状图。增强异常处理与日志记录为网络请求、数据解析等关键步骤添加更完善的try...except块并将运行状态、错误信息写入日志文件便于后期维护和排查问题。爬虫项目的核心魅力在于“让机器自动完成重复的网页信息收集工作”。在这个过程中你对网络协议、数据结构和特定网站架构的理解会不断加深。每一次成功抓取和数据规整都是对问题解决能力的一次锻炼。希望这个详细的爬取天气预报数据的项目能成为你探索Python自动化世界的一块坚实垫脚石。如果在实现过程中遇到任何问题回顾一下“常见问题”部分或者多利用打印语句调试耐心分析网页源代码你总能找到解决方案。
返回列表