Spark-csv kullanarak tek bir CSV dosyası yazın


Yanıtlar:


171

Her bölüm ayrı ayrı kaydedildiğinden, birden çok dosyadan oluşan bir klasör oluşturmaktır. Tek bir çıktı dosyasına ihtiyacınız varsa (hala bir klasörde) şunları yapabilirsiniz repartition(yukarı akış verileri büyükse ancak karıştırma gerektiriyorsa tercih edilir):

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

veya coalesce:

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

kaydetmeden önce veri çerçevesi:

Tüm veriler yazılacaktır mydata.csv/part-00000. Bu seçeneği kullanmadan önce , neler olup bittiğini ve tüm verileri tek bir çalışana aktarmanın maliyetini anladığınızdan emin olun . Çoğaltma ile dağıtılmış dosya sistemi kullanıyorsanız, veriler birden çok kez aktarılır - önce tek bir çalışana getirilir ve ardından depolama düğümleri üzerinden dağıtılır.

Alternatif olarak, kodunuzu olduğu gibi bırakabilir ve daha sonra tüm parçaları birleştirmek için catveya HDFSgetmerge gibi genel amaçlı araçları kullanabilirsiniz .


6
coalesce'ı şu şekilde de kullanabilirsiniz: df.coalesce (1) .write.format ("com.databricks.spark.csv") .option ("başlık", "true") .save ("mydata.csv")
ravi

spark 1.6 .coalesce(1), _temporary dizini üzerinde bazı FileNotFoundException belirlediğimizde bir hata atar . Hala kıvılcımdaki bir hata: issues.apache.org/jira/browse/SPARK-2984
Harsha

@Harsha Muhtemel. Oldukça coalesce(1)pahalı olmanın ve genellikle pratik olmayışının basit bir sonucudur .
zero323

@ Zero323 kabul edildi, ancak tek bir dosyada konsolide etmek için özel bir gereksiniminiz varsa, yeterli kaynaklara ve zamana sahip olduğunuz göz önüne alındığında yine de mümkün olmalıdır.
Harsha

2
@Harsha Yok demiyorum. GC'yi doğru şekilde ayarlarsanız, gayet iyi çalışması gerekir, ancak bu sadece bir zaman kaybıdır ve büyük olasılıkla genel performansa zarar verir. Kişisel olarak, özellikle bellek kullanımı konusunda endişelenmeden dosyaları Spark dışında birleştirmek çok basit olduğu için rahatsız etmek için herhangi bir neden görmüyorum.
zero323

36

Spark'ı HDFS ile çalıştırıyorsanız, csv dosyalarını normal şekilde yazarak ve birleştirme yapmak için HDFS'den yararlanarak sorunu çözüyorum. Bunu doğrudan Spark (1.6) ile yapıyorum:

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

Bu numarayı nerede öğrendiğimi hatırlayamıyorum, ama senin için işe yarayabilir.


Ben denemedim - ve basit olmayabileceğinden şüpheleniyorum.
Minkymorgan

1
Teşekkürler. Ben ettik bir cevap eklenen bu Databricks üzerinde çalışır
Josiah Yoder

@Minkymorgan Benzer bir sorunum var ama doğru bir şekilde yapamıyorum .. Lütfen şu soruya
bakar mısınız

4
@SUDARSHAN Yukarıdaki fonksiyonum sıkıştırılmamış verilerle çalışıyor. Örneğinizde, dosyaları yazarken - ve sonra - başarısız olan bunları bir araya getirmeye çalışırken gzip sıkıştırmasını kullandığınızı düşünüyorum. Gzip dosyalarını bir araya getiremeyeceğiniz için bu işe yaramayacak. Gzip, Bölünebilir Sıkıştırma algoritması değildir, bu nedenle kesinlikle "birleştirilebilir" değildir. "Hızlı" veya "bz2" sıkıştırmasını test edebilirsiniz - ancak içgüdüsel his, bu birleştirme sırasında da başarısız olacaktır. Muhtemelen en iyisi sıkıştırmayı kaldırmak, ham dosyaları birleştirmek ve daha sonra bölünebilir bir codec kullanarak sıkıştırmaktır.
Minkymorgan

