Enregistrer Dataframe en csv directement dans s3 Python

126

J'ai un DataFrame pandas que je souhaite télécharger dans un nouveau fichier CSV. Le problème est que je ne veux pas enregistrer le fichier localement avant de le transférer vers s3. Existe-t-il une méthode comme to_csv pour écrire directement le dataframe dans s3? J'utilise boto3.
Voici ce que j'ai jusqu'à présent:

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
user2494275
la source
3
df.to_csv('s3://mybucket/dfs/somedf.csv'). stackoverflow.com/a/56275519/908886 pour plus d'informations.
Peter Berg

Réponses:

160

Vous pouvez utiliser:

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
la source
10
S'il s'agit d'un fichier volumineux, qu'est-ce que cela fait pour la mémoire ...?
citynorman
2
Si le fichier est plus gros alors la RAM dont vous disposez, l'action échouera et sauf une exception (je ne sais pas laquelle). Cela devrait être accepté comme réponse
Eran Moshe
5
J'ai eu une TypeError: unicode argument expected, got 'str'erreur lors de l'utilisation StringIO. Je l'ai utilisé BytesIOet cela a parfaitement fonctionné. Remarque: c'était en Python 2.7
Abhishek Upadhyaya
1
qu'est-ce que l' bucketobjet? comment as-tu créé ça?
Charles Chow
1
bucketest l'endroit où vous stockez les objets sur S3. Le code suppose que vous avez déjà créé la destination (pensez: répertoire) où stocker cela. Voir la documentation S3
Stefan
68

Vous pouvez utiliser directement le chemin S3. J'utilise 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

Note de version:

Gestion des fichiers S3

pandas utilise maintenant s3fs pour gérer les connexions S3. Cela ne devrait casser aucun code. Cependant, comme s3fs n'est pas une dépendance requise, vous devrez l'installer séparément, comme boto dans les versions précédentes de pandas. GH11915 .

mesure 17
la source
7
c'est certainement la réponse la plus simple maintenant, elle utilise s3fs dans les coulisses, vous devez donc l'ajouter à vos exigences.txt
JD D
1
J'aime que ce soit facile, mais il semble que cela ne fonctionne pas vraiment car j'obtiens l'erreur suivante NoCredentialsError: Unable to locate credentials. Aucune suggestion?
CathyQian
1
Je peux confirmer que cela ne fonctionne pas avec les pandas <= 0.23.4, alors assurez-vous de passer à pandas 0.24
Guido
1
C'est l'erreur que je vois lorsque j'essaie d'utiliser la commande to_csv TypeError: l'argument 1 de write () doit être unicode, pas str
Raj
13
J'utilise pandas 0.24.2 et ce que j'obtiens est NotImplementedError: Text mode not supported, use mode='wb' and manage bytes. Aucune suggestion?
Binyamin Even
57

J'aime s3fs qui vous permet d'utiliser s3 (presque) comme un système de fichiers local.

Tu peux le faire:

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)

s3fsprend en charge uniquement rbet les wbmodes d'ouverture du fichier, c'est pourquoi j'ai fait ce bytes_to_writetruc.

michcio1234
la source
Génial! Comment puis-je obtenir l'URL du fichier en utilisant le même module s3fs?
M.Zaman
Je cherchais l'URL à partir de laquelle je peux télécharger le fichier écrit, de toute façon je l'obtiens via S3FileSystem. Merci
M.Zaman
c'est ce que j'utilise; Merci. Je suis curieux de savoir pourquoi pd.read_csv (<s3path>) fonctionne comme prévu, mais pour l'écriture, nous devons utiliser ce travail, sauf dans le cas où j'écris directement dans le seau s3 dans lequel se trouve mon jupyter.
Renée
@ michcio1234 comment puis-je faire la même chose en mode ajout? Je dois ajouter les données dans le csv existant sur s3
j '
@j ' s3fsne semble pas prendre en charge le mode d'ajout.
michcio1234
43

Voici une réponse plus à jour:

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)

