स्पार्क-सीएसवी का उपयोग करके एकल सीएसवी फ़ाइल लिखें


108

मैं https://github.com/databricks/spark-csv का उपयोग कर रहा हूं, मैं एक एकल CSV लिखने की कोशिश कर रहा हूं, लेकिन सक्षम नहीं है, यह एक फ़ोल्डर बना रहा है।

एक स्कैला फ़ंक्शन की आवश्यकता है जो पथ और फ़ाइल नाम जैसे पैरामीटर लेगा और उस CSV फ़ाइल को लिखेगा।

जवाबों:


168

यह कई फ़ाइलों के साथ एक फ़ोल्डर बना रहा है, क्योंकि प्रत्येक विभाजन को व्यक्तिगत रूप से सहेजा जाता है। यदि आपको एक एकल आउटपुट फाइल (अभी भी एक फ़ोल्डर में) की repartitionआवश्यकता होती है, तो आप कर सकते हैं (पसंदीदा यदि अपस्ट्रीम डेटा बड़ा है, लेकिन इसमें फेरबदल की आवश्यकता है):

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

या coalesce:

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

बचत से पहले डेटा फ्रेम:

सभी डेटा को लिखा जाएगा mydata.csv/part-00000। इस विकल्प का उपयोग करने से पहले सुनिश्चित करें कि आप समझ रहे हैं कि क्या चल रहा है और किसी एक कार्यकर्ता को सभी डेटा स्थानांतरित करने की लागत क्या है । यदि आप प्रतिकृति के साथ वितरित फ़ाइल सिस्टम का उपयोग करते हैं, तो डेटा को कई बार स्थानांतरित किया जाएगा - पहले एक ही कार्यकर्ता को लाया जाता है और बाद में भंडारण नोड्स पर वितरित किया जाता है।

वैकल्पिक रूप से आप अपने कोड को छोड़ सकते हैं क्योंकि यह है और सामान्य प्रयोजन उपकरण जैसे कि catया एचडीएफएस का उपयोगgetmerge करने के बाद बस सभी भागों को मर्ज करना है।


6
आप coalesce का भी उपयोग कर सकते हैं: df.coalesce (1) .write.format ("com.databricks.spark.csv") .option ("हैडर", "सत्य") .save ("mydata.csv")
ravi

स्पार्क 1.6 एक त्रुटि फेंकता है जब हम .coalesce(1)यह सेट करते हैं कि कुछ फाइलनॉटफ़ेक्सेशन _temporary डायरेक्टरी पर है। यह अभी भी चिंगारी में एक बग है: issues.apache.org/jira/browse/SPARK-2984
हर्षा

@ हर्ष अनलिखा। बल्कि coalesce(1)अत्यधिक महंगा होने का एक साधारण परिणाम और आमतौर पर व्यावहारिक नहीं है।
15:32 बजे जीरो 323

सहमत @ शून्य 323, लेकिन अगर आपको एक फ़ाइल में समेकित करने की विशेष आवश्यकता है, तो यह अभी भी संभव है कि आपके पास पर्याप्त संसाधन और समय हो।
हर्षा

2
@ हर्षा मैं नहीं कहता कि वहाँ नहीं है। यदि आप GC को सही ढंग से ट्यून करते हैं तो यह ठीक काम करना चाहिए लेकिन यह बस समय की बर्बादी है और सबसे अधिक संभावना समग्र प्रदर्शन को नुकसान पहुंचाएगा। इसलिए व्यक्तिगत रूप से मुझे विशेष रूप से परेशान करने का कोई कारण नहीं दिखता क्योंकि स्मृति के बारे में चिंता किए बिना स्पार्क के बाहर फ़ाइलों को मर्ज करना बहुत ही सरल है।
शून्य 323

36

यदि आप एचडीएफएस के साथ स्पार्क चला रहे हैं, तो मैं सामान्य रूप से सीएसवी फाइलें लिखकर और एचडीएफएस को विलय करने के लिए लाभ उठाकर समस्या को हल कर रहा हूं। मैं स्पार्क में कर रहा हूँ (1.6) सीधे:

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

याद नहीं कर सकते हैं जहाँ मैं इस चाल सीखा है, लेकिन यह आप के लिए काम कर सकते हैं।


मैंने इसकी कोशिश नहीं की है - और संदेह है कि यह सीधे आगे नहीं हो सकता है।
मिन्कीमोरंग

