簡體   English   中英

合並帶有單個標題的 Spark 輸出 CSV 文件

[英]Merge Spark output CSV files with a single header

我想在 AWS 中創建一個數據處理管道,最終將處理后的數據用於機器學習。

我有一個 Scala 腳本,它從 S3 獲取原始數據,處理它並將其寫入 HDFS 甚至 S3 與Spark-CSV 如果我想使用AWS 機器學習工具來訓練預測模型,我想可以使用多個文件作為輸入。 但是如果我想使用其他東西,我認為最好是收到一個 CSV 輸出文件。

目前,由於我不想使用repartition(1)coalesce(1)來達到性能目的,因此我使用了hadoop fs -getmerge進行手動測試,但由於它只是合並了作業輸出文件的內容,因此我遇到了一個小問題。 我需要數據文件中的一行標題來訓練預測模型。

如果我對 spark-csv 使用.option("header","true") ,那么它會將標頭寫入每個輸出文件,合並后,數據中的標頭行數與輸出文件的行數一樣多。 但是如果 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的最后一個參數,但這仍然多次添加標題,而不是一次。

你可以像這樣四處走動。

  • 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. 使用 dataframe.schema ( val header = dataDF.schema.fieldNames.reduce(_ + "," + _)) 輸出標頭
  2. 在 dsefs 上創建一個帶有標題的文件
  3. 使用 hadoop Filesystem API 將所有分區文件(無頭文件)附加到 #2 中的文件

要將文件夾中的文件合並為一個文件:

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)
}

如果要將所有文件合並為一個文件,但仍位於同一文件夾中(但這會將所有數據帶到驅動程序節點):

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

另一種解決方案是使用解決方案#2,然后將文件夾內的一個文件移動到另一個路徑(使用我們的 CSV 文件的名稱)。

def df2csv(df: DataFrame, fileName: String, sep: String = ",", header: Boolean = false): Unit = {
    val tmpDir = "tmpDir"

    df.repartition(1)
      .write
      .format("com.databricks.spark.csv")
      .option("header", header.toString)
      .option("delimiter", sep)
      .save(tmpDir)

    val dir = new File(tmpDir)
    val tmpCsvFile = tmpDir + File.separatorChar + "part-00000"
    (new File(tmpCsvFile)).renameTo(new File(fileName))

    dir.listFiles.foreach( f => f.delete )
    dir.delete
}

嘗試指定標題的架構並使用 spark-csv 格式錯誤的選項 drop 從文件夾中讀取所有文件。 這應該讓您讀取文件夾中的所有文件,只保留標題(因為您刪除了格式錯誤)。 示例:

val headerSchema = List(
  StructField("example1", StringType, true),
  StructField("example2", StringType, true),
  StructField("example3", StringType, true)
)

val header_DF =sqlCtx.read
  .option("delimiter", ",")
  .option("header", "false")
  .option("mode","DROPMALFORMED")
  .option("inferSchema","false")
  .schema(StructType(headerSchema))
  .format("com.databricks.spark.csv")
  .load("folder containg the files")

在 header_DF 中,您將只有標題的行,由此您可以按照需要的方式轉換數據幀。

我們遇到了類似的問題,按照以下方法獲取單個輸出文件-

  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)時與合並或重新分區相關的性能問題。 第二步提供帶有一個標題行的單個輸出文件。

 // 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/

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM