天气预测系统实战:从爬虫到Hadoop+Spark+Hive的机器学习全流程
做大数据课程设计或者毕业设计,很多人都会选天气预测这个方向。但真正动手的时候才发现,从数据采集到模型上线,中间隔着好几座山:爬虫怎么写才不会封IP,Hadoop集群怎么搭才稳定,Hive怎么建表才高效,Spark的机器学习库到底怎么调用——每一步都是坑。我最近刚好完整做完了一个基于Hadoop+Spark+Hive的天气预测系统,从数据爬虫到可视化大屏走通了全流程,这里把整个项目的技术选型、架构设计、核心代码和踩坑记录完整整理出来,希望能给正在做类似项目的朋友一些参考。
这个系统做的事情并不复杂:定时从公开气象网站爬取历史气象数据,存储到Hadoop HDFS上,用Hive做数据清洗和特征宽表构建,再用Spark MLlib的线性回归算法训练模型,预测未来某一天的最高温度、最低温度、降雨概率等指标,最后把预测结果和真实数据放到大屏上做可视化对比。整套系统跑通了离线数仓的完整链路,也可以当作一个mini版的气象数据平台来理解。
1. 整体架构设计与技术选型思路
1.1 系统架构全景
这个项目本质上就是一个完整的大数据离线处理链路。数据从外部采集进来,经过存储、清洗、建模、训练、预测、展示六个环节。我画了一张架构图(基于常见离线数仓标准架构实现),整个链路如下:
- 数据采集层:Python爬虫定时抓取气象数据,包括逐小时温度、湿度、气压、风速、风向、降水量、天气现象等原始数据,同时把爬虫采集到的数据实时写入HDFS指定目录。
- 数据存储层:Hadoop HDFS作为底层分布式存储,存放所有原始日志和清洗后的中间数据。Hive建立在HDFS之上,以SQL方式管理数据仓库表。
- 数据计算层:Hive负责ETL和数据清洗,Spark负责特征工程和模型训练。两个引擎通过Hive元数据服务共享同一个数据仓库。
- 数据应用层:Spring Boot提供后端服务,ECharts做可视化大屏展示,展示历史数据趋势、预测曲线和模型评估指标。
设计这套方案的时候,我特意把数据采集和数据处理拆开,爬虫进程和Spark任务互不干扰,即使某个环节挂了,其他模块照常运行。这也是工程化项目比较稳妥的做法——模块解耦,故障隔离。
1.2 为什么选线性回归而不是更复杂的算法
标题里明确要求用线性回归,这里也顺便解释一下这个选择的合理性。气象预测领域,虽然深度学习模型(LSTM、Transformer)效果更好,但线性回归依然有它的价值:
- 可解释性强:线性回归的每个特征对应一个权重系数,可以直观看出温度、湿度、气压对预测结果的影响方向,对初学者理解机器学习非常有帮助。
- 训练成本低:Spark MLlib实现线性回归只用了不到20行代码,模型训练在本地伪分布式集群上几分钟就能出结果。
- 基线模型价值:在实际工程项目里,先跑一个线性回归做baseline,再决定要不要上更复杂的模型,这是非常标准的流程。
1.3 数据流向设计
整个系统的数据流是单向串行的。每天凌晨2点,调度器触发当天的数据采集任务;采集完成后,Hive定时任务开始执行ETL,把原始数据从ODS层清洗到DWD层;接着Spark任务读取DWD层数据做特征工程;最后模型训练完成,预测结果写回Hive的ADS层。这个流程清晰简单,也是离线数仓最经典的层次划分——ODS原始数据层、DWD明细数据层、ADS应用数据层。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与大数据组件安装
2.1 Hadoop集群搭建与伪分布式部署
这里给新手一个建议:如果你的机器配置一般(8G内存以下),先不要折腾三节点集群,伪分布式足够跑通全流程。我用的就是单机伪分布式模式,Hadoop版本选的3.3.6,安装路径放在/usr/local/hadoop,JAVA_HOME配置好之后,主要操作就三步:配置core-site.xml、hdfs-site.xml、yarn-site.xml三个核心文件,然后执行hdfs namenode -format初始化,最后用start-dfs.sh和start-yarn.sh启动即可。
xml复制<!-- core-site.xml -->
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://localhost:9000</value>
</property>
</configuration>
<!-- hdfs-site.xml -->
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>/usr/local/hadoop/tmp/name</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>/usr/local/hadoop/tmp/data</value>
</property>
</configuration>
注意:伪分布式模式下dfs.replication必须设为1,否则数据块会一直处于等待副本的状态。我第一次搭的时候忘了改这个参数,上传文件后等了半天副本数一直是1/3,后来检查才发现问题出在这里。
启动完成后执行jps命令,能看到NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager五个进程说明Hadoop跑起来了。这一步也是面试高频考点,我在hadoop面试题库里见过无数次——Hadoop启动后有哪些进程,各自的作用是什么。
2.2 Hive安装与MySQL元数据库配置
Hive的安装相对简单,只需要把Hive解压到指定目录,配置hive-site.xml。关键点在于元数据存储方式。默认的Derby内嵌模式只适合测试,无法支持多用户并发访问,而且元数据存在临时目录里,重启就丢了。我直接用了MySQL存储元数据,配置步骤如下:
sql复制-- 在MySQL中创建hive用户和数据库
CREATE DATABASE hive_metastore DEFAULT CHARACTER SET utf8;
CREATE USER 'hive'@'%' IDENTIFIED BY 'hive123';
GRANT ALL PRIVILEGES ON hive_metastore.* TO 'hive'@'%';
FLUSH PRIVILEGES;
然后把mysql-connector-java的jar包放到Hive的lib目录下,在hive-site.xml里配置好连接地址。有个细节注意:Hive 3.x版本不再自动初始化数据库表结构,需要手动执行schematool -dbType mysql -initSchema完成初始化。
启动Hive之后,可以通过hive命令进入交互式终端,也可以直接用beeline连接HiveServer2。实际项目里我是用beeline方式,因为调度脚本里可以通过JDBC方式连接Hive执行SQL,更方便自动化。
2.3 Spark环境配置与Hive集成
Spark是这套系统里最关键的组件。我用的Spark版本是3.4.1,需要注意的坑是:如果Hadoop是3.x,必须用Spark 3.0以上版本,否则会报javax.servlet包冲突之类的问题。
Spark和Hive集成的核心在于让Spark能读取Hive的元数据。做法是:把Hive的hive-site.xml复制到Spark的conf目录,同时把MySQL驱动jar包放到Spark的jars目录。这样Spark就能通过Hive Metastore找到表结构信息。
安装完成后,执行spark-sql命令,如果能查询到Hive里的表,说明集成成功。这里有个验证技巧:在Hive里建一张测试表并插入数据,然后用spark-sql查询,看数据和Hive查询结果是否一致。
2.4 组件间整合的先后顺序
很多人在搭环境的时候容易乱,我记录一下正确顺序:先搞定Hadoop,确认HDFS能正常读写;再装Hive,用SQL可以建表查数;然后装Spark,确保spark-sql能查Hive表。每一步验证通过再进入下一步,这样排查问题时能快速定位到具体组件。如果一次全部装好再测试,报错的时候根本分不清是哪个组件的问题。
3. 气象数据爬虫与HDFS落地
3.1 爬虫目标分析与数据源选择
做天气预测系统,数据质量直接影响模型效果。我一开始想用requests直接请求某个天气API,但后来发现免费的API要么限流特别严重,要么历史数据不够全。最后综合考虑,我选择从公开的天气历史数据平台爬取数据(注意遵守网站robots协议,做好请求间隔控制),数据字段包括:日期、城市、最高温度、最低温度、天气现象、风向、风力、湿度、气压、降水量、能见度等。
爬虫这块有几个关键设计:
- User-Agent轮换池:准备10个以上不同的User-Agent,每次请求随机取一个,避免被服务端识别为爬虫。
- 请求间隔控制:两个请求之间sleep 2到5秒,防止对目标站点造成压力。
- 异常重试机制:遇到请求失败的情况,指数退避重试最多3次。
- 数据校验:爬取的数据先做格式校验再落盘,字段缺失的记录直接丢弃。
3.2 爬虫代码结构与HDFS写入实现
python复制import requests
import pandas as pd
import time
import random
from datetime import datetime, timedelta
from hdfs import InsecureClient
# 用户代理池
UA_POOL = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
]
def fetch_weather_data(city_code, date):
"""抓取指定城市、指定日期的气象数据"""
headers = {
'User-Agent': random.choice(UA_POOL),
'Accept': 'application/json, text/plain, */*',
'Referer': 'https://example-weather-site.com'
}
url = f'https://example-weather-api.com/api/history?city={city_code}&date={date}'
for retry in range(3):
try:
resp = requests.get(url, headers=headers, timeout=10)
if resp.status_code == 200:
data = resp.json()
return {
'city_code': city_code,
'date': date,
'high_temp': data['highTemp'],
'low_temp': data['lowTemp'],
'weather': data['weather'],
'wind_direction': data['windDirection'],
'wind_level': data['windLevel'],
'humidity': data['humidity'],
'pressure': data['pressure'],
'precipitation': data.get('precipitation', 0)
}
else:
time.sleep(5)
except Exception as e:
print(f"请求失败: {e}, 重试次数: {retry + 1}")
time.sleep(2 ** retry)
return None
def write_to_hdfs(data_list):
"""将数据写入HDFS"""
client = InsecureClient('http://localhost:9870', user='hadoop')
df = pd.DataFrame(data_list)
csv_data = df.to_csv(index=False, header=False).encode('utf-8')
hdfs_path = f"/weather/ods/weather_raw/{datetime.now().strftime('%Y%m%d')}/part.csv"
with client.write(hdfs_path, encoding='utf-8', overwrite=True) as writer:
writer.write(csv_data)
print(f"数据已写入HDFS: {hdfs_path}")
这段代码里有几个值得注意的地方。第一,我把数据落成了CSV格式但去掉了表头,这样后续Hive建表时可以直接指定目标表字段名。第二,写入HDFS用的是WebHDFS客户端InsecureClient,这种方式不需要在Windows下装Hadoop环境也能操作HDFS,调试起来方便很多。第三,整体写入前先转成DataFrame做统一处理,这一步其实就是数据清洗的雏形。
3.3 爬虫调度与增量数据管理
爬虫不是跑一次就完事的,我用Linux的crontab做了定时调度,每天凌晨1点执行一次:
code复制0 1 * * * cd /opt/weather_crawler && python3 crawler.py >> /opt/weather_crawler/logs/crawler.log 2>&1
增量数据管理方面,数据按日期分区存放,HDFS路径结构是/weather/ods/weather_raw/YYYYMMDD/part.csv,这样Hive可以轻松建立分区表,按天动态挂载新数据。调度脚本里还加入了日志记录和失败发送邮件告警的机制,避免数据漏采了几天才发现。
4. Hive数仓建设与数据清洗
4.1 数仓分层设计与建表语句
Hive建表是整个数据仓库设计的第一步。我更推荐外部表方式,因为数据文件的物理位置由我们自己管理,删除表不会误删HDFS上的底层文件,这样更安全可控。核心表的设计代码如下:
sql复制-- 创建ODS层原始数据外部表,按日期分区
CREATE EXTERNAL TABLE weather_ods.ods_weather_raw (
city_code STRING,
city_name STRING,
high_temp INT,
low_temp INT,
weather STRING,
wind_direction STRING,
wind_level INT,
humidity INT,
pressure INT,
precipitation DECIMAL(5,2)
)
PARTITIONED BY (dt STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
STORED AS TEXTFILE
LOCATION '/weather/ods/weather_raw';
-- 加载最新一天的数据分区
ALTER TABLE weather_ods.ods_weather_raw
ADD PARTITION (dt='2024-01-15');
-- 创建DWD层清洗后数据表,按日期分区
CREATE TABLE weather_dwd.dwd_weather_clean (
city_code STRING,
city_name STRING,
high_temp INT,
low_temp INT,
weather_type STRING,
wind_direction STRING,
wind_level INT,
humidity INT,
pressure INT,
precipitation DECIMAL(5,2),
year INT,
month INT,
day INT,
day_of_week INT
)
PARTITIONED BY (dt STRING)
STORED AS PARQUET;
ODS层保存的是爬虫抓来的原始数据,DWD层保存的是清洗后的规范数据。DWD层我特意把日期拆成年、月、日、星期几四个字段,这是为了后续特征工程方便——线性回归模型可以直接用月份、星期几这些离散特征作为输入。
4.2 清洗规则与SQL实现
数据清洗这一步是整个数据工程最耗时的地方。我遇到的脏数据主要有几类:字段缺失(湿度为空)、数值越界(温度超过50度或低于-60度)、重复数据(同一天同一城市抓了两次)、格式不一致(天气现象有的是"晴"有的是"晴天")。
sql复制-- 清洗逻辑:过滤异常值,规范化天气类型,增加时间特征
INSERT OVERWRITE TABLE weather_dwd.dwd_weather_clean PARTITION (dt='2024-01-15')
SELECT
city_code,
city_name,
high_temp,
low_temp,
CASE
WHEN weather IN ('晴', '太阳', 'sunny') THEN '晴'
WHEN weather LIKE '%雨%' THEN '雨'
WHEN weather LIKE '%雪%' THEN '雪'
ELSE '多云'
END AS weather_type,
wind_direction,
wind_level,
humidity,
pressure,
precipitation,
YEAR(dt) AS year,
MONTH(dt) AS month,
DAY(dt) AS day,
weekday(dt) AS day_of_week
FROM weather_ods.ods_weather_raw
WHERE dt = '2024-01-15'
AND high_temp IS NOT NULL
AND low_temp IS NOT NULL
AND high_temp BETWEEN -50 AND 50
AND low_temp BETWEEN -60 AND 60
AND humidity BETWEEN 0 AND 100
AND pressure BETWEEN 900 AND 1100;
4.3 Hive性能优化实践
Hive的任务如果直接跑,经常会遇到慢查询的问题。我在这个项目里用了几个优化手段:
- 列式存储:DWD层表用Parquet代替TEXTFILE,同样的数据量,查询性能提升3到5倍,尤其适合只需要读取少数列的场景。
- 分区裁剪:所有表都按日期分区,查询时指定dt字段让Hive只扫描对应分区目录,而不是全表扫描。
- 小文件合并:爬虫每天生成的文件比较碎,用INSERT OVERWRITE重写一遍数据,把同一天的数据合并成一个大文件。HDFS上一个文件如果只有几MB,NameNode内存压力会很大。
这里说一下Hive执行流程这块的概念理解:一条Hive SQL最终会被翻译成MapReduce或Tez任务执行。用户通过beeline提交SQL到HiveServer2,HiveServer2解析SQL生成执行计划,然后提交给YARN去调度执行。理解这个流程对于排查Hive执行慢的问题特别有帮助——90%的Hive慢查询问题,要么是数据倾斜,要么是扫描了太多无效分区。
5. Spark特征工程与线性回归模型实现
5.1 特征宽表构建
机器学习模型的输入需要一张完整的特征表,每一行代表一天某个城市的天气情况,每一列是模型可用的特征。我在Hive里做了一张ADS层特征宽表,核心是构建时序特征和天气特征。
模型特征设计是预测效果好坏的关键。我在这里设计了以下几个特征组合:
- 时间特征:月份、星期几,因为季节和星期都会影响气温。
- 最近气温特征:前一天的最高气温、最低气温、平均气温。天气有惯性,今天的气温和昨天高度相关。
- 综合气象特征:湿度、气压、风级、天气类型,这些字段全部作为特征输入。
- 滞后特征:过去3天的平均高温,用来捕捉短期气候趋势。
构建特征宽表的SQL大致如下:
sql复制-- 为每个城市每天构建特征宽表
INSERT OVERWRITE TABLE weather_ads.ads_weather_features PARTITION (dt='2024-01-15')
SELECT
city_code,
month,
day_of_week,
humidity,
pressure,
wind_level,
-- 昨天和前天的高温,构建滞后特征
LAG(high_temp, 1) OVER (PARTITION BY city_code ORDER BY dt) AS prev_day_high,
LAG(high_temp, 2) OVER (PARTITION BY city_code ORDER BY dt) AS prev2_day_high,
LAG(low_temp, 1) OVER (PARTITION BY city_code ORDER BY dt) AS prev_day_low,
-- 真实值作为label
high_temp AS label_high,
low_temp AS label_low
FROM weather_dwd.dwd_weather_clean
WHERE dt >= DATE_SUB('2024-01-15', 7)
AND dt <= '2024-01-15';
这段SQL用了窗口函数LAG来构建滞后特征,这是时序预测里面最常见的特征工程方法。如果用Pandas做同样的操作,数据量大了内存根本扛不住,但Hive的窗口函数天然支持分布式计算,几百万行的表也就几十秒跑完。
5.2 Spark读取Hive表与数据预处理
Spark读取Hive表非常方便,前提是之前把hive-site.xml放进Spark的conf目录做完了集成。我先把特征宽表读出来转成DataFrame,再做特征转换和标准化处理:
scala复制import org.apache.spark.ml.feature.{StringIndexer, VectorAssembler, StandardScaler}
import org.apache.spark.ml.regression.{LinearRegression, LinearRegressionModel}
import org.apache.spark.sql.SparkSession
// 创建SparkSession并启用Hive支持
val spark = SparkSession.builder()
.appName("WeatherPrediction")
.config("spark.sql.warehouse.dir", "/user/hive/warehouse")
.enableHiveSupport()
.getOrCreate()
// 读取ADS层特征数据
val featureDF = spark.sql("""
SELECT
month, day_of_week, humidity, pressure, wind_level,
prev_day_high, prev2_day_high, prev_day_low,
label_high
FROM weather_ads.ads_weather_features
WHERE dt = '2024-01-15'
""")
// 把天气类型转为数值
val weatherIndexer = new StringIndexer()
.setInputCol("weather_type")
.setOutputCol("weather_index")
.fit(featureDF)
val indexedDF = weatherIndexer.transform(featureDF)
5.3 线性回归模型训练与评估
数据准备好了之后,模型训练本身反而简单。Spark MLlib的LinearRegression用法非常标准化:
scala复制// 组合所有特征
val featureCols = Array("month", "day_of_week", "humidity", "pressure",
"wind_level", "prev_day_high", "prev2_day_high", "prev_day_low", "weather_index")
val assembler = new VectorAssembler()
.setInputCols(featureCols)
.setOutputCol("features")
val assembledDF = assembler.transform(indexedDF)
// 划分训练集和测试集
val Array(trainData, testData) = assembledDF.randomSplit(Array(0.8, 0.2), seed = 42)
// 创建线性回归模型
val lr = new LinearRegression()
.setFeaturesCol("features")
.setLabelCol("label_high")
.setMaxIter(100)
.setRegParam(0.1)
.setElasticNetParam(0.8)
// 训练模型
val lrModel = lr.fit(trainData)
// 在测试集上评估
val predictions = lrModel.transform(testData)
val evaluator = new RegressionEvaluator()
.setLabelCol("label_high")
.setPredictionCol("prediction")
.setMetricName("rmse")
val rmse = evaluator.evaluate(predictions)
val r2 = evaluator.setMetricName("r2").evaluate(predictions)
println(s"训练集 RMSE = $rmse")
println(s"R2 Score = $r2")
// 输出模型系数
println("模型系数: " + lrModel.coefficients.mkString(", "))
println("模型截距: " + lrModel.intercept)
// 保存模型
lrModel.write.overwrite().save("/weather/model/linear_regression_high_temp")
这里有几个关键Hyperparameter需要根据自己的数据情况调整:
- setMaxIter(100):最大迭代次数,线性回归用的是迭代优化算法。如果loss不收敛,可以考虑调大到200。
- setRegParam(0.1):正则化参数,防止过拟合。天气数据特征不多,0.1的惩罚力度比较合适。
- setElasticNetParam(0.8):ElasticNet混合参数,0.8表示混合L1和L2惩罚,偏向Lasso回归。设置L1正则化可以起到特征选择作用,让不重要的特征权重逼近0。
我实测下来的结果,对于最高温度预测,R2在0.85左右,RMSE在2.3度左右,这个精度作为基线模型已经可以接受。而且通过输出的特征权重可以发现,前一天的温度和气压对预测结果影响最大,这也能帮我们验证模型是否学习到了合理的天气规律。
5.4 预测结果存储与回写
模型预测出结果后,要把数据写回Hive,供可视化层查询展示:
scala复制import org.apache.spark.sql.SaveMode
// 预测结果回写Hive
predictions.select(
$"city_code",
$"dt",
$"label_high".alias("actual_high"),
$"prediction".alias("predicted_high")
).write
.mode(SaveMode.Overwrite)
.saveAsTable("weather_ads.ads_weather_prediction")
这里我建议把真实温度和预测温度放在同一张表,方便后续做对比可视化。如果需要按不同日期分区,也可以改成INSERT OVERWRITE TABLE xxx PARTITION(dt=xxx)的写法。
6. 可视化展示与前后端联动
6.1 后端服务接口设计
后端我用了Spring Boot,通过MyBatis连接Hive元数据库MySQL来查询ADS层的数据。写一个简单的REST API,前端通过HTTP调用即可:
java复制@RestController
@RequestMapping("/api/weather")
public class WeatherController {
@Autowired
private WeatherService weatherService;
// 查询某城市的温度预测趋势
@GetMapping("/prediction")
public Result prediction(@RequestParam String cityCode,
@RequestParam String startDate,
@RequestParam String endDate) {
List<WeatherPredictionVO> list = weatherService.getPredictionList(cityCode, startDate, endDate);
return Result.success(list);
}
// 查询历史气象数据
@GetMapping("/history")
public Result history(@RequestParam String cityCode,
@RequestParam String date) {
return Result.success(weatherService.getHistoryData(cityCode, date));
}
}
后端接口注意一点:不要直接把Hive表数据全量返回给前端。数据量大的时候会导致前端渲染卡顿。正确做法是接口层做聚合和分页,比如只返回最近30天的预测数据。
6.2 前端可视化大屏
可视化我用了ECharts,它对于温度曲线、柱状图这类图表支持很好,配置也简单。核心展示内容有四个部分:
- 近30天温度趋势折线图:实际温度和预测温度两条曲线做对比,可以直观看到模型预测的偏差。
- 气象要素面板:展示气压、湿度、风速的实时值和历史变化。
- 城市对比柱状图:对比不同城市同一时间的预测温度差异。
- 模型评估指标卡:显示R2分数、RMSE等模型指标。
前端代码的核心部分就是ECharts的option配置,把后端返回的JSON数据映射到xAxis和series里就行了。需要注意的是,ECharts在数据为空的情况下容易渲染异常,建议前端加一层数据判空逻辑。
7. 常见问题与排查技巧实录
7.1 HDFS数据块副本不足问题
这是最常遇到的一个报错,表现为向HDFS上传文件后一直显示副本状态为UNDER_REPLICATED。原因和处理办法如下:
code复制现象:hdfs dfsadmin -report显示副本数为1,但上传文件后一直不复制
原因:伪分布式模式下datanode只有一个,无法满足默认的3副本要求
解决:把hdfs-site.xml里的dfs.replication设为1,重启HDFS
7.2 Spark任务OOM内存溢出
处理几百万行特征数据的时候,如果Spark Executor内存配置太小,很容易报Java heap space。我的经验是,如果数据量大,先把Spark的executor内存调到2G以上:
code复制spark.executor.memory=2g
spark.driver.memory=2g
如果加了内存还是OOM,那就是代码问题而不是配置问题。检查一下是不是有groupBy操作产生了大量数据倾斜分区,或者缓存了过大的DataFrame。
7.3 Hive分区表查不到新数据
每次爬虫写完新数据后,Hive表如果查不到,大概率是分区的元数据没有更新。Hive分区表不会自动发现新加的文件,必须执行MSCK REPAIR TABLE或者手动ALTER TABLE ADD PARTITION。我的调度脚本里直接用MSCK自动修复:
sql复制MSCK REPAIR TABLE weather_ods.ods_weather_raw;
7.4 中文乱码问题
爬虫和Hive交互的时候,经常出现中文乱码。这个问题根源是编码不一致:Python爬虫输出的是UTF-8,Hive终端如果默认使用GBK,读出来就是乱码。解决方法是建表的时候明确指定字符集,或者在使用beeline时加上编码设置:
code复制-- Hive建表指定中文编码
CREATE EXTERNAL TABLE ...
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES ('field.delim'=',', 'serialization.encoding'='UTF-8')
7.5 Hive SQL执行失败jar包报错
还有一个小坑:启动Hive或者SparkSQL的时候,偶尔会报jar包不存在的错误。这是因为不同组件之间的版本依赖问题。我建议在安装新组件时,统一检查一下各组件lib目录下有没有重复或冲突的jar包。比如Spark和Hive的lib目录下可能会同时存在不同版本的log4j等基础库,这里需要手工清理版本保持统一。
8. 项目扩展方向与实战心得总结
这套系统虽然叫天气预测,但整个架构完全可以套用到其他大数据应用场景。我在做这个项目的过程中,几个比较深的体会分享给大家:
关于架构设计:大数据项目里很多问题不是某个组件的问题,而是数据流设计的问题。比如爬虫写数据到HDFS,如果数据文件命名不规范、目录结构混乱,后续Hive做分区管理会非常痛苦。项目一开始就要设计好目录结构和分区规则。
关于调优思路:Hive跑得慢,先看是不是扫描的数据量太大了;Spark跑OOM,先看是不是数据倾斜了;模型效果差,先看特征工程是不是做扎实了。按照这个排查思路,大部分问题都能快速定位。
关于学习路线:以这个项目为抓手,可以把大数据生态里最核心的几个组件串起来。Hadoop解决存储问题,Hive解决SQL处理问题,Spark解决计算和机器学习问题。把这些组件配合起来用一遍,比单独看任何一个框架的文档收获都大得多。
后续如果还想扩展,可以往两个方向走。一是换更复杂的模型,比如LSTM做时序预测,对比线性回归的效果提升;二是引入实时流处理,用Kafka接实时气象数据源,Spark Streaming做实时预测,让系统的时效性进一步提升。
最后再分享一个小技巧:教学演示或课程答辩的时候,建议提前准备几个小Demo数据文件放在HDFS上作为备用。现场跑一遍完整流程虽然很有说服力,但如果临时翻车,有一个备用的静态数据集,演示就不会太尴尬。我自己就遇到过演示前网络抽风爬虫跑不通的情况,那时候才发现备用数据有多重要。