1
धन्यवाद। मैंने एक उत्तर जोड़ा है जो Databricks पर काम करता है
जोशिया योडर

@Minkymorgan मैं इसी तरह की समस्या है, लेकिन इसे सही ढंग से करने के लिए ..Can आप इस प्रश्न पर नज़र कृपया सक्षम नहीं है stackoverflow.com/questions/46812388/...
सुदर्शन

4
@SUDARSHAN ऊपर मेरा फ़ंक्शन असम्पीडित डेटा के साथ काम करता है। आपके उदाहरण में, मुझे लगता है कि आप gzip कम्प्रेशन का उपयोग कर रहे हैं जैसा कि आप फाइलें लिखते हैं - और फिर बाद में - इन्हें एक साथ मिलाने की कोशिश करते हैं जो विफल हो जाता है। यह काम नहीं करने वाला है, क्योंकि आप एक साथ gzip फ़ाइलों को मर्ज नहीं कर सकते हैं। Gzip एक स्प्लिटेबल कंप्रेशन एल्गोरिथम नहीं है, इसलिए निश्चित रूप से "मर्जेबल" नहीं है। आप "तड़क" या "bz2" संपीड़न का परीक्षण कर सकते हैं - लेकिन यह महसूस होता है कि यह विलय पर भी विफल होगा। शायद सबसे अच्छा सबसे अच्छा है कि संपीड़न को हटा दें, कच्ची फ़ाइलों को मर्ज करें, फिर एक स्प्लिटेबल कोडेक का उपयोग करके संपीड़ित करें।
मिन्कीमोरंग

और क्या होगा अगर मैं हेडर को संरक्षित करना चाहता हूं? यह प्रत्येक फ़ाइल भाग के लिए डुप्लिकेट करता है
सामान्य

32

मुझे यहां खेल में थोड़ी देर हो सकती है, लेकिन छोटे डेटा-सेट के लिए उपयोग coalesce(1)या repartition(1)काम कर सकता है, लेकिन बड़े डेटा-सेट सभी को एक नोड पर एक विभाजन में फेंक दिया जाएगा। यह ओओएम त्रुटियों को फेंकने की संभावना है, या सबसे अच्छा, धीरे-धीरे संसाधित करने के लिए।

मैं अत्यधिक सुझाव दूंगा कि आप FileUtil.copyMerge()Hadoop API से फ़ंक्शन का उपयोग करें । यह आउटपुट को एक सिंगल फाइल में मर्ज कर देगा।

EDIT - यह प्रभावी रूप से एक निष्पादक नोड के बजाय चालक को डेटा लाता है। Coalesce()अगर चालक की तुलना में एक ही निष्पादक के पास अधिक रैम है तो ठीक होगा।

EDIT 2 : copyMerge()Hadoop 3.0 में निकाला जा रहा है। नवीनतम संस्करण के साथ काम करने के तरीके के बारे में अधिक जानकारी के लिए निम्नलिखित स्टैक ओवरफ़्लो लेख देखें: Hadoop 3.0 में CopyMerge कैसे करें?


कैसे इस तरह एक हेडर पंक्ति के साथ एक सीएसवी पाने के लिए पर कोई विचार? फ़ाइल को हेडर बनाने की इच्छा नहीं होगी, क्योंकि यह पूरे विभाजन के हेडर को प्रत्येक विभाजन के लिए एक के बाद एक होगा।
नोजा

यहाँ एक विकल्प है जो मैंने पिछले दस्तावेज में इस्तेमाल किया है: markhneedham.com/blog/2014/11/30/…
etspaceman

@etspaceman कूल। मेरे पास वास्तव में ऐसा करने का एक अच्छा तरीका नहीं है, दुर्भाग्य से, जैसा कि मुझे जावा (या स्पार्क में ऐसा करने में सक्षम होने की आवश्यकता है, लेकिन एक तरह से जो बहुत सारी मेमोरी का उपभोग नहीं करता है और बड़ी फ़ाइलों के साथ काम कर सकता है) । मुझे अभी भी विश्वास नहीं हो रहा है कि उन्होंने इस API कॉल को हटा दिया है ... यह एक बहुत ही सामान्य उपयोग है भले ही Hadoop पारिस्थितिकी तंत्र में अन्य अनुप्रयोगों द्वारा उपयोग नहीं किया गया हो।
woot

