স্পার্ক-সিএসভি ব্যবহার করে একক সিএসভি ফাইল লিখুন


108

আমি https://github.com/databricks/spark-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
আপনার কাছে একসঙ্গে বেড়ে ওঠা ব্যবহার করতে পারেন: df.coalesce (1) .write.format ( "com.databricks.spark.csv") .option ( "হেডার", "সত্য") .save ( "mydata.csv")
রবি

স্পার্ক 1.6 একটি ত্রুটি ছুড়ে দেয় যখন আমরা .coalesce(1)এটি সেট করি _ অস্থায়ী ডিরেক্টরিতে কিছু ফাইলনটফাউন্ডএক্সসেপশন বলে। এটি এখনও স্পার্কে একটি বাগ: ইস্যু.এপাচি.আর.জিরা
হর্ষ

পছন্দ করেছেন বরং coalesce(1)অত্যন্ত ব্যয়বহুল এবং সাধারণত ব্যবহারিক না হওয়ার সহজ ফল ।
শূন্য323

শূন্য ৩৩৩৩ এর সাথে সম্মত, তবে আপনার যদি একটি ফাইলে একীভূত করার জন্য বিশেষ প্রয়োজন হয় তবে আপনার পর্যাপ্ত সংস্থান এবং সময় থাকতে পারে তা এখনও সম্ভব হওয়া উচিত।
হর্ষ

2
@ হর্ষ আমি বলি না যে সেখানে নেই। আপনি যদি জিসি টিউনটি সঠিকভাবে করেন তবে এটি ঠিক কাজ করা উচিত তবে এটি কেবল সময়ের অপচয় এবং সম্ভবত সামগ্রিক কর্মক্ষমতা ক্ষতিগ্রস্থ করবে। তাই ব্যক্তিগতভাবে আমি বিরক্ত করার কোনও কারণ দেখতে পাচ্ছি না বিশেষত যেহেতু স্পার্কের বাইরে ফাইলগুলি মার্জ করা মোটেই মেমরির ব্যবহারের বিষয়ে চিন্তা না করেই তুচ্ছ।
শূন্য323

36

