公司遇到一点需求,平时load文件基本上都是csv格式的文件,可是就有那么一个文件是xml文件,这也正常,因为文件是别的team推过来的,自然要遵循他们的格式,于是就要想办法解析xml文件。
目标是把xml文件转换为DataFrame,然后写到表中。
可是spark.reader并没有读取xml格式文件的方法,于是需要看有没有别的jar包辅助完成这项任务。
百度google搜索之后,确实发现有一个jar包可以解决
groupId: com.databricks
artifactId: spark-xml_2.11
version: 0.5.0
网上也有很多例子,但数官网将的比较清楚https://github.com/databricks/spark-xml
这上边的例子大家一看就会明白,也许能解决80%的问题,但是没能解决我的问题。
原因如下:
例子中的xml文件格式太简单,实际工作中的文件结构会很复杂,但是,例子中没有给出一个例子来处理复杂结构的xml文件。
继续找,找到一篇文件介绍了复杂文件结构如何解析,其实解析的方式到是一样的,只是选哪个节点作为root节点,以及怎样把嵌套的数组等拉平,此类API也许第一次没有接触过,不知道怎么使用。下面就来举一个例子。
<Item>
<CDate>2018-05-08T00:00::00</CDate>
<ListItemData>
<ItemData>
<IdKey>2</IdKeyData>
<Value>1</Value>
</ItemData>
<ItemData>
<IdKey>61</IdKeyData>
<Value>2</Value>
</ItemData>
<ListItemData>
</Item>
这里这个例子就比官网的books.xml复杂,此时就不知道选谁作为rootTag,如果选择ItemData,那么无法获取CDate,如果选择Item,那么怎么把ItemData展开?
val innerSchema = StructType(
StructField("ItemData",
ArrayType(
StructType(
StructField("IdKey",LongType,true)::
StructField("Value",LongType,true)::Nil
)
),true)::Nil
)
val schema = StructType(
StructField("CDate",StringType,true)::
StructField("ListItemData", innerSchema, true):: Nil
)
import spark.implicits._
val df = spark.read.format("com.databricks.spark.xml")
.option("rowTag", "Item")
.schema(schema)
.load(xmlFile)
//Selecy nested field and explode to get the flattern result
//把ItemData拉平
.withColumn("ItemData", explode($"ListItemData.ItemData"))
.select("CDate", "ItemData.*") // select required column
结果如下:
+--------------------+-----+-----+
|CDate |IdKey|Value|
+--------------------+-----+-----+
|2018-05-08T00:00::00|2 |1 |
|2018-05-08T00:00::00|61 |2 |
+--------------------+-----+-----+
当然也可以省去schema,spark会根据xml文件推断schema
import spark.implicits._
val df = spark.read.format("com.databricks.spark.xml")
.option("rowTag", "Item")
//.schema(schema)
.load(xmlFile)
.withColumn("ItemData", explode($"ListItemData.ItemData"))
.select("CDate", "ItemData.*")
注意:
问题1: 如果省去shcema会有什么问题?
由于spark会根据xml文件自动推断schema,如果xml文件局部节点不完整,不会有问题,如果全部文件都少掉了一个节点,那么推断出来的shcema将得不到你想要的完整的schema,例如:
<Item>
<CDate>2018-05-08T00:00::00</CDate>
<ListItemData>
<ItemData>
<IdKey>2</IdKeyData>
<Value>1</Value>
</ItemData>
<ItemData>
<IdKey>61</IdKeyData>
</ItemData>
<ListItemData>
</Item>
这个文件依然能推断出schema为:
root
|-- CDate: string (nullable = true)
|-- ListItemData: struct (nullable = true)
| |-- ItemData: array (nullable = true)
| | |-- element: struct (containsNull = true)
| | | |-- IdKey: string (nullable = true)
| | | |-- Value: string (nullable = true)
但是下面的文件:
<Item>
<CDate>2018-05-08T00:00::00</CDate>
<ListItemData>
<ItemData>
<IdKey>2</IdKeyData>
</ItemData>
<ItemData>
<IdKey>61</IdKeyData>
</ItemData>
<ListItemData>
</Item>
就不能推断出有Value节点,如果你要使用Value字段,将会报错,没有Value字段
root
|-- CDate: string (nullable = true)
|-- ListItemData: struct (nullable = true)
| |-- ItemData: array (nullable = true)
| | |-- element: struct (containsNull = true)
| | | |-- IdKey: string (nullable = true)
如果你强制给他指定schema,那么就会为Value填充null值,但是不会报错Value字段不存在
强制指定schema后,schema为:
root
|-- CDate: string (nullable = true)
|-- ListItemData: struct (nullable = true)
| |-- ItemData: array (nullable = true)
| | |-- element: struct (containsNull = true)
| | | |-- IdKey: string (nullable = true)
| | | |-- Value: string (nullable = true)
问题2:如何给字段重命名?
import spark.implicits._
val df = spark.read.format("com.databricks.spark.xml")
.option("rowTag", "Item")
//.schema(schema)
.load(xmlFile)
.withColumn("ItemData", explode($"ListItemData.ItemData"))
.select("CDate",
"ItemData.IdKey as key",
"ItemData.Value as value"
)
这样会报错,无法解析ItemData.IdKey 和ItemData.Value,使用如下方式即可:selectExpr
import spark.implicits._
val df = spark.read.format("com.databricks.spark.xml")
.option("rowTag", "Item")
//.schema(schema)
.load(xmlFile)
.withColumn("ItemData", explode($"ListItemData.ItemData"))
.selectExpr("CDate",
"ItemData.IdKey as key",
"ItemData.Value as value"
)
参考:
https://stackoverflow.com/questions/50237710/parsing-xml-files-with-scala