ve başlığı korumak istersem ne olur? her dosya bölümü için çoğaltılır
Normal

32

Burada oyuna biraz geç kalmış olabilirim , ancak küçük veri kümeleri kullanmak coalesce(1)veya repartition(1)bunlar için işe yarayabilir, ancak büyük veri kümelerinin hepsi bir düğümdeki tek bir bölüme atılır. Bu, büyük olasılıkla OOM hatalarını veya en iyi ihtimalle yavaş işlemeyi sağlar.

FileUtil.copyMerge()Hadoop API'sindeki işlevi kullanmanızı şiddetle tavsiye ederim . Bu, çıktıları tek bir dosyada birleştirecektir.

DÜZENLE - Bu, verileri bir yürütme düğümü yerine sürücüye etkili bir şekilde getirir. Coalesce()tek bir yürütücünün kullanım için sürücüden daha fazla RAM olması iyi olur.

EDIT 2 : copyMerge()Hadoop 3.0'da kaldırılıyor. En yeni sürümle nasıl çalışılacağı hakkında daha fazla bilgi için aşağıdaki yığın taşması makalesine bakın: Hadoop 3.0'da CopyMerge nasıl yapılır?


Bu şekilde başlık satırı olan bir csv'nin nasıl alınacağına dair herhangi bir fikriniz var mı? Dosyanın bir başlık oluşturmasını istemezdim, çünkü bu, her bölüm için bir tane olmak üzere dosya boyunca üstbilgileri serpiştirir.
nojo

Geçmişte burada belgelenen bir seçenek var: markhneedham.com/blog/2014/11/30/…
etspaceman

@etspaceman Cool. Bunu Java (veya Spark, ancak çok fazla bellek tüketmeyen ve büyük dosyalarla çalışabilecek bir şekilde) yapabilmem gerektiğinden, maalesef bunu yapmanın gerçekten iyi bir yolu yok. . Hala bu API çağrısını kaldırdıklarına inanamıyorum ... Hadoop ekosistemindeki diğer uygulamalar tarafından tam olarak kullanılmasa bile bu çok yaygın bir kullanımdır.
woot

20

Databricks kullanıyorsanız ve tüm verileri tek bir işçi üzerindeki RAM'e sığdırabiliyorsanız (ve bu nedenle kullanabilirsiniz .coalesce(1)), elde edilen CSV dosyasını bulmak ve taşımak için dbfs'yi kullanabilirsiniz:

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)

Dosyanız çalışan üzerindeki RAM'e uymuyorsa, Chaotic3quilibrium'un FileUtils.copyMerge () kullanma önerisini dikkate almak isteyebilirsiniz . Bunu yapmadım ve henüz mümkün olup olmadığını bilmiyorum, örneğin S3'te.

Bu yanıt, bu soruya verilen önceki yanıtların yanı sıra sağlanan kod parçacığına yönelik kendi testlerime dayanmaktadır. Başlangıçta Databricks'e gönderdim ve burada yeniden yayınlıyorum .

Bulduğum dbfs'nin rm özyinelemeli seçeneği için en iyi belgeler bir Databricks forumundadır .


3

Minkymorgan'dan modifiye edilmiş S3 için çalışan bir çözüm.

Yalnızca geçici bölümlenmiş dizin yolunu (son yoldan farklı bir adla) srcPathve tek son csv / txt olarak destPath iletin deleteSource. Orijinal dizini kaldırmak isteyip istemediğinizi de belirtin .

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

copyMerge uygulaması tüm dosyaları listeler ve üzerlerinde yineler, bu s3'te güvenli değildir. dosyalarınızı yazıp sonra listelerseniz - bu, hepsinin listeleneceğini garanti etmez. [bu | docs.aws.amazon.com/AmazonS3/latest/dev/…
LiranBo

3

