如何将多个文本文件读入单个RDD?


178

我想从hdfs位置读取一堆文本文件,并使用spark在迭代中对其执行映射。

JavaRDD<String> records = ctx.textFile(args[1], 1); 一次只能读取一个文件。

我想读取多个文件并将它们作为单个RDD处理。怎么样?

Answers:


298

您可以指定整个目录,使用通配符,甚至可以使用目录和通配符的CSV。例如:

sc.textFile("/my/dir1,/my/paths/part-00[0-5]*,/another/dir,/a/specific/file")

正如尼克·查马斯(Nick Chammas)所指出的,这是对Hadoop的了解FileInputFormat,因此,它也适用于Hadoop(和扩展)。


10
是的,这是将多个文件作为单个RDD打开的最方便的方法。这里的API只是Hadoop的FileInputFormat API的公开,因此所有相同的Path选项都适用。
Nick Chammas 2014年

7
sc.wholeTextFiles对于非行定界的数据非常方便
MichalČizmazia'15

1
奇怪的是,如果您执行此操作并指定并行性,则说它sc.textFile(multipleCommaSeparatedDirs,320)导致了19430总任务而不是320...它的行为就像union是从非常低的并行性中也导致了疯狂的任务数量
lisak 2015年

2
我终于发现了这种邪恶的文件模式匹配是如何工作的stackoverflow.com/a/33917492/306488,所以我不再需要用逗号定界了
lisak 2015年

@femibyte我不这么认为,尽管我不知道为什么在除for之外的任何情况下您都想知道文件名wholeTextFiles。您的用例是什么?如果您使用与文件相同数量的分区,我可以想到一种解决方法...
samthebest

35

用法union如下:

val sc = new SparkContext(...)
val r1 = sc.textFile("xxx1")
val r2 = sc.textFile("xxx2")
...
val rdds = Seq(r1, r2, ...)
val bigRdd = sc.union(rdds)

然后bigRdd是所有文件的RDD。


谢谢云,这样我就可以读取所有想要的文件,但是一个!但是,我仍然要写很多东西……
gsamaras


9

你可以用这个

首先,您可以获得S3路径的缓冲区/列表:

import scala.collection.JavaConverters._
import java.util.ArrayList
import com.amazonaws.services.s3.AmazonS3Client
import com.amazonaws.services.s3.model.ObjectListing
import com.amazonaws.services.s3.model.S3ObjectSummary
import com.amazonaws.services.s3.model.ListObjectsRequest

def listFiles(s3_bucket:String, base_prefix : String) = {
    var files = new ArrayList[String]

    //S3 Client and List Object Request
    var s3Client = new AmazonS3Client();
    var objectListing: ObjectListing = null;
    var listObjectsRequest = new ListObjectsRequest();

    //Your S3 Bucket
    listObjectsRequest.setBucketName(s3_bucket)

    //Your Folder path or Prefix
    listObjectsRequest.setPrefix(base_prefix)

    //Adding s3:// to the paths and adding to a list
    do {
      objectListing = s3Client.listObjects(listObjectsRequest);
      for (objectSummary <- objectListing.getObjectSummaries().asScala) {
        files.add("s3://" + s3_bucket + "/" + objectSummary.getKey());
      }
      listObjectsRequest.setMarker(objectListing.getNextMarker());
    } while (objectListing.isTruncated());

    //Removing Base Directory Name
    files.remove(0)

    //Creating a Scala List for same
    files.asScala
  }

现在,将此List对象传递给以下代码,注意:sc是SQLContext的对象

var df: DataFrame = null;
  for (file <- files) {
    val fileDf= sc.textFile(file)
    if (df!= null) {
      df= df.unionAll(fileDf)
    } else {
      df= fileDf
    }
  }

现在您有了最终的Unified RDD,即df

可选,您还可以将其重新分区到单个BigRDD中

val files = sc.textFile(filename, 1).repartition(1)

重新分区始终有效:D


这是否意味着文件列表必须相对较小?没有数百万个文件。
Mathieu Longtin

2
我们可以并行执行读取列出文件的操作吗?像sc.parallelize之类的东西?
lazywiz

1
@MathieuLongtin:如果您可以将分区发现应用于您的Spark代码,那将是非常不错的选择,否则您需要这样做。我曾经在大约一分钟内打开10k文件。
Murtaza Kanchwala

@lazywiz如果您不想创建单个rdd,则只需删除重新分区操作即可。
Murtaza Kanchwala

3

在PySpark中,我发现了另一种有用的解析文件的方法。也许在Scala中有一个等效的版本,但是我不太满意提出一个可行的翻译。实际上,它是一个带有标签的textFile调用(在下面的示例中,键=文件名,值=文件的1行)。

“标签”文本文件

输入:

import glob
from pyspark import SparkContext
SparkContext.stop(sc)
sc = SparkContext("local","example") # if running locally
sqlContext = SQLContext(sc)

for filename in glob.glob(Data_File + "/*"):
    Spark_Full += sc.textFile(filename).keyBy(lambda x: filename)

