Salva Dataframe in csv direttamente in s3 Python


126

Ho un DataFrame panda che voglio caricare in un nuovo file CSV. Il problema è che non voglio salvare il file localmente prima di trasferirlo su s3. Esiste un metodo come to_csv per scrivere direttamente il dataframe su s3? Sto usando boto3.
Ecco cosa ho finora:

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 per maggiori informazioni.
Peter Berg

Risposte:


160

Puoi usare:

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 si tratta di un file di grandi dimensioni, che effetto ha sulla memoria ...?
citynorman

2
Se il file è più grande della RAM che hai a disposizione, l'azione fallirà e farà eccezione un'eccezione (non so quale). Questo dovrebbe essere accettato come risposta
Eran Moshe

5
Ho ricevuto un TypeError: unicode argument expected, got 'str'errore durante l'utilizzo StringIO. L'ho usato BytesIOe ha funzionato perfettamente. Nota: questo era in Python 2.7
Abhishek Upadhyaya

1
cos'è l' bucketoggetto? come l'hai creato?
Charles Chow

1
bucketè dove archivi gli oggetti su S3. Il codice presuppone che tu abbia già creato la destinazione (pensa: directory) in cui archiviarlo. Vedi i documenti di S3
Stefan l'

66

Puoi utilizzare direttamente il percorso S3. Sto 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 di rilascio:

Gestione dei file S3

Panda ora usa s3fs per gestire le connessioni S3. Questo non dovrebbe rompere alcun codice. Tuttavia, poiché s3fs non è una dipendenza richiesta, sarà necessario installarlo separatamente, come boto nelle versioni precedenti di panda. GH11915 .


7
questa è sicuramente la risposta più semplice ora, utilizza s3fs dietro le quinte quindi è necessario aggiungerla alle proprie esigenze.txt
JD D

1
Mi piace che sia facile, ma sembra che non funzioni davvero poiché continuo a ricevere il seguente errore NoCredentialsError: Unable to locate credentials. Eventuali suggerimenti?
CathyQian

1
Posso confermare che questo non funziona con i panda <= 0.23.4, quindi assicurati di eseguire l'aggiornamento a panda 0.24
Guido

1
Questo è l'errore che vedo quando provo a usare il comando to_csv TypeError: write () l'argomento 1 deve essere unicode, non str
Raj

13
Sto usando i panda 0.24.2 e quello che ottengo è NotImplementedError: Text mode not supported, use mode='wb' and manage bytes. eventuali suggerimenti?
Binyamin Even

57

Mi piace s3fs che ti permette di usare s3 (quasi) come un filesystem locale.

Puoi farlo:

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)

s3fssupporta solo rbe wbmodalità di apertura del file, ecco perché ho fatto questa bytes_to_writeroba.


Grande! Come posso ottenere l'URL del file utilizzando lo stesso modulo s3fs?
M.Zaman

Stavo cercando l'URL da cui posso scaricare il file scritto, comunque lo ottengo tramite S3FileSystem. Grazie
M.Zaman

questo è quello che uso; Grazie. Sono curioso di sapere perché pd.read_csv (<s3path>) funziona come previsto, ma per scrivere dobbiamo usare questo lavoro in giro .. tranne nel caso in cui sto scrivendo direttamente sul bucket s3 in cui si trova il mio jupyter.
Renée

@ michcio1234 come posso fare lo stesso in modalità di aggiunta? Devo aggiungere i dati nel csv esistente su s3
j '

@j ' s3fsnon sembra supportare la modalità di aggiunta .
michcio1234

43

Questa è una risposta più aggiornata:

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)

Il problema con StringIO è che divorerà la tua memoria. Con questo metodo, stai trasmettendo il file in s3, invece di convertirlo in stringa, quindi scrivendolo in s3. Tenere il dataframe panda e la sua copia di stringa in memoria sembra molto inefficiente.

Se stai lavorando in un istante ec2, puoi assegnargli un ruolo IAM per abilitare la scrittura su s3, quindi non è necessario passare direttamente le credenziali. Tuttavia, puoi anche connetterti a un bucket passando le credenziali alla S3FileSystem()funzione. Vedere la documentazione: https://s3fs.readthedocs.io/en/latest/


Per qualche motivo, quando l'ho fatto, ogni riga è stata saltata nell'output CSV
kjmerf

hmm. non sono sicuro del motivo per cui sarebbe successo magari prova con un altro panda df per vedere se hai ancora il problema? Se la tua versione di panda lo supporta, prova la risposta di @ amit-kushwaha, dove passi direttamente l'URL s3 to_csv(). sembra un'implementazione più pulita.
erncyp

@erncyp Mi sembra di aver ricevuto un errore: botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the PutObject operation: Access Denied ... ho persino creato il bucket PUBLIC READ e ho aggiunto le seguenti azioni, sotto il mio account specifico utente IAM, nella Bucket Policy:"Action": [ "s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:GetObjectAcl", "s3:DeleteObject" ]
ajoros

sembra che ti manchino i permessi? Assicurati di allegare i permessi di lettura e scrittura S3 al ruolo IAM che stai utilizzando
erncyp

@erncyp Ho la policy AdministratorAccess collegata al mio utente IAM, quindi in teoria dovrei essere in grado di leggere / scrivere bene ... Stranamente, sono in grado di scrivere bene quando utilizzo la seguente funzione che ho creato, utilizzando un altro utente StackOverflow consiglio (fyi i punti e virgola sono di fine riga poiché non so come formattare nella sezione commenti):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 passi Nonecome primo argomento to_csv()i dati verranno restituiti come una stringa. Da lì è un semplice passaggio per caricarlo su S3 in una volta sola.

Dovrebbe anche essere possibile passare un StringIOoggetto a to_csv(), ma usare una stringa sarà più facile.


In che modo sarà più facile? Qual è il modo corretto per farlo?
Eran Moshe

@EranMoshe: in entrambi i casi funziona correttamente, ma ovviamente è più facile passare Noneper to_csv()e utilizzare la stringa restituita quello che è quello di creare un StringIOoggetto e quindi leggere i dati indietro.
mhawke

Come programmatore pigro, è quello che ho fatto. E intendevi più facile per il programmatore che scrive meno codice:>
Eran Moshe

3

Ho scoperto che questo può essere fatto usando clientanche e non 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')

2

Puoi anche utilizzare AWS Data Wrangler :

import awswrangler

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

Nota che verrà diviso in più parti poiché lo carica in parallelo.


0

visto che stai usando boto3.client(), prova:

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

Ho trovato una soluzione molto semplice che sembra funzionare:

s3 = boto3.client("s3")

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

Spero che aiuti !


-5

Ho letto un csv con due colonne dal bucket s3 e il contenuto del file csv l'ho inserito in pandas dataframe.

Esempio:

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
per favore, non limitarti a pubblicare la soluzione, ma aggiungi anche una spiegazione.
sjaustirni

C'è qualche vantaggio nel realizzare una soluzione così complessa (per un principiante in Python)?
Javier López Tomás

1
Questo legge un file da s3, la domanda era come scrivere un df in s3.
Damian Satterthwaite-Phillips
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.