当数据源的分隔符混乱或不统一时(如 ,、|、\t 混合使用),可采用以下方法:
1.1 动态检测分隔符
// 示例:自动检测前100行的常用分隔符
val sampleLines = spark.read.text("data.csv").limit(100).collect()
val commonDelimiters = List(",", "|", "\t", ";")
val delimiter = commonDelimiters.maxBy(d => sampleLines.count(_.getString(0).contains(d))
val df = spark.read
.option("sep", delimiter)
.csv("data.csv")
1.2 正则表达式分割
val rawRDD = spark.sparkContext.textFile("data.txt")
val df = rawRDD.map { line =>
val parts = line.split("[,|;\\t]+") // 匹配多种分隔符
(parts(0), parts(1).toInt, parts(2))
}.toDF("name", "age", "city")
1.3 自定义 CSV 解析器
import com.univocity.parsers.csv._
val settings = new CsvParserSettings()
settings.setDelimiterDetectionEnabled(true) // 启用自动检测
settings.setLineSeparatorDetectionEnabled(true)
val parser = new CsvParser(settings)
val rows = spark.sparkContext.parallelize(parser.parseAll(new File("data.csv")))
二、常见难处理数据源格式及解决方案
1. 多行 JSON
JSON 数据跨越多行,标准解析器无法识别。
{
"user": "Alice",
"orders": [
{"id": 1, "amount": 100},
{"id": 2, "amount": 200}
]
}
解决方案:
val df = spark.read
.option("multiLine", true)
.json("data.json")
// 展开嵌套结构
val explodedDF = df.selectExpr("user", "explode(orders) as "order")
.select("user", "order.id", "order.amount")
2. 嵌套 XML
深层嵌套的 XML 结构解析困难。
Alice
New York
10001
解决方法:
val df = spark.read
.format("com.databricks.spark.xml")
.option("rowTag", "user")
.option("attributePrefix", "_")
.load("data.xml")
// 提取嵌套字段
val result = df.select(
$"_id".alias("user_id"),
$"name",
$"address.city",
$"address.zip"
)
3. 非结构化日志
如,无固定格式的日志文件(如 Apache 日志)。
127.0.0.1 - - [10/Oct/2023:13:55:36 +0000] "GET /index.html HTTP/1.1" 200 2326
解决方法:
val logPattern = """^(\S+) (\S+) (\S+) \[([\w:/]+\s[+\-]\d{4})\] "(\S+) (\S+) (\S+)" (\d{3}) (\d+)""".r
val parsedLogs = spark.sparkContext.textFile("access.log")
.flatMap { line =>
logPattern.findFirstMatchIn(line).map { m =>
(m.group(1), m.group(4), m.group(5), m.group(8).toInt)
}
}.toDF("ip", "timestamp", "method", "status_code")
三、多格式文件导入 MySQL 方案
1. 通用导入流程
// 读取数据(以 Parquet 为例)
val df = spark.read.parquet("data.parquet")
// 写入 MySQL
df.write
.format("jdbc")
.option("url", "jdbc:mysql://localhost:3306/mydb")
.option("dbtable", "target_table")
.option("user", "root")
.option("password", "password")
.mode("append") // 可选 overwrite/append
.save()
2. 各格式文件导入示例
2.1 CSV 导入 MySQL
spark.read
.option("delimiter", "|")
.csv("data.csv")
.select($"_c0" as "name", $"_c1" cast "int" as "age")
.write.jdbc(url, "table", properties)
2.2 json导入mysql
spark.read.json("data.json")
.withColumn("created_at", current_timestamp())
.write.jdbc(url, "table", properties)
2.3 复杂 XML 导入 MySQL
spark.read.xml("data.xml")
.selectExpr("xpath_string(address, '/address/city') as city")
.write.jdbc(url, "table", properties)
2.4 流式数据(Kafka)导入 MySQL
val kafkaDF = spark.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "localhost:9092")
.option("subscribe", "topic")
.load()
kafkaDF.selectExpr("CAST(value AS STRING) as json")
.select(from_json($"json", schema) as "data")
.select("data.*")
.writeStream
.foreachBatch { (batchDF: DataFrame, _: Long) =>
batchDF.write.jdbc(url, "table", properties)
}.start()
补充,关于正则表达式的解释
(\S+) (\S+) (\S+) \[([\w:/]+\s[+\-]\d{4})\] "(\S+) (\S+) (\S+)" (\d{3}) (\d+)
匹配字符串开头
(\S+)
第1组:非空字符(IP地址)
(\S+)
第2组:非空字符(通常为-
,占位符)
(\S+)
第3组:非空字符(通常为-
,用户标识)
\[([\w:/]+\s[+\-]\d{4})\]
第4组:时间戳(如10/Oct/2023:13:55:36 +0000
)
"(\S+) (\S+) (\S+)"
第5-7组:请求方法、URI、协议(如GET /index.html HTTP/1.1
)
(\d{3})
第8组:3位数字(HTTP状态码,如200
)
(\d+)
第9组:数字(响应大小,如2326
)
127.0.0.1 - - [10/Oct/2023:13:55:36 +0000] "GET /index.html HTTP/1.1" 200 2326
正则匹配结果
127.0.0.1 | IP地址
占位符(未使用)
用户标识(未使用)
10/Oct/2023:13:55:36 +0000 时间戳
GET | HTTP方法
index.html 请求URI
HTTP/1.1 协议版本
200 状态码
2326 响应大小(字节)