是否可以从 S3 下载具有数百万行的大文件的前 100 行?

Is it possible to download the first 100 lines of a big file with millions of lines from S3?

我有多个 100MB 的原始文件,其中包含 CSV 格式的一系列用户活动。我只想下载文件的前 100 行。

问题是每个文件可能有不同的 CSV header 列和数据值,因为它们是来自使用不同 activity 跟踪提供商的多个子域的用户活动。这意味着每行可以是50个字符长或500个字符长,直到我全部阅读它们才知道。

S3 支持 getObject API 和 Range 参数,您可以使用该参数下载文件的特定 XX 字节范围。

https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html#API_GetObject_RequestSyntax

如果我使用这个 API 来解析前 1Mb 的文件,迭代每个字节直到我看到 100 个新行字符 \n,这在技术上可行吗?对于这种方法,我有什么需要注意的地方吗? (例如多字节字符?)

您可以这样使用 smart_open:

from smart_open import open

with open('s3://bucket/path/file.csv', 'r') as f:
    csv_reader = csv.DictReader(f, delimiter=',')
    data = ''
    for i, row in enumerate(csv_reader):
        data += row +'\n'
        if i > 100:
            store(data)

您需要在本地计算机上打开另一个具有写入权限的文件,以存储 100 行或任意多行。如果您想要来自多个文件的第一行,您可以执行相同的操作,但使用 boto3 函数列出文件并将 path/file 名称发送到使用 smart_open.

的函数
s3client = boto3.client('s3')

listObj = s3client.list_objects_v2(Bucket=bucket, Prefix=prefix)
for obj in listObj['Contents']:
    smart_function(obj['Key'])

obj['Key']包含了那个Bucket中每个文件的路径和文件名+Path(Prefix)

没有built-in的方法,byte-range fetches是最好的前进方法。

由于您不确定每种情况下的 header 或行长度,下载 1MB 块直到您有 100 行是一种安全有效的方法。

多字节字符等在这个级别并不重要,您纯粹希望在 100 \n 个字符后停止阅读。但是,根据文件的来源,我也会意识到 \r\n\r 是有效的行结尾。

我写了下面的 Java 代码来获取 last n 字节,请随意使用它作为获取第一个 n 个字节:

public String getLastBytesOfObjectAsString(String bucket, String key, long lastBytesCount) {
    try {
        final ObjectMetadata objectMetadata = client.getObjectMetadata(bucket, key);
        final long fileSizeInBytes = objectMetadata.getContentLength();

        long rangeStart = fileSizeInBytes - lastBytesCount;
        if (rangeStart < 0) {
            rangeStart = 0;
        }

        final GetObjectRequest getObjectRequest =
                    new GetObjectRequest(bucket, key).withRange(rangeStart);

        try (S3Object s3Object = client.getObject(getObjectRequest);
             InputStream inputStream = s3Object.getObjectContent()) {
            return new String(inputStream.readAllBytes());
        }
    } catch (Exception ex) {
        ...
    }
}