Cómo importar un archivo de texto en AWS S3 a pandas sin escribir en el disco

90

Tengo un archivo de texto guardado en S3 que es una tabla delimitada por tabulaciones. Quiero cargarlo en pandas pero no puedo guardarlo primero porque lo estoy ejecutando en un servidor heroku. Esto es lo que tengo hasta ahora.

import io
import boto3
import os
import pandas as pd

os.environ["AWS_ACCESS_KEY_ID"] = "xxxxxxxx"
os.environ["AWS_SECRET_ACCESS_KEY"] = "xxxxxxxx"

s3_client = boto3.client('s3')
response = s3_client.get_object(Bucket="my_bucket",Key="filename.txt")
file = response["Body"]


pd.read_csv(file, header=14, delimiter="\t", low_memory=False)

el error es

OSError: Expected file path name or file-like object, got <class 'bytes'> type

¿Cómo convierto el cuerpo de la respuesta a un formato que aceptarán los pandas?

pd.read_csv(io.StringIO(file), header=14, delimiter="\t", low_memory=False)

returns

TypeError: initial_value must be str or None, not StreamingBody

pd.read_csv(io.BytesIO(file), header=14, delimiter="\t", low_memory=False)

returns

TypeError: 'StreamingBody' does not support the buffer interface

ACTUALIZACIÓN: el uso de lo siguiente funcionó

file = response["Body"].read()

y

pd.read_csv(io.BytesIO(file), header=14, delimiter="\t", low_memory=False)
alpalalpal
fuente
io.BytesIO(file)io.StringIO(file)fileread_csv()
Pruébelo de
Puede usar io.StringIOcomo en esta respuesta .
IanS
Ninguna de estas sugerencias funcionó. Puedes ver los errores en la edición de mi publicación.
alpalalpal
1
La parte de ACTUALIZACIÓN funcionó para mí. Gracias.
Wim Berchmans

Respuestas:

110

pandasutiliza botopara read_csv, por lo que debería poder:

import boto
data = pd.read_csv('s3://bucket....csv')

Si lo necesita boto3porque está encendido python3.4+, puede

import boto3
import io
s3 = boto3.client('s3')
obj = s3.get_object(Bucket='bucket', Key='key')
df = pd.read_csv(io.BytesIO(obj['Body'].read()))

Dado que la versión 0.20.1 pandas usa s3fs, vea la respuesta a continuación.

Stefan
fuente
¿Hay alguna forma de usar una URL sin hacerla pública para todos? El archivo debe permanecer privado.
alpalalpal
Los boto3documentos muestran cómo configurar la autenticación para que también pueda acceder a archivos privados: boto3.readthedocs.io/en/latest/guide/quickstart.html
Stefan
1
Está lanzando NoCredentialsError. ¿Cómo configuro las credenciales s3 para ti? Soy nuevo en python y boto
Sunil Rao
15
Descubrí que tenía que hacer lo siguiente en el último ejemplo con boto3: df = pd.read_csv(io.BytesIO(obj['Body'].read()), encoding='utf8')
user394430
Esta respuesta está desactualizada . Por favor, vea la respuesta de Wesam .
gerrit
79

Ahora los pandas pueden manejar las URL de S3 . Simplemente podrías hacer:

import pandas as pd
import s3fs

df = pd.read_csv('s3://bucket-name/file.csv')

Debe instalarlos3fs si no lo tiene. pip install s3fs

Autenticación

Si su bucket de S3 es privado y requiere autenticación, tiene dos opciones:

1- Agregue credenciales de acceso a su ~/.aws/credentialsarchivo de configuración

[default]
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

O

2- Establezca las siguientes variables de entorno con sus valores adecuados:

  • aws_access_key_id
  • aws_secret_access_key
  • aws_session_token
