使用Python高效地转换、压缩(内存中)和摄取CSV文件到AWS S3。

188 阅读7分钟

如何有效地转换CSV文件并以压缩的形式上传到AWS S3 (Python, Boto3)

如果你一直在数据工程领域工作,你有可能参与处理CSV文件。尽管它不是最有效的分析格式,但CSV格式在当前的数据领域仍然享有相当重要的地位。它是一种被广泛支持的格式,通常在源系统(如数据库)以文件形式提供数据时遇到,这些数据将被摄入数据湖(以及随后的专门存储,如数据仓库,以服务于特定用途)。如果你是一个AWS商店,并且需要处理CSV文件并将其上传到S3,这篇文章涵盖了使用Python和AWS SDK(boto3)的方法之一。虽然人们可以用很多方法来解决这个问题,但有几个考虑因素值得强调,以使它成为一个相当可行的解决方案。

  • 摄取前的压缩 - 网络通常是任何系统架构中的主要瓶颈。另一方面,CSV格式在压缩方面的效率并不高,因此在未压缩的情况下,其大小通常很庞大。因此,将一个庞大的CSV文件输入到AWS S3可能是一个相当昂贵的操作。我们总是建议在录入数据之前采用压缩的方式。现在,在选择正确的压缩格式方面可能有多种考虑,这取决于如何进行下游处理,例如,如果你正在使用像Spark这样的大数据工具,建议使用可分割的压缩格式。为了简单起见,我使用gzip作为压缩格式。
  • 避免ETL中的I/O - 只要有可能,建议在ETL过程中避免磁盘I/O。磁盘I/O是相当昂贵/缓慢的操作,因此,如果有任何事情你可以在内存中完成,它最终有助于你的方法的整体优化。

有了这些基本的考虑,让我们来看看一个例子,看看你如何通过Python的本地库和AWS SDK(boto3)来转换一个CSV文件,压缩它(实时或在内存中)并上传S3。

要求。

  • AWS账户
  • IAM用户
  • S3桶
  • Python
  • Boto3

让我们假设你有一个简单的CSV文件,看起来像这样。

sensor_code,parameter_name,start_timestamp,finish_timestamp,sensor_value

从概念上讲,这可以被认为是传感器指标的时间序列数据。你可能已经注意到,时间戳的格式不是 "标准 "格式,即yyyy-mm-dd HH:MM:SS。而如果你要把数据加载到数据仓库,它们在解析非标准时间格式时可能会有一些限制。例如,Redshift可以识别许多时间戳格式,但不是全部。而上面例子中的那个是Redshift在加载数据时无法识别的格式(比如通过S3的COPY命令)。因此,如果目标是将这些数据加载到Redshift中,这就证明需要做一些基本的转换,使源文件中的时间戳格式标准化,从而在COPY操作中被Redshift识别。

让我们来看看如何在Python中以最佳方式实现这一任务的示例代码。

在上面的片段中发生了很多事情。为了更好的理解,让我们把它分解一下。

  • 第7行:我们通过boto3.client()方法创建一个S3客户端。建议使用boto3.Session(),然后从中创建boto3.client(这篇文章给出了一个很好的解释)。为了简单起见,我只用了boto3.client()
  • 第9行:我们使用内存中的字节缓冲器创建一个二进制流来存储字节。简单地说,可以把它看作是向内存中的文件而不是磁盘上的实际文件写入字节的一种方式。这将被用来存储压缩(gzipped)对象的数据,这些数据将被上传到S3。
  • 第10到11行:我们通过python的*open()函数打开我们的源CSV文件,使用基于 "with "上下文管理的方法,以确保文件在最后被正确关闭,即使在出现异常的情况下。然后我们利用csv.reader()*并将打开的文件对象传递给它。我们还指定了我们的CSV文件的分隔符。
  • 第12行:由于我们的CSV文件包含头文件,所以我们使用python的Next函数来获取第一项/行。(在这种情况下是头,它将是一个列表类型)。
  • 第13至15行:这是我们进行实际处理的地方,即把时间戳格式解析为标准格式。我们在csv_rdr对象中进行迭代,每次迭代都会产生一条记录。我们使用datetime.datetime.strptime(x[2], "%d/%m/%Y %I:%M:%S %p") 来解析第三列中的时间戳值(由于x[2]),按照时间戳格式*(%d/%m/%Y %I:%M:%S %p*),将其转换为字符串,然后存储。这给了我们标准的yyyy-mm-dd HH:MM:SS格式的时间戳值。
  • 第16行:我们将转换后的行追加到transformed_rows 列表中。需要注意的是,对于大文件来说,这并不是一个优化的方法,因为内存的利用率不高。为了有效处理大文件,请考虑使用生成器。
  • 第18行:我们创建了一个新的列表transformed_rows_header ,它是两个列表的连接,即headertransformed_rows。 因此,transformed_rows_header 是一个列表的列表。这个transformed_rows_header 的第一个元素是header。之后的每个元素都是经过解析的时间戳格式的转换行。在这个阶段,我们已经将数据转换为我们所需要的状态,但是它还活在Python的对象中,即一个列表。
  • 第 19 行:类似于我们打开文件的方式,我们通过 gzip.GzipFile 初始化一个上下文管理器,指定我们要写一个 gzip 文件。我们指定我们在第 8 行初始化的 mem_file BytesIO 缓冲区作为我们的目标,即 gzip 操作的输出将被写入其中。我们指定'wb'作为模式,以指定我们要写入字节。对于compresslevel,我们使用6,这也是默认的,并在速度和压缩率之间取得了良好的平衡。因此,简而言之,我们准备将我们的gzip文件的输出写入哪里(在这种情况下,写入内存缓冲区中)
  • 第20到22行:我们需要将转换后的数据(目前在transformed_rows_header 列表中)冲成CSV格式,然后可以压缩并作为一个文件上传。为此,我们初始化一个名为buff的内存字符串缓冲区(类似于内存字节IO缓冲区),并使用 "csv "模块将我们的列表即transformed_rows_header 中的结果写入这个内存文件(buff),又称字符串缓冲区。你可以认为是将CSV数据写入文件,但在这种情况下,我们是在进行内存操作。由于前面讨论的原因,我们没有把数据写到磁盘上。因此,在这个阶段,我们的 "缓冲区 "现在包含了以整齐的CSV格式转换的数据。
  • 第23行:我们将内存中转换后的CSV文件(buff)的内容,进行压缩并写入内存中的BytesIO缓冲区(mem_file)。同时,也照顾到了字符编码(即UTF-8)。最后,我们将CSV数据转化为gzip压缩形式的字节,并写入mem_file的Bytes Buffer。
  • 第24行:我们把流的位置带到内存缓冲区的起点。把它想象成一个光标,现在它指向文件的开始。这样,当我们上传到S3时,整个文件就会从头开始读取。
  • 第25行:我们使用*s3.put_object()*方法来上传数据到指定的桶和前缀。在这种情况下,对于Body参数,我们指定mem_file(内存中的字节缓冲区),它持有压缩和转化的CSV数据

and viola!如果你已经处理了AWS方面的事情,例如,你有一个账户,一个桶,一个有权限写入桶的IAM用户,配置了一个AWS CLI配置文件(或者如果你已经在AWS中,有一个角色),那么它应该读取文件,转换,压缩和上传它到S3桶

所以,这就差不多了。这篇文章展示了一个非常简单的场景,有基本的转换逻辑。你可以使用同样的逻辑,同时使用像Pandas这样的库来进行稍微高级的转换。编码愉快!