https://github.com/databricks/spark-csv를 사용 하고 있습니다. 단일 CSV를 작성하려고하지만 할 수 없습니다. 폴더를 만들고 있습니다.
경로 및 파일 이름과 같은 매개 변수를 가져와 해당 CSV 파일을 작성하는 Scala 함수가 필요합니다.
https://github.com/databricks/spark-csv를 사용 하고 있습니다. 단일 CSV를 작성하려고하지만 할 수 없습니다. 폴더를 만들고 있습니다.
경로 및 파일 이름과 같은 매개 변수를 가져와 해당 CSV 파일을 작성하는 Scala 함수가 필요합니다.
답변:
각 파티션이 개별적으로 저장되기 때문에 여러 파일이있는 폴더를 생성합니다. 단일 출력 파일 (여전히 폴더에 있음)이 필요한 경우 다음을 수행 할 수 있습니다 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)
은 _temporary 디렉토리에 FileNotFoundException을 설정하면 오류가 발생 합니다. 여전히 스파크의 버그입니다. issues.apache.org/jira/browse/SPARK-2984
coalesce(1)
매우 비싸고 일반적으로 실용적이지 않은 단순한 결과입니다 .
HDFS로 Spark를 실행하는 경우 정상적으로 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에서 제거됩니다. 최신 버전으로 작업하는 방법에 대한 자세한 내용은 다음 스택 오버플로 문서를 참조하십시오. Hadoop 3.0에서 CopyMerge를 수행하는 방법은 무엇입니까?
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)
파일이 작업자의 RAM에 맞지 않으면 chaotic3quilibrium의 FileUtils.copyMerge () 사용 제안을 고려할 수 있습니다 . 나는 이것을하지 않았으며, 예를 들어 S3에서 가능한지 아직 알지 못합니다.
이 답변은이 질문에 대한 이전 답변과 제공된 코드 조각에 대한 자체 테스트를 기반으로 작성되었습니다. 나는 원래 그것을 Databricks에 게시했고 여기에 다시 게시하고 있습니다.
내가 찾은 dbfs의 rm의 재귀 옵션에 대한 최고의 문서 는 Databricks 포럼에 있습니다.
Minkymorgan에서 수정 된 S3에서 작동하는 솔루션입니다.
임시 파티션 된 디렉터리 경로 (최종 경로와 다른 이름)를 최종 경로와 다른 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는 힘 스파크 쓰기에 ... 주어진 경로 내에서 단지 하나의 파트 파일 사용 여러 부품 파일이 생성됩니다 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)
원하는 파일 이름으로 단일 csv를 작성할 수도 있습니다
저장하기 전에 파티션을 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 단계 :-(데이터 프레임 생성 및 HDFS에 쓰기)
df.coalesce(1).write.format("csv").option("header", "false").mode(SaveMode.Overwrite).save("/hdfsfolder/blah/")
2 단계 :-(Hadoop 구성 생성)
val hadoopConfig = new Configuration()
val hdfs = FileSystem.get(hadoopConfig)
Step3 :-(hdfs 폴더 경로에서 경로 가져 오기)
val pathFiles = new Path("/hdfsfolder/blah/")
4 단계 :-(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 파일 순서 필터링)
// get files name which are not _SUCCESS
val partFileName = fileNamesList.filterNot(filenames => filenames == "_SUCCESS")
7 단계 :-(스칼라 목록을 문자열로 변환하고 원하는 파일 이름을 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 경로
DariaWriters.writeSingleFile
S3에서이 메서드를 사용하려면 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 피하기
copyMerge는 하둡 제으로부터 제거 DariaWriters.writeSingleFile
구현 용도 fs.rename
, 여기에 설명 된 바와 같이 . Spark 3는 여전히 Hadoop 2를 사용 했기 때문에 copyMerge 구현은 2020 년에 작동 할 것입니다. Spark가 Hadoop 3으로 언제 업그레이드 될지는 확실하지 않지만 Spark가 Hadoop을 업그레이드 할 때 코드가 손상되는 copyMerge 접근 방식을 피하는 것이 좋습니다.
소스 코드
DariaWriters
구현을 검사하려면 spark-daria 소스 코드에서 객체를 찾으십시오 .
PySpark 구현
DataFrame을 기본적으로 단일 파일로 기록되는 Pandas DataFrame으로 변환 할 수 있으므로 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()
파이썬은 작은 데이터 세트에 대해서만 작업을 접근. 거대한 데이터 세트는 단일 파일로 쓸 수 없습니다. 데이터를 병렬로 쓸 수 없기 때문에 데이터를 단일 파일로 쓰는 것은 성능 측면에서 최적이 아닙니다.
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)}