scala针对复杂数据源导入与分隔符乱码处理

复杂的数据源,和奇怪的数据格式是生产中经常遇到的难题,本文将探讨如何解析分隔符混乱的数据,和如何导入各种数据源文件
一、 非标准分隔符处理

当数据源的分隔符混乱或不统一时(如 ,、|、\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  响应大小(字节)

 

你可能感兴趣的:(scala,javascript,后端,java,数据结构)