Jak zaimportować plik tekstowy z AWS S3 do pand bez zapisywania na dysku

90

Mam plik tekstowy zapisany na S3, który jest tabelą rozdzielaną tabulatorami. Chcę załadować go do pandy, ale nie mogę go najpierw zapisać, ponieważ pracuję na serwerze heroku. Oto, co mam do tej pory.

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)

błąd jest

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

Jak przekonwertować treść odpowiedzi na format, który przyjmą pandy?

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

UPDATE - Korzystanie z następujących działało

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

i

pd.read_csv(io.BytesIO(file), header=14, delimiter="\t", low_memory=False)
alpalalpal
źródło
spróbuj tego w ten sposób: io.BytesIO(file)lub io.StringIO(file)zamiast filew trakcie read_csv()rozmowy
MaxU
Możesz użyć io.StringIOjak w tej odpowiedzi .
IanS,
Żadna z tych sugestii nie zadziałała. Możesz zobaczyć błędy w mojej edycji postu.
alpalalpal
1
Część UPDATE działała dla mnie. Dzięki.
Wim Berchmans

Odpowiedzi:

110

pandasużywa botodo read_csv, więc powinieneś być w stanie:

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

Jeśli potrzebujesz, boto3bo jesteś włączony python3.4+, możesz

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

Od wersji 0.20.1 pandas zastosowań s3fs, patrz odpowiedź poniżej.

Stefan
źródło
Czy istnieje sposób na użycie adresu URL bez upubliczniania go wszystkim? Plik musi pozostać prywatny.
alpalalpal
Dokumentacja boto3pokazuje, jak skonfigurować uwierzytelnianie, aby mieć również dostęp do prywatnych plików: boto3.readthedocs.io/en/latest/guide/quickstart.html
Stefan
1
Wyrzuca NoCredentialsError. Jak ustawić poświadczenia S3 TL IT? Jestem nowy w Pythonie i Boto
Sunil Rao
15
Okazało się, że na ostatnim przykładzie z boto3 musiałem wykonać następujące czynności: df = pd.read_csv(io.BytesIO(obj['Body'].read()), encoding='utf8')
user394430
Ta odpowiedź jest nieaktualna . Zobacz odpowiedź Wesams .
gerrit
79

Teraz pandy mogą obsługiwać adresy URL S3 . Możesz po prostu zrobić:

import pandas as pd
import s3fs

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

Musisz zainstalować,s3fs jeśli go nie masz. pip install s3fs

Poświadczenie

Jeśli Twój zasobnik S3 jest prywatny i wymaga uwierzytelnienia, masz dwie możliwości:

1- Dodaj poświadczenia dostępu do ~/.aws/credentialspliku konfiguracyjnego

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

Lub

2- Ustaw następujące zmienne środowiskowe z ich odpowiednimi wartościami:

  • aws_access_key_id
  • aws_secret_access_key
  • aws_session_token
Wesam
źródło
Piękny. Działa w Pythonie3.
Kyler Brown
a co z uwierzytelnianiem…?
James Wierzba
1
@JamesWierzba, dodałem więcej szczegółów dotyczących uwierzytelniania do mojej odpowiedzi powyżej.
Wesam
3
W przypadku wielu profili AWS, jak wybrać, który profil ma być używany? s3fs ma opcję profile_name, ale nie jestem pewien, jak to działa z pandami.
Ivo Merchiers
1
@IanS Nie bardzo, obecnie najpierw otwieram obiekt pliku za pomocą s3fs (używając określonego profilu), a następnie czytam go za pomocą pand, tak jak robią to tutaj github.com/pandas-dev/pandas/issues/16692
Ivo Merchiers
15

Jest to teraz obsługiwane w najnowszych pandach. Widzieć

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

na przykład.,

df = pd.read_csv('s3://pandas-test/tips.csv')
Raveen Beemsingh
źródło
4
Pamiętaj, że „adresy URL S3 są również obsługiwane, ale wymagają zainstalowania biblioteki S3Fs”
Julio Villane
a co z uwierzytelnianiem
James Wierzba
adres URL z uwierzytelnianiem może być trudny, chyba że adres URL zostanie ujawniony jako publiczny, nie jestem pewien, czy proste / podstawowe uwierzytelnianie http będzie działać,
Raveen Beemsingh
9

Z s3fs można to zrobić w następujący sposób:

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
źródło
2
Myślę, że z s3fs możesz nawet pisaćdf = pd.read_csv('s3://mybucket/path/to/object/foo.pkl')
louis_guitton
1
@louis_guitton to wydaje się działać z pd-read_csv, ale nie z read_pickle
Sip
1

Ponieważ pliki mogą być zbyt duże, nie jest rozsądne ładowanie ich w całości do ramki danych. Dlatego czytaj wiersz po wierszu i zapisz go w ramce danych. Tak, możemy również podać rozmiar porcji w read_csv, ale wtedy musimy zachować liczbę odczytanych wierszy.

Dlatego wymyśliłem tę inżynierię:

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=",")

Po zakończeniu pracy usuwam także plik df. del df

aviral sanjay
źródło
1

W przypadku plików tekstowych możesz użyć poniższego kodu z plikiem rozdzielanym pionową kreską, na przykład: -

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
źródło
0

Opcją jest przekonwertowanie df.to_dict()pliku csv na json za pośrednictwem, a następnie zapisanie go jako ciągu. Zauważ, że ma to znaczenie tylko wtedy, gdy plik CSV nie jest wymagany, ale chcesz po prostu szybko umieścić ramkę danych w zasobniku S3 i odzyskać ją ponownie.

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

Spowoduje to przekonwertowanie df na ciąg dict, a następnie zapisanie go jako json w S3. Możesz później przeczytać go w tym samym formacie json:

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

Inne rozwiązania też są dobre, ale to trochę prostsze. Yaml może niekoniecznie być wymagane, ale potrzebujesz czegoś do przeanalizowania łańcucha json. Jeśli plik S3 niekoniecznie musi być plikiem CSV, może to być szybka naprawa.

billmanH
źródło