☰
Hadoop+Spark+Hive实战:气象数据预测与可视化系统全解析
2026/10/21 22:09:58 网站建设 项目流程

前阵子帮一个做气象数据方向的朋友梳理了一套大数据分析项目,技术栈正好就是标题里这套组合:Hadoop、Spark、Hive,配合爬虫抓气象数据,最后用线性回归做天气预测,再做可视化展示。整个系统串下来,覆盖了数据采集、存储、清洗、建模、展示的全链路,很适合拿来练手,也是面试时能讲清楚的好素材。这篇文章就把我从零搭建这套环境、写爬虫、建模到出图的全过程,以及中间踩过的坑,完整记录下来。

先说清楚这套系统能干什么:定时抓取城市历史气象数据,落地到HDFS,通过Hive做数据仓库管理,用Spark做特征工程和线性回归训练,对未来的温度、湿度等气象指标做预测,最后把预测结果和历史趋势通过可视化面板展示出来。适合正在做大数据课程设计、准备大数据岗位面试、或者想完整跑通一套数据项目的同学参考。

1. 项目概览与技术选型思路

1.1 一套系统里的三驾马车:Hadoop、Spark、Hive各担什么角色

说实话,第一次接触Hadoop、Spark、Hive这套组合的人,很容易被绕晕。这三兄弟在系统里各有明确分工,想清楚数据流就全通了。

Hadoop是底座,核心是HDFS分布式文件系统和YARN资源调度。气象数据爬下来之后,原始文件统一丢到HDFS里,不管是一天一个文件还是一小时一个文件,HDFS都能稳稳接住。YARN管的是计算资源的分配,后面Spark任务跑在YARN上,内存和CPU怎么分、分给谁,都由YARN说了算。

Hive解决的是“怎么查数据”的问题。原始数据是结构化文本或CSV,Hive把它映射成一张张表,用SQL就能查询聚合。这相当于把HDFS上的文件变成了数据仓库,分析人员不用写Java或Python,一条SELECT AVG(temperature) FROM weather WHERE dt='2024-06-01'就能拿到某天的平均温度。

Spark负责的是“怎么算”的部分。Hive适合跑批查询,但模型训练、特征工程这类复杂的计算任务,明显是Spark的强项。Spark基于内存计算,跑迭代式机器学习算法比MapReduce快很多倍。我这边把Hive里的历史数据通过Spark SQL加载进来,做特征拼接、归一化,再丢给MLlib的线性回归模型训练。整个流程都在Spark内存里流转,不用反复落盘,效率高很多。

打个比方:Hadoop是仓库和货架,Hive是仓库管理员用的登记册,Spark是仓库里的加工车间。货存进仓库(HDFS),管理员登记造册(Hive),车间加工成成品(Spark训练模型)。

1.2 为什么用线性回归做天气预测

很多人问,天气预测为什么不直接上深度学习?这里要分清场景。深度学习做短临预报,靠的是海量雷达、卫星数据,那种项目需要的是GPU集群、TB级数据,不是一套课程设计能撑起来的。

线性回归的价值在于可解释性和快速迭代。气象数据里,温度、湿度、气压、风速这些因素之间存在明显的线性相关性。比如某一地区夏天的最高温度,往往和前一天温度、湿度、日照时长呈近似线性关系。线性回归模型训练快、参数透明,能够明确告诉我们每个特征的权重,解释“哪个因素对温度影响最大”。

当然,线性回归处理不了强烈的非线性天气系统,比如台风路径、强对流天气,这些场景下模型误差会变大。但作为一套入门级预测系统,线性回归完全够用,而且方便后续扩展到决策树、XGBoost等更复杂的模型。先把基础链路跑通,后面换算法就是替换一个模型类的事。

1.3 系统整体流程设计:从爬虫到可视化

整个系统的数据流我设计成了6个环节:

爬虫采集 → 数据落地HDFS → Hive建表管理 → Spark清洗与特征工程 → 线性回归训练预测 → 可视化展示

