web-dev-qa-db-de.com

Speichern Sie den Inhalt von Spark DataFrame als einzelne CSV-Datei

Angenommen, ich habe einen Spark-DataFrame, den ich als CSV-Datei speichern möchte. Nach Spark 2.0.0 , DataFrameWriter class unterstützt das direkte Speichern als CSV-Datei.

Das Standardverhalten ist das Speichern der Ausgabe in mehreren part - *. Csv - Dateien innerhalb des angegebenen Pfads. 

Wie würde ich ein DF speichern mit:

  1. Pfadzuordnung auf den exakten Dateinamen anstelle des Ordners
  2. Header in der ersten Zeile verfügbar
  3. Speichern Sie als einzelne Datei anstelle mehrerer Dateien.

Eine Möglichkeit, damit umzugehen, besteht darin, die DF zusammenzuführen und die Datei dann zu speichern. 

df.coalesce(1).write.option("header", "true").csv("sample_file.csv")

Dies hat jedoch einen Nachteil beim Sammeln auf dem Master-Computer und es muss ein Master mit genügend Speicherplatz vorhanden sein. 

Ist es möglich, eine einzige CSV-Datei zu schreiben, ohne coalesce zu verwenden? Wenn nicht, gibt es einen effizienteren Weg als den oben genannten Code?

15

Ich habe das selbst mit Hilfe von pyspark mit dbutils gelöst, um die .csv zu erhalten und den gewünschten Dateinamen umzubenennen.

save_location= "s3a://landing-bucket-test/export/"+year
csv_location = save_location+"temp.folder'
file_location = save_location+'export.csv'

df.repartition(1).write.csv(path=csv_location, mode="append", header="true")

file = dbutils.fs.ls(csv_location)[-1].path
dbutils.fs.cp(file, file_location)
dbutils.fs.rm(csv_location, recurse=True)

Diese Antwort kann verbessert werden, indem [-1] nicht verwendet wird. Die .csv-Datei scheint jedoch immer der letzte im Ordner zu sein. Einfache und schnelle Lösung, wenn Sie nur mit kleineren Dateien arbeiten und repartition (1) oder coalesce (1) verwenden können.

6
user1217169

Verwenden Sie: df.toPandas().to_csv("sample_file.csv", header=True)

Für Details siehe Dokumentation: https://spark.Apache.org/docs/latest/api/python/pyspark.sql.html?highlight=dataframe#pyspark.sql.DataFrame.toPandas

3
osbon123

Für alle, die dies noch tun möchten, habe ich es mit Spark 2.1 in scala und Java.nio.file help gemacht.

Basierend auf https://fullstackml.com/how-to-export-data-frame-from-Apache-spark-3215274ee9d6

    val df: org.Apache.spark.sql.DataFrame = ??? // data frame to write
    val file: Java.nio.file.Path = ??? // target output file (i.e. 'out.csv')

    import scala.collection.JavaConversions._

    // write csv into temp directory which contains the additional spark output files
    // could use Files.createTempDirectory instead
    val tempDir = file.getParent.resolve(file.getFileName + "_tmp")
    df.coalesce(1)
        .write.format("com.databricks.spark.csv")
        .option("header", "true")
        .save(tempDir.toAbsolutePath.toString)

    // find the actual csv file
    val tmpCsvFile = Files.walk(tempDir, 1).iterator().toSeq.find { p => 
        val fname = p.getFileName.toString
        fname.startsWith("part-00000") && fname.endsWith(".csv") && Files.isRegularFile(p)
    }.get

    // move to desired final path
    Files.move(tmpCsvFile, file)

    // delete temp directory
    Files.walk(tempDir)
        .sorted(Java.util.Comparator.reverseOrder())
        .iterator().toSeq
        .foreach(Files.delete(_))
1
Thien

Die folgende scala - Methode arbeitet im lokalen Modus oder im Client-Modus und schreibt die Datenbankdatei in eine einzelne CSV des ausgewählten Namens. Es erfordert, dass die df in den Speicher passt, andernfalls wird collect () explodieren. 

import org.Apache.hadoop.fs.{FileSystem, Path}

val SPARK_WRITE_LOCATION = some_directory
val SPARKSESSION = org.Apache.spark.sql.SparkSession

