ฉันใช้https://github.com/databricks/spark-csvฉันพยายามเขียน CSV เดียว แต่ไม่สามารถทำได้มันกำลังสร้างโฟลเดอร์
ต้องการฟังก์ชัน Scala ซึ่งจะใช้พารามิเตอร์เช่นเส้นทางและชื่อไฟล์และเขียนไฟล์ CSV นั้น
ฉันใช้https://github.com/databricks/spark-csvฉันพยายามเขียน CSV เดียว แต่ไม่สามารถทำได้มันกำลังสร้างโฟลเดอร์
ต้องการฟังก์ชัน Scala ซึ่งจะใช้พารามิเตอร์เช่นเส้นทางและชื่อไฟล์และเขียนไฟล์ CSV นั้น
คำตอบ:
เป็นการสร้างโฟลเดอร์ที่มีไฟล์หลายไฟล์เนื่องจากแต่ละพาร์ติชันจะถูกบันทึกแยกกัน หากคุณต้องการไฟล์เอาท์พุตเดียว (ยังอยู่ในโฟลเดอร์) คุณสามารถทำได้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
หรือHDFSgetmerge
เพื่อรวมส่วนทั้งหมดในภายหลัง
.coalesce(1)
ว่า FileNotFoundException บางรายการในไดเร็กทอรี _tem Contemporary มันยังคงเป็นจุดบกพร่องในจุดประกาย: issue.apache.org/jira/browse/SPARK-2984
coalesce(1)
การมีราคาแพงมากและมักใช้ไม่ได้จริง
หากคุณใช้งาน Spark กับ HDFS ฉันได้แก้ปัญหาโดยการเขียนไฟล์ csv ตามปกติและใช้ประโยชน์จาก HDFS เพื่อทำการรวม ฉันกำลังทำสิ่งนั้นใน Spark (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()
จำไม่ได้ว่าฉันเรียนรู้เคล็ดลับนี้มาจากไหน แต่อาจได้ผลสำหรับคุณ
ฉันอาจจะเล่นเกมช้าเล็กน้อยที่นี่ แต่การใช้coalesce(1)
หรือrepartition(1)
อาจใช้ได้กับชุดข้อมูลขนาดเล็ก แต่ชุดข้อมูลขนาดใหญ่ทั้งหมดจะถูกโยนลงในพาร์ติชันเดียวในโหนดเดียว สิ่งนี้มีแนวโน้มที่จะทำให้เกิดข้อผิดพลาด OOM หรืออย่างดีที่สุดก็คือการประมวลผลช้า
ฉันขอแนะนำให้คุณใช้FileUtil.copyMerge()
ฟังก์ชันจาก Hadoop API การดำเนินการนี้จะรวมเอาต์พุตเป็นไฟล์เดียว
แก้ไข - นำข้อมูลไปยังไดรเวอร์ได้อย่างมีประสิทธิภาพแทนที่จะเป็นโหนดตัวดำเนินการ Coalesce()
จะดีถ้าตัวดำเนินการเดียวมี RAM สำหรับใช้งานมากกว่าไดรเวอร์
แก้ไข 2 : copyMerge()
กำลังถูกลบออกใน Hadoop 3.0 ดูบทความสแตกล้นต่อไปนี้สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการทำงานกับเวอร์ชันใหม่ล่าสุด: จะทำ CopyMerge ใน Hadoop 3.0 ได้อย่างไร
หากคุณใช้ Databricks และสามารถใส่ข้อมูลทั้งหมดลงใน RAM ในผู้ปฏิบัติงานคนเดียว (และสามารถใช้ได้.coalesce(1)
) คุณสามารถใช้ dbfs เพื่อค้นหาและย้ายไฟล์ CSV ที่ได้:
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)
หากไฟล์ของคุณไม่พอดีกับแรมในผู้ปฏิบัติงานที่คุณอาจต้องการที่จะต้องพิจารณา ข้อเสนอแนะ chaotic3quilibrium ที่จะใช้ FileUtils.copyMerge () ฉันยังไม่ได้ทำและยังไม่รู้ว่าเป็นไปได้หรือไม่เช่นใน S3
คำตอบนี้สร้างขึ้นจากคำตอบก่อนหน้าสำหรับคำถามนี้รวมทั้งการทดสอบข้อมูลโค้ดที่ให้มา เดิมฉันโพสต์ไว้ที่ Databricksและกำลังเผยแพร่ใหม่ที่นี่
เอกสารที่ดีที่สุดสำหรับตัวเลือก recursive RM dBFS ของของฉันได้พบอยู่ในฟอรั่ม Databricks
โซลูชันที่ใช้ได้กับ S3 ที่แก้ไขจาก Minkymorgan
เพียงแค่ส่งพา ธ ไดเร็กทอรีที่แบ่งพาร์ติชันชั่วคราว (ที่มีชื่อแตกต่างจากพา ธ สุดท้าย) เป็น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
)
}
df.write()
API ของ spark จะสร้างไฟล์ชิ้นส่วนหลายไฟล์ภายในพา ธ ที่กำหนด ... เพื่อบังคับให้ spark เขียนเฉพาะไฟล์ส่วนเดียวใช้df.coalesce(1).write.csv(...)
แทนdf.repartition(1).write.csv(...)
เนื่องจาก coalesce เป็นการแปลงที่แคบในขณะที่ repartition เป็นการแปลงแบบกว้างดูSpark - repartition () vs coalesce ()
df.coalesce(1).write.csv(filepath,header=True)
จะสร้างโฟลเดอร์ใน filepath ที่กำหนดโดยpart-0001-...-c000.csv
ใช้ไฟล์เดียว
cat filepath/part-0001-...-c000.csv > filename_you_want.csv
มีชื่อไฟล์ที่ใช้งานง่าย
df.toPandas().to_csv(path)
สิ่งนี้จะเขียน csv เดียวด้วยชื่อไฟล์ที่คุณต้องการ
แบ่งพาร์ติชั่นใหม่ / รวมกันเป็น 1 พาร์ติชันก่อนที่คุณจะบันทึก (คุณยังคงได้รับโฟลเดอร์ แต่จะมีไฟล์ส่วนหนึ่งอยู่ในนั้น)
คุณสามารถใช้ได้ rdd.coalesce(1, true).saveAsTextFile(path)
มันจะจัดเก็บข้อมูลเป็นไฟล์ singile ใน path / part-00000
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: - (สร้างกรอบข้อมูลและเขียนลงใน HDFS)
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)
ขั้นตอนที่ 3: - (รับเส้นทางในเส้นทางโฟลเดอร์ hdfs)
val pathFiles = new Path("/hdfsfolder/blah/")
ขั้นตอนที่ 4: - (รับชื่อไฟล์ spark จากโฟลเดอร์ hdfs)
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)
ขั้นตอนที่ 6: - (กรองลำดับไฟล์ _SUCESS จากรายการชื่อไฟล์ scala)
// get files name which are not _SUCCESS
val partFileName = fileNamesList.filterNot(filenames => filenames == "_SUCCESS")
ขั้นตอนที่ 7: - (แปลงรายการ scala เป็นสตริงและเพิ่มชื่อไฟล์ที่ต้องการลงในสตริงโฟลเดอร์ hdfs จากนั้นใช้การเปลี่ยนชื่อ)
val partFileSourcePath = new Path("/yourhdfsfolder/"+ partFileName.mkString(""))
val desiredCsvTargetPath = new Path(/yourhdfsfolder/+ "op_"+ ".csv")
hdfs.rename(partFileSourcePath , desiredCsvTargetPath)
ฉันใช้สิ่งนี้ใน Python เพื่อรับไฟล์เดียว:
df.toPandas().to_csv("/tmp/my.csv", sep=',', header=True, index=False)
คำตอบนี้ขยายจากคำตอบที่ยอมรับให้บริบทเพิ่มเติมและให้ข้อมูลโค้ดที่คุณสามารถเรียกใช้ใน Spark Shell บนเครื่องของคุณ
บริบทเพิ่มเติมเกี่ยวกับคำตอบที่ยอมรับ
คำตอบที่ยอมรับอาจทำให้คุณรู้สึกว่าโค้ดตัวอย่างจะส่งออก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
เป็นโฟลเดอร์ในคำตอบที่ยอมรับ - ไม่ใช่ไฟล์!
วิธีการส่งออกไฟล์เดียวที่มีชื่อเฉพาะ
เราสามารถใช้spark-dariaเพื่อเขียน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
คุณจะต้องส่งเส้นทาง s3a DariaWriters.writeSingleFile
เพื่อใช้วิธีนี้ใน S3:
DariaWriters.writeSingleFile(
df = df,
format = "csv",
sc = spark.sparkContext,
tmpFolder = "s3a://bucket/data/src",
filename = "s3a://bucket/data/dest/my_cool_file.csv"
)
ดูที่นี่สำหรับข้อมูลเพิ่มเติม
การหลีกเลี่ยง copyMerge
copyMerge ถูกลบออกจาก Hadoop 3. DariaWriters.writeSingleFile
การดำเนินการใช้งานfs.rename
, ตามที่อธิบายไว้ที่นี่ Spark 3 ยังคงใช้ Hadoop 2ดังนั้นการใช้งาน copyMerge จะใช้งานได้ในปี 2020 ฉันไม่แน่ใจว่า Spark จะอัปเกรดเป็น Hadoop 3 เมื่อใด แต่ควรหลีกเลี่ยงวิธี copyMerge ที่จะทำให้โค้ดของคุณพังเมื่อ Spark อัปเกรด Hadoop
รหัสแหล่งที่มา
มองหาDariaWriters
วัตถุในซอร์สโค้ด spark-daria หากคุณต้องการตรวจสอบการใช้งาน
การใช้งาน PySpark
การเขียนไฟล์เดียวด้วย PySpark นั้นง่ายกว่าเพราะคุณสามารถแปลง DataFrame เป็น Pandas DataFrame ที่เขียนเป็นไฟล์เดียวตามค่าเริ่มต้น
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
วิธี Scala และdf.toPandas()
งูหลามวิธีการทำงานเฉพาะสำหรับชุดข้อมูลขนาดเล็ก ไม่สามารถเขียนชุดข้อมูลขนาดใหญ่เป็นไฟล์เดียวได้ การเขียนข้อมูลเป็นไฟล์เดียวไม่ดีที่สุดจากมุมมองด้านประสิทธิภาพเนื่องจากไม่สามารถเขียนข้อมูลพร้อมกันได้
โดยใช้ 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()
มีอีกหนึ่งวิธีในการใช้ Java
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)}