使用单个标头合并Spark输出CSV文件

时间:2016-06-27 14:09:57

标签: scala csv hadoop apache-spark

我想在AWS中创建一个数据处理管道,最终将处理后的数据用于机器学习。

我有一个Scala脚本,它从S3获取原始数据,处理它并使用 Spark-CSV 将其写入HDFS甚至S3。我想如果我想使用 AWS Machine Learning 工具来训练预测模型,我可以使用多个文件作为输入。但是如果我想使用别的东西,我认为最好是收到一个CSV输出文件。

目前,由于我不想使用重新分区(1)合并(1)来达到性能目的,我使用了 hadoop fs -getmerge < / strong>用于手动测试,但因为它只是合并了作业输出文件的内容,所以我遇到了一个小问题。我需要在数据文件中使用单行标题来训练预测模型。

如果我使用.option("header","true")作为spark-csv,那么它会将标头写入每个输出文件,并且在合并之后我在数据中有与输出文件一样多的标题行。但是如果header选项为false,则它不会添加任何标题。

现在我找到了一个选项,可以将Scala脚本中的文件与Hadoop API FileUtil.copyMerge合并。我在spark-shell中尝试了以下代码。

import org.apache.hadoop.fs.FileUtil
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
val configuration = new Configuration();
val fs = FileSystem.get(configuration);
FileUtil.copyMerge(fs, new Path("smallheaders"), fs, new Path("/home/hadoop/smallheaders2"), false, configuration, "")

但是这个解决方案仍然只是将文件连接在一起,并且不处理标题。 如何获得只有一行标题的输出文件?

我甚至尝试添加df.columns.mkString(",")作为copyMerge的最后一个参数,但这仍然多次添加标题,而不是一次。

6 个答案:

答案 0 :(得分:3)

您可以像这样走动。

  • 1。创建一个包含标题名称的新DataFrame(headerDF)。
  • 2。与包含数据的DataFrame(dataDF)合并。
  • 3。使用 option(“ header”,“ false”)将联合的DataFrame输出到磁盘。
  • 4。使用hadoop FileUtil合并分区文件(part-0000 ** 0.csv)

通过这种方式,所有分区都没有标题,除了单个分区的内容具有来自headerDF的标题名称行。当所有分区合并在一起时,文件顶部只有一个标头。示例代码如下

  //dataFrame is the data to save on disk
  //cast types of all columns to String
  val dataDF = dataFrame.select(dataFrame.columns.map(c => dataFrame.col(c).cast("string")): _*)

  //create a new data frame containing only header names
  import scala.collection.JavaConverters._
  val headerDF = sparkSession.createDataFrame(List(Row.fromSeq(dataDF.columns.toSeq)).asJava, dataDF.schema)

  //merge header names with data
  headerDF.union(dataDF).write.mode(SaveMode.Overwrite).option("header", "false").csv(outputFolder)

  //use hadoop FileUtil to merge all partition csv files into a single file
  val fs = FileSystem.get(sparkSession.sparkContext.hadoopConfiguration)
  FileUtil.copyMerge(fs, new Path(outputFolder), fs, new Path("/folder/target.csv"), true, spark.sparkContext.hadoopConfiguration, null)

答案 1 :(得分:1)

  1. 使用dataframe.schema输出标头 (val header = dataDF.schema.fieldNames.reduce(_ +“,” + _))
  2. 使用dsefs上的标头创建文件
  3. 使用hadoop Filesystem API将所有分区文件(无头)附加到#2中的文件中

答案 2 :(得分:0)

将文件夹中的文件合并到一个文件中:

Models

如果要将所有文件合并到一个文件中,但仍在同一文件夹中(这会将所有数据都带到驱动程序节点):

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs._

def merge(srcPath: String, dstPath: String): Unit =  {
  val hadoopConfig = new Configuration()
  val hdfs = FileSystem.get(hadoopConfig)
  FileUtil.copyMerge(hdfs, new Path(srcPath), hdfs, new Path(dstPath), false, hadoopConfig, null)
}

另一个解决方案是使用解决方案#2然后将文件夹中的一个文件移动到另一个路径(使用我们的CSV文件的名称)。

dataFrame
      .coalesce(1)
      .write
      .format("com.databricks.spark.csv")
      .option("header", "true")
      .save(out)

答案 3 :(得分:0)

尝试使用选项drop malformed of spark-csv指定标题的架构并从文件夹中读取所有文件。这应该让你读取文件夹中的所有文件,只保留标题(因为你丢弃了格式错误)。 例如:

DT_DIR

在header_DF中,您将只拥有标题行,您可以根据需要对数据帧进行转换。

答案 4 :(得分:0)

按照以下方法获取单个输出文件,我们遇到了类似的问题-

  1. 在转换后,不使用coalescerepartition将数据帧写入具有标题的hdfs。
dataframe.write.format("csv").option("header", "true").save(hdfs_path_for_multiple_files)
  1. 读取上一步中的文件,并使用coalesce(1)写回hdfs上的其他位置。
dataframe = spark.read.option('header', 'true').csv(hdfs_path_for_multiple_files)

dataframe.coalesce(1).write.format('csv').option('header', 'true').save(hdfs_path_for_single_file)

这样,您将避免在执行转换时(步骤1)与合并或重新分区有关的性能问题。 第二步,提供一个标题行的单个输出文件。

答案 5 :(得分:-4)

 // Convert JavaRDD  to CSV and save as text file
        outputDataframe.write()
                .format("com.databricks.spark.csv")
                // Header => true, will enable to have header in each file
                .option("header", "true")

请关注如何编写单个标题的集成测试链接

  

http://bytepadding.com/big-data/spark/write-a-csv-text-file-from-spark/