这个链路看起来很直白,实际做的时候每两个环节之间都会遇到坑。比如爬虫抓下来的数据格式不统一,日期字段有的是2024/06/01,有的是2024-06-01,清洗时就得统一处理;Hive里的表字段类型定错了,后面Spark SQL读进来类型转换就报错;线性回归的输入特征是字符串类型,还需要手动做StringIndexer数值化。

我当时用Python写爬虫抓取公开天气网站的历史数据,每天定时跑一次增量抓取,JSON格式落地。然后用Hive建外部表,直接把HDFS上的JSON目录映射成表结构。Spark任务从这张表读取数据,经过特征工程后训练模型。预测结果写回HDFS,可视化后端通过接口读取预测结果,渲染ECharts图表。整个流程闭环后,每天早上自动出当天的温度预测。

2. 环境搭建:Hadoop、Hive、Spark整合实战

2.1 大数据三件套版本选型与伪分布式部署

搭建这套环境,最核心的决策是版本选型。网上教程五花八门,照着老版本教程装新版本,大概率踩坑。

我先说结论,这只组合是我的亲测方案:

组件推荐版本说明
JDK1.8Hadoop 2.x/3.x最稳定的运行环境
Hadoop3.3.4稳定,生态兼容性好
Hive3.1.3与Hadoop 3.x兼容,支持Beeline
Spark3.3.0兼容Hive 3.x的元数据协议
MySQL5.7 / 8.0存放Hive元数据,比默认derby靠谱

这里特别提醒,Hive默认使用Derby存储元数据,只支持单会话连接,一旦同时打开多个Hive客户端就会报错。生产环境必须把元数据放到MySQL里。配置方法不复杂,核心是把MySQL驱动包放到Hive的lib目录下,然后修改hive-site.xml里的连接串。

伪分布式部署是单机学习的最佳选择。所谓伪分布式,就是在一台机器上同时启动NameNode、DataNode、ResourceManager、NodeManager这些角色,模拟集群的效果。内存建议至少8G,我实测下来,4G内存跑起三件套,加载数据时容易卡死。

安装Hadoop时,核心是配置core-site.xml、hdfs-site.xml、yarn-site.xml这三个文件。core-site.xml里指定NameNode的地址,hdfs-site.xml里设置副本数为1,因为只有一个DataNode,副本数设3会一直报块复制警告。yarn-site.xml要配置好内存分配参数,否则Spark任务分配不到足够资源。

<!-- hdfs-site.xml 关键配置 --> <property> <name>dfs.replication</name> <value>1</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>/usr/local/hadoop/data/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/usr/local/hadoop/data/datanode</value> </property>

2.2 Hive与Hadoop的元数据整合

Hive和Hadoop的整合,本质就是让Hive能够读写HDFS上的文件。装好Hive后直接启动hive命令,如果出现元数据连接错误,十有八九是hive-site.xml没配置好。

用MySQL存放元数据的配置模板:

<property> <name>javax.jdo.option.ConnectionURL</name> <value>jdbc:mysql://localhost:3306/hive_meta?createDatabaseIfNotExist=true</value> </property> <property> <name>javax.jdo.option.ConnectionDriverName</name> <value>com.mysql.cj.jdbc.Driver</value> </property> <property> <name>javax.jdo.option.ConnectionUserName</name> <value>hive</value> </property> <property> <name>javax.jdo.option.ConnectionPassword</name> <value>your_password</value> </property>

配置完成后,先执行schematool -dbType mysql -initSchema初始化元数据表,然后启动Hive。打开Hive后执行CREATE TABLE test_weather(id INT, temp DOUBLE);,如果建表成功,在HDFS的/user/hive/warehouse/目录下就能看到对应的目录。

这里有一个很典型的坑:Hive启动报java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversal。这个错误通常是Hadoop和Hive自带的xml-apisjar包冲突导致。解决办法是把Hive的lib目录下旧的xml-apisjar替换成Hadoop里的版本,或者反过来,具体看报错的是哪个组件加载类失败。

2.3 Spark整合Hive及运行模式选择