20

यदि आप डेटाब्रिक का उपयोग कर रहे हैं और एक कर्मचारी (और इस प्रकार उपयोग कर सकते हैं .coalesce(1)) पर रैम में सभी डेटा को फिट कर सकते हैं , तो आप परिणामी सीएसवी फ़ाइल को खोजने और स्थानांतरित करने के लिए dbfs का उपयोग कर सकते हैं:

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)

यदि आपकी फ़ाइल कार्यकर्ता पर RAM में फिट नहीं होती है, तो आप FileUtils.copyMerge () का उपयोग करने के लिए chaotic3quilibrium के सुझाव पर विचार करना चाह सकते हैं । मैंने ऐसा नहीं किया है, और अभी तक नहीं जानता कि क्या संभव है या नहीं, उदाहरण के लिए, S3 पर।

यह उत्तर इस प्रश्न के पिछले उत्तर और साथ ही प्रदान किए गए कोड स्निपेट के मेरे अपने परीक्षणों पर बनाया गया है। मैंने मूल रूप से इसे डाटाब्रिक्स में पोस्ट किया है और इसे यहां पुनः प्रकाशित कर रहा हूं

Dbfs के rm के पुनरावर्ती विकल्प के लिए सबसे अच्छा दस्तावेज जो मुझे मिला है वह एक डेटाब्रिक्स फोरम पर है


3

एक समाधान जो M3 के लिए काम करता है, जो कि मिंकमॉर्गन से संशोधित है।

बस के रूप में srcPathऔर एक अंतिम csv / txt के रूप में अस्थायी विभाजन निर्देशिका पथ (अंतिम पथ की तुलना में अलग नाम) पास destPath करें deleteSourceयदि आप मूल निर्देशिका को निकालना चाहते हैं तो भी निर्दिष्ट करें ।

