Scala 如何将源文件名添加到Spark中的每一行?

Scala 如何将源文件名添加到Spark中的每一行?,scala,apache-spark,Scala,Apache Spark,我是Spark的新手,正在尝试向每个输入行插入一列,其中包含它来自的文件名 我见过其他人问过类似的问题,但他们的答案都使用了wholeTextFile,但我尝试对更大的CSV文件(使用Spark CSV库读取)、JSON文件和拼花文件(不仅仅是小文本文件)这样做 我可以使用sparkshell获取文件名列表: val df = sqlContext.read.parquet("/blah/dir") val names = df.select(inputFileName()) names.sho

我是Spark的新手,正在尝试向每个输入行插入一列,其中包含它来自的文件名

我见过其他人问过类似的问题,但他们的答案都使用了
wholeTextFile
,但我尝试对更大的CSV文件(使用Spark CSV库读取)、JSON文件和拼花文件(不仅仅是小文本文件)这样做

我可以使用
sparkshell
获取文件名列表:

val df = sqlContext.read.parquet("/blah/dir")
val names = df.select(inputFileName())
names.show
但这是一个数据帧。
我不知道如何将其作为列添加到每一行中(如果结果的顺序与初始数据相同,尽管我假设它总是相同的),以及如何将其作为所有输入类型的通用解决方案

从文本文件创建RDD时,可能需要将数据映射到case类中,以便在该阶段添加输入源:

case class Person(inputPath: String, name: String, age: Int)
val inputPath = "hdfs://localhost:9000/tmp/demo-input-data/persons.txt"
val rdd = sc.textFile(inputPath).map {
    l =>
      val tokens = l.split(",")
      Person(inputPath, tokens(0), tokens(1).trim().toInt)
  }
rdd.collect().foreach(println)
如果您不想将“业务数据”与元数据混合使用:

case class InputSourceMetaData(path: String, size: Long)
case class PersonWithMd(name: String, age: Int, metaData: InputSourceMetaData)

// Fake the size, for demo purposes only
val md = InputSourceMetaData(inputPath, size = -1L)
val rdd = sc.textFile(inputPath).map {
  l =>
    val tokens = l.split(",")
    PersonWithMd(tokens(0), tokens(1).trim().toInt, md)
}
rdd.collect().foreach(println)
如果将RDD升级为数据帧:

import sqlContext.implicits._
val df = rdd.toDF()
df.registerTempTable("x")
你可以像这样查询它

sqlContext.sql("select name, metadata from x").show()
sqlContext.sql("select name, metadata.path from x").show()
sqlContext.sql("select name, metadata.path, metadata.size from x").show()
更新

您可以使用
org.apache.hadoop.fs.FileSystem.listFiles()
递归地读取HDFS中的文件

给定值
files
(包含
org.apache.hadoop.fs.LocatedFileStatus的标准Scala集合)中的文件名列表,您可以为每个文件创建一个RDD:

val rdds = files.map { f =>
  val md = InputSourceMetaData(f.getPath.toString, f.getLen)

  sc.textFile(md.path).map {
    l =>
      val tokens = l.split(",")
      PersonWithMd(tokens(0), tokens(1).trim().toInt, md)
  }
}
现在,您可以
reduce
将RDD列表缩减为单个RDD:reduce
函数将所有RDD合并为单个RDD:

val rdd = rdds.reduce(_ ++ _)
rdd.collect().foreach(println)

这是可行的,但我无法测试它是否能很好地分发/处理大型文件。

我刚刚找到的另一个解决方案是将文件名添加为DataFrame中的一列

val df = sqlContext.read.parquet("/blah/dir")

val dfWithCol = df.withColumn("filename",input_file_name())
参考:

为什么要/需要它?每个记录都需要显示它原来是哪个文件。。。当你知道它经过的整个路径(比如一个格式错误的输入文件)时,调试起来就更容易了。我当然很欣赏这一点,但唯一的问题是你必须指定输入文件的完整路径和文件名。我只是指定输入目录,将其中的所有输入文件都拉入其中。您当前使用的是哪个函数?是不是
wholeTextFiles()
?对于CSV文件,我使用的是databricks/spark CSV库
sqlContext.read.format(“com.databricks.spark.CSV”).load(“/path/dir/”)
。对于拼花地板文件,请使用
sqlContext.read.parquet(“/path/parquetdir/”)
。我已更新了显示一般方法的答案。如果加载文件后重新分区(1),是否可以获取文件名?