Spark要读取Hive的表数据,最关键的一步是让Spark找到Hive的元数据。做法是把Hive的hive-site.xml复制到Spark的conf目录下,同时把MySQL驱动包放到Spark的jars目录。

整合好之后,在Spark的Scala或Python代码里就可以直接写:

from pyspark.sql import SparkSession spark = SparkSession.builder \ .appName("WeatherPrediction") \ .enableHiveSupport() \ .getOrCreate() df = spark.sql("SELECT * FROM weather.ods_weather_data") df.show(5)

enableHiveSupport()是Spark跟Hive打交道的开关,不打开这个选项,Spark根本识别不了Hive的表。

运行模式我建议用yarn-client或者local模式。单机学习阶段直接用local[*]模式最简单,不需要提交到YARN上,本地跑完就出结果。但如果你想体验集群任务调度,可以配置spark.master=yarn,Spark任务会交给YARN分配容器执行。YARN模式跑起来后,能在ResourceManager的Web UI上看到任务状态和日志,面试时讲这个细节会很加分。

资源参数配置也要留个心眼。YARN模式下,如果yarn-site.xml里的yarn.nodemanager.resource.memory-mb设置过小,Spark Executor启动会直接被Kill。我一开始按默认配置跑,Executor反复挂掉,日志里全是Container killed by YARN for exceeding memory limits。后来把yarn.nodemanager.resource.memory-mb调到8G,同时Spark的Executor内存控制在2G以内,才算稳定。

2.4 部署中的典型问题与性价比配置推荐

把三件套装在同一台机器上,最尴尬的是启动时的端口冲突。Hive的HiveServer2默认端口是10000,Spark的HistoryServer默认端口是18080,Hadoop的NameNode是9870。如果本机其他服务占用了这些端口,启动就会失败。我建议安装前用netstat -tlnp | grep 端口号检查一下端口占用情况。

内存这块,如果机器只有8G内存,我的建议是:Hadoop NameNode和DataNode各分配1G,YARN的NodeManager分配3G,HiveServer2分配1G,Spark任务Executer内存分配到1G-2G。留一点余量给系统本身。

环境搭建阶段最容易出问题的还有SSH免密登录。Hadoop启动时会通过SSH免密登录到各节点执行命令,单机伪分布式模式下,如果没配置localhost的免密登录,每次启动都会要求输入密码或者直接报错。执行ssh-copy-id localhost配置好即可。

3. 气象数据爬虫与数据仓库建设

3.1 爬虫目标分析与数据源规划

爬虫是整个系统的数据入口,目标是把指定城市(我选了北京、上海、广州三地)的历史天气数据抓下来。数据源我选择的是公开的天气历史数据网站,这类网站通常提供按日期查询的接口,返回JSON格式的天气信息,包括最高温、最低温、天气现象、风力风向、空气质量等字段。

爬虫策略上,我并没有做太大规模的并发抓取,而是采用定时增量采集。每天凌晨1点抓取前一天的天气数据,一次请求一个城市一天的数据,请求间隔控制在1-2秒,避免对目标服务器造成压力。整体看下来,每天只需请求3个接口,属于非常轻量级的爬虫。

这里要强调一下数据源的合法性。本项目抓取的是公开的、非敏感的气象历史数据,采集频率极低,仅用于学习研究。如果要商用,需要联系数据源获取授权。做爬虫项目,数据合规这条底线一定不能碰。

3.2 爬虫实现与技术细节

爬虫我用的是Python的requests加BeautifulSoup组合。requests负责发请求拿响应,BeautifulSoup负责解析HTML或JSON提取字段。对于返回JSON的接口,直接response.json()解析就行,更省事。

import requests import json from datetime import datetime, timedelta def fetch_weather(city_code, date): url = f"https://xxx/api/weather/history?city={city_code}&date={date}" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } try: resp = requests.get(url, headers=headers, timeout=10) if resp.status_code == 200: return resp.json() else: print(f"请求失败: {resp.status_code}") return None except Exception as e: print(f"请求异常: {e}") return None def main(): yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d") cities = {"beijing": "101010100", "shanghai": "101020100", "guangzhou": "101280101"} for city, code in cities.items(): data = fetch_weather(code, yesterday) if data: with open(f"/home/user/weather_data/{city}_{yesterday}.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) time.sleep(2)