/**
* 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 कार्यान्वयन सभी फाइलों को सूचीबद्ध करता है और उन पर पुनरावृत्त करता है, यह s3 में सुरक्षित नहीं है। यदि आप अपनी फ़ाइलें लिखते हैं और फिर उन्हें सूचीबद्ध करते हैं - यह गारंटी नहीं देता है कि वे सभी सूचीबद्ध होंगे। देखें [यह | docs.aws.amazon.com/AmazonS3/latest/dev/…
LiranBo

3

चिंगारी के df.write()एपीआई केवल एक ही हिस्सा फ़ाइल उपयोग दिए गए पथ के अंदर कई भाग फ़ाइलें पैदा करेगा ... बल चिंगारी लिखने के लिए df.coalesce(1).write.csv(...)के बजाय df.repartition(1).write.csv(...)जबकि पुनर्विभाजन एक विस्तृत परिवर्तन देखने के रूप में सम्मिलित एक संकीर्ण परिवर्तन है स्पार्क - पुनर्विभाजन () सम्मिलित बनाम ()

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

एक part-0001-...-c000.csvफ़ाइल के उपयोग के साथ दिए गए फ़ाइलपथ में फ़ोल्डर बनाएगा

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

उपयोगकर्ता के अनुकूल फ़ाइल नाम रखना


वैकल्पिक रूप से यदि डेटाफ़्रेम बहुत बड़ा नहीं है (~ GBs या ड्राइवर मेमोरी में फिट हो सकता है) तो आप इसका उपयोग भी कर सकते हैं df.toPandas().to_csv(path), अपने पसंदीदा फ़ाइल नाम के साथ सिंगल सीएसवी लिखेंगे
pprasad009

1
ऊ, तो निराशा यह है कि यह केवल पंडों में परिवर्तित करके कैसे किया जा सकता है। इसमें कुछ यूयूआईडी के बिना सिर्फ एक फाइल लिखना कितना मुश्किल है?
ijoseph

2

आपके द्वारा सहेजे जाने से पहले पुन: विभाजन / 1 विभाजन के लिए (आप अभी भी एक फ़ोल्डर प्राप्त करेंगे, लेकिन इसमें एक हिस्सा फ़ाइल होगा)


2

आप उपयोग कर सकते हैं rdd.coalesce(1, true).saveAsTextFile(path)

यह पथ / भाग -00000 में एकल फ़ाइल के रूप में डेटा संग्रहीत करेगा


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._

मैंने नीचे दिए गए दृष्टिकोण का उपयोग करके हल किया (एचडीएफएस फ़ाइल का नाम बदलें): -

चरण 1: - (टोकरा डेटा फ़्रेम और एचडीएफएस पर लिखें)

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

चरण 2: - (Hadoop config बनाएँ)

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

Step3: - (hdfs फ़ोल्डर पथ में पथ पाएं)

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

Step4: - (hdfs फ़ोल्डर से स्पार्क फ़ाइल नाम प्राप्त करें)

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

setp5: - (सभी फ़ाइल नामों को सहेजने और सूची में जोड़ने के लिए scala उत्परिवर्ती सूची बनाएं)

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

चरण 6: - (फ़िल्टर नाम फ़ाइल नाम scala सूची से फ़ाइल क्रम)

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

चरण 7: - (स्केला सूची को स्ट्रिंग में बदलें और वांछित फाइल नाम को एचडीएफएस फ़ोल्डर स्ट्रिंग में जोड़ें और फिर नाम बदलें)

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

1

मैं पायथन में इसका उपयोग कर रहा हूँ एक एकल फ़ाइल पाने के लिए:

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

1

यह उत्तर स्वीकृत उत्तर पर फैलता है, अधिक संदर्भ देता है, और कोड स्निपेट प्रदान करता है जिसे आप अपनी मशीन पर स्पार्क शेल में चला सकते हैं।

स्वीकृत उत्तर पर अधिक संदर्भ

स्वीकृत उत्तर आपको यह आभास दे सकता है कि नमूना कोड एकल mydata.csvफ़ाइल को आउटपुट करता है और ऐसी स्थिति नहीं है। आइए प्रदर्शित करें:

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

यहाँ क्या उत्पादन किया गया है:

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

NB mydata.csvस्वीकृत उत्तर में एक फ़ोल्डर है - यह एक फ़ाइल नहीं है!

किसी विशिष्ट नाम के साथ किसी एकल फ़ाइल को कैसे आउटपुट करें

हम सिंगल फाइल लिखने के लिए स्पार्क-डारिया का उपयोग कर सकते हैं 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"
)

यह फाइल को निम्नानुसार आउटपुट करेगा:

Documents/
  better/
    mydata.csv

S3 पथ

आपको DariaWriters.writeSingleFileS3 में इस विधि का उपयोग करने के लिए s3a पथ को पास करना होगा :

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

अधिक जानकारी के लिए यहां देखें ।

नकल से बचना

copyMerge Hadoop 3. हटा दिया गया था DariaWriters.writeSingleFileकार्यान्वयन का उपयोग करता है fs.rename, के रूप में यहाँ वर्णितस्पार्क 3 अभी भी Hadoop 2 का उपयोग करता है , इसलिए copyMerge कार्यान्वयन 2020 में काम करेगा। मुझे यकीन नहीं है कि जब स्पार्क Hadoop 3 में अपग्रेड होगा, लेकिन किसी भी copyMerge के दृष्टिकोण से बचने के लिए बेहतर है कि स्पार्क अपग्रेड किए जाने पर आपका कोड टूट जाएगा।

सोर्स कोड

के लिए देखो DariaWritersचिंगारी दारिया स्रोत कोड में वस्तु यदि आप कार्यान्वयन का निरीक्षण करना चाहते हैं।

PySpark कार्यान्वयन

पाइस्पार्क के साथ एक एकल फ़ाइल को लिखना आसान है क्योंकि आप डेटाफ़्रेम को पांडस डेटाफ़्रेम में बदल सकते हैं जो डिफ़ॉल्ट रूप से एकल फ़ाइल के रूप में लिखा जाता है।

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)

सीमाएं

DariaWriters.writeSingleFileस्काला दृष्टिकोण और df.toPandas()अजगर छोटे डेटासेट के लिए केवल काम दृष्टिकोण। विशाल डेटासेट को एकल फ़ाइलों के रूप में नहीं लिखा जा सकता है। डेटा को एकल फ़ाइल के रूप में लिखना प्रदर्शन के दृष्टिकोण से इष्टतम नहीं है क्योंकि डेटा को समानांतर में नहीं लिखा जा सकता है।


0

Listbuffer का उपयोग करके हम डेटा को एकल फ़ाइल में सहेज सकते हैं:

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

जावा का उपयोग करने का एक और तरीका है

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

नाम 'सत्य' परिभाषित नहीं है
एरॉन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.