def saveResults(results : DataFrame, filename: String) {
    var fs = FileSystem.get(this.SPARKSESSION.sparkContext.hadoopConfiguration)
    
    if (SPARKSESSION.conf.get("spark.master").toString.contains("local")) {
      fs = FileSystem.getLocal(new conf.Configuration())
    }
    
    val tempWritePath = new Path(SPARK_WRITE_LOCATION)
    
    if (fs.exists(tempWritePath)) {
    
      val x = fs.delete(new Path(SPARK_WRITE_LOCATION), true)
      assert(x)
    }
    
    if (results.count > 0) {
      val hadoopFilepath = new Path(SPARK_WRITE_LOCATION, filename)
      val writeStream = fs.create(hadoopFilepath, true)
      val bw = new BufferedWriter( new OutputStreamWriter( writeStream, "UTF-8" ) )
    
      val x = results.collect()
      for (row : Row <- x) {
        val rowString = row.mkString(start = "", sep = ",", end="\n")
        bw.write(rowString)
      }
    
      bw.close()
      writeStream.close()
    
      val resultsWritePath = new Path(WRITE_DIRECTORY, filename)
    
      if (fs.exists(resultsWritePath)) {
        fs.delete(resultsWritePath, true)
      }
      fs.copyToLocalFile(false, hadoopFilepath, resultsWritePath, true)
    } else {
      System.exit(-1)
    }
}

1
Bryan Davis

Diese Lösung basiert auf einem Shell-Skript und ist nicht parallelisiert, ist jedoch besonders auf SSDs sehr schnell. Es verwendet cat und die Umleitung von Ausgaben auf Unix-Systemen. Angenommen, das CSV-Verzeichnis, das Partitionen enthält, befindet sich in /my/csv/dir und die Ausgabedatei ist /my/csv/output.csv:

#!/bin/bash
echo "col1,col2,col3" > /my/csv/output.csv
for i in /my/csv/dir/*.csv ; do
    echo "Processing $i"
    cat $i >> /my/csv/output.csv
    rm $i
done
echo "Done"

Jede Partition wird nach dem Anhängen an die endgültige CSV entfernt, um Speicherplatz freizugeben.

"col1,col2,col3" ist der CSV-Header (hier haben wir drei Spalten mit den Namen col1, col2 und col3). Sie müssen Spark anweisen, den Header nicht in jede Partition einzufügen (dies wird mit .option("header", "false") durchgeführt, da das Shell-Skript dies tut.

1
pietrop

So funktioniert verteiltes Computing! Mehrere Dateien in einem Verzeichnis sind genau so, wie das verteilte Computing funktioniert. Dies ist überhaupt kein Problem, da alle Programme damit umgehen können.

Ihre Frage sollte lauten "Wie kann ein CSV aus mehreren Dateien heruntergeladen werden?" -> In SO gibt es bereits viele Lösungen.

Ein anderer Ansatz könnte darin bestehen, Spark als JDBC-Quelle (mit dem fantastischen Spark Thrift-Server) zu verwenden, eine SQL-Abfrage zu schreiben und das Ergebnis in CSV umzuwandeln. 

Verwenden Sie inkrementelle Erfassung .__, um OOM im Treiber zu verhindern (da der Treiber ALL Die Daten erhält). (spark.sql.thriftServer.incrementalCollect=true), weitere Informationen unter http://www.russellspitzer.com/2017/05/19/Spark-Sql-Thriftserver/ .


Kleine Zusammenfassung zum Spark-Konzept "Datenpartition":

INPUT (X PARTITIONs) -> COMPUTING (Y PARTITIONs) -> OUTPUT (Z PARTITIONs)

Zwischen "Stufen" können Daten zwischen Partitionen übertragen werden, dies ist der "Shuffle". Sie wollen "Z" = 1, aber mit Y> 1, ohne Shuffle? das ist unmöglich.

0
Thomas Decaux
df.coalesce(1).write.option("inferSchema","true").csv("/newFolder",header = 
'true',dateFormat = "yyyy-MM-dd HH:mm:ss")
0
manny

Das FileUtil.copyMerge () von der Hadoop-API sollte Ihr Problem lösen. 

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), true, hadoopConfig, null) 
   // the "true" setting deletes the source files once they are merged into the new output
}

Siehe Einzelne CSV-Datei mit spark-csv schreiben

0
shants