这里有两个细节值得注意。第一,请求头里的User-Agent一定要伪装成浏览器,很多网站会对Python默认的User-Agent做拦截。第二,抓下来的JSON文件,日期字段和城市名称一定要在文件名里体现,这样后续按日期增量导入HDFS非常方便,也方便排查哪天的数据缺失。

3.3 数据清洗与Hive分区表设计

爬虫落地的是JSON文件,数据里通常会混入一些异常值,比如个别字段为空、温度数值出现明显偏差。在导入Hive之前,我建议先用脚本做一轮初步清洗。

清洗规则我定了几条:

  • 日期字段统一格式化为yyyy-MM-dd,避免混入时间戳
  • 最高温与最低温字段转为Double类型,剔除明显超出合理范围的记录(比如温度>50或<-50)
  • 天气现象为空时,用“未知”补齐
  • 城市名称统一小写,去空格

清洗完成后,数据放到HDFS的指定目录,比如/user/hive/warehouse/weather.db/ods_weather_data/。然后Hive建表:

CREATE TABLE ods_weather_data ( city STRING, date STRING, temp_max DOUBLE, temp_min DOUBLE, humidity DOUBLE, pressure DOUBLE, wind_speed DOUBLE, weather_condition STRING ) PARTITIONED BY (dt STRING) ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' STORED AS TEXTFILE;

这里用到了org.openx.data.jsonserde.JsonSerDe,这是Hive解析JSON文件的利器。没有这个SerDe,JSON数据读进来会是整行一个字段,没法直接用SQL查。把这个jar包放到Hive的lib目录下,建表时指定SerDe,Hive就能自动把JSON解析成多列。

分区表的设计也很关键。我按日期字符串做分区,每天的数据存一个分区。查询时指定dt='2024-06-01'就能快速定位数据,不用全表扫描。缺点是分区数会随时间增长,一年就是365个分区,对于这种规模的数据量完全能接受。

3.4 数据质量检查

数据入仓之后,先别急着建模,做一轮数据质量检查很重要。我通常会用几条SQL来验证:

-- 检查分区数据是否完整 SELECT dt, COUNT(*) FROM ods_weather_data GROUP BY dt ORDER BY dt; -- 检查温度字段是否有明显异常 SELECT MAX(temp_max), MIN(temp_min) FROM ods_weather_data WHERE dt='2024-06-01'; -- 检查缺失值情况 SELECT SUM(CASE WHEN humidity IS NULL THEN 1 ELSE 0 END) AS humidity_missing, SUM(CASE WHEN pressure IS NULL THEN 1 ELSE 0 END) AS pressure_missing FROM ods_weather_data WHERE dt='2024-06-01';

这步做得好,后面Spark阶段就不会被脏数据折腾。我有一次没做质量检查,结果模型训练出来的权重全是NaN,排查了两小时,最后发现是某一天的数据里湿度字段混入了字符串"null",导致特征向量里出现了非数值类型,Spark在向量化时直接失败。数据质量这个环节省不得,一定要反复验证。

4. Spark数据处理与线性回归模型实现

4.1 从Hive到Spark:数据读取与特征工程

数据清洗完成后,就轮到Spark登场了。整个建模流程包括数据读取、特征工程、模型训练、模型评估四个环节。

数据读取这块前面说过,用spark.sql("SELECT * FROM ods_weather_data")拿到DataFrame。接下来是特征工程,这一步直接决定模型的预测效果。

线性回归需要的是数值型特征,所以先把天气现象weather_condition转成数值。Spark MLlib提供StringIndexer可以把字符串标签映射成数值索引:

from pyspark.ml.feature import StringIndexer, VectorAssembler, StandardScaler indexer = StringIndexer(inputCol="weather_condition", outputCol="weather_index") df_indexed = indexer.fit(df).transform(df)

然后选择特征列,拼成特征向量。我的特征选择逻辑是:预测目标设为temp_max(当天最高温),特征用前一天的最低温度、湿度、气压、风速、天气Index和采集日期的月份。月份是为了捕捉季节周期性,这个特征在真实场景里对气温预测帮助很大。

feature_cols = ["temp_min", "humidity", "pressure", "wind_speed", "weather_index", "month"] assembler = VectorAssembler(inputCols=feature_cols, outputCol="features_vector") df_features = assembler.transform(df_indexed)

特征拼好后,用StandardScaler做标准化。这一步容易被忽略,但很重要。温度、湿度、气压的单位和数值范围差异很大,气压通常是1000左右,湿度是0~100,如果不做标准化,线性回归的梯度下降收敛会很慢,甚至不收敛。标准化后所有特征都在均值0、标准差1的范围内,模型训练效率会显著提升。

4.2 线性回归模型训练与参数调优

Spark MLlib的线性回归在pyspark.ml.regression.LinearRegression里,训练代码非常简洁:

from pyspark.ml.regression import LinearRegression from pyspark.ml.evaluation import RegressionEvaluator train_df, test_df = df_features.randomSplit([0.8, 0.2], seed=42) lr = LinearRegression( featuresCol="features_vector", labelCol="temp_max", maxIter=100, regParam=0.01, elasticNetParam=0.0 ) model = lr.fit(train_df)

这里regParam是正则化系数,控制模型复杂度,防止过拟合。elasticNetParam=0.0表示使用L2正则化(Ridge回归),如果设为1.0就是Lasso回归。对气象数据这种特征数量不多、特征间相关性较高的场景,L2正则化效果通常更好。

模型训练完成后,可以打印模型系数和截距:

print("系数: ", model.coefficients) print("截距: ", model.intercept)

系数会告诉我们每个特征对最高温的贡献方向。比如temp_min的系数是0.7,说明前一天最低温每升高1度,当天最高温预计升高0.7度,这是符合气象常识的。如果发现某个系数明显不符合常理,比如湿度系数是-8,就要回头检查特征工程是不是有问题。

参数调优方面,我建议用ParamGridBuilder加CrossValidator做交叉验证,网格搜索最优的regParam和elasticNetParam。虽然训练时间会变长,但能显著提升模型稳定性。

from pyspark.ml.tuning import ParamGridBuilder, CrossValidator param_grid = ParamGridBuilder() \ .addGrid(lr.regParam, [0.001, 0.01, 0.1]) \ .addGrid(lr.elasticNetParam, [0.0, 0.5, 1.0]) \ .build() cross_val = CrossValidator( estimator=lr, estimatorParamMaps=param_grid, evaluator=RegressionEvaluator(labelCol="temp_max", metricName="rmse"), numFolds=5 ) cv_model = cross_val.fit(train_df)

4.3 模型评估与预测效果分析

模型训练完,用测试集评估效果。我用的指标是RMSE(均方根误差)和R²(决定系数)。RMSE就是在测试集上预测值和真实值差值的平方和的均值再开根号,单位是度,能直观反映预测误差的大小。R²越接近1说明模型对数据的解释能力越强。

predictions = cv_model.transform(test_df) evaluator_rmse = RegressionEvaluator(labelCol="temp_max", metricName="rmse") evaluator_r2 = RegressionEvaluator(labelCol="temp_max", metricName="r2") rmse = evaluator_rmse.evaluate(predictions) r2 = evaluator_r2.evaluate(predictions) print(f"RMSE: {rmse:.4f}") print(f"R2 Score: {r2:.4f}")

我跑出来的效果是:RMSE在2.1度左右,R²在0.86左右。对于只用了6个特征、没有雷达和卫星数据的入门级模型来说,这个效果已经不错了——平均误差2度,意味着预测最高温的误差基本在正负2度范围内,作为趋势参考完全够用。