Le problème avec StringIO est qu'il va ronger votre mémoire. Avec cette méthode, vous diffusez le fichier en s3, plutôt que de le convertir en chaîne, puis vous l'écrivez en s3. Tenir le dataframe pandas et sa copie de chaîne en mémoire semble très inefficace.

Si vous travaillez dans un instant ec2, vous pouvez lui attribuer un rôle IAM pour lui permettre de l'écrire dans s3, vous n'avez donc pas besoin de transmettre directement les informations d'identification. Cependant, vous pouvez également vous connecter à un compartiment en transmettant des informations d'identification à la S3FileSystem()fonction. Voir la documentation: https://s3fs.readthedocs.io/en/latest/

Erncyp
la source
Pour une raison quelconque, lorsque j'ai fait cela, chaque ligne a été ignorée dans le CSV de sortie
kjmerf
hmm. je ne sais pas pourquoi cela arriverait. peut-être essayer avec un autre pandas df pour voir si vous obtenez toujours le problème? Si votre version de pandas le prend en charge, essayez la réponse de @ amit-kushwaha, où vous passez directement l'URL s3 to_csv(). semble être une mise en œuvre plus propre.
erncyp le
@erncyp Il semble y avoir une erreur: botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the PutObject operation: Access Denied ... J'ai même rendu le bucket PUBLIC READ et j'ai ajouté les actions suivantes, sous mon compte utilisateur IAM spécifique, dans la politique du bucket:"Action": [ "s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:GetObjectAcl", "s3:DeleteObject" ]
ajoros
semble que vous ne disposez pas des autorisations? Assurez-vous d'attacher les autorisations de lecture et d'écriture S3 au rôle IAM que vous utilisez
erncyp
@erncyp J'ai une politique AdministratorAccess attachée à mon utilisateur IAM, donc en théorie je devrais être capable de lire / écrire très bien ... Curieusement, je suis capable d'écrire très bien lorsque j'utilise la fonction suivante que j'ai créée, en utilisant un autre utilisateur StackOverflow conseil (les points-virgules fyi sont en fin de ligne car je ne sais pas comment formater dans la section commentaire):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 vous passez Nonecomme premier argument aux to_csv()données, elles seront renvoyées sous forme de chaîne. À partir de là, il est facile de télécharger cela sur S3 en une seule fois.

Il devrait également être possible de passer un StringIOobjet à to_csv(), mais l'utilisation d'une chaîne sera plus facile.

mhawke
la source
Sera-t-il plus facile de quelle manière? Quelle est la bonne façon de procéder?
Eran Moshe
@EranMoshe: De toute façon fonctionnera correctement, mais il est évident qu'il est plus facile de passer Noneà to_csv()et utiliser la chaîne retournée que de créer un StringIOobjet, puis lire les données arrière.
mhawke
En tant que programmeur paresseux, c'est ce que j'ai fait. Et vous vouliez dire plus facile pour le programmeur qui écrit moins de code:>
Eran Moshe
3

J'ai trouvé que cela pouvait être fait en utilisant client aussi et pas seulement 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
la source
2

Vous pouvez également utiliser AWS Data Wrangler :

import awswrangler

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

Notez qu'il se divisera en plusieurs parties car il le télécharge en parallèle.

gabra
la source
0

depuis que vous utilisez boto3.client(), essayez:

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
la source
-1

J'ai trouvé une solution très simple qui semble fonctionner:

s3 = boto3.client("s3")

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

J'espère que cela pourra aider !

Antoine Krajnc
la source
-5

J'ai lu un csv avec deux colonnes du bucket s3, et le contenu du fichier csv que j'ai mis dans pandas dataframe.

Exemple:

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
la source
4
s'il vous plaît, ne publiez pas simplement la solution, ajoutez-y aussi une explication
sjaustirni
Y a-t-il un avantage à créer une solution aussi complexe (pour un débutant en Python)?
Javier López Tomás
1
Cela lit un fichier à partir de s3, la question était de savoir comment écrire un df dans s3.
Damian Satterthwaite-Phillips