Salve Dataframe em csv diretamente no s3 Python


125

Eu tenho um DataFrame do pandas que desejo enviar para um novo arquivo CSV. O problema é que não quero salvar o arquivo localmente antes de transferi-lo para o s3. Existe algum método como to_csv para gravar o dataframe em s3 diretamente? Estou usando o boto3.
Aqui está o que tenho até agora:

import boto3
s3 = boto3.client('s3', aws_access_key_id='key', aws_secret_access_key='secret_key')
read_file = s3.get_object(Bucket, Key)
df = pd.read_csv(read_file['Body'])

# Make alterations to DataFrame

# Then export DataFrame to CSV through direct transfer to s3

3
df.to_csv('s3://mybucket/dfs/somedf.csv'). stackoverflow.com/a/56275519/908886 para mais informações.
Peter Berg

Respostas:


158

Você pode usar:

from io import StringIO # python3; python2: BytesIO 
import boto3

bucket = 'my_bucket_name' # already created on S3
csv_buffer = StringIO()
df.to_csv(csv_buffer)
s3_resource = boto3.resource('s3')
s3_resource.Object(bucket, 'df.csv').put(Body=csv_buffer.getvalue())

9
Se este for um arquivo grande, o que isso causa na memória ...?
citynorman

2
Se o arquivo for maior que a RAM disponível, a ação irá falhar e irá exceto uma Exceção (não sei qual). Isso deve ser aceito como resposta
Eran Moshe

5
Recebi um TypeError: unicode argument expected, got 'str'erro ao usar StringIO. Usei BytesIOe funcionou perfeitamente bem. Observação: isso foi em Python 2.7
Abhishek Upadhyaya

1
o que é bucketobjeto? como você criou isso?
Charles Chow

1
bucketé onde você armazena objetos no S3. O código assume que você já criou o destino (pense: diretório) onde armazená-lo. Ver S3 docs
Stefan

65

Você pode usar diretamente o caminho S3. Estou usando o Pandas 0.24.1

In [1]: import pandas as pd

In [2]: df = pd.DataFrame( [ [1, 1, 1], [2, 2, 2] ], columns=['a', 'b', 'c'])

In [3]: df
Out[3]:
   a  b  c
0  1  1  1
1  2  2  2

In [4]: df.to_csv('s3://experimental/playground/temp_csv/dummy.csv', index=False)

In [5]: pd.__version__
Out[5]: '0.24.1'

In [6]: new_df = pd.read_csv('s3://experimental/playground/temp_csv/dummy.csv')

In [7]: new_df
Out[7]:
   a  b  c
0  1  1  1
1  2  2  2

Nota de lançamento:

Tratamento de arquivos S3

O pandas agora usa s3fs para lidar com conexões S3. Isso não deve quebrar nenhum código. No entanto, como o s3fs não é uma dependência necessária, você precisará instalá-lo separadamente, como o boto nas versões anteriores do pandas. GH11915 .


7
esta é definitivamente a resposta mais fácil agora, ele usa s3fs nos bastidores, então você precisa adicioná-lo ao seu requirements.txt
JD D

1
Eu gosto de ser fácil, mas parece que não está funcionando, visto que continuo recebendo o seguinte erro NoCredentialsError: Unable to locate credentials. Alguma sugestão?
CathyQian

1
Posso confirmar que isso não funciona com pandas <= 0.23.4, então certifique-se de atualizar para pandas 0.24
Guido

1
Este é o erro que vejo quando tento usar o comando to_csv TypeError: write () o argumento 1 deve ser Unicode, não str
Raj

13
Estou usando o pandas 0.24.2 e o que recebo é NotImplementedError: Text mode not supported, use mode='wb' and manage bytes. alguma sugestão?
Binyamin Even

57

Eu gosto do s3fs, que permite que você use o s3 (quase) como um sistema de arquivos local.

Você consegue fazer isso:

import s3fs

bytes_to_write = df.to_csv(None).encode()
fs = s3fs.S3FileSystem(key=key, secret=secret)
with fs.open('s3://bucket/path/to/file.csv', 'wb') as f:
    f.write(bytes_to_write)

