Guardar Dataframe en csv directamente en s3 Python

125

Tengo un DataFrame de pandas que quiero cargar en un nuevo archivo CSV. El problema es que no quiero guardar el archivo localmente antes de transferirlo a s3. ¿Existe algún método como to_csv para escribir el marco de datos en s3 directamente? Estoy usando boto3.
Esto es lo que tengo hasta ahora:

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
usuario2494275
fuente
3
df.to_csv('s3://mybucket/dfs/somedf.csv'). stackoverflow.com/a/56275519/908886 para obtener más información.
Peter Berg

Respuestas:

158

Puedes 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())
Stefan
fuente
9
Si se trata de un archivo grande, ¿qué le hace a la memoria ...?
citynorman
2
Si el archivo es más grande que la RAM que tiene disponible, la acción fallará y saldrá una excepción (no sé cuál). Esto debería aceptarse como respuesta
Eran Moshe
5
Recibí un TypeError: unicode argument expected, got 'str'error al usar StringIO. Lo usé BytesIOy funcionó perfectamente bien. Nota: esto fue en Python 2.7
Abhishek Upadhyaya
1
que es bucketobjeto? ¿cómo creaste eso?
Charles Chow
1
bucketes donde almacena objetos en S3. El código asume que ya ha creado el destino (piense: directorio) donde almacenar esto. Ver documentos de S3
Stefan
65

Puede utilizar directamente la ruta S3. Estoy usando 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 lanzamiento:

Manejo de archivos S3

pandas ahora usa s3fs para manejar conexiones S3. Esto no debería romper ningún código. Sin embargo, dado que s3fs no es una dependencia necesaria, deberá instalarlo por separado, como boto en versiones anteriores de pandas. GH11915 .

vara de medir17
fuente
7
esta es definitivamente la respuesta más fácil ahora, usa s3fs detrás de escena, por lo que debe agregar eso a sus requisitos.txt
JD D
1
Me gusta que sea fácil, pero parece que realmente no funciona ya que sigo recibiendo el siguiente error NoCredentialsError: Unable to locate credentials. ¿Alguna sugerencia?
CathyQian
1
Puedo confirmar que esto no funciona con pandas <= 0.23.4, así que asegúrese de actualizar a pandas 0.24
Guido
1
Este es el error que veo cuando intento usar el comando to_csv TypeError: el argumento 1 de write () debe ser unicode, no str
Raj
13
Estoy usando pandas 0.24.2 y lo que obtengo es NotImplementedError: Text mode not supported, use mode='wb' and manage bytes. ¿alguna sugerencia?
Binyamin Even
57

Me gusta s3fs que te permite usar s3 (casi) como un sistema de archivos local.

Puedes hacerlo:

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)

s3fssolo admite rby wbmodos de abrir el archivo, es por eso que hice esto bytes_to_write.

michcio1234
fuente
¡Excelente! ¿Cómo puedo obtener la URL del archivo usando el mismo módulo s3fs?
M.Zaman
Estaba buscando la URL desde donde puedo descargar el archivo escrito, de todos modos lo obtengo a través de S3FileSystem. Gracias
M.Zaman
esto es lo que uso; Gracias. Tengo curiosidad por saber por qué pd.read_csv (<s3path>) funciona como se esperaba, pero para escribir tenemos que usar esta solución ... excepto en el caso de que esté escribiendo directamente en el cubo s3 en el que está mi jupyter.
Renée
@ michcio1234 ¿cómo puedo hacer lo mismo en el modo adjuntar? Necesito agregar los datos en el csv existente en s3
j '
@j ' s3fsno parece admitir el modo de adición .
michcio1234
43

Esta es una respuesta más actualizada:

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)

El problema con StringIO es que devorará su memoria. Con este método, está transmitiendo el archivo a s3, en lugar de convertirlo en una cadena y luego escribirlo en s3. Mantener el marco de datos de pandas y su copia de cadena en la memoria parece muy ineficiente.

Si está trabajando en un instante ec2, puede asignarle un rol de IAM para habilitar la escritura en s3, por lo que no necesita pasar las credenciales directamente. Sin embargo, también puede conectarse a un depósito pasando credenciales a la S3FileSystem()función. Ver documentación: https://s3fs.readthedocs.io/en/latest/

erncyp
fuente
Por alguna razón, cuando hice esto, todas las líneas se omitieron en el CSV de salida
kjmerf
hmm. No estoy seguro de por qué sucedería eso. tal vez intente con otro pandas df para ver si todavía tiene el problema. Si su versión de pandas lo admite, pruebe la respuesta de @ amit-kushwaha, donde pasa la URL s3 directamente a to_csv(). parece una implementación más limpia.
erncyp
@erncyp Parece que estoy obteniendo el error: botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the PutObject operation: Access Denied ... Incluso hice que el bucket sea PUBLIC READ y agregué las siguientes Acciones, en mi cuenta específica de usuario de IAM, en la Política del Bucket:"Action": [ "s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:GetObjectAcl", "s3:DeleteObject" ]
ajoros
parece que no tienes los permisos? Asegúrese de adjuntar permisos de lectura y escritura de S3 al rol de IAM que está utilizando
erncyp
@erncyp Tengo una política de AdministratorAccess adjunta a mi usuario de IAM, por lo que, en teoría, debería poder leer / escribir bien ... Curiosamente, puedo escribir bien cuando uso la siguiente función que hice, usando otro usuario de StackOverflow consejo (para su información, los puntos y coma están al final de la línea ya que no sé cómo formatear en la sección de comentarios):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

Si pasa Nonecomo primer argumento, to_csv()los datos se devolverán como una cadena. A partir de ahí, es un paso fácil cargarlo en S3 de una vez.

También debería ser posible pasar un StringIOobjeto to_csv(), pero usar una cadena será más fácil.

mhawke
fuente
¿Será más fácil de qué manera? ¿Cuál es la forma correcta de hacerlo?
Eran Moshe
@EranMoshe: de cualquier manera va a funcionar correctamente, pero es obvio que es más fácil pasar Nonea to_csv()y el uso de la cadena devuelta de lo que es para crear un StringIOobjeto y luego vuelve a leer los datos.
mhawke
Como programador vago, eso es lo que hice. Y significaba más fácil para el programador que escribe menos código:>
Eran Moshe
2

También puede utilizar AWS Data Wrangler :

import awswrangler

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

Tenga en cuenta que se dividirá en varias partes, ya que lo carga en paralelo.

gabra
fuente
2

Descubrí que esto se puede hacer usando clienttambién y no solo 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')
Harry_pb
fuente
0

ya que está usando boto3.client(), intente:

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')
Jerrytim
fuente
-1

Encontré una solución muy simple que parece estar funcionando:

s3 = boto3.client("s3")

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

Espero que ayude !

Antoine Krajnc
fuente
-5

Leí un csv con dos columnas del bucket s3 y el contenido del archivo csv que puse en pandas dataframe.

Ejemplo:

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()
Jamir Josimar Huamán Campos
fuente
4
por favor, no solo publique la solución, agregue una explicación también.
sjaustirni
¿Hay alguna ventaja en hacer una solución tan compleja (para un novato en Python)?
Javier López Tomás
1
Esto lee un archivo de s3, la pregunta era cómo escribir un df en s3.
Damian Satterthwaite-Phillips