আপনি যদি এইচডিএফএসের সাথে স্পার্ক চালাচ্ছেন তবে আমি সিএসভি ফাইলগুলি সাধারণত লিখে এবং মার্জ করার জন্য এইচডিএফএসের সুবিধা দিয়ে সমস্যার সমাধান করছি solving আমি স্পার্কে (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
ধন্যবাদ। আমি একটি উত্তর যুক্ত করেছি যা ডেটাব্রিক্সে কাজ করে
জোশিয়াহ

@Minkymorgan আমি একই সমস্যা কিন্তু এটি সঠিকভাবে করতে ..Can আপনি যদি এই প্রশ্নের এ বর্ণন খুশি পারবে না আছে stackoverflow.com/questions/46812388/...
সুদর্শন

4
@ সুদর্শন উপরের আমার ফাংশনটি সঙ্কুচিত ডেটা নিয়ে কাজ করে। আপনার উদাহরণে আমি মনে করি আপনি ফাইল লেখার সাথে সাথে আপনি gzip সংক্ষেপণটি ব্যবহার করছেন - এবং তারপরে - এগুলিকে একত্রিত করার চেষ্টা করছেন যা ব্যর্থ হয়। এটি কাজ করবে না, কারণ আপনি gzip ফাইলগুলি একসাথে একত্রী করতে পারবেন না। জিজিপ কোনও বিভাজনযোগ্য সংক্ষেপণ অ্যালগরিদম নয়, সুতরাং অবশ্যই "মার্জেবল" নয়। আপনি "চটজলদি" বা "bz2" সংক্ষেপণের পরীক্ষা করতে পারেন - তবে অন্ত্রের অনুভূতি হ'ল এটি মার্জ করার ক্ষেত্রেও ব্যর্থ হবে। সম্ভবত সর্বোত্তম সেরা হ'ল সংকোচনের অপসারণ, কাঁচা ফাইলগুলি মার্জ করা, তারপরে একটি বিভাজনযোগ্য কোডেক ব্যবহার করে সংকুচিত করা।
মিনকিওমরগান

এবং আমি যদি হেডার সংরক্ষণ করতে চাই? এটি প্রতিটি ফাইল অংশের জন্য সদৃশ
সাধারণ

32

আমি এখানে খেলায় কিছুটা দেরি করতে পেরেছি, তবে ছোট ডেটা-সেট ব্যবহার করে coalesce(1)বা repartition(1)কাজ করতে পারে, তবে বড় ডেটা-সেটগুলি সমস্তই একটি নোডের একটি পার্টিশনে ফেলে দেওয়া হবে। এটি ধীরে ধীরে প্রক্রিয়া করতে OOM ত্রুটিগুলি বা সর্বোত্তমভাবে ছুঁড়ে ফেলতে পারে।

আমি আপনাকে FileUtil.copyMerge()হাদুপ এপিআই থেকে ফাংশনটি ব্যবহার করার পরামর্শ দিচ্ছি । এটি আউটপুটগুলিকে একক ফাইলে একীভূত করবে।

সম্পাদনা - এটি কার্যকরভাবে এক্সিকিউটার নোডের পরিবর্তে ড্রাইভারের কাছে ডেটা নিয়ে আসে। Coalesce()যদি কোনও একক এক্সিকিউটারের ড্রাইভারের চেয়ে ব্যবহারের জন্য আরও বেশি র্যাম থাকে তবে তা ঠিক থাকবে।

সম্পাদনা 2 : copyMerge()হ্যাডোপ 3.0 এ সরানো হচ্ছে। নতুন সংস্করণটির সাথে কীভাবে কাজ করবেন সে সম্পর্কে আরও তথ্যের জন্য নিম্নলিখিত স্ট্যাক ওভারফ্লো নিবন্ধটি দেখুন: হাদোপ ৩.০ এ কপিরাইটমার্জ কীভাবে করবেন?


এইভাবে একটি শিরোনাম সারি দিয়ে কোনও সিএসভি পাবেন কীভাবে কোনও ধারণা? ফাইলটি একটি শিরোনাম তৈরি করতে চাইবে না, যেহেতু এটি ফাইলের পুরো শিরোনামকে ছেদ করে, প্রতিটি বিভাজনের জন্য একটি করে।
nojo

আমি এখানে নথিবদ্ধ অতীতে একটি বিকল্প ব্যবহার করেছি: markhneedham.com/blog/2014/11/30/…
স্পেসম্যান

পছন্দ করুন দুর্ভাগ্যক্রমে, জাভাতে (বা স্পার্ক, তবে এমন একটি উপায়ে যা প্রচুর স্মৃতি গ্রহণ করে না এবং বড় ফাইলগুলির সাথে কাজ করতে পারে) এমনটি করার জন্য আমার কাছে এখনও সত্যিই ভাল উপায় নেই don't । আমি এখনও বিশ্বাস করতে পারি না যে তারা এই এপিআই কলটি সরিয়ে নিয়েছে ... হ্যাডোপ বাস্তুতন্ত্রের অন্যান্য অ্যাপ্লিকেশনগুলি হুবহু ব্যবহার না করা সত্ত্বেও এটি খুব সাধারণ ব্যবহার।
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)

যদি আপনার ফাইলটি কর্মীর র‍্যামের সাথে ফিট না করে তবে আপনি ফাইল ইউটিলস.কপিমারজি () ব্যবহারের জন্য বিশৃঙ্খলা3 কুইলিব্রিয়ামের পরামর্শ বিবেচনা করতে পারেন । আমি এটি করিনি, এবং এখনও জানি না সম্ভব কিনা, যেমন, এস 3-তে।

এই উত্তরটি এই প্রশ্নের পূর্ববর্তী উত্তরের পাশাপাশি প্রদত্ত কোড স্নিপেটের নিজস্ব পরীক্ষাগুলিতে তৈরি করা হয়েছে। আমি এটি মূলত ডেটাব্রিক্সে পোস্ট করেছি এবং এটি এখানে পুনরায় প্রকাশ করছি।

Dbfs এর RM এর রিকার্সিভ বিকল্প জন্য শ্রেষ্ঠ ডকুমেন্টেশন আমি খুঁজে পেয়েছি হয় একটি Databricks ফোরাম


3

মিনকিওমরগান থেকে সংশোধিত এস 3 এর জন্য কাজ করে এমন একটি সমাধান।

