ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python天气数据爬取与可视化:从API调用到交互式图表实战

Python天气数据爬取与可视化:从API调用到交互式图表实战 简介本资源是一份面向Python初学者与课程设计实践者的天气数据爬取与可视化项目聚焦网络数据获取、清洗及图表呈现全流程适用于高校编程入门、数据分析基础课或小型课程设计作业。压缩包为单文件ZIP内含1个核心Python脚本.py体积仅3KB代码精炼涵盖requests发起API请求、BeautifulSoup解析网页、pandas结构化处理及matplotlib绘制温度趋势图等关键环节便于快速运行与理解底层逻辑。已有3270人学习下载反映出其在教学实践中的广泛认可度。读者可直接复用该脚本获取实时/历史天气数据掌握从HTTP请求到可视化输出的完整链路代码注释清晰、模块划分合理附带典型城市示例与常见异常处理逻辑特别适合作为爬虫与可视化交叉学习的轻量级范例。 最近整理资料时翻到一个之前写的项目——Python实现对天气数据爬取及可视化.zip当时是给一个做户外活动策划的朋友应急用的后来断断续续迭代了几个版本也顺手发到过一些技术社区反馈还不错。趁着周末有空把整个项目的思路、踩坑过程和核心实现完整梳理一遍方便需要的人直接参考也当是自己做一次复盘。这个项目解决什么问题呢核心就两件事一是自动抓取目标城市的天气数据包括实时温度、湿度、风向风力、空气质量等二是把这些数据用图表的方式直观展示出来。听起来挺简单但真正做下来你会发现天气数据这东西源头分散、格式五花八门、有些接口还得鉴权爬下来之后怎么清洗、怎么存储、怎么可视化每一步都有不少细节。先给项目定个位适合什么人群刚学完Python基础语法、想通过真实项目练手的入门者需要做数据可视化作业或毕设的学生运营、策划、物流等岗位需要定期关注多城市天气情况的非技术同学换句话说这个项目不需要你有很深的技术功底只要会基本的Python语法、装过第三方库照着下面的步骤走基本能跑通。但如果你希望把代码改得更健壮、扩展成真正“能用”的工具我后面的避坑经验和优化思路应该能帮到你。1. 整体设计这块到底该怎么做才靠谱1.1 需求拆解与方案选型先别急着写代码把需求拆清楚更重要。天气数据爬取及可视化看起来简单实际拆开至少有三层第一层是数据获取。这里有个分岔口是爬网页还是调接口我刚开始做的时候直接用 requests 去请求天气网站的 HTML 页面再用 BeautifulSoup 解析。后来发现这种方式又慢又脆——页面结构一改代码就废了。而且很多天气网站的页面是服务端渲染和客户端渲染混着的部分数据还是通过 AJAX 异步加载的纯靠 requests 拿不到完整内容。后面换了思路优先找公开的天气 API。这里有个筛选标准是否免费个人练手项目没必要掏钱是否支持按城市查询最好是城市名或城市 ID返回格式是否友好JSON 是首选解析成本最低是否稳定有些免费 API 一天只能调几次那种就算了我最终选了心知天气的免费版现在叫 Seniverse一天有 1000 次免费调用额度个人用途足够了。另外 OpenWeatherMap 也不错但国内访问有时候不太稳定如果你是做国内城市的数据心知更省心。关于这一点我在后面的实操部分会给出完整代码。第二层是数据存储。数据量其实很小每天每个城市也就百来条记录量级根本不需要上 MySQL 这种重型数据库。CSV 文件或者 SQLite 就够了。我在项目里是两种方案都实现了单独跑一次查询就用 CSV做长期积累就存 SQLite后面做趋势分析很方便。第三层是数据可视化。这里选择就更多了Matplotlib、Seaborn、Pyecharts、Plotly还有偏大屏风格的 Dash、Superset 之类。我最终选了 Pyecharts原因是它和 ECharts 深度绑定图表交互性好而且主题风格好看代码写起来也直观。1.2 整体架构与模块划分整个项目我分成了四个模块各管各的互不干扰weather_crawler.py负责获取数据封装了请求逻辑、重试机制、数据解析data_processor.py负责数据清洗、去重、格式转换data_storage.py负责数据落盘支持 CSV 和 SQLite 两种方式visualizer.py负责把数据画成图表支持单城市温度趋势、多城市温度对比、风力风向玫瑰图等这样拆的好处是后面替换数据源或者换可视化库的时候只改动对应模块就行不用把整个项目推倒重来。你如果是做自己的小项目也建议按这个思路拆哪怕代码量不大后期维护的幸福感完全不一样。2. 核心细节解析数据获取的几种主流方式2.1 爬虫方案 vs API方案先说结论能用 API 就别用爬虫。但这不代表爬虫方案没价值很多场景下你确实找不到合适的 API那爬网页就成了唯一出路。用 requests 爬静态页面时有个容易忽视的问题请求头。默认的User-Agent是python-requests/2.x.x服务器一眼就能识别出是脚本在访问很容易被拦。我一般都会带上完整的浏览器请求头包括User-Agent、Accept-Language、Referer等。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;q0.9, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8 } resp requests.get(https://example-weather-site.com/city/101010100, headersheaders, timeout10) print(resp.status_code)拿到 HTML 之后用 BeautifulSoup 或者 lxml 提取目标字段。这里有个关键点一定要把解析逻辑和数据源解耦。我见过很多人把 CSS 选择器硬编码在业务代码里结果网站改版后整个程序报废还得从头翻 HTML 结构。正确的做法是先把有用的节点数据抽出来转成 JSON 或字典再往下传递。2.2 API 调用的基础封装如果走 API 方案代码会清爽很多。我用的是心知天气调用方式类似下面这样import requests class WeatherAPI: def __init__(self, api_key: str): self.api_key api_key self.base_url https://api.seniverse.com/v3/weather/now.json def get_current_weather(self, city: str) - dict: params { key: self.api_key, location: city, language: zh-Hans, unit: c } try: resp requests.get(self.base_url, paramsparams, timeout8) resp.raise_for_status() data resp.json() return data[results][0] except requests.exceptions.Timeout: print(f请求超时: {city}) return {} except (KeyError, IndexError, ValueError) as e: print(f解析失败: {city}, 错误: {e}) return {}注意几个设计细节timeout必须设置。不设的话如果网络抖动脚本可能卡住几分钟甚至更久。返回值中的results是个列表正常情况只有一个元素但代码里还是做了防御性判断防止异常数据结构导致程序崩溃。返回空字典而不是直接抛出异常是为了让主流程能继续跑下去不至于因为一个城市的数据失败就整个任务中断。2.3 多城市批量抓取与并发优化项目里需要同时抓取多个城市的数据最笨的办法就是 for 循环一个个请求。但如果城市数量上了两位数串行请求的耗时就会变得很难受。我后来改用concurrent.futures.ThreadPoolExecutor做并发请求速度提升非常明显。from concurrent.futures import ThreadPoolExecutor, as_completed def fetch_all_cities(city_list: list[str]) - dict: results {} with ThreadPoolExecutor(max_workers5) as executor: future_map {executor.submit(api.get_current_weather, city): city for city in city_list} for future in as_completed(future_map): city future_map[future] try: data future.result() if data: results[city] data except Exception as e: print(f获取 {city} 数据失败: {e}) return results线程数控制在 5 左右比较合适不是越多越好。很多免费 API 有 QPS 限制开 20 个线程并发请求大概率会被限流甚至封禁。如果你的 API 没写 QPS 限制我建议你手动加一个别把源站打挂了这是爬虫和数据采集的基本素养。3. 实操全过程数据采集 清洗 存储 可视化3.1 数据采集完整实现为了让你拿到就能跑我直接把完整的爬虫代码贴出来基于心知天气 API不用注册也能理解整个流程实际使用时需要替换为自己的 API Key。import json import time import csv from pathlib import Path import requests from dataclasses import dataclass, asdict from typing import Optional dataclass class WeatherData: city: str temperature: float feels_like: float humidity: int wind_direction: str wind_scale: str last_update: str class WeatherCollector: def __init__(self, api_key: str): self.api_key api_key self.base_url https://api.seniverse.com/v3/weather/now.json def fetch(self, city: str) - Optional[WeatherData]: params { key: self.api_key, location: city, language: zh-Hans, unit: c } try: resp requests.get(self.base_url, paramsparams, timeout8) resp.raise_for_status() result resp.json()[results][0] now result[now] return WeatherData( cityresult[location][name], temperaturefloat(now[temperature]), feels_likefloat(now[feels_like]), humidityint(now[humidity]), wind_directionnow[wind_direction], wind_scalenow[wind_scale], last_updateresult[last_update] ) except Exception as e: print(f[{city}] 请求失败: {e}) return None def batch_fetch(self, cities: list[str], delay: float 0.5) - list[WeatherData]: results [] for city in cities: data self.fetch(city) if data: results.append(data) time.sleep(delay) # 控制请求频率对免费接口友好一点 return results这个类做了几件重要的事情用dataclass定义数据结构代码清晰序列化也方便请求失败时返回None而不是抛异常批量抓取时某个城市挂掉不影响其他城市每次请求之间加了delay君子协议避免给源站造成压力3.2 数据清洗和数据存储爬下来的数据不能直接用还得清洗。常见的问题包括温度字段里混入单位字符比如12℃需要去掉单位再转 float湿度是字符串形式的56%需要去掉百分号转 int时间字段的时区问题心知天气返回的是带时区的 ISO 格式按需转成北京时间重复数据去重——如果你一天跑 10 次脚本数据会重复记录需要按城市名加时间戳做去重清洗逻辑我放到data_processor.py里核心代码如下import pandas as pd from pathlib import Path def clean_weather_data(raw_data: list[dict]) - pd.DataFrame: df pd.DataFrame(raw_data) if df.empty: return df # 去重 df df.drop_duplicates(subset[city, last_update], keeplast) # 类型转换 df[temperature] pd.to_numeric(df[temperature], errorscoerce) df[humidity] pd.to_numeric(df[humidity], errorscoerce) # 时间字段标准化 df[last_update] pd.to_datetime(df[last_update], errorscoerce) # 删除异常记录 df df.dropna(subset[temperature]) df df[df[temperature].between(-30, 50)] # 合理的温度范围 return df然后存 CSV 和 SQLiteimport sqlite3 import pandas as pd def save_to_csv(df: pd.DataFrame, filepath: str): filepath Path(filepath) filepath.parent.mkdir(parentsTrue, exist_okTrue) df.to_csv(filepath, indexFalse, encodingutf-8-sig) print(f数据已保存至 {filepath}) def save_to_sqlite(df: pd.DataFrame, db_path: str, table_name: str weather): conn sqlite3.connect(db_path) df.to_sql(table_name, conn, if_existsappend, indexFalse) conn.close()这里有个小技巧存 CSV 时编码用utf-8-sig而不是utf-8否则用 Excel 打开中文会乱码。这个问题很多人踩过包括我自己第一次存完用 Excel 打开全是乱码折腾半天才发现是编码问题。3.3 数据可视化的两种层次可视化这块我推荐两个层次的做法。第一层是探索性可视化适合自己看数据、快速验证结果。用 Matplotlib 画折线图几行代码就能出图import matplotlib.pyplot as plt import pandas as pd from matplotlib import rcParams rcParams[font.sans-serif] [SimHei, Microsoft YaHei] # 解决中文乱码 rcParams[axes.unicode_minus] False # 解决负号显示问题 def plot_temperature_trend(df: pd.DataFrame, city: str): city_df df[df[city] city].sort_values(last_update) plt.figure(figsize(10, 5)) plt.plot(city_df[last_update], city_df[temperature], markero, labelf{city}温度) plt.xlabel(时间) plt.ylabel(温度 (°C)) plt.title(f{city} 温度变化趋势) plt.legend() plt.grid(True, alpha0.3) plt.tight_layout() plt.savefig(f{city}_temperature_trend.png, dpi150) plt.show()第二层是展示型可视化适合做汇报、看板、作业展示。用 Pyecharts 画交互式图表from pyecharts.charts import Bar, Line from pyecharts import options as opts import pandas as pd def plot_multi_city_temperature(df: pd.DataFrame): cities df[city].unique().tolist() temps [] for city in cities: city_df df[df[city] city] temps.append(round(city_df[temperature].mean(), 1)) bar ( Bar() .add_xaxis(cities) .add_yaxis(平均温度 (°C), temps) .set_global_opts( title_optsopts.TitleOpts(title多城市平均温度对比), yaxis_optsopts.AxisOpts(name温度), xaxis_optsopts.AxisOpts(name城市), toolbox_optsopts.ToolboxOpts(), ) ) bar.render(multi_city_temperature.html)Matplotlib 出图是静态图片适合直接贴在文档里Pyecharts 出的是 HTML 文件浏览器打开后可以鼠标悬停看数据、缩放交互体验完全不在一个层级。我的建议是自查数据用 Matplotlib交付展示用 Pyecharts。如果想走更炫酷的路线可以看看 Pyecharts 的地图组件画中国地图按省份着色展示各省省会城市温度分布。这个做出来视觉效果很好但注意需要下载中国地图的 GeoJSON 数据Pyecharts 内置的有时候不太全。4. 常见问题与排查技巧实录4.1 常见报错及解决方案我把实际运行中遇到的高频问题整理成了一张表方便你对着排查问题现象可能原因解决方案请求超时网络不稳定或 API 响应慢增加重试机制用tenacity库装饰器或自己写循环JSONDecodeError接口返回的不是 JSON可能是 502 或验证码页打印返回前 200 字定位问题确认请求头是否完整中文字体乱码系统缺少中文字体Matplotlib 设置SimHei或安装中文字体到系统数据重复多次执行脚本未去重先按时间戳去重再写入SQLite 建唯一索引市名解析失败API 不支持该城市名/城市名有歧义确认城市行政区划代码用城市 ID 查询更准确权限 401/403API Key 无效或过期检查 Key 是否有空字符确认额度是否用完图表数据为 0清洗时把数据都过滤掉了打印清洗前后行数检查过滤条件是否过于严格本地无法显示 HTML 图表文件路径有中文或浏览器限制把渲染后的 HTML 放到纯英文路径下打开4.2 我踩过的坑提前帮你避开第一个坑API Key 泄露。老版本代码把 Key 硬编码在文件里有一次传给朋友后忘记提醒结果被晒在代码仓库里。第二天就收到邮件说接口被刷爆额度瞬间用完。后来我改成从环境变量读取import os API_KEY os.getenv(SENIVERSE_API_KEY, ) if not API_KEY: raise ValueError(请先设置 SENIVERSE_API_KEY 环境变量)个人项目这样做可能有点小题大做但你如果打算开源或分享代码建议从一开始就养成好习惯。第二个坑时区问题。有一次本地跑得好好的部署到服务器后发现所有数据的last_update都是 UTC 时间画出来的图和北京时间差 8 小时第一眼看起来很别扭。解决方案是在清洗阶段强制转换为北京时间df[last_update] pd.to_datetime(df[last_update], utcTrue) df[last_update] df[last_update].dt.tz_convert(Asia/Shanghai)第三个坑时间戳陷阱。做数据采集时很多人喜欢用datetime.now()来记录时间。但因为采集脚本是串行跑的抓 10 个城市的数据可能需要 20 秒而这 20 秒内datetime.now()每次调用都是不同时刻。如果你拿这个时间戳作为数据唯一标识同一轮采集的记录会得出不同的时间戳后续去重逻辑就全乱了。解决办法用轮次 ID 加上固定的采集时间戳run_timestamp datetime.now().isoformat() for city in cities: save_record(city, run_timestamp, ...)4.3 定期自动执行的思路项目做完之后如果每次都要手动运行脚本体验会打折扣。你可以用系统自带的定时任务来做Windows 下用任务计划程序Mac/Linux 下用 crontab# 每天早上 8 点执行一次 0 8 * * * cd /path/to/project /usr/bin/python3 main.py weather_crawler.log 21这里有几个健壮性设计值得留意脚本内部增加try/except异常时把错误信息写入日志文件而不是屏幕输出方便事后排查日志加上日期方便按天归档重要步骤打一句日志比如“开始抓取 10 个城市”“清洗完成 89 条记录”“可视化图表已生成”这样定时任务挂了也能快速定位在哪一步挂的5. 进阶优化从“能跑”到“好用”5.1 接入 Redis 做缓存项目跑了一段时间后我发现一个痛点频繁调用 API 查询同一个城市的数据很多数据在短时间内根本没变化纯属浪费接口额度。后来我引入了 Redis 做缓存缓存时长 30 分钟命中缓存就直接返回不再请求外部接口。import redis import json r redis.Redis(hostlocalhost, port6379, db0, decode_responsesTrue) def get_weather_with_cache(city: str, collector: WeatherCollector, cache_ttl: int 1800) - dict: cache_key fweather:{city} cached r.get(cache_key) if cached: return json.loads(cached) data collector.fetch(city) if data: r.setex(cache_key, cache_ttl, json.dumps(asdict(data), ensure_asciiFalse)) return data这样改造之后接口调用量直接降了一个数量级。原来一天 1000 次的额度可能半天用完现在一天 100 次出头就够覆盖 10 个城市的日常监控了。在“热点词汇”里看到有“redis可视化工具”“redis可视化管理工具”这些词说明你也可能在做 Redis 相关的开发调试。这里给你推荐两个顺手的小工具RedisInsight官方出品功能全但偏重Another Redis Desktop Manager国产开源轻量简洁。日常调试缓存数据第二个就够用了。5.2 可视化大屏的思路延伸项目基础功能做完后如果想让效果更吸睛可以往数据大屏方向延展。思路是多城市天气数据半小时刷新一次结合 Pyecharts 生成多个 HTML 图块再用 iframe 拼装成一个总览大屏。每块图对应一个城市或一类指标温度、湿度、风力自动刷新。这里有一个技术点Pyecharts 生成的 HTML 页面本身不会自动刷新数据你需要用 JavaScript 定时器或者让后端接口配合。我的做法是写一个简单的 Flask 服务提供 JSON 接口返回最新数据前端页面用 ECharts 接收数据并定时刷新。这样就把“爬虫采集”和“可视化展示”彻底解耦了爬虫只负责把数据写进数据库前端负责把数据读出来画成图。代码结构大致是这样weather-server/ ├── app.py # Flask 服务 ├── templates/ │ └── dashboard.html # 大屏页面 ├── static/ │ └── echarts.min.js ├── data/ │ └── weather.db # SQLite 数据 └── crawler/ ├── collector.py # 爬虫采集 └── processor.py # 清洗存储不过这个属于进阶玩法如果你只是练手先把基础版本的爬虫和可视化做扎实比直接上大屏更有价值。5.3 项目后续可扩展的几个方向当你把整个流程跑通之后可以在这个基础上做很多延伸加入预报数据除了实时天气加上未来 7 天预报做趋势预测的数据积累加入历史对比把去年同期的温度拉出来对比看今年是偏暖还是偏冷用机器学习做简单预测基于历史温度数据用线性回归或时间序列模型预测未来几天的温度走势接入微信通知每天早上定时把当天天气推送到微信这就是一个很实用的个人助理了6. 避坑清单这十个问题我建议你提前知道写代码只是整个项目的一小部分运行、维护、迭代才是大头。以下几个问题是我在多次实践中总结出来的建议你提前做好心理准备第一数据源的不稳定是常态。免费 API 可能随时调整策略甚至停止服务。项目代码里至少要有两套数据获取方式一套 primary 一套 backup主挂了切备不用临时抱佛脚。第二不要把 API Key 写死在代码里。不管是传到 GitHub 还是发给同事Key 一旦泄露你辛苦攒的接口额度分分钟被刷完。用环境变量或者配置文件管理密钥在代码里只留引用。第三爬虫讲究频率克制。即使目标网站没有明确限制也要控制请求频率。为了一点点数据把别人服务器打挂既不道德也容易被封 IP。批量抓取时加延时并发数保持在小范围。第四数据结构永远是核心。天气数据看起来简单但不同来源的字段命名、单位、时区都可能不一样。建议第一步就把数据结构定好后面所有模块都围绕这个数据结构展开。第五可视化不是为了好看而好看。图表的类型选择要适配数据本身和分析目的看趋势用折线做对比用柱状看构成用饼图看分布用箱线图。天气数据最常见的需求就是趋势和对比其他花活优先级没那么高。第六自动化不等于一键执行。定时任务虽然省心但脚本跑挂了没人发现更可怕。重要的采集任务一定要有告警机制哪怕是简单的日志检测加上邮件通知也比什么都没做强。第七数据库备份是底线。SQLite 虽然轻量但也是你的数据资产。定期把 DB 文件复制一份到其他目录或者用 git 管理数据文件的版本成本极低收益可观。第八中文字体问题在 Linux 服务器上尤其需要注意。Windows 下默认有微软雅黑但 Linux 服务器经常没有画图时中文字体会变成方框。建议在代码里做一次字体检测缺失时自动切换。第九脚本运行环境要固定。不同版本的 Python 和第三方库可能存在兼容性问题建议用 requirements.txt 锁定版本pip freeze requirements.txt换环境时先装好 requirements.txt 再跑代码可以省去一半的“我这报错你那不报错”的麻烦。第十留好接口别把代码写死。今天你查的是天气明天可能就要查航班、查股票、查菜价。把采集、清洗、存储、可视化四个环节写成独立函数后面接新数据源只需要写一个新的 collector 就行其他模块几乎零改动。我在实际使用中发现用 Python 做数据采集和可视化真正值钱的往往不是代码本身而是你对数据源的理解、对异常情况的处理、对长期运行稳定性的考量。这份项目能帮你把基础链路跑通但更进一步提升的空间在你后续迭代的每一步里。最后再分享一个小技巧项目里可以加一个run.py作为统一入口把“采集—清洗—存储—可视化”四个步骤串起来每次更新数据只需要一行命令python run.py --cities 北京,上海,广州,深圳 --visual all把这个脚本加到定时任务里你就可以得到一个完全自动化的多城市天气追踪系统。后期如果想做成 Web 服务给别人用也只是在这个入口再加一层 API 封装的事。希望这个项目的拆解对你有帮助欢迎在实际运行中遇到问题时对照前面的排查清单快速定位。做完之后你会发现Python 的爬虫和可视化真的不难难的永远是“能不能持续稳定地跑下去”这件事。本文还有配套的精品资源点击获取
返回列表