如何从Kafka读取XML格式的流数据?

时间:2017-09-01 16:38:24

标签: apache-spark xml-parsing pyspark-sql spark-structured-streaming

我正在尝试使用Spark Structured streaming从Kafka主题中读取XML数据。

我尝试使用Databricks spark-xml包,但是我收到一条错误消息,说明此包不支持流式读取。有什么办法可以使用结构化流媒体从Kafka主题中提取XML数据吗?

我目前的代码:

df = spark \
      .readStream \
      .format("kafka") \
      .format('com.databricks.spark.xml') \
      .options(rowTag="MainElement")\
      .option("kafka.bootstrap.servers", "localhost:9092") \
      .option(subscribeType, "test") \
      .load()

错误:

py4j.protocol.Py4JJavaError: An error occurred while calling o33.load.
: java.lang.UnsupportedOperationException: Data source com.databricks.spark.xml does not support streamed reading
        at org.apache.spark.sql.execution.datasources.DataSource.sourceSchema(DataSource.scala:234)

6 个答案:

答案 0 :(得分:5)

.format("kafka") \
.format('com.databricks.spark.xml') \

com.databricks.spark.xml的最后一个获胜并成为流媒体源(隐藏Kafka作为源)。

换言之,上述内容仅相当于.format('com.databricks.spark.xml')

正如您可能经历的那样,Databricks spark-xml包不支持流式读取(即不能充当流式源)。该软件包不适用于流式传输。

  

有什么办法可以使用结构化流媒体从Kafka主题中提取XML数据吗?

您可以使用标准函数或UDF自行访问和处理XML。结构化流中直到Spark 2.2.0的流式XML处理没有内置支持。

无论如何,这应该不是什么大问题。 Scala代码可能如下所示。

val input = spark.
  readStream.
  format("kafka").
  ...
  load