কেবলমাত্র অস্থায়ী পার্টিশনযুক্ত ডিরেক্টরি পাথ (চূড়ান্ত পাথের চেয়ে আলাদা নামের সাথে) srcPathএবং একক চূড়ান্ত সিএসভি / টিএসটিএস হিসাবে 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 বাস্তবায়ন সমস্ত ফাইল তালিকাভুক্ত করে এবং তার উপরে পুনরাবৃত্তি করে, এটি এস 3 এ নিরাপদ নয়। যদি আপনি নিজের ফাইলগুলি লিখে থাকেন এবং সেগুলি তালিকাভুক্ত করেন - এটি গ্যারান্টি দেয় না যে সেগুলি সমস্ত তালিকাভুক্ত হবে। দেখুন [এটি | docs.aws.amazon.com/AmamazS3/latest/dev/…
লিরানবো

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 

একটি ব্যবহারকারী বান্ধব ফাইলের নাম আছে


বিকল্প হিসাবে যদি ডেটাফ্রেম খুব বেশি না হয় (~ গিগাবাইট বা ড্রাইভারের মেমরিতে ফিট করতে পারে) তবে আপনি এটি ব্যবহার করতে পারেন df.toPandas().to_csv(path)আপনার পছন্দসই ফাইল
নামের

1
উহ্, তাই হতাশার ফলে কীভাবে এটি কেবল পান্ডায় রূপান্তর করেই করা যায়। এতে কোনও ইউইউডি ছাড়াই কেবল কোনও ফাইল লেখা কত কঠিন?
আইজোসেফ

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

আমি নীচের পদ্ধতির (hdfs ফাইলের নাম পুনরায় নামকরণ) ব্যবহার করে সমাধান করেছি: -

পদক্ষেপ 1: - (ক্রিট ডেটা ফ্রেম এবং এইচডিএফএসে লিখুন)

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

পদক্ষেপ 2: - (হ্যাডোপ কনফিগারেশন তৈরি করুন)

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

স্টিপি 3: - (এইচডিএফএস ফোল্ডারের পথে পাথ পান)

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

পদক্ষেপ:: - (এইচডিএফএস ফোল্ডার থেকে স্পার্ক ফাইলের নাম পান)

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

setp5: - (সমস্ত ফাইলের নাম সংরক্ষণ করতে স্কালার পরিবর্তনীয় তালিকা তৈরি করুন এবং এটি তালিকায় যুক্ত করুন)

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

পদক্ষেপ:: - (ফাইলের নাম স্কাল তালিকা থেকে ফিল্টার _SUCESS ফাইল অর্ডার)

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

পদক্ষেপ:: - (স্কেল তালিকায় স্ট্রিংতে রূপান্তর করুন এবং পছন্দসই ফাইলের নামটি এইচডিএফএস ফোল্ডারের স্ট্রিংয়ে যুক্ত করুন এবং তারপরে পুনরায় নাম প্রয়োগ করুন)

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


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

এনবি 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

এস 3 পাথ

DariaWriters.writeSingleFileএস 3 এ এই পদ্ধতিটি ব্যবহার করতে আপনাকে 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 এখনও হ্যাডোপ 2 ব্যবহৃত হয়েছে , সুতরাং কপিরমর্জ বাস্তবায়নগুলি 2020 সালে কার্যকর হবে I'm স্পার্ক হ্যাডোপ 3 এ কখন আপগ্রেড হবে তা আমি নিশ্চিত নই, তবে স্পার্ক আপগ্রেড হ্যাডোপ আপগ্রেড করার সময় আপনার কোডটি ভেঙে ফেলবে এমন কোনও কপিমার্জ পদ্ধতির এড়ানো ভাল।

সোর্স কোড

দেখুন DariaWritersস্ফুলিঙ্গ-Daria সোর্স কোডে অবজেক্ট আপনি বাস্তবায়ন পরিদর্শন করতে চাই।

পাইস্পার্ক বাস্তবায়ন

পাইস্পার্কের সাথে একটি একক ফাইল লিখতে আরও সহজ কারণ আপনি ডেটাফ্রেমকে একটি পান্ডাস ডেটা ফ্রেমে রূপান্তর করতে পারেন যা ডিফল্টরূপে একক ফাইল হিসাবে লিখিত হয়।

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.writeSingleFileScala পদ্ধতি এবং df.toPandas()পাইথন ছোট ডেটাসেট কেবল কাজ কাছে। বিশাল ডেটাসেট একক ফাইল হিসাবে লেখা যায় না। একক ফাইল হিসাবে ডেটা লেখাই পারফরম্যান্সের দৃষ্টিভঙ্গি থেকে অনুকূল নয় কারণ সমান্তরালে ডেটা লেখা যায় না।


0

লিস্টবফার ব্যবহার করে আমরা একক ফাইলে ডেটা সংরক্ষণ করতে পারি:

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

'সত্য' নামটি সংজ্ঞায়িত করা হয়নি
অ্যারন
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.