Збережіть Dataframe в CSV безпосередньо на s3 Python


125

У мене є панда DataFrame, яку я хочу завантажити в новий файл CSV. Проблема полягає в тому, що я не хочу зберігати файл локально, перш ніж перенести його в s3. Чи є такий метод, як to_csv для запису фрейму даних в s3 безпосередньо? Я використовую boto3.
Ось що я маю досі:

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 для отримання додаткової інформації.
Пітер Берг

Відповіді:


158

Ви можете використовувати:

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
Якщо це великий файл, що це робить для пам'яті ...?
citynorman

2
Якщо файл більший, то наявна у вас оперативна пам’ять дія не вдасться і не буде винятком виключення (не знаю, який). Це слід прийняти як відповідь
Еран Моше

5
Я отримав TypeError: unicode argument expected, got 'str'помилку під час використання StringIO. Я використовував, BytesIOі це працювало чудово. Зауважте: це було в Python 2.7
Абхішек Упадхяя

1
що є bucketоб'єктом? як ти це створив?
Чарльз Чоу

1
bucketце місце, де ви зберігаєте об’єкти на S3. Код передбачає, що ви вже створили пункт призначення (думаю: каталог), де це зберігати. Дивіться документи S3
Стефан

65

Ви можете безпосередньо використовувати шлях до S3. Я використовую 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

Примітка до випуску:

Обробка файлів S3

pandas тепер використовує s3fs для обробки з'єднань S3. Це не повинно порушувати жодного коду. Однак, оскільки s3fs не є необхідною залежністю, вам потрібно буде встановити його окремо, як boto в попередніх версіях панд. GH11915 .


7
це, безумовно, найпростіша відповідь зараз, він використовує s3fs за лаштунками, тому вам потрібно додати це до своїх
JD D

1
Мені подобається, що це легко, але, здається, це насправді не працює, оскільки я продовжую отримувати таку помилку NoCredentialsError: Unable to locate credentials. Будь-які пропозиції?
CathyQian

1
Я можу підтвердити, що це не працює з пандами <= 0,23,4, тому не забудьте оновити до панд 0,24
Guido

1
Це помилка, яку я бачу, коли я намагаюся використовувати команду to_csv TypeError: аргумент 1 () () (аргумент 1) повинен бути unicode, а не str
Raj

13
Я використовую панди 0,24,2, і те, що я отримую, це NotImplementedError: Text mode not supported, use mode='wb' and manage bytes. будь-які пропозиції?
Біньямін Навіть

57

Мені подобається s3fs, що дозволяє використовувати s3 (майже) як локальну файлову систему.

Ви можете зробити це:

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)

s3fsпідтримує лише rbта wbрежими відкриття файлу, тому я і робив цей bytes_to_writeматеріал.


Чудово! Як я можу отримати URL-адресу файлу за допомогою того ж модуля s3fs?
М.Заман

Я шукав URL-адресу, звідки я можу завантажити письмовий файл, все одно я отримую це через S3FileSystem. Спасибі
М.Заман

це те, що я використовую; Дякую. Мені цікаво, чому pd.read_csv (<s3path>) працює так, як очікувалося, але для написання нам потрібно використовувати цю роботу навколо, за винятком випадків, коли я пишу прямо у відро s3, в якому знаходиться мій юпітер.
Renée,

@ michcio1234 Як я можу зробити те саме в режимі додавання? Мені потрібно додати дані в існуючий csv на s3
j '

@j ' s3fs, схоже, не підтримує режим додавання.
michcio1234

43

Це більш актуальна відповідь:

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)

Проблема StringIO полягає в тому, що він з'їсть вашу пам’ять. За допомогою цього методу ви передаєте файл в s3, а не перетворюєте його в рядок, а потім записуєте його в s3. Зберігання фрейму даних панди та його копіювання рядків у пам'яті здається дуже неефективним.

Якщо ви працюєте в ec2 миттєво, ви можете надати йому роль IAM, щоб дозволити записати його на s3, таким чином, вам не потрібно безпосередньо передавати облікові дані. Однак ви також можете підключитися до відра, передавши облікові дані S3FileSystem()функції. Дивіться документацію: https://s3fs.readthedocs.io/en/latest/


Чомусь, коли я це робив, кожен рядок був пропущений у вихідному CSV
kjmerf

хм. не впевнений, чому це станеться. можливо, спробуйте з іншим пандом df, щоб дізнатися, чи все-таки у вас проблема? Якщо ваша версія панд підтримує це, спробуйте відповідь @ amit-kushwaha, куди ви передаєте URL-адресу s3 безпосередньо to_csv(). видається більш чистим впровадженням.
erncyp

@erncyp Мені здається, виникає помилка: botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the PutObject operation: Access Denied ... Я навіть зробив відро ПУБЛІЧНОГО ЧИТАННЯ, і я додав такі дії, під моїм конкретним обліковим записом користувача IAM, у політику щодо ковша:"Action": [ "s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:GetObjectAcl", "s3:DeleteObject" ]
ajoros

здається, вам не вистачає дозволів? Обов’язково додайте до ролі IAM, яку ви використовуєте, дозволу на читання S3
erncyp

@erncyp У мене до мого користувача IAM додана політика AdministratorAccess, тому теоретично я повинен вміти читати / писати просто чудово ... Як не дивно, я можу писати чудово, коли використовую наступну функцію, яку я зробив, використовуючи іншого користувача StackOverflow порада (напівколони фії - це кінець рядка, оскільки я не знаю, як відформатувати в розділі коментарів):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

Якщо ви передасте Noneяк перший аргумент, to_csv()дані будуть повернуті у вигляді рядка. Звідти простий крок, щоб завантажити його на S3 за один раз.

Також має бути можливість передавати StringIOоб’єкт to_csv(), але використовувати рядок буде простіше.


Чи буде простіше, в який спосіб? Який правильний спосіб це зробити?
Еран Моше

@EranMoshe: будь-який спосіб буде працювати правильно, але очевидно, простіше перейти Noneдо to_csv()та повернути рядок, ніж повернути, ніж створити StringIOоб’єкт, а потім прочитати дані назад.
mhawke

Як ледачий програміст, це я і зробив. І ви мали на увазі простіше для програміста, який пише менше коду:>
Еран Моше

2

Ви також можете використовувати AWS Data Wrangler :

import awswrangler

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

Зауважте, що він розділиться на кілька частин, оскільки завантажує його паралельно.


2

Я виявив, що це можна зробити, використовуючи clientтакож і не просто 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

оскільки ви використовуєте boto3.client(), спробуйте:

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

Я знайшов дуже просте рішення, яке, здається, працює:

s3 = boto3.client("s3")

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

Сподіваюся, що це допомагає!


-5

Я читав csv з двома стовпцями з відра s3, а вміст файлу csv я вкладав у фрейм даних pandas.

Приклад:

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
будь ласка, не просто публікуйте рішення, додайте також пояснення до нього.
sjaustirni

Чи є якась перевага у створенні такого складного рішення (для новачка в Python)?
Хав'єр Лопес

1
Тут читається файл із s3, питання було як записати df до s3.
Damian Satterthwaite-Phillips
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.