บันทึก Dataframe เป็น csv โดยตรงไปยัง s3 Python


126

ฉันมี DataFrame แพนด้าที่ต้องการอัปโหลดไปยังไฟล์ CSV ใหม่ ปัญหาคือฉันไม่ต้องการบันทึกไฟล์ในเครื่องก่อนที่จะโอนไปยัง s3 มีวิธีใดเช่น to_csv สำหรับเขียน dataframe ไปยัง 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สำหรับข้อมูลเพิ่มเติม
Peter Berg

คำตอบ:


161

คุณสามารถใช้ได้:

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())

10
ถ้าเป็นไฟล์ขนาดใหญ่หน่วยความจำจะทำอย่างไร ... ?
citynorman

2
หากไฟล์มีขนาดใหญ่ขึ้น RAM ที่คุณมีอยู่การดำเนินการจะล้มเหลวและจะยกเว้น Exception (ไม่รู้ว่าอันไหน) สิ่งนี้ควรเป็นคำตอบ
Eran Moshe

5
ผมได้รับข้อผิดพลาดในขณะที่ใช้TypeError: unicode argument expected, got 'str' StringIOฉันใช้BytesIOและทำงานได้ดีอย่างสมบูรณ์ หมายเหตุ: นี่คือใน Python 2.7
Abhishek Upadhyaya

1
bucketวัตถุคืออะไร? คุณสร้างสิ่งนั้นได้อย่างไร
Charles Chow

1
bucketเป็นที่ที่คุณจัดเก็บวัตถุบน S3 โค้ดจะถือว่าคุณได้สร้างปลายทางแล้ว (คิดว่าไดเร็กทอรี) ที่เก็บข้อมูลนี้ ดูเอกสาร S3
Stefan

68

คุณสามารถใช้เส้นทาง 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

ตอนนี้แพนด้าใช้ 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: write () อาร์กิวเมนต์ 1 ต้องเป็น Unicode ไม่ใช่ str
Raj

13
ฉันใช้หมีแพนด้า 0.24.2 NotImplementedError: Text mode not supported, use mode='wb' and manage bytesและสิ่งที่ฉันได้รับคือ ข้อเสนอแนะใด ๆ
Binyamin

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 เดียวกันได้อย่างไร
M.Zaman

ฉันกำลังมองหา URL จากที่ที่ฉันสามารถดาวน์โหลดไฟล์ที่เขียน แต่ฉันได้รับสิ่งนั้นผ่าน S3FileSystem ขอบคุณ
M.Zaman

นี่คือสิ่งที่ฉันใช้ ขอบคุณ ฉันสงสัยว่าทำไม pd.read_csv (<s3path>) ทำงานได้ตามที่คาดไว้ แต่สำหรับการเขียนเราต้องใช้วิธีนี้ .. ยกเว้นในกรณีที่ฉันเขียนลงในถัง s3 โดยตรง jupyter ของฉันอยู่ใน
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 's ที่คุณผ่าน 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

ดูเหมือนว่าคุณขาดสิทธิ์? ตรวจสอบให้แน่ใจว่าได้แนบสิทธิ์ในการอ่าน S3 เข้ากับบทบาท IAM ที่คุณใช้
erncyp

@erncyp ฉันมีนโยบาย AdministratorAccess ที่แนบมากับผู้ใช้ IAM ของฉันดังนั้นในทางทฤษฎีฉันควรจะอ่าน / เขียนได้ดี ... แปลกฉันสามารถเขียนได้ดีเมื่อฉันใช้ฟังก์ชันต่อไปนี้ที่ฉันทำโดยใช้ผู้ใช้ StackOverflow อื่น คำแนะนำ (fyi semi-colons เป็นจุดสิ้นสุดของบรรทัดเนื่องจากฉันไม่รู้วิธีจัดรูปแบบในส่วนความคิดเห็น):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()แต่การใช้สตริงจะง่ายกว่า


จะง่ายขึ้นด้วยวิธีไหน? วิธีทำที่ถูกต้องคืออะไร?
Eran Moshe

@EranMoshe: วิธีการอย่างใดอย่างหนึ่งจะทำงานได้อย่างถูกต้อง แต่เห็นได้ชัดว่ามันง่ายที่จะผ่านNoneไปto_csv()และใช้สตริงกลับกว่าก็คือการสร้างStringIOวัตถุแล้วอ่านข้อมูลกลับออกมา
mhawke

ในฐานะโปรแกรมเมอร์ขี้เกียจนั่นคือสิ่งที่ฉันทำ และคุณทำได้ง่ายขึ้นสำหรับโปรแกรมเมอร์ที่เขียนโค้ดน้อยลง:>
Eran Moshe

3

ฉันพบนี้สามารถทำได้โดยใช้และยังไม่ได้เป็นเพียงclientresource

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

คุณยังสามารถใช้AWS Data Wrangler :

import awswrangler

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

โปรดทราบว่าจะแบ่งออกเป็นหลายส่วนเนื่องจากอัปโหลดพร้อมกัน


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 ที่ฉันใส่ในดาต้าเฟรมของแพนด้า

ตัวอย่าง:

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) หรือไม่?
Javier

1
สิ่งนี้อ่านไฟล์จาก s3 คำถามคือวิธีเขียน df ถึง s3
Damian Satterthwaite-Phillips
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.