val values = input.select('value cast "string")

val extractValuesFromXML = udf { (xml: String) => ??? }
val numbersFromXML = values.withColumn("number", extractValuesFromXML('value))

// print XMLs and numbers to the stdout
val q = numbersFromXML.
  writeStream.
  format("console").
  start

另一种可能的解决方案是编写自己的自定义流Source,以处理def getBatch(start: Option[Offset], end: Offset): DataFrame中的XML格式。那个 应该有效。

答案 1 :(得分:2)

import xml.etree.ElementTree as ET
df = spark \
      .readStream \
      .format("kafka") \
      .option("kafka.bootstrap.servers", "localhost:9092") \
      .option(subscribeType, "test") \
      .load()

然后我写了一个python UDF

def parse(s):
  xml = ET.fromstring(s)
  ns = {'real_person': 'http://people.example.com',
      'role': 'http://characters.example.com'}
  actor_el = xml.find("DNmS:actor",ns)

  if(actor_el ):
       actor = actor_el.text
  role_el.find('real_person:role', ns)
  if(role_el):
       role = role_el.text
  return actor+"|"+role

注册此UDF

extractValuesFromXML = udf(parse)

   XML_DF= df .withColumn("mergedCol",extractroot("value"))

   AllCol_DF= xml_DF.withColumn("actorName", split(col("mergedCol"), "\\|").getItem(0))\
        .withColumn("Role", split(col("mergedCol"), "\\|").getItem(1))

答案 2 :(得分:1)

你不能以这种方式混合格式。 Kafka源加载为Row,包括keyvaluetopic等值的数量,其中value列存储payload as a binary type

  

请注意,无法设置以下Kafka参数,Kafka源或接收器将抛出异常:

     

...

     

value.deserializer :值始终使用ByteArrayDeserializer反序列化为字节数组。使用DataFrame操作显式反序列化值。

解析此内容是用户的责任,不能委托给其他数据源。例如,请参阅我对How to read records in JSON format from Kafka using Structured Streaming?的回答。

对于XML,您可能需要一个UDF(UserDefinedFunction),尽管您可以先尝试Hive XPath functions。您还应解码二进制数据。

答案 3 :(得分:0)

使用现有库,

https://github.com/databricks/spark-xml

foreachBatch(Spark 2.4 +)

inputStream.writeStream.foreachBatch { (batchDF: DataFrame, batchId: Long) =>

        var parameters = collection.mutable.Map.empty[String, String]
        var schema: StructType = null

        val rdd:RDD[String] = batchDF.as[String].rdd

        val relation = XmlRelation(
          () => rdd,
          None,
          parameters.toMap,
          schema)(spark.sqlContext)

        spark.baseRelationToDataFrame(relation)
          .write.format("parquet")
          .mode("append")
          .saveAsTable("default.catalog_sink")

    }.start()

spark.baseRelationToDataFrame(relation)将返回在批处理模式下执行spark-xml的所有操作,您可以在该数据帧上使用sparksql来得出所需的确切结果。

答案 4 :(得分:0)

看起来上面的方法可行,但是它没有使用传递的模式来解析XML文档。

如果打印关系模式,则始终为

INFO  XmlToAvroConverter - .convert() : XmlRelation Schema ={} root
 |-- fields: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- name: string (nullable = true)
 |    |    |-- nullable: boolean (nullable = true)
 |    |    |-- type: string (nullable = true)
 |-- type: string (nullable = true)

例如:我正在按照Kafka主题流式传输以下XML文档

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Book>
<Author>John Doe</Author>
<Title>Test</Title>
<PubishedDate></PublishedDate>
</Book>

这是我必须将XML解析为DataFrame的代码

kafkaValueAsStringDF = kafakDF.selectExpr("CAST(key AS STRING) msgKey","CAST(value AS STRING) xmlString")

  var parameters = collection.mutable.Map.empty[String, String]

  parameters.put("rowTag", "Book")

kafkaValueAsStringDF.writeStream.foreachBatch {
          (batchDF: DataFrame, batchId: Long) =>

 val xmlStringDF:DataFrame = batchDF.selectExpr("xmlString")

            xmlStringDF.printSchema()

            val rdd: RDD[String] = xmlStringDF.as[String].rdd


            val relation = XmlRelation(
              () => rdd,
              None,
              parameters.toMap,
              xmlSchema)(spark.sqlContext)


            logger.info(".convert() : XmlRelation Schema ={} "+relation.schema.treeString)

}
        .start()
        .awaitTermination()

当我从文件系统或S3中读取相同的XML文档并使用spark-xml时,它将按预期方式解析架构。

谢谢 卫星

答案 5 :(得分:0)

您可以使用 SQL built-in 函数 xpath 等从作为 Kafka 消息的出现的嵌套 XML 结构中提取数据。

给定一个像嵌套的 XML

<root>
  <ExecutionTime>20201103153839</ExecutionTime>
  <FilterClass>S</FilterClass>
  <InputData>
    <Finance>
      <HeaderSegment>
        <Version>6</Version>
        <SequenceNb>1</SequenceNb>
      </HeaderSegment>
    </Finance>
  </InputData>
</root>

然后您就可以在 selectExpr 语句中使用这些 SQL 函数,如下所示:

df.readStream.format("kafka").options(...).load()
  .selectExpr("CAST(value AS STRING) as value")
  .selectExpr(
    "xpath(value, '/CofiResults/ExecutionTime/text()') as ExecutionTimeAsArryString",
    "xpath_long(value, '/CofiResults/ExecutionTime/text()') as ExecutionTimeAsLong",
    "xpath_string(value, '/CofiResults/ExecutionTime/text()') as ExecutionTimeAsString",
    "xpath_int(value, '/CofiResults/InputData/Finance/HeaderSegment/Version/text()') as VersionAsInt")

请记住,xpath 函数将返回一个 Array 字符串,而您可能会发现将值提取为 String 甚至 Long 会更方便。在带有控制台接收器流的 Spark 3.0.1 中应用上面的代码将导致:

+-------------------------+-------------------+---------------------+------------+
|ExecutionTimeAsArryString|ExecutionTimeAsLong|ExecutionTimeAsString|VersionAsInt|
+-------------------------+-------------------+---------------------+------------+
|[20201103153839]         |20201103153839     |20201103153839       |6           |
+-------------------------+-------------------+---------------------+------------+