不过要泼一盆冷水:如果遇到极端天气,比如寒潮突袭或者台风影响,误差可能飙到5度以上。这是线性模型的天然局限,它学不到气压骤降、风速剧增这类非线性气象变化。如果想让模型适应极端天气,需要引入更多特征或者换用梯度提升树模型。

4.4 天气预测的局限性与改进方向

线性回归在天气预测里的天花板很明显,主要体现在三个方面。

第一,气象系统的复杂性。气温变化受纬度、地形、海洋、大气环流等多因素影响,不是简单的线性叠加。第二,特征之间的交互效应。比如“高温+高湿”和“高温+低湿”对人体的体感温度影响完全不同,线性模型无法捕捉这种交互。第三,时间序列的自相关性。气象数据本质上是有时间依赖的序列,线性回归默认样本独立,无法直接建模这种时间依赖。

针对这三点,改进方向也明确:

  • 引入时间窗口特征,比如连续三天的温度变化趋势、昨天的温度差分值,让模型感知到短期趋势
  • 考虑用随机森林或XGBoost替代线性回归,Tree模型天然能捕捉非线性关系和特征交互
  • 如果是纯时间序列预测,可以尝试ARIMA或Prophet,专门处理时间依赖
  • 数据量再大一些,可以试试LSTM这类循环神经网络

但我觉得,作为一套学习型项目,线性回归已经出色地完成了“跑通链路”的任务。它把模型训练的每个环节充分暴露出来,后续换模型是水到渠成的事,而不是一头雾水。

5. 可视化分析系统设计

5.1 可视化技术选型

可视化环节,我选择了前后端分离的方案。后端用Flask提供数据接口,前端用ECharts渲染图表。

技术选型的考虑有几个维度:一是ECharts对中文文档友好,像温度曲线、柱状图、雷达图这些图表,几行配置就能实现;二是Flask轻量,不用引入大型框架,一套天气预报面板没必要上Spring Boot;三是前后端通过JSON交互,后续要接别的可视化库或换前端框架都很灵活。

如果你的运行环境允许,建议把可视化结果打包成Web服务,浏览器打开就能看。我在服务器上直接跑了Flask服务,本机浏览器访问,实时看预测结果和历史数据对比图。

5.2 图表设计与交互逻辑

可视化面板我设计了四个核心模块:

  • 历史温度趋势折线图:展示最近30天最高温、最低温变化,直观看出温度波动
  • 未来7天预测柱状图:展示模型对未来一周最高温的预测结果
  • 湿度与降水关系散点图:展示湿度、气压和天气现象之间的分布关系
  • 特征重要性雷达图:展示各特征对预测结果的贡献度,用系数归一化后的值绘制

折线图和柱状图是核心。历史趋势用双Y轴,左轴温度,右轴湿度,方便对比温度与湿度的相关性。预测柱状图在柱子顶部标注预测温度值,鼠标悬浮时显示对应日期的详细天气信息。

ECharts配置里最核心的是series的data字段,直接从后端接口拿JSON数组填充。前端代码大致逻辑如下:

fetch('/api/prediction') .then(response => response.json()) .then(data => { var chart = echarts.init(document.getElementById('prediction-chart')); var option = { title: { text: '未来7天最高温预测' }, tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: data.dates }, yAxis: { type: 'value', name: '温度(℃)' }, series: [{ name: '预测最高温', type: 'bar', data: data.temp_max, label: { show: true, position: 'top' } }] }; chart.setOption(option); });

5.3 系统模块划分

整个可视化系统从代码上分三层。数据访问层负责从Hive或HDFS读取模型预测结果和统计数据,通过Spark SQL或Hive JDBC实现。业务逻辑层负责组装聚合接口,比如按日期返回历史趋势、按城市返回预测对比。展示层就是前端页面,通过Ajax请求后端接口,渲染ECharts。

这样一个模块划分,跑起来以后维护也很方便。比如要增加一个城市,只需要在爬虫配置和数据接口里加上城市参数。要增加一个图表,只需要在后端加一个接口,前端加一个ECharts实例。

6. 常见问题与排查技巧实录

6.1 环境与整合类问题