输出:数组,每个条目包含一个使用filename-as-key的元组,并且value =文件的每一行。(从技术上讲,使用此方法,除了实际的文件路径名之外,您还可以使用其他键-可能是散列表示形式,以节省内存)。即。

[('/home/folder_with_text_files/file1.txt', 'file1_contents_line1'),
 ('/home/folder_with_text_files/file1.txt', 'file1_contents_line2'),
 ('/home/folder_with_text_files/file1.txt', 'file1_contents_line3'),
 ('/home/folder_with_text_files/file2.txt', 'file2_contents_line1'),
  ...]

您也可以将其重新组合为行列表:

Spark_Full.groupByKey().map(lambda x: (x[0], list(x[1]))).collect()

[('/home/folder_with_text_files/file1.txt', ['file1_contents_line1', 'file1_contents_line2','file1_contents_line3']),
 ('/home/folder_with_text_files/file2.txt', ['file2_contents_line1'])]

或将整个文件重新组合为单个字符串(在本示例中,结果与从WholeTextFiles获得的结果相同,但从文件路径中去除了字符串“ file:”。):

Spark_Full.groupByKey().map(lambda x: (x[0], ' '.join(list(x[1])))).collect()


当我运行这一行代码时,Spark_Full += sc.textFile(filename).keyBy(lambda x: filename) 出现了错误,即TypeError: 'PipelinedRDD' object is not iterable。我的理解是,该行创建的RDD是不可变的,因此我想知道您如何将其附加到另一个变量?
KartikKannapur

3

您可以使用

JavaRDD<String , String> records = sc.wholeTextFiles("path of your directory")

在这里,您将获得文件的路径以及该文件的内容。因此您可以一次执行整个文件的任何操作,从而节省了开销


2

所有答案都是正确的 sc.textFile

我只是想知道为什么不这样做wholeTextFiles,例如,在这种情况下...

val minPartitions = 2
val path = "/pathtohdfs"
    sc.wholeTextFiles(path,minPartitions)
      .flatMap{case (path, text) 
    ...

一个限制是,我们必须加载小文件,否则性能会很差,并可能导致OOM。

注意 :

  • 整个文件应适合内存
  • 适用于无法按行分割的文件格式...例如XML文件

进一步参考访问


或者只是sc.wholeTextFiles(folder).flatMap...
Evhz

sc.wholeTextFiles(“ / path / to / dir”)
Ram Ghadiyaram

1

有直接的干净解决方案可用。使用wholeTextFiles()方法。这将得到一个目录并形成一个键值对。返回的RDD将是一对RDD。在Spark docs的说明下方找到:

SparkContext.wholeTextFiles使您可以读取包含多个小文本文件的目录,并将每个小文本文件作为(文件名,内容)对返回。这与textFile相反,后者将在每个文件的每一行返回一条记录


-1

试试这个 接口,用于将DataFrame写入外部存储系统(例如文件系统,键值存储等)。使用DataFrame.write()来访问它。

1.4版的新功能。

csv(path,mode = None,compression = None,sep = None,quote = None,escape = None,header = None,nullValue = None,escapeQuotes = None,quoteAll = None,dateFormat = None,timestampFormat = None)保存指定路径中CSV格式的DataFrame的内容。

参数:path –任何Hadoop支持的文件系统模式下的路径–指定当数据已经存在时保存操作的行为。

append:将此DataFrame的内容追加到现有数据。覆盖:覆盖现有数据。忽略:如果数据已经存在,则静默忽略此操作。错误(默认情况):如果数据已经存在,则引发异常。压缩–保存到文件时使用的压缩编解码器。这可以是已知的不区分大小写的缩写名称之一(none,bzip2,gzip,lz4,snappy和deflate)。sep –将单个字符设置为每个字段和值的分隔符。如果设置为None,则使用默认值.。quote –设置用于转义带引号的值的单个字符,其中分隔符可以是值的一部分。如果设置为None,则使用默认值“。如果要关闭引号,则需要设置一个空字符串。escape-设置用于在已引号的值内转义引号的单个字符。如果设置为None ,它使用默认值\\ scapeQuotes –一个标志,指示是否应始终将包含引号的值括在引号中。如果设置为None,它将使用默认值true,转义包含引号字符的所有值。quoteAll –一个标志,指示是否应始终将所有值括在引号中。如果设置为None,它将使用默认值false,仅转义包含引号字符的值。标头–将列名写为第一行。如果设置为None,则使用默认值false。nullValue –设置空值的字符串表示形式。如果设置为None,则使用默认值空字符串。dateFormat –设置指示日期格式的字符串。自定义日期格式遵循java.text.SimpleDateFormat的格式。这适用于日期类型。如果设置为None,则使用默认值yyyy-MM-dd。timestampFormat –设置指示时间戳格式的字符串。自定义日期格式遵循java.text.SimpleDateFormat的格式。这适用于时间戳类型。如果设置为None,它将使用默认值yyyy-MM-dd'T'HH:mm:ss.SSSZZ。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.