web-dev-qa-db-fra.com

Écrire un seul fichier CSV en utilisant spark-csv

J'utilise https://github.com/databricks/spark-csv , j'essaie d'écrire un seul fichier CSV, mais je ne le peux pas, un dossier est créé.

Besoin d'une fonction Scala qui prendra les paramètres tels que chemin et nom de fichier et écrira ce fichier CSV.

84
user1735076

Il crée un dossier avec plusieurs fichiers, car chaque partition est enregistrée individuellement. Si vous avez besoin d'un seul fichier de sortie (toujours dans un dossier), vous pouvez repartition (préférable si les données en amont sont volumineuses, mais nécessitent une lecture aléatoire):

df
   .repartition(1)
   .write.format("com.databricks.spark.csv")
   .option("header", "true")
   .save("mydata.csv")

ou coalesce:

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

trame de données avant de sauvegarder:

Toutes les données seront écrites sur mydata.csv/part-00000. Avant d’utiliser cette option , assurez-vous de bien comprendre ce qui se passe et quel est le coût du transfert de toutes les données à un seul opérateur . Si vous utilisez un système de fichiers distribué avec réplication, les données seront transférées plusieurs fois. Elles seront d'abord extraites vers un seul opérateur, puis distribuées sur des nœuds de stockage.

Sinon, vous pouvez laisser votre code tel quel et utiliser des outils d'usage général tels que cat ou HDFS getmerge pour simplement fusionner toutes les parties par la suite.

138
zero323

Si vous utilisez Spark avec HDFS, j'ai résolu le problème en écrivant des fichiers csv normalement et en exploitant HDFS pour effectuer la fusion. Je le fais dans Spark (1.6) directement:

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
}


val newData = << create your dataframe >>


val outputfile = "/user/feeds/project/outputs/subject"  
var filename = "myinsights"
var outputFileName = outputfile + "/temp_" + filename 
var mergedFileName = outputfile + "/merged_" + filename
var mergeFindGlob  = outputFileName

    newData.write
        .format("com.databricks.spark.csv")
        .option("header", "false")
        .mode("overwrite")
        .save(outputFileName)
    merge(mergeFindGlob, mergedFileName )
    newData.unpersist()

Je ne me souviens pas où j'ai appris cette astuce, mais cela pourrait fonctionner pour vous.

33
Minkymorgan

Je suis peut-être un peu en retard au jeu ici, mais utiliser coalesce(1) ou repartition(1) peut fonctionner pour de petits ensembles de données, mais les grands ensembles de données seraient tous projetés dans une partition sur un nœud. Cela risque de provoquer des erreurs dans le MOO ou, au mieux, de procéder lentement.

Je vous suggère fortement d'utiliser la fonction FileUtil.copyMerge() de l'API Hadoop. Cela va fusionner les sorties dans un seul fichier.

EDIT - Cela amène efficacement les données au pilote plutôt qu’à un nœud exécuteur. Coalesce() serait acceptable si un seul exécuteur a plus de RAM à utiliser que le pilote.

EDIT 2: copyMerge() est en cours de suppression dans Hadoop 3.0. Voir l'article suivant sur le dépassement de pile pour plus d'informations sur l'utilisation de la version la plus récente: Hadoop comment copier CopyMerge dans Hadoop 3.

22
etspaceman

Si vous utilisez Databricks et pouvez insérer toutes les données dans RAM sur un opérateur (et donc utiliser .coalesce(1)), vous pouvez utiliser dbfs pour rechercher et déplacer le fichier CSV résultant:

val fileprefix= "/mnt/aws/path/file-prefix"

dataset
  .coalesce(1)       
  .write             
//.mode("overwrite") // I usually don't use this, but you may want to.
  .option("header", "true")
  .option("delimiter","\t")
  .csv(fileprefix+".tmp")

val partition_path = dbutils.fs.ls(fileprefix+".tmp/")
     .filter(file=>file.name.endsWith(".csv"))(0).path

dbutils.fs.cp(partition_path,fileprefix+".tab")

dbutils.fs.rm(fileprefix+".tmp",recurse=true)

Si votre fichier ne rentre pas dans RAM sur le poste de travail, vous pouvez envisager suggestion de chaotic3quilibrium d'utiliser FileUtils.copyMerge () . Je n'ai pas fait cela et je ne sais pas encore si c'est possible ou non, par exemple sur S3.

Cette réponse est construite sur les réponses précédentes à cette question ainsi que sur mes propres tests de l'extrait de code fourni. Je l'ai posté à l'origine à Databricks et je le republie ici.

La meilleure documentation que j'ai trouvée sur l'option récursive de rm de dbfs est sur n forum Databricks .

14
Josiah Yoder

Une solution qui fonctionne pour S3 modifié par Minkymorgan.

Passez simplement le chemin du répertoire partitionné temporaire (avec un nom différent du chemin final) en tant que srcPath et le dernier fichier csv/txt en tant que destPath Indiquez également deleteSource si vous souhaitez supprimer le répertoire d'origine.

/**
* Merges multiple partitions of spark text file output into single file. 
* @param srcPath source directory of partitioned files
* @param dstPath output path of individual path
* @param deleteSource whether or not to delete source directory after merging
* @param spark sparkSession
*/
def mergeTextFiles(srcPath: String, dstPath: String, deleteSource: Boolean): Unit =  {
  import org.Apache.hadoop.fs.FileUtil
  import Java.net.URI
  val config = spark.sparkContext.hadoopConfiguration
  val fs: FileSystem = FileSystem.get(new URI(srcPath), config)
  FileUtil.copyMerge(
    fs, new Path(srcPath), fs, new Path(dstPath), deleteSource, config, null
  )
}
2
John Zhu

vous pouvez utiliser rdd.coalesce(1, true).saveAsTextFile(path)

il stockera les données sous forme de fichier unique dans chemin/partie-00000

2
Gourav

repartitionner/fusionner sur 1 partition avant de sauvegarder (vous obtiendrez tout de même un dossier mais il contiendrait un fichier partiel)

2
Arnon Rotem-Gal-Oz

l'API df.write() de spark va créer plusieurs fichiers de pièce dans un chemin donné ... pour forcer spark écrire un seul fichier de pièce, utilisez df.coalesce(1).write.csv(...) au lieu de df.repartition(1).write.csv(...) comme coalesce est un transformation étroite alors que la répartition est une transformation large, voir Spark - repartition () vs coalesce ()

df.coalesce(1).write.csv(filepath,header=True) 

créera un dossier dans un chemin de fichier donné avec un fichier part-0001-...-c000.csv

cat filepath/part-0001-...-c000.csv > filename_you_want.csv 

avoir un nom de fichier convivial

0
prasad gaikwad