搭建这套环境,几乎每一步都可能踩坑。我把这次实战中遇到的典型问题整理成表格,方便对照排查。

问题现象可能原因解决办法
Hive启动报元数据连接失败MySQL驱动缺失或连接串不对确认MySQL驱动jar在Hive lib目录,检查hive-site.xml连接配置
Spark任务被YARN KillExecutor内存超过NodeManager剩余资源调大yarn.nodemanager.resource.memory-mb,或调小spark.executor.memory
HDFS NameNode启动失败元数据目录权限问题或端口被占用检查dfs.namenode.name.dir目录是否存在并有写权限,换端口或停掉占用进程
端口19999被占用HiveServer2默认端口冲突修改hive.server2.thrift.port为其他端口
Spark读不到Hive表缺少hive-site.xml或启用了错误的模式将hive-site.xml复制到Spark conf目录,代码里加enableHiveSupport()

6.2 数据与任务运行类问题

数据集本身也是一个坑点来源。天气预测这个场景里,爬虫抓取的数据通常会遇到:

  • 某一天的天气数据完全缺失。原因可能是数据源更新延迟,或者爬虫程序当天挂了。要让模型训练稳定,建议把缺失日期标记出来,用前一天的数值填充,或者直接把缺失日期剔除出训练集。

  • 温度字段出现null或空字符串。Hive表里如果字段类型是DOUBLE,空字符串会导致Spark SQL读取报错。清洗阶段统一把空字符串转成NULL,数据集里用NaN表示。

  • 特征DataFrame里出现NaN。模型训练前如果特征列有NaN,线性回归会直接报错。我建议在特征组装后加一步处理:

df_features = df_features.fillna({"humidity": df_features.select("humidity").agg({"humidity": "mean"}).collect()[0][0]})

用均值填充缺失值,简单有效。

6.3 模型与业务效果类问题

模型效果不理想,很多人第一反应是调参,其实大部分时候应该回头检查数据和特征。

如果RMSE偏大,先要判断是不是数据量太小。我只用了3个城市、3个月的数据,模型可学习的样本不足200条,效果自然有限。多爬几年历史数据,样本量提升到几千条,RMSE会明显下降。

如果模型系数出现异常大的值,比如温度系数跑到几十,大概率是特征间存在多重共线性。湿度、气压、天气现象之间本身就有相关性,解决办法是用VectorAssembler之前先做相关性分析,把高度相关的特征剔除一个。

类别的编码也是常见问题。StringIndexer会把天气现象转换成0、1、2这种数值,但模型会默认这些数值之间有顺序关系,比如“晴”=0、“雨”=1,模型可能错误学习到“雨大于晴”这种含义。如果分类特征没有顺序关系,建议改用OneHotEncoder做成哑变量,避免误导模型。

最后的实操建议

这套项目我从零开始,前后花了一个月左右才稳定跑通。回顾整个过程,最想给后来者提三点建议。

第一,环境搭建阶段要有耐心。Hadoop、Spark、Hive的版本组合,网上没有一份绝对正确的答案,一定要根据你的JDK版本和操作系统去选。先小步验证,比如先用HDFS上传一个文件,再用Hive建一张表,最后用Spark读这张表,每一步都确认无误后再往下走。

第二,数据一定要在早期就盯紧。我前面踩过不少数据质量导致的模型坑,实际上很大部分可以在Hive阶段通过SQL检查发现。爬虫脚本、清洗脚本、Hive表结构,每一个环节都要有数据校验的意识。

第三,代码写完之后,把关键命令整理成shell脚本或文档。比如启动Hadoop、启动Hive、跑Spark训练脚本、启动Flask服务,这些操作串成一条龙,否则隔几天再操作,很容易忘记顺序。

这套系统的扩展空间其实很大。比如把线性回归换成GBDT或XGBoost,预测精度会有提升;把数据源扩展到全国几百个城市,可以做区域性气象分析;加上前端地图组件,还能做成全国气温热力分布图。技术栈的每一个环节都有对应的大厂生产实践,把基础链路吃透,后面的进阶道路会顺畅很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询