基于Python爬虫的天气数据获取与可视化分析项目实战
简介一份基于Python的天气数据爬取与可视化分析完整源码面向计算机专业学生及爬虫、数据可视化入门者适用于课程设计、期末大作业或实训项目参考。项目围绕“城市天气数据”设计采集、清洗、分析、展示一体化流程采用Scrapy爬虫框架与自实现请求解析模块获取天气信息结合Spark完成数据清洗与统计分析后端使用Python构建接口服务前端基于Vue搭建可视化页面形成从爬取到展示的完整数据闭环。压缩包内含九十文件其中四十余个py脚本承担爬虫、数据处理与后端逻辑vue与js文件组成前端可视化界面另有JSON、CSS、Markdown等辅助配置与说明文档整体不足一兆结构清晰便于阅读。当前已有三千余人学习浏览该作品为高分设计代码注释详细既适合初学者对照学习也可在原有基础上扩展更多城市或气象指标是综合性大作业的可复用范例。 先聊点实在的。如果你是正在为“python大作业”发愁的学生或者想用一个小而完整的项目把 Python 爬虫、数据处理、可视化这一条链路串起来那这个基于 Python 实现网络爬虫爬取天气数据并做可视化分析的题目绝对是性价比极高的选择。它不碰复杂的分布式、不涉及高并发核心就是 requests 拿数据、pandas 清洗、matplotlib/pyecharts 画图一套标准流程走下来既能展示你的工程能力又能让答辩老师一眼看出你掌握了 Python 数据分析的主流工具链。这篇博文我会从项目拆解、爬虫设计、可视化实现到常见坑位排查完整梳理一遍这个“高分项目”到底该怎么做以及为什么这样做。1. 项目整体设计与技术选型1.1 项目到底在做什么一句话版本写一个爬虫程序从免费的天气数据源获取指定城市的历史天气或实时天气存成结构化数据再做可视化展示。听起来很简单但这类题目想拿高分关键不在于“爬”而在于整个流程是不是完整、规范。很多同学交上来的作业是一坨脚本打开网页、正则匹配、打印到控制台、画个折线图完事。这只能算 demo不能算项目。一个能拿高分的版本至少需要具备四个模块数据采集层负责发送请求、处理响应、解析数据数据存储层把解析后的数据写入 CSV 或数据库保证数据可复用数据处理层清洗缺失值、统一日期格式、构造分析字段可视化层基于处理后的数据生成趋势图、分布图、对比图。我见过不少把爬虫和可视化写在一个文件里的作业代码一长串又臭又长。你换个思路把功能拆成spider.py、analysis.py、visualize.py再配一个main.py串联入口结构立刻清晰了。对评判的人来说“结构清晰”本身就是加分项。1.2 技术选型背后的考量这个项目我建议的技术栈是requestspandasmatplotlibpyechartscsv。先说requests。它足够轻量代码直白适合新手理解 HTTP 请求的过程。虽然scrapy更工业级但一个大作业用 scrapy 有点杀鸡用牛刀而且框架封装了太多细节你答辩时反而说不清楚底层原理。urllib虽然内置但写起来繁琐处理 headers、编码都要手动折腾没必要自己找罪受。pandas是为了让数据处理变得可维护。天气数据里经常会有空值、格式不一致的问题用纯 Python 列表处理这些小毛病会写一堆循环而 pandas 的dropna()、to_datetime()、astype()几行就能搞定。可视化层面matplotlib适合画静态图风格偏学术生成的是比较朴实的折线图、柱状图pyecharts则是交互式图表生成 HTML 页面鼠标悬停能看数值视觉效果更炫。我的建议是两个都写上在 Jupyter 里用 matplotlib 快速验证数据最后输出一个 pyecharts 的交互页面作为成果展示。这样既有深度又有展示效果是一道“双保险”。为什么用免费 API 而不是直接解析网页 HTML这个问题必须想清楚。爬取网页 HTML 会频繁遭遇反爬限制、页面结构调整、动态加载等问题对于课程项目来说维护成本高且不稳定。而国内很多天气网站提供了免费的 JSON API返回的是结构化数据解析非常方便。用 API 不算“作弊”因为核心的请求、参数构造、数据解析、异常处理逻辑依然在只是数据源更友好。当然我不会直接让你去用那些动不动就要注册 token 的商业接口后面会给出一个实测可用的数据源和请求写法。2. 爬虫模块从请求到数据的完整链路2.1 数据源分析与请求构造这个项目我实测过几个免费的天气数据源稳定性最好的是通过公开的 HTTP 接口返回 JSON 数据。它的 URL 规律大概是这样的import requests import json def fetch_weather(city_code, date): url fhttps://tianqiapi.com/api?versionv6appidxxxappsecretxxxcity{city_code} resp requests.get(url, timeout10) return resp.json()先别急着复制这个接口需要注册 appid而且请求次数有限。换成完全不需要注册的方案可以直接用一些开源项目维护的免 key 接口或者退而求其次选择爬取静态页面。我实际项目里用的是通过tianqi.com的静态页面配合正则解析页面结构稳定时效果很好。更稳妥的方案是使用wttr.in这个开源天气服务支持输出 JSON 格式指定城市、指定日期完全免费无需 keyimport requests def fetch_weather_by_city(city): url fhttps://wttr.in/{city}?formatj1 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } resp requests.get(url, headersheaders, timeout10) if resp.status_code 200: return resp.json() return Noneformatj1是它的 JSON 输出模式返回的数据结构里包含当前温度、体感温度、湿度、风速、天气描述还有未来几天的预报数据层级清晰。这里说明一下为什么j1这个参数很重要——不指定它默认返回是终端风格的 ANSI 文本解析起来非常难受指定j1后整个返回值变成了标准的嵌套 JSON后续操作空间就大多了。代码里我特意加上了headers是因为就算是不需要登录的接口也会对明显的爬虫 UA 做拦截。加上一个浏览器的 User-Agent是把“我可能是个正常人”的信号给到对方服务器。这是一种基本的请求礼仪也提前规避了反爬问题。2.2 数据解析与字段抽取拿到 JSON 之后需要把里面的天气数据抽成整齐的表格结构。这一步我会用 pandas 做清洗顺便处理缺字段的情况import pandas as pd def parse_weather_data(data): if not data: return pd.DataFrame() current data.get(current_condition, [])[0] weather_desc current.get(weatherDesc, [])[0].get(value, ) temp current.get(temp_C, ) humidity current.get(humidity, ) wind_speed current.get(windspeedKmph, ) records [] for day in data.get(weather, []): date day.get(date, ) maxtemp day.get(maxtempC, ) mintemp day.get(mintempC, ) for hour in day.get(hourly, []): records.append({ date: date, hour: hour.get(time, ), temp: hour.get(tempC, ), humidity: hour.get(humidity, ), weather_desc: hour.get(weatherDesc, [{}])[0].get(value, ), }) df pd.DataFrame(records) return df这段代码的阅读逻辑很直接先取当前天气再循环每一天的逐小时预报把日期、小时、温度、湿度、天气现象全部拉平成一个记录列表最后转成 DataFrame。一个容易踩的地方是weatherDesc这个字段它是一个列表里面套着字典直接索引[0][value]在字段缺失时会报IndexError。所以我用了[{}]作为默认值让它至少不会崩。这类“防御性写法”在实际爬虫中特别重要因为你永远不知道上游数据哪一天会少个字段。2.3 存储与增量策略解析出来的 DataFrame 要落盘保存这里我建议直接用 CSV轻量、可读性好、Excel 能直接打开。做作业不需要上 SQLite但如果你想在答辩中显得更专业可以用 pandas 直接写 SQLite 也行差别不大。df.to_csv(./weather_data.csv, indexFalse, encodingutf-8-sig)注意这里编码用了utf-8-sig而不是默认的utf-8。原因是如果用 Excel 打开生成的 CSVutf-8带 BOM 的写法打开不会乱码。这个细节很小但展示成果时用 Excel 打开表格格子整整齐齐观感就比乱码强多了。如果你要爬多天的数据再汇总分析可以在每次请求前先查看已经保存的数据范围只请求缺失的日期避免重复请求浪费请求次数。这个“增量更新”的思路在项目答辩里提出来是很加分的。3. 数据可视化如何让图表自己会说话3.1 图表设计不是画完就行很多同学的可视化作业就是把温度画了个折线图完事。这样只能算完成基本功能谈不上分析。想一想这道题的评分点在哪里爬虫是手段可视化是表达而“分析”才是目标。如果你能通过图表讲出几个结论比如“昼夜温差在城市 A 比城市 B 大”“连续一周湿度偏高体感闷热”那这个项目就从工具型上升到了分析型。所以我建议至少产出四张图近 24 小时温度变化折线图看日内波动一周最高/最低温度对比柱状图看趋势和温差湿度分布直方图看湿度集中在哪个区间不同城市天气情况对比雷达图或多城市温度曲线如果有多个城市的数据3.2 pyecharts 出交互页面的关键写法pyecharts 的语法和 matplotlib 很不一样它是链式调用每个图表类型都有一个对应的类。一个温度的折线图可以这样写from pyecharts.charts import Line from pyecharts import options as opts line ( Line() .add_xaxis(hours) .add_yaxis(温度, temps, is_smoothTrue) .set_global_opts( title_optsopts.TitleOpts(title24小时温度变化), yaxis_optsopts.AxisOpts(name温度(°C)), tooltip_optsopts.TooltipOpts(triggeraxis) ) ) line.render(temperature_line.html)这个代码里有两个容易被忽略的地方。第一个是is_smoothTrue加了之后折线会变成平滑曲线视觉效果更专业。第二个是triggeraxis它让鼠标悬停时能同时展示横轴上所有系列的值对比起来很方便。pyecharts 默认输出是独立的 HTML 文件双击就能打开不需要起服务对作业展示很友好。如果你的 project 里希望嵌进 Flask 网页pyecharts 也提供了render_embed()的方法可以把图表脚本直接嵌入模板。不过大作业阶段输出独立 HTML 就足够了。3.3 matplotlib 画图的注意事项如果只用 pyecharts答辩时老师可能会问“你有没有用过其他可视化库”。所以建议在数据处理阶段也顺手用 matplotlib 画一张检查图。matplotlib 的核心逻辑是面向对象的先建画布再填充内容import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False fig, ax plt.subplots(figsize(10, 5)) ax.plot(hours, temps, markero, color#2E86AB, linewidth2) ax.fill_between(hours, temps, alpha0.2, color#2E86AB) ax.set_xlabel(时间) ax.set_ylabel(温度°C) ax.set_title(24小时温度变化曲线) plt.tight_layout() plt.savefig(temperature.png, dpi150) plt.show()这里有一个中国用户必踩的坑matplotlib 默认是不支持中文显示的如果不设置font.sans-serif图上的中文会全部变成小方块。axes.unicode_minus则是修正负号显示为方块的问题。这两行设置建议写在所有画图代码的最前面。用fill_between给折线图加上半透明填充区域在视觉上会让数据分布显得更有层次这是很简单的“看起来专业”的小技巧。4. 源码结构设计与项目答辩思路4.1 代码目录怎么组织一个高分项目源码组织必须清晰。我会按下面的结构来建目录weather_project/ ├── main.py # 主入口串联爬虫与可视化 ├── spider.py # 爬虫模块 ├── analysis.py # 数据处理与分析模块 ├── visualize.py # 可视化模块 ├── requirements.txt # 依赖清单 ├── README.md # 项目说明 ├── data/ │ └── weather_data.csv # 爬虫生成的数据 └── output/ ├── temperature.html # 可视化结果 └── temperature.pngmain.py不要写太多逻辑它的作用就是按顺序调用各部分像一份工程蓝图from spider import fetch_weather_by_city, parse_weather_data from analysis import clean_data, compute_summary from visualize import plot_temperature_curve, plot_week_comparison if __name__ __main__: city Beijing raw_data fetch_weather_by_city(city) df parse_weather_data(raw_data) clean_df clean_data(df) compute_summary(clean_df) plot_temperature_curve(clean_df) plot_week_comparison(clean_df)写代码时每个函数只做一件事。fetch_weather_by_city只负责发请求parse_weather_data只负责解析 JSON画图的只画图。这种职责单一的设计答辩时你完全可以说“我参考了模块化设计思想”这句话比任何花哨功能都值钱。requirements.txt也很重要写清楚版本号让环境在别人机器上能一键复现requests2.31.0 pandas2.2.2 matplotlib3.8.4 pyecharts2.0.54.2 README 里面写什么README 是很多学生的盲区但这恰恰是老师区分“花了心思”和“应付交差”的地方。一个好的 README 至少包括项目简介这个项目解决什么问题包含哪些功能环境要求Python 版本、依赖库运行步骤从安装依赖到最终产出图表的完整命令结果展示放几张运行截图或图表截图目录结构说明每个文件负责什么能把这个文件写清楚说明你做的事情是经得起别人复现的这也是工程素养的体现。很多老师给高分并不是因为代码写得多高级而是因为你把一个完整的小项目讲明白了。4.3 答辩里的亮点话术项目的完成度决定你分数下限而讲解亮点决定分数上限。以下几点是你可以着重展开的第一说明你是如何应对反爬的。哪怕你只是加了一个 User-Agent也可以说“我通过分析请求头发现服务器会校验客户端标识因此设置了浏览器的 User-Agent 字段后续还可通过重试机制和代理池增强稳定性”。这种说法展示了你的安全意识。第二说明数据清洗的必要性。对比清洗前后的数据质量例如原始格式里时间是 24 小时制字符串你转成了日期类型方便做时序分析。用具体的例子证明你不是盲目调库。第三说明你如何验证结果。比如你画完图表发现某一天的凌晨温度异常偏高你会回溯检查原始 JSON发现那个时间点的数据本身有误于是做了异常值的剔除。这种“发现问题、定位问题、解决问题”的逻辑链条是最能给答辩老师留下印象的。5. 常见问题与排错实录5.1 爬虫阶段的典型问题问题现象可能原因解决办法请求超时网络波动或目标服务器响应慢在requests.get()中设置timeout10并捕获requests.exceptions.Timeout异常返回 403缺少请求头或触发反爬添加 User-Agent、Referer降低请求频率必要时加time.sleep()间隔JSON 解析报错接口返回的不是 JSON 而是 HTML 或空内容先打印响应前 500 个字符确认返回格式再决定解析方式CSV 打开中文乱码保存时用了utf-8改用encodingutf-8-sig或使用gbk编码字段为空API 单次请求次数超限检查免费接口的速率限制拆成多次请求或改用缓存数据前两个问题是每个爬虫新手都会遇到的。我在写这个项目时第一次请求wttr.in时也遇到过一次 500原因是那段时间该服务有点不稳定。我当时就加了一个重试装饰器最多重试 3 次每次间隔 2 秒代码立刻稳定了很多。下面的函数可以当作通用工具函数直接抄import time from functools import wraps def retry(max_retries3, delay2): def decorator(func): wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt max_retries - 1: raise e time.sleep(delay) return wrapper return decorator这个函数的思路是如果请求失败等delay秒后重试最多重试max_retries次。注意区分“该放弃”和“重试有可能成功”的异常——KeyError重试一万次也没用说明是你代码逻辑或数据结构问题而超时、连接错误这类异常才值得重试。5.2 数据清洗阶段的几个坑数据清洗看似简单实际上最容易出低级错误。我遇到过的总结成三条第一时间字段的格式混乱。wttr.in返回的小时字段是 “300”、“600” 这种格式原意是 “03:00”、“06:00”但它是按字符串存的。直接拿去画图的横轴排序就乱了。所以要先做一次格式转换df[hour] df[hour].str.pad(width4, fillchar0) df[datetime] pd.to_datetime(df[date] df[hour], format%Y-%m-%d %H%M)str.pad(width4, fillchar0)的作用是把 “300” 补成 “0300”这样拼接出来的字符串才能被pd.to_datetime正确解析。第二异常值处理要谨慎。天气数据里偶尔会出现温度突然变成 999 这种明显错误的数值。最简单的处理方法就是设一个业务合理范围超出范围的直接替换为NaN再用前后值填充df.loc[df[temp] 60, temp] pd.NA df[temp] df[temp].interpolate()interpolate()是线性插值用前后两个有效值取中间值补齐缺口在温度这种连续变化的数据里效果很好。第三重复数据去重。如果你不小心重复跑了几次爬虫CSV 里会有重复行画图时会出现锯齿状折线。处理方式很简单按日期和时间去重保留最后一条df df.drop_duplicates(subset[date, hour], keeplast)这条命令虽然简单但防止了很多后续问题可以在爬虫存储前就做一次。5.3 可视化的显示问题可视化最大坑是中文乱码前面提到了解决方案。另一个常见问题是 pyecharts 图在 Jupyter 里不显示。这通常是因为没有先调用render_notebook()。不过我的建议是直接用render()输出 HTML在浏览器里看这比在笔记本里折腾显示配置更直接。还有一个细节是图片尺寸。用 matplotlib 保存图片时如果figsize不设默认是 6.4x4.8 英寸放在文档里有点小气。我习惯设成figsize(12, 6)再配合dpi150保存在论文或 PPT 里放大也不会糊。6. 扩展思路这个项目还能怎么玩如果你做完了基础版想冲刺更高分或者想让这个项目成为以后简历上的一个亮点还有几个很自然的扩展方向。第一个方向是多城市对比。现在只爬了一个城市你可以把城市名改成列表循环爬取 5~10 个城市的数据然后画一张多城市温度对比图。分析不同城市同一时间段的气候差异。这个扩展做起来很简单但对数据的组织能力有要求适合展示你对 pandas 分组操作的掌握。第二个方向是加上地理信息。用pyecharts的中国地图组件把城市天气映射到地图上用视觉颜色深浅表示温度高低。这套方案做出来效果非常震撼几乎就是大作业的“天花板”级别。代码量也不多Map类的用法和Line大同小异。第三个方向是做一个简单的预测模型。基于最近 30 天的温度数据用线性回归预测未来一周温度变化趋势。虽然预测精度一般但思路值得展示——它证明你不只是会调用现成的库而是能把机器学习的方法和爬虫项目结合起来。这种跨界思维在答辩和简历里都非常加分。第四个方向是做一个 Flask 网站。爬虫和可视化都是后台准备好数据然后用 Flask 渲染一个网页提供一个下拉选择城市、选择日期范围、前台展示图表的功能。这样项目就从“脚本”升级成了“系统”系统性表述的直接支撑。如果你有时间这个扩展是最推荐做的因为大作业的技术难度往往不是峰值难度而是工程完整度。我当年做这个项目的时候就是先从单城市爬虫开始然后突然发现“只画温度折线图”太单薄了就把湿度、风速、天气现象全加进来了后来干脆加了一个城市对比的页面。慢慢演进的过程反而比一开始就规划好全功能要扎实得多因为每一个新功能都基于对已有代码的真正理解。如果你现在正拿这个题目做大作业我的建议是先把最基础的单城市爬虫 CSV 存储 温度折线图画通然后再按难度逐个叠加功能。等所有功能都跑通之后再回头整理代码结构和 README这个过程你会慢慢发现代码整洁给项目带来的“安全感”是实实在在的它让你敢改代码、敢加功能而不怕改一处崩三处。踩过几次坑之后我现在做任何爬虫项目都会遵循几个习惯所有请求必有超时和重试所有解析都做防御性处理所有数据落盘都用utf-8-sig编码所有可视化代码开头先设中文字体。这些习惯看起来琐碎但省下的时间远超写它们的成本。希望这篇拆解能让你少走点弯路把这个大作业做成一个真正能拿出手的作品。本文还有配套的精品资源点击获取