spark'ın df.write()API'si verilen yol içinde birden fazla parça dosyası oluşturacaktır ... kıvılcımı zorlamak için birleştirme dar bir dönüşüm df.coalesce(1).write.csv(...)yerine yalnızca tek bir parça dosyası kullanın df.repartition(1).write.csv(...), oysa yeniden bölümleme geniş bir dönüşümdür bkz. Spark - repartition () vs coalesce ()

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

tek part-0001-...-c000.csvdosya kullanımıyla verilen dosya yolunda klasör oluşturacak

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

kullanıcı dostu bir dosya adına sahip olmak


alternatif olarak, veri çerçevesi çok büyük değilse (~ GB veya sürücü belleğine sığabiliyorsa), bunu df.toPandas().to_csv(path)tercih ettiğiniz dosya
adıyla

2
Ugh, bunun sadece pandalara dönüştürülerek yapılabilmesi çok sinir bozucu. İçinde UUID olmadan bir dosya yazmak ne kadar zor?
ijoseph

nasıl üzerine yazarım? yazmak için çalışıyor ama üzerine
yazılamıyor

2

kaydetmeden önce 1 bölüme yeniden bölümleme / birleştirme (yine de bir klasör alırsınız, ancak içinde bir bölüm dosyası olur)


2

kullanabilirsiniz rdd.coalesce(1, true).saveAsTextFile(path)

veriyi tekil dosya olarak yolda / part-00000'de saklayacaktır


1
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs._
import org.apache.spark.sql.{DataFrame,SaveMode,SparkSession}
import org.apache.spark.sql.functions._

Aşağıdaki yaklaşımı kullanarak çözdüm (hdfs dosya adını yeniden adlandır): -

Adım 1: - (Veri Çerçevesini oluşturun ve HDFS'ye yazın)

df.coalesce(1).write.format("csv").option("header", "false").mode(SaveMode.Overwrite).save("/hdfsfolder/blah/")

Adım 2: - (Hadoop Yapılandırması Oluşturun)

val hadoopConfig = new Configuration()
val hdfs = FileSystem.get(hadoopConfig)

Adım3: - (hdfs klasör yolundaki yolu al)

val pathFiles = new Path("/hdfsfolder/blah/")

Adım4: - (hdfs klasöründen spark dosya adlarını alın)

val fileNames = hdfs.listFiles(pathFiles, false)
println(fileNames)

setp5: - (tüm dosya adlarını kaydetmek ve listeye eklemek için ölçeklenebilir bir liste oluşturun)

    var fileNamesList = scala.collection.mutable.MutableList[String]()
    while (fileNames.hasNext) {
      fileNamesList += fileNames.next().getPath.getName
    }
    println(fileNamesList)

Adım 6: - (_SUCESS dosya sırasını dosya adları ölçek listesinden filtreleyin)

    // get files name which are not _SUCCESS
    val partFileName = fileNamesList.filterNot(filenames => filenames == "_SUCCESS")

adım 7: - (scala listesini dizeye dönüştürün ve istenen dosya adını hdfs klasör dizesine ekleyin ve ardından yeniden adlandırmayı uygulayın)

val partFileSourcePath = new Path("/yourhdfsfolder/"+ partFileName.mkString(""))
    val desiredCsvTargetPath = new Path(/yourhdfsfolder/+ "op_"+ ".csv")
    hdfs.rename(partFileSourcePath , desiredCsvTargetPath)

1

Bunu Python'da tek bir dosya almak için kullanıyorum:

df.toPandas().to_csv("/tmp/my.csv", sep=',', header=True, index=False)

1

Bu cevap, kabul edilen cevabı genişletir, daha fazla bağlam sağlar ve makinenizdeki Spark Shell'de çalıştırabileceğiniz kod parçacıkları sağlar.

Kabul edilen cevapla ilgili daha fazla bağlam

Kabul edilen yanıt size örnek kodun tek bir mydata.csvdosya çıkardığı izlenimini verebilir ve bu durum böyle değildir. Gösterelim:

val df = Seq("one", "two", "three").toDF("num")
df
  .repartition(1)
  .write.csv(sys.env("HOME")+ "/Documents/tmp/mydata.csv")

İşte elde edilenler:

Documents/
  tmp/
    mydata.csv/
      _SUCCESS
      part-00000-b3700504-e58b-4552-880b-e7b52c60157e-c000.csv

NB mydata.csv, kabul edilen cevaptaki bir klasördür - bu bir dosya değildir!

Belirli bir ada sahip tek bir dosya nasıl çıkarılır

Tek bir dosya yazmak için spark-daria kullanabiliriz mydata.csv.

import com.github.mrpowers.spark.daria.sql.DariaWriters
DariaWriters.writeSingleFile(
    df = df,
    format = "csv",
    sc = spark.sparkContext,
    tmpFolder = sys.env("HOME") + "/Documents/better/staging",
    filename = sys.env("HOME") + "/Documents/better/mydata.csv"
)

Bu, dosyanın çıktısını şu şekilde verecektir:

Documents/
  better/
    mydata.csv

S3 yolları

DariaWriters.writeSingleFileBu yöntemi S3'te kullanmak için s3a yollarını geçmeniz gerekir :

DariaWriters.writeSingleFile(
    df = df,
    format = "csv",
    sc = spark.sparkContext,
    tmpFolder = "s3a://bucket/data/src",
    filename = "s3a://bucket/data/dest/my_cool_file.csv"
)

Daha fazla bilgi için buraya bakın .

CopyMerge'den kaçınma

copyMerge, Hadoop 3'ten kaldırılmıştır. DariaWriters.writeSingleFileUygulama fs.rename, burada açıklandığı gibi kullanır . Spark 3 hala Hadoop 2'yi kullandığından , copyMerge uygulamaları 2020'de çalışacaktır. Spark'ın Hadoop 3'e ne zaman yükselteceğinden emin değilim, ancak Spark Hadoop'u yükselttiğinde kodunuzun bozulmasına neden olacak herhangi bir copyMerge yaklaşımından kaçınmak daha iyidir.

Kaynak kodu

DariaWritersUygulamayı incelemek istiyorsanız, spark-daria kaynak kodundaki nesneyi arayın .

PySpark uygulaması

PySpark ile tek bir dosya yazmak daha kolaydır çünkü DataFrame'i varsayılan olarak tek bir dosya olarak yazılan bir Pandas DataFrame'e dönüştürebilirsiniz.

from pathlib import Path
home = str(Path.home())
data = [
    ("jellyfish", "JALYF"),
    ("li", "L"),
    ("luisa", "LAS"),
    (None, None)
]
df = spark.createDataFrame(data, ["word", "expected"])
df.toPandas().to_csv(home + "/Documents/tmp/mydata-from-pyspark.csv", sep=',', header=True, index=False)

Sınırlamalar

DariaWriters.writeSingleFileScala yaklaşımı ve df.toPandas()Python küçük veri setleri için tek çalışma yaklaşımı. Büyük veri kümeleri tek dosyalar olarak yazılamaz. Verileri tek bir dosya olarak yazmak performans açısından ideal değildir çünkü veriler paralel olarak yazılamaz.


0

Listbuffer'ı kullanarak verileri tek bir dosyaya kaydedebiliriz:

import java.io.FileWriter
import org.apache.spark.sql.SparkSession
import scala.collection.mutable.ListBuffer
    val text = spark.read.textFile("filepath")
    var data = ListBuffer[String]()
    for(line:String <- text.collect()){
      data += line
    }
    val writer = new FileWriter("filepath")
    data.foreach(line => writer.write(line.toString+"\n"))
    writer.close()

-2

Java'yı kullanmanın bir yolu daha var

import java.io._

def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) 
  {
     val p = new java.io.PrintWriter(f);  
     try { op(p) } 
     finally { p.close() }
  } 

printToFile(new File("C:/TEMP/df.csv")) { p => df.collect().foreach(p.println)}

'doğru' adı tanımlanmadı
Arron
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.