Używam boto3 do pobierania plików z zasobnika s3. Potrzebuję podobnej funkcjonalności jakaws s3 sync
Mój obecny kod to
#!/usr/bin/python
import boto3
s3=boto3.client('s3')
list=s3.list_objects(Bucket='my_bucket_name')['Contents']
for key in list:
s3.download_file('my_bucket_name', key['Key'], key['Key'])
Działa to dobrze, o ile zasobnik zawiera tylko pliki. Jeśli folder jest obecny w zasobniku, generuje błąd
Traceback (most recent call last):
File "./test", line 6, in <module>
s3.download_file('my_bucket_name', key['Key'], key['Key'])
File "/usr/local/lib/python2.7/dist-packages/boto3/s3/inject.py", line 58, in download_file
extra_args=ExtraArgs, callback=Callback)
File "/usr/local/lib/python2.7/dist-packages/boto3/s3/transfer.py", line 651, in download_file
extra_args, callback)
File "/usr/local/lib/python2.7/dist-packages/boto3/s3/transfer.py", line 666, in _download_file
self._get_object(bucket, key, filename, extra_args, callback)
File "/usr/local/lib/python2.7/dist-packages/boto3/s3/transfer.py", line 690, in _get_object
extra_args, callback)
File "/usr/local/lib/python2.7/dist-packages/boto3/s3/transfer.py", line 707, in _do_get_object
with self._osutil.open(filename, 'wb') as f:
File "/usr/local/lib/python2.7/dist-packages/boto3/s3/transfer.py", line 323, in open
return open(filename, mode)
IOError: [Errno 2] No such file or directory: 'my_folder/.8Df54234'
Czy to właściwy sposób na pobranie pełnego zasobnika s3 przy użyciu boto3. Jak pobierać foldery.
Odpowiedzi:
Podczas pracy z zasobnikami, które mają ponad 1000 obiektów, konieczne jest zaimplementowanie rozwiązania wykorzystującego
NextContinuationToken
sekwencyjne zestawy maksymalnie 1000 kluczy. To rozwiązanie najpierw kompiluje listę obiektów, a następnie iteracyjnie tworzy określone katalogi i pobiera istniejące obiekty.import boto3 import os s3_client = boto3.client('s3') def download_dir(prefix, local, bucket, client=s3_client): """ params: - prefix: pattern to match in s3 - local: local path to folder in which to place files - bucket: s3 bucket with target contents - client: initialized s3 client object """ keys = [] dirs = [] next_token = '' base_kwargs = { 'Bucket':bucket, 'Prefix':prefix, } while next_token is not None: kwargs = base_kwargs.copy() if next_token != '': kwargs.update({'ContinuationToken': next_token}) results = client.list_objects_v2(**kwargs) contents = results.get('Contents') for i in contents: k = i.get('Key') if k[-1] != '/': keys.append(k) else: dirs.append(k) next_token = results.get('NextContinuationToken') for d in dirs: dest_pathname = os.path.join(local, d) if not os.path.exists(os.path.dirname(dest_pathname)): os.makedirs(os.path.dirname(dest_pathname)) for k in keys: dest_pathname = os.path.join(local, k) if not os.path.exists(os.path.dirname(dest_pathname)): os.makedirs(os.path.dirname(dest_pathname)) client.download_file(bucket, k, dest_pathname)
źródło
while next_token is not None:
Mam te same potrzeby i stworzyłem następującą funkcję, która rekurencyjnie pobiera pliki.
Katalogi są tworzone lokalnie tylko wtedy, gdy zawierają pliki.
import boto3 import os def download_dir(client, resource, dist, local='/tmp', bucket='your_bucket'): paginator = client.get_paginator('list_objects') for result in paginator.paginate(Bucket=bucket, Delimiter='/', Prefix=dist): if result.get('CommonPrefixes') is not None: for subdir in result.get('CommonPrefixes'): download_dir(client, resource, subdir.get('Prefix'), local, bucket) for file in result.get('Contents', []): dest_pathname = os.path.join(local, file.get('Key')) if not os.path.exists(os.path.dirname(dest_pathname)): os.makedirs(os.path.dirname(dest_pathname)) resource.meta.client.download_file(bucket, file.get('Key'), dest_pathname)
Funkcja nazywa się w ten sposób:
def _start(): client = boto3.client('s3') resource = boto3.resource('s3') download_dir(client, resource, 'clientconf/', '/tmp', bucket='my-bucket')
źródło
resource.meta.client
.OSError: [Errno 21] Is a directory
więc zawarłem wywołanie download_file,if not file.get('Key').endswith('/')
aby rozwiązać. Dziękuję @glefait i @Shanaws s3 sync
w bibliotece boto3 nie ma odpowiednika polecenia aws-cli ?dist
tu jest ?Amazon S3 nie ma folderów / katalogów. Jest to płaska struktura plików .
Aby zachować wygląd katalogów, nazwy ścieżek są przechowywane jako część obiektu Klucz (nazwa pliku). Na przykład:
images/foo.jpg
W tym przypadku cały klucz jest
images/foo.jpg
, a nie tylkofoo.jpg
.Podejrzewam, że twój problem polega na tym, że
boto
zwraca plik o nazwiemy_folder/.8Df54234
i próbuje zapisać go w lokalnym systemie plików. Jednak lokalny system plików interpretuje tęmy_folder/
część jako nazwę katalogu, a ten katalog nie istnieje w lokalnym systemie plików .Możesz albo skrócić nazwę pliku, aby zapisać tylko
.8Df54234
część, albo musiałbyś utworzyć niezbędne katalogi przed zapisaniem plików. Zauważ, że mogą to być zagnieżdżone katalogi wielopoziomowe.Łatwiejszym sposobem byłoby skorzystanie z interfejsu wiersza poleceń AWS (CLI) , który wykona całą tę pracę za Ciebie, np .:
Istnieje również
sync
opcja, która kopiuje tylko nowe i zmodyfikowane pliki.źródło
aws s3 sync
. Czy jest to możliwe w boto3.foo/bar.txt
), Będziesz odpowiedzialny za utworzenie katalogu (foo
) przed wywołaniems3.download_file
. Nie jest to automatyczna możliwośćboto
.s3.list_objects(Bucket='my_bucket_name')['Contents']
i przefiltrować klucze folderów i je utworzyć.import os import boto3 #initiate s3 resource s3 = boto3.resource('s3') # select bucket my_bucket = s3.Bucket('my_bucket_name') # download file into current directory for s3_object in my_bucket.objects.all(): # Need to split s3_object.key into path and file name, else it will give error file not found. path, filename = os.path.split(s3_object.key) my_bucket.download_file(s3_object.key, filename)
źródło
os.makedirs(path)
a następnie powinno być miejsce docelowe pobieraniaobject.key
.Obecnie realizuję to zadanie, korzystając z poniższych
#!/usr/bin/python import boto3 s3=boto3.client('s3') list=s3.list_objects(Bucket='bucket')['Contents'] for s3_key in list: s3_object = s3_key['Key'] if not s3_object.endswith("/"): s3.download_file('bucket', s3_object, s3_object) else: import os if not os.path.exists(s3_object): os.makedirs(s3_object)
Chociaż spełnia swoje zadanie, nie jestem pewien, czy dobrze jest robić w ten sposób. Zostawiam to tutaj, aby pomóc innym użytkownikom i uzyskać dalsze odpowiedzi, z lepszym sposobem osiągnięcia tego
źródło
Lepiej późno niż wcale :) Poprzednia odpowiedź z paginatorem jest naprawdę dobra. Jednak jest rekurencyjny i może skończyć się osiągnięciem limitów rekurencji Pythona. Oto alternatywne podejście z kilkoma dodatkowymi kontrolami.
import os import errno import boto3 def assert_dir_exists(path): """ Checks if directory tree in path exists. If not it created them. :param path: the path to check if it exists """ try: os.makedirs(path) except OSError as e: if e.errno != errno.EEXIST: raise def download_dir(client, bucket, path, target): """ Downloads recursively the given S3 path to the target directory. :param client: S3 client to use. :param bucket: the name of the bucket to download from :param path: The S3 directory to download. :param target: the local directory to download the files to. """ # Handle missing / at end of prefix if not path.endswith('/'): path += '/' paginator = client.get_paginator('list_objects_v2') for result in paginator.paginate(Bucket=bucket, Prefix=path): # Download each file individually for key in result['Contents']: # Calculate relative path rel_path = key['Key'][len(path):] # Skip paths ending in / if not key['Key'].endswith('/'): local_file_path = os.path.join(target, rel_path) # Make sure directories exist local_file_dir = os.path.dirname(local_file_path) assert_dir_exists(local_file_dir) client.download_file(bucket, key['Key'], local_file_path) client = boto3.client('s3') download_dir(client, 'bucket-name', 'path/to/data', 'downloads')
źródło
KeyError: 'Contents'
. ścieżka wejściowa'/arch/R/storeincomelogs/
, pełna ścieżka/arch/R/storeincomelogs/201901/01/xxx.parquet
.Mam obejście tego problemu, które uruchamia interfejs wiersza poleceń AWS w tym samym procesie.
Zainstaluj
awscli
jako python lib:Następnie zdefiniuj tę funkcję:
from awscli.clidriver import create_clidriver def aws_cli(*cmd): old_env = dict(os.environ) try: # Environment env = os.environ.copy() env['LC_CTYPE'] = u'en_US.UTF' os.environ.update(env) # Run awscli in the same process exit_code = create_clidriver().main(*cmd) # Deal with problems if exit_code > 0: raise RuntimeError('AWS CLI exited with code {}'.format(exit_code)) finally: os.environ.clear() os.environ.update(old_env)
Wykonać:
aws_cli('s3', 'sync', '/path/to/source', 's3://bucket/destination', '--delete')
źródło
sync
polecenia, a raczej po prostu wykonałem polecenieaws s3 cp s3://{bucket}/{folder} {local_folder} --recursive
. Czas skrócił się z minut (prawie 1h) do dosłownie sekundlogging.basicConfig(format='%(levelname)s: %(message)s', level=logging.WARNING) logger = logging.getLogger()
i chcę, aby dzienniki były wyprowadzane tylko z roota. Jakieś pomysły?Bardzo złym pomysłem jest pobieranie wszystkich plików za jednym zamachem, lepiej jest pobierać je partiami.
Jedną z implementacji, której używam do pobierania określonego folderu (katalogu) z S3 jest:
def get_directory(directory_path, download_path, exclude_file_names): # prepare session session = Session(aws_access_key_id, aws_secret_access_key, region_name) # get instances for resource and bucket resource = session.resource('s3') bucket = resource.Bucket(bucket_name) for s3_key in self.client.list_objects(Bucket=self.bucket_name, Prefix=directory_path)['Contents']: s3_object = s3_key['Key'] if s3_object not in exclude_file_names: bucket.download_file(file_path, download_path + str(s3_object.split('/')[-1])
a mimo to, jeśli chcesz uzyskać cały wiadro, użyj go przez CIL, jak wspomniany poniżej @John Rotenstein ,
źródło
for objs in my_bucket.objects.all(): print(objs.key) path='/tmp/'+os.sep.join(objs.key.split(os.sep)[:-1]) try: if not os.path.exists(path): os.makedirs(path) my_bucket.download_file(objs.key, '/tmp/'+objs.key) except FileExistsError as fe: print(objs.key+' exists')
Ten kod pobierze zawartość w
/tmp/
katalogu. Jeśli chcesz, możesz zmienić katalog.źródło
Jeśli chcesz wywołać skrypt bash za pomocą Pythona, oto prosta metoda załadowania pliku z folderu w zasobniku S3 do folderu lokalnego (na komputerze z systemem Linux):
import boto3 import subprocess import os ###TOEDIT### my_bucket_name = "your_my_bucket_name" bucket_folder_name = "your_bucket_folder_name" local_folder_path = "your_local_folder_path" ###TOEDIT### # 1.Load thes list of files existing in the bucket folder FILES_NAMES = [] s3 = boto3.resource('s3') my_bucket = s3.Bucket('{}'.format(my_bucket_name)) for object_summary in my_bucket.objects.filter(Prefix="{}/".format(bucket_folder_name)): # print(object_summary.key) FILES_NAMES.append(object_summary.key) # 2.List only new files that do not exist in local folder (to not copy everything!) new_filenames = list(set(FILES_NAMES )-set(os.listdir(local_folder_path))) # 3.Time to load files in your destination folder for new_filename in new_filenames: upload_S3files_CMD = """aws s3 cp s3://{}/{}/{} {}""".format(my_bucket_name,bucket_folder_name,new_filename ,local_folder_path) subprocess_call = subprocess.call([upload_S3files_CMD], shell=True) if subprocess_call != 0: print("ALERT: loading files not working correctly, please re-check new loaded files")
źródło
Otrzymałem podobny wymóg i otrzymałem pomoc, czytając kilka z powyższych rozwiązań i na innych stronach internetowych, wymyśliłem poniższy skrypt, po prostu chciałem się nim podzielić, jeśli może to komuś pomóc.
from boto3.session import Session import os def sync_s3_folder(access_key_id,secret_access_key,bucket_name,folder,destination_path): session = Session(aws_access_key_id=access_key_id,aws_secret_access_key=secret_access_key) s3 = session.resource('s3') your_bucket = s3.Bucket(bucket_name) for s3_file in your_bucket.objects.all(): if folder in s3_file.key: file=os.path.join(destination_path,s3_file.key.replace('/','\\')) if not os.path.exists(os.path.dirname(file)): os.makedirs(os.path.dirname(file)) your_bucket.download_file(s3_file.key,file) sync_s3_folder(access_key_id,secret_access_key,bucket_name,folder,destination_path)
źródło
Ponowne zamieszczenie odpowiedzi @glefait z warunkiem if na końcu, aby uniknąć błędu systemu operacyjnego 20. Pierwszym otrzymanym kluczem jest sama nazwa folderu, której nie można zapisać w ścieżce docelowej.
def download_dir(client, resource, dist, local='/tmp', bucket='your_bucket'): paginator = client.get_paginator('list_objects') for result in paginator.paginate(Bucket=bucket, Delimiter='/', Prefix=dist): if result.get('CommonPrefixes') is not None: for subdir in result.get('CommonPrefixes'): download_dir(client, resource, subdir.get('Prefix'), local, bucket) for file in result.get('Contents', []): print("Content: ",result) dest_pathname = os.path.join(local, file.get('Key')) print("Dest path: ",dest_pathname) if not os.path.exists(os.path.dirname(dest_pathname)): print("here last if") os.makedirs(os.path.dirname(dest_pathname)) print("else file key: ", file.get('Key')) if not file.get('Key') == dist: print("Key not equal? ",file.get('Key')) resource.meta.client.download_file(bucket, file.get('Key'), dest_pathname)enter code here
źródło
Od jakiegoś czasu napotykam ten problem i na wszystkich różnych forach, na których przeglądałem, nie widziałem pełnego, kompleksowego wycinka tego, co działa. Więc poszedłem naprzód i wziąłem wszystkie elementy (dodaj trochę rzeczy samodzielnie) i stworzyłem pełny, kompleksowy program do pobierania S3!
Spowoduje to nie tylko automatyczne pobranie plików, ale jeśli pliki S3 znajdują się w podkatalogach, utworzy je w pamięci lokalnej. W instancji mojej aplikacji muszę ustawić uprawnienia i właścicieli, więc też to dodałem (można to skomentować, jeśli nie jest to potrzebne).
Zostało to przetestowane i działa w środowisku Docker (K8), ale dodałem zmienne środowiskowe w skrypcie na wypadek, gdybyś chciał przetestować / uruchomić go lokalnie.
Mam nadzieję, że pomoże to komuś w poszukiwaniu automatyzacji S3 Download. Z zadowoleniem przyjmuję również wszelkie porady, informacje itp., Jak można to lepiej zoptymalizować w razie potrzeby.
#!/usr/bin/python3 import gc import logging import os import signal import sys import time from datetime import datetime import boto from boto.exception import S3ResponseError from pythonjsonlogger import jsonlogger formatter = jsonlogger.JsonFormatter('%(message)%(levelname)%(name)%(asctime)%(filename)%(lineno)%(funcName)') json_handler_out = logging.StreamHandler() json_handler_out.setFormatter(formatter) #Manual Testing Variables If Needed #os.environ["DOWNLOAD_LOCATION_PATH"] = "some_path" #os.environ["BUCKET_NAME"] = "some_bucket" #os.environ["AWS_ACCESS_KEY"] = "some_access_key" #os.environ["AWS_SECRET_KEY"] = "some_secret" #os.environ["LOG_LEVEL_SELECTOR"] = "DEBUG, INFO, or ERROR" #Setting Log Level Test logger = logging.getLogger('json') logger.addHandler(json_handler_out) logger_levels = { 'ERROR' : logging.ERROR, 'INFO' : logging.INFO, 'DEBUG' : logging.DEBUG } logger_level_selector = os.environ["LOG_LEVEL_SELECTOR"] logger.setLevel(logger_level_selector) #Getting Date/Time now = datetime.now() logger.info("Current date and time : ") logger.info(now.strftime("%Y-%m-%d %H:%M:%S")) #Establishing S3 Variables and Download Location download_location_path = os.environ["DOWNLOAD_LOCATION_PATH"] bucket_name = os.environ["BUCKET_NAME"] aws_access_key_id = os.environ["AWS_ACCESS_KEY"] aws_access_secret_key = os.environ["AWS_SECRET_KEY"] logger.debug("Bucket: %s" % bucket_name) logger.debug("Key: %s" % aws_access_key_id) logger.debug("Secret: %s" % aws_access_secret_key) logger.debug("Download location path: %s" % download_location_path) #Creating Download Directory if not os.path.exists(download_location_path): logger.info("Making download directory") os.makedirs(download_location_path) #Signal Hooks are fun class GracefulKiller: kill_now = False def __init__(self): signal.signal(signal.SIGINT, self.exit_gracefully) signal.signal(signal.SIGTERM, self.exit_gracefully) def exit_gracefully(self, signum, frame): self.kill_now = True #Downloading from S3 Bucket def download_s3_bucket(): conn = boto.connect_s3(aws_access_key_id, aws_access_secret_key) logger.debug("Connection established: ") bucket = conn.get_bucket(bucket_name) logger.debug("Bucket: %s" % str(bucket)) bucket_list = bucket.list() # logger.info("Number of items to download: {0}".format(len(bucket_list))) for s3_item in bucket_list: key_string = str(s3_item.key) logger.debug("S3 Bucket Item to download: %s" % key_string) s3_path = download_location_path + "/" + key_string logger.debug("Downloading to: %s" % s3_path) local_dir = os.path.dirname(s3_path) if not os.path.exists(local_dir): logger.info("Local directory doesn't exist, creating it... %s" % local_dir) os.makedirs(local_dir) logger.info("Updating local directory permissions to %s" % local_dir) #Comment or Uncomment Permissions based on Local Usage os.chmod(local_dir, 0o775) os.chown(local_dir, 60001, 60001) logger.debug("Local directory for download: %s" % local_dir) try: logger.info("Downloading File: %s" % key_string) s3_item.get_contents_to_filename(s3_path) logger.info("Successfully downloaded File: %s" % s3_path) #Updating Permissions logger.info("Updating Permissions for %s" % str(s3_path)) #Comment or Uncomment Permissions based on Local Usage os.chmod(s3_path, 0o664) os.chown(s3_path, 60001, 60001) except (OSError, S3ResponseError) as e: logger.error("Fatal error in s3_item.get_contents_to_filename", exc_info=True) # logger.error("Exception in file download from S3: {}".format(e)) continue logger.info("Deleting %s from S3 Bucket" % str(s3_item.key)) s3_item.delete() def main(): killer = GracefulKiller() while not killer.kill_now: logger.info("Checking for new files on S3 to download...") download_s3_bucket() logger.info("Done checking for new files, will check in 120s...") gc.collect() sys.stdout.flush() time.sleep(120) if __name__ == '__main__': main()
źródło
Z dokumentacji AWS S3 (Jak używać folderów w zasobniku S3?):
Aby pobrać wszystkie pliki z „mybucket” do bieżącego katalogu, przestrzegając emulowanej struktury katalogów zasobnika (tworząc foldery z zasobnika, jeśli nie istnieją jeszcze lokalnie):
import boto3 import os bucket_name = "mybucket" s3 = boto3.client("s3") objects = s3.list_objects(Bucket = bucket_name)["Contents"] for s3_object in objects: s3_key = s3_object["Key"] path, filename = os.path.split(s3_key) if len(path) != 0 and not os.path.exists(path): os.makedirs(path) if not s3_key.endswith("/"): download_to = path + '/' + filename if path else filename s3.download_file(bucket_name, s3_key, download_to)
źródło