Wesam
fuente
Hermosa. Funciona en python3.
Kyler Brown
¿qué hay de la autenticación ...?
James Wierzba
1
@JamesWierzba, agregué más detalles sobre la autenticación a mi respuesta anterior.
Wesam
3
Cuando se trata de varios perfiles de AWS, ¿cómo se puede seleccionar qué perfil se debe utilizar? s3fs tiene la opción profile_name, pero no estoy seguro de cómo funciona eso con pandas.
Ivo Merchiers
1
@IanS En realidad no, actualmente, primero abro el objeto de archivo con s3fs (usando el perfil especificado) y luego lo leo con pandas, como lo hacen aquí github.com/pandas-dev/pandas/issues/16692
Ivo Merchiers
15

Esto ahora es compatible con los últimos pandas. Ver

http://pandas.pydata.org/pandas-docs/stable/io.html#reading-remote-files

p.ej.,

df = pd.read_csv('s3://pandas-test/tips.csv')
Raveen Beemsingh
fuente
4
Recuerde 'Las URL de S3 también se manejan pero requieren la instalación de la biblioteca S3Fs'
Julio Villane
¿Qué hay de la autenticación
James Wierzba
url con autenticación puede ser difícil a menos que la URL esté expuesta como pública, no estoy seguro de si la autenticación http simple / básica funcionará,
Raveen Beemsingh
9

Con s3fs se puede hacer de la siguiente manera:

import s3fs
import pandas as pd
fs = s3fs.S3FileSystem(anon=False)

# CSV
with fs.open('mybucket/path/to/object/foo.pkl') as f:
    df = pd.read_csv(f)

# Pickle
with fs.open('mybucket/path/to/object/foo.pkl') as f:
    df = pd.read_pickle(f)
Dror
fuente
2
Creo que con s3fs incluso puedes escribirdf = pd.read_csv('s3://mybucket/path/to/object/foo.pkl')
louis_guitton
1
@louis_guitton esto parece funcionar con pd-read_csv pero no con read_pickle
Sip
1

Dado que los archivos pueden ser demasiado grandes, no es aconsejable cargarlos en el marco de datos por completo. Por lo tanto, lea línea por línea y guárdelo en el marco de datos. Sí, también podemos proporcionar el tamaño del fragmento en read_csv, pero luego tenemos que mantener el número de filas leídas.

Por lo tanto, se me ocurrió esta ingeniería:

def create_file_object_for_streaming(self):
        print("creating file object for streaming")
        self.file_object = self.bucket.Object(key=self.package_s3_key)
        print("File object is: " + str(self.file_object))
        print("Object file created.")
        return self.file_object

for row in codecs.getreader(self.encoding)(self.response[u'Body']).readlines():
            row_string = StringIO(row)
            df = pd.read_csv(row_string, sep=",")

También elimino el df una vez que se realiza el trabajo. del df

aviral sanjay
fuente
1

Para archivos de texto, puede usar el siguiente código con un archivo delimitado por tuberías, por ejemplo: -

import pandas as pd
import io
import boto3
s3_client = boto3.client('s3', use_ssl=False)
bucket = #
prefix = #
obj = s3_client.get_object(Bucket=bucket, Key=prefix+ filename)
df = pd.read_fwf((io.BytesIO(obj['Body'].read())) , encoding= 'unicode_escape', delimiter='|', error_bad_lines=False,header=None, dtype=str)
Harry_pb
fuente
0

Una opción es convertir el csv a json mediante df.to_dict()y luego almacenarlo como una cadena. Tenga en cuenta que esto solo es relevante si el CSV no es un requisito, pero solo desea colocar rápidamente el marco de datos en un depósito S3 y recuperarlo nuevamente.

from boto.s3.connection import S3Connection
import pandas as pd
import yaml

conn = S3Connection()
mybucket = conn.get_bucket('mybucketName')
myKey = mybucket.get_key("myKeyName")

myKey.set_contents_from_string(str(df.to_dict()))

Esto convertirá el df en una cadena dict y luego lo guardará como json en S3. Más tarde puede leerlo en el mismo formato json:

df = pd.DataFrame(yaml.load(myKey.get_contents_as_string()))

Las otras soluciones también son buenas, pero esto es un poco más simple. Es posible que Yaml no sea necesariamente necesario, pero necesita algo para analizar la cadena json. Si el archivo S3 no tiene por qué ser un CSV, esta puede ser una solución rápida.

billmanH
fuente