Https://github.com/databricks/spark-csv kullanıyorum , tek bir CSV yazmaya çalışıyorum ama yapamıyorum, bir klasör oluşturuyor.
Yol ve dosya adı gibi parametreleri alacak ve o CSV dosyasını yazacak bir Scala işlevine ihtiyacınız var.
Https://github.com/databricks/spark-csv kullanıyorum , tek bir CSV yazmaya çalışıyorum ama yapamıyorum, bir klasör oluşturuyor.
Yol ve dosya adı gibi parametreleri alacak ve o CSV dosyasını yazacak bir Scala işlevine ihtiyacınız var.
Yanıtlar:
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 cat
veya HDFSgetmerge
gibi genel amaçlı araçları kullanabilirsiniz .
.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
coalesce(1)
pahalı olmanın ve genellikle pratik olmayışının basit bir sonucudur .
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.
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?
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 .
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) srcPath
ve 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
)
}
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.csv
dosya 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
df.toPandas().to_csv(path)
tercih ettiğiniz dosya
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)
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)
Bunu Python'da tek bir dosya almak için kullanıyorum:
df.toPandas().to_csv("/tmp/my.csv", sep=',', header=True, index=False)
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.csv
dosya çı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.writeSingleFile
Bu 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.writeSingleFile
Uygulama 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
DariaWriters
Uygulamayı 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.writeSingleFile
Scala 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.
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()
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)}