s3fssuporta apenas rbe wbmodos de abrir o arquivo, é por isso que fiz isso bytes_to_write.


Ótimo! Como posso obter o url do arquivo usando o mesmo módulo s3fs?
M.Zaman

Eu estava procurando a URL de onde posso baixar o arquivo escrito, de qualquer forma, consigo isso via S3FileSystem. Obrigado
M.Zaman,

é isso que eu uso; obrigado. Estou curioso para saber por que pd.read_csv (<s3path>) funciona como esperado, mas para escrever, temos que usar esta solução alternativa .. exceto no caso de eu estar escrevendo diretamente para o balde s3 em que meu jupyter está.
Renée

@ michcio1234 como posso fazer o mesmo no modo anexar? Preciso anexar os dados no csv existente em s3
j '

@j ' s3fsnão parece suportar o modo de acréscimo.
michcio1234

43

Esta é uma resposta mais atualizada:

import s3fs

s3 = s3fs.S3FileSystem(anon=False)

# Use 'w' for py3, 'wb' for py2
with s3.open('<bucket-name>/<filename>.csv','w') as f:
    df.to_csv(f)

O problema com o StringIO é que ele vai consumir sua memória. Com este método, você está transmitindo o arquivo para s3, em vez de convertê-lo em string e, em seguida, gravá-lo em s3. Manter o dataframe do pandas e sua cópia da string na memória parece muito ineficiente.

Se você estiver trabalhando em um instante ec2, poderá atribuir a ele uma função IAM para permitir gravá-lo em s3, portanto, não é necessário passar credenciais diretamente. No entanto, você também pode se conectar a um intervalo passando credenciais para a S3FileSystem()função. Veja a documentação: https://s3fs.readthedocs.io/en/latest/


Por alguma razão, quando fiz isso, todas as linhas foram puladas na saída CSV
kjmerf

Hmm. não tenho certeza por que isso aconteceria. talvez tente com outro pandas df para ver se você ainda pega o problema? Se a sua versão do pandas for compatível, tente a resposta de @amit-kushwaha, para onde você passa o url s3 diretamente to_csv(). parece uma implementação mais limpa.
erncyp

@erncyp Parece que estou recebendo o erro: botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the PutObject operation: Access Denied ... Eu até mesmo fiz o intervalo PUBLIC READ e adicionei as seguintes ações, sob minha conta específica de usuário IAM, na Política do "Action": [ "s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:GetObjectAcl", "s3:DeleteObject" ]
intervalo

parece que você está sem as permissões? Certifique-se de anexar permissões de leitura e gravação S3 à função IAM que você está usando
erncyp

@erncyp Eu tenho a política de Acesso do Administrador anexada ao meu usuário IAM, então, em teoria, eu deveria ser capaz de ler / escrever muito bem ... Estranhamente, eu sou capaz de escrever muito bem quando uso a seguinte função que criei, usando outro usuário do StackOverflow conselho (ponto e vírgula fyi são fim de linha, pois não sei como formatar na seção de comentários):def send_to_bucket(df, fn_out, bucketname): csv_buffer = StringIO(); df.to_csv(csv_buffer); s3_resource = boto3.resource('s3'); s3_resource.Object(bucketname, fn_out).put(Body=csv_buffer.getvalue());
ajoros

13

Se passar Nonecomo primeiro argumento to_csv()os dados serão devolvidos como string. A partir daí, é uma etapa fácil fazer o upload para o S3 de uma vez.

Também deve ser possível passar um StringIOobjeto para to_csv(), mas usar uma string será mais fácil.


Será mais fácil de que maneira? Qual é a maneira correta de fazer isso?
Eran Moshe

@EranMoshe: de qualquer forma vai funcionar correctamente, mas, obviamente, é mais fácil de passar Nonepara to_csv()e usar a string retornada do que é criar um StringIOobjeto e, em seguida, ler novamente os dados para fora.
mhawke

Como um programador preguiçoso, foi o que fiz. E você quis mais fácil para o programador que escreve menos código:>
Eran Moshe

2

Você também pode usar o AWS Data Wrangler :

import awswrangler

session = awswrangler.Session()
session.pandas.to_csv(
    dataframe=df,
    path="s3://...",
)

Observe que ele será dividido em várias partes, uma vez que carrega em paralelo.


2

Descobri que isso pode ser feito usando clienttambém e não apenas resource.

from io import StringIO
import boto3
s3 = boto3.client("s3",\
                  region_name=region_name,\
                  aws_access_key_id=aws_access_key_id,\
                  aws_secret_access_key=aws_secret_access_key)
csv_buf = StringIO()
df.to_csv(csv_buf, header=True, index=False)
csv_buf.seek(0)
s3.put_object(Bucket=bucket, Body=csv_buf.getvalue(), Key='path/test.csv')

0

já que você está usando boto3.client(), tente:

import boto3
from io import StringIO #python3 
s3 = boto3.client('s3', aws_access_key_id='key', aws_secret_access_key='secret_key')
def copy_to_s3(client, df, bucket, filepath):
    csv_buf = StringIO()
    df.to_csv(csv_buf, header=True, index=False)
    csv_buf.seek(0)
    client.put_object(Bucket=bucket, Body=csv_buf.getvalue(), Key=filepath)
    print(f'Copy {df.shape[0]} rows to S3 Bucket {bucket} at {filepath}, Done!')

copy_to_s3(client=s3, df=df_to_upload, bucket='abc', filepath='def/test.csv')

-1

Encontrei uma solução muito simples que parece estar funcionando:

s3 = boto3.client("s3")

s3.put_object(
    Body=open("filename.csv").read(),
    Bucket="your-bucket",
    Key="your-key"
)

Espero que ajude !


-5

Eu li um csv com duas colunas do intervalo s3 e o conteúdo do arquivo csv eu coloquei no dataframe do pandas.

Exemplo:

config.json

{
  "credential": {
    "access_key":"xxxxxx",
    "secret_key":"xxxxxx"
}
,
"s3":{
       "bucket":"mybucket",
       "key":"csv/user.csv"
   }
}

cls_config.json

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import json

class cls_config(object):

    def __init__(self,filename):

        self.filename = filename


    def getConfig(self):

        fileName = os.path.join(os.path.dirname(__file__), self.filename)
        with open(fileName) as f:
        config = json.load(f)
        return config

cls_pandas.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pandas as pd
import io

class cls_pandas(object):

    def __init__(self):
        pass

    def read(self,stream):

        df = pd.read_csv(io.StringIO(stream), sep = ",")
        return df

cls_s3.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import boto3
import json

class cls_s3(object):

    def  __init__(self,access_key,secret_key):

        self.s3 = boto3.client('s3', aws_access_key_id=access_key, aws_secret_access_key=secret_key)

    def getObject(self,bucket,key):

        read_file = self.s3.get_object(Bucket=bucket, Key=key)
        body = read_file['Body'].read().decode('utf-8')
        return body

test.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from cls_config import *
from cls_s3 import *
from cls_pandas import *

class test(object):

    def __init__(self):
        self.conf = cls_config('config.json')

    def process(self):

        conf = self.conf.getConfig()

        bucket = conf['s3']['bucket']
        key = conf['s3']['key']

        access_key = conf['credential']['access_key']
        secret_key = conf['credential']['secret_key']

        s3 = cls_s3(access_key,secret_key)
        ob = s3.getObject(bucket,key)

        pa = cls_pandas()
        df = pa.read(ob)

        print df

if __name__ == '__main__':
    test = test()
    test.process()

4
por favor, não publique apenas a solução, acrescente uma explicação sobre ela.
sjaustirni de

Existe alguma vantagem em fazer uma solução tão complexa (para um novato em Python)?
Javier López Tomás

1
Isso lê um arquivo de s3, a questão era como escrever um df para s3.
Damian Satterthwaite-Phillips
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.