vérifier si une clé existe dans un compartiment en s3 en utilisant boto3

165

Je voudrais savoir si une clé existe dans boto3. Je peux boucler le contenu du seau et vérifier la clé si elle correspond.

Mais cela semble plus long et exagéré. Les documents officiels de Boto3 indiquent explicitement comment procéder.

Peut-être que je manque l'évidence. Quelqu'un peut-il m'indiquer comment je peux y parvenir.

Prabhakar Shanmugam
la source

Réponses:

196

L' boto.s3.key.Keyobjet de Boto 2 avait une existsméthode qui vérifiait si la clé existait sur S3 en faisant une requête HEAD et en regardant le résultat, mais il semble que cela n'existe plus. Vous devez le faire vous-même:

import boto3
import botocore

s3 = boto3.resource('s3')

try:
    s3.Object('my-bucket', 'dootdoot.jpg').load()
except botocore.exceptions.ClientError as e:
    if e.response['Error']['Code'] == "404":
        # The object does not exist.
        ...
    else:
        # Something else has gone wrong.
        raise
else:
    # The object does exist.
    ...

load() fait une requête HEAD pour une seule clé, ce qui est rapide, même si l'objet en question est volumineux ou si vous avez de nombreux objets dans votre compartiment.

Bien sûr, vous pouvez vérifier si l'objet existe parce que vous prévoyez de l'utiliser. Si tel est le cas, vous pouvez simplement oublier le load()et faire un get()ou download_file()directement, puis gérer le cas d'erreur ici.

Wander Nauta
la source
Merci pour la réponse rapide Wander. J'ai juste besoin de la même chose pour boto3.
Prabhakar Shanmugam
12
Car boto3, il semble que le mieux que vous puissiez faire pour le moment est d'appeler head_objectpour essayer de récupérer les métadonnées de la clé, puis de gérer l'erreur qui en résulte si elle n'existe pas.
Wander Nauta
1
@Leonid Vous pourriez certainement, mais seulement si vous enveloppez cela dans une fonction ou une méthode, ce qui dépend de vous. J'ai modifié un peu l'exemple de code pour que le existsbooléen disparaisse, et il est plus clair (j'espère!) Que les gens sont censés adapter cela à leur situation.
Wander Nauta
2
-1; ne fonctionne pas pour moi. Sur la version 1.5.26 de boto3, je vois e.response['Error']['Code']avoir une valeur comme "NoSuchKey", non "404". Je n'ai pas vérifié si cela était dû à une différence dans les versions de la bibliothèque ou à un changement dans l'API elle-même depuis que cette réponse a été écrite. Quoi qu'il en soit, dans ma version de boto3, une approche plus courte que la vérification e.response['Error']['Code']consiste à attraper seulement s3.meta.client.exceptions.NoSuchKeyen premier lieu.
Mark Amery
2
si vous utilisez un s3 client(par opposition à a resource), faites à la s3.head_object(Bucket='my_bucket', Key='my_key')place des3.Object(...).load()
user2426679
127

Je ne suis pas un grand fan de l'utilisation d'exceptions pour le flux de contrôle. C'est une approche alternative qui fonctionne dans boto3:

import boto3

s3 = boto3.resource('s3')
bucket = s3.Bucket('my-bucket')
key = 'dootdoot.jpg'
objs = list(bucket.objects.filter(Prefix=key))
if any([w.key == path_s3 for w in objs]):
    print("Exists!")
else:
    print("Doesn't exist")
EvilPuppetMaster
la source
Merci pour la mise à jour EvilPuppetMaster. Malheureusement, lors de ma dernière vérification, je n'avais pas les droits d'accès au bucket de liste. Votre réponse convient à ma question, donc je vous ai voté. Mais j'avais déjà marqué la première réponse comme réponse bien avant. Merci de votre aide.
Prabhakar Shanmugam
27
Cela ne compte-t-il pas comme une demande d'inscription (12,5 fois plus cher que get)? Si vous faites cela pour 100 millions d'objets, cela pourrait devenir un peu cher ... J'ai le sentiment que la méthode de capture d'exception est malheureusement la meilleure à ce jour.
Pierre D
21
La liste peut être 12,5 fois plus chère par requête, mais une seule requête peut également renvoyer 100 millions d'objets alors qu'un seul get ne peut en renvoyer qu'un. Donc, dans votre cas hypothétique, il serait moins cher d'aller chercher les 100 millions avec la liste et de comparer ensuite localement, que de faire 100 millions de gains individuels. Sans parler de 1000 fois plus rapide car vous n'auriez pas besoin de l'aller-retour http pour chaque objet.
EvilPuppetMaster
Cela ne fonctionne pas lorsque mon fichier se trouve dans des dossiers dans un
compartiment
2
@ user3186866 C'est parce que S3 n'a pas réellement de "dossiers". Tous les objets existent sous forme de fichiers à leurs chemins d'accès donnés. Les dossiers sont un outil qui nous aide à organiser et à comprendre la structure de notre stockage, mais en réalité, les seaux S3 ne sont que cela, des seaux.
ibtokin
114

Le moyen le plus simple que j'ai trouvé (et probablement le plus efficace) est le suivant:

import boto3
from botocore.errorfactory import ClientError

s3 = boto3.client('s3')
try:
    s3.head_object(Bucket='bucket_name', Key='file_path')
except ClientError:
    # Not found
    pass
o_c
la source
2
Remarque: Vous n'avez pas à passer aws_access_key_id / aws_secret_access_key etc. si vous utilisez un rôle ou si vous avez les clés dans votre configuration .aws, vous pouvez simplement le faires3 = boto3.client('s3')
Andy Hayden
20
Je pense que l'ajout de ce test vous donne un peu plus de confiance que l'objet n'existe vraiment pas, plutôt qu'une autre erreur soulevant l'exception - notez que 'e' est l'instance d'exception ClientError:if e.response['ResponseMetadata']['HTTPStatusCode'] == 404:
Richard
@AndyHayden Combien chaque essai compte-t-il en termes de coût aws?
boucle
2
@Taylor c'est une demande d'obtention mais sans transfert de données.
Andy Hayden
1
ClientError est un fourre-tout pour 400, pas seulement pour 404 donc il n'est pas robuste.
mickzer le
21

Dans Boto3, si vous recherchez un dossier (préfixe) ou un fichier à l'aide de list_objects. Vous pouvez utiliser l'existence de «Contenu» dans le dict de la réponse pour vérifier si l'objet existe. C'est une autre façon d'éviter les captures try / except comme le suggère @EvilPuppetMaster

import boto3
client = boto3.client('s3')
results = client.list_objects(Bucket='my-bucket', Prefix='dootdoot.jpg')
return 'Contents' in results
Lucian Thorr
la source
2
J'ai eu un problème à ce sujet. list_objects ("2000") renverra des clés comme "2000-01", "2000-02"
Gunnar Cheng
3
Cela ne renvoie que jusqu'à 1000 objets! boto3.amazonaws.com/v1/documentation/api/latest/reference/…
RoachLord
C'est la solution la plus efficace car cela ne nécessite pas d' s3:GetObjectautorisations mais uniquement les s3:ListBucketautorisations
Vishrant
11

Non seulement clientmais bucketaussi:

import boto3
import botocore
bucket = boto3.resource('s3', region_name='eu-west-1').Bucket('my-bucket')

try:
  bucket.Object('my-file').get()
except botocore.exceptions.ClientError as ex:
  if ex.response['Error']['Code'] == 'NoSuchKey':
    print('NoSuchKey')
Vitaly Zdanevich
la source
3
Vous ne voudrez peut-être pas obtenir l'objet, mais voyez simplement s'il est là. Vous pouvez utiliser une méthode qui dirige l'objet comme d'autres exemples ici, tels que bucket.Object(key).last_modified.
ryanjdillon le
10

Vous pouvez utiliser S3F , qui est essentiellement un wrapper autour de boto3 qui expose les opérations typiques de style de système de fichiers:

import s3fs
s3 = s3fs.S3FileSystem()
s3.exists('myfile.txt')
VinceP
la source
Bien que je pense que cela fonctionnerait, la question demande comment faire cela avec boto3; dans ce cas, il est pratique de résoudre le problème sans installer de bibliothèque supplémentaire.
paulkernfeld le
5
import boto3
client = boto3.client('s3')
s3_key = 'Your file without bucket name e.g. abc/bcd.txt'
bucket = 'your bucket name'
content = client.head_object(Bucket=bucket,Key=s3_key)
    if content.get('ResponseMetadata',None) is not None:
        print "File exists - s3://%s/%s " %(bucket,s3_key) 
    else:
        print "File does not exist - s3://%s/%s " %(bucket,s3_key)
Vivek
la source
5

FWIW, voici les fonctions très simples que j'utilise

import boto3

def get_resource(config: dict={}):
    """Loads the s3 resource.

    Expects AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to be in the environment
    or in a config dictionary.
    Looks in the environment first."""

    s3 = boto3.resource('s3',
                        aws_access_key_id=os.environ.get(
                            "AWS_ACCESS_KEY_ID", config.get("AWS_ACCESS_KEY_ID")),
                        aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY", config.get("AWS_SECRET_ACCESS_KEY")))
    return s3


def get_bucket(s3, s3_uri: str):
    """Get the bucket from the resource.
    A thin wrapper, use with caution.

    Example usage:

    >> bucket = get_bucket(get_resource(), s3_uri_prod)"""
    return s3.Bucket(s3_uri)


def isfile_s3(bucket, key: str) -> bool:
    """Returns T/F whether the file exists."""
    objs = list(bucket.objects.filter(Prefix=key))
    return len(objs) == 1 and objs[0].key == key


def isdir_s3(bucket, key: str) -> bool:
    """Returns T/F whether the directory exists."""
    objs = list(bucket.objects.filter(Prefix=key))
    return len(objs) > 1
Andy Reagan
la source
1
c'est la seule réponse que j'ai vue qui concernait la vérification de l'existence d'un «dossier» par rapport à un «fichier». c'est très important pour les routines qui ont besoin de savoir si un dossier spécifique existe, pas les fichiers spécifiques dans un dossier.
dave campbell
Bien qu'il s'agisse d'une réponse prudente, elle n'est utile que si l'utilisateur comprend que la notion de dossier est trompeuse dans ce cas. Un `` dossier '' vide peut exister dans S3 à l'intérieur d'un compartiment et si c'est le cas, isdir_s3 retournera False m'a pris quelques minutes pour régler cela.Je pensais à modifier la réponse comme si l'expression était modifiée en> 0, vous obtiendrez le résultat que vous attendez
PyNEwbie
5

En supposant que vous vouliez simplement vérifier si une clé existe (au lieu de l'écraser tranquillement), faites d'abord cette vérification:

import boto3

def key_exists(mykey, mybucket):
  s3_client = boto3.client('s3')
  response = s3_client.list_objects_v2(Bucket=mybucket, Prefix=mykey)
  if response:
      for obj in response['Contents']:
          if mykey == obj['Key']:
              return True
  return False

if key_exists('someprefix/myfile-abc123', 'my-bucket-name'):
    print("key exists")
else:
    print("safe to put new bucket object")
    # try:
    #     resp = s3_client.put_object(Body="Your string or file-like object",
    #                                 Bucket=mybucket,Key=mykey)
    # ...check resp success and ClientError exception for errors...
marvles
la source
4

Cela peut vérifier à la fois le préfixe et la clé, et récupère au plus 1 clé.

def prefix_exits(bucket, prefix):
    s3_client = boto3.client('s3')
    res = s3_client.list_objects_v2(Bucket=bucket, Prefix=prefix, MaxKeys=1)
    return 'Contents' in res
Fang Zhang
la source
3

Essayez ceci simple

import boto3
s3 = boto3.resource('s3')
bucket = s3.Bucket('mybucket_name') # just Bucket name
file_name = 'A/B/filename.txt'      # full file path
obj = list(bucket.objects.filter(Prefix=file_name))
if len(obj) > 0:
    print("Exists")
else:
    print("Not Exists")
Alkesh Mahajan
la source
1

Si vous en avez moins de 1000 dans un répertoire ou un compartiment, vous pouvez en obtenir un ensemble et après vérifier si une telle clé dans cet ensemble:

files_in_dir = {d['Key'].split('/')[-1] for d in s3_client.list_objects_v2(
Bucket='mybucket',
Prefix='my/dir').get('Contents') or []}

Un tel code fonctionne même s'il my/dirn'existe pas.

http://boto3.readthedocs.io/en/latest/reference/services/s3.html#S3.Client.list_objects_v2

Vitaly Zdanevich
la source
1
S3_REGION="eu-central-1"
bucket="mybucket1"
name="objectname"

import boto3
from botocore.client import Config
client = boto3.client('s3',region_name=S3_REGION,config=Config(signature_version='s3v4'))
list = client.list_objects_v2(Bucket=bucket,Prefix=name)
for obj in list.get('Contents', []):
    if obj['Key'] == name: return True
return False

la source
1

Pour boto3, ObjectSummary peut être utilisé pour vérifier si un objet existe.

Contient le résumé d'un objet stocké dans un compartiment Amazon S3. Cet objet ne contient pas les métadonnées complètes de l'objet ni aucun de ses contenus

import boto3
from botocore.errorfactory import ClientError
def path_exists(path, bucket_name):
    """Check to see if an object exists on S3"""
    s3 = boto3.resource('s3')
    try:
        s3.ObjectSummary(bucket_name=bucket_name, key=path).load()
    except ClientError as e:
        if e.response['Error']['Code'] == "404":
            return False
        else:
            raise e
    return True

path_exists('path/to/file.html')

Dans ObjectSummary.load

Appelle s3.Client.head_object pour mettre à jour les attributs de la ressource ObjectSummary.

Cela montre que vous pouvez utiliser ObjectSummaryau lieu de Objectsi vous prévoyez de ne pas utiliser get(). La load()fonction ne récupère pas l'objet, elle obtient uniquement le résumé.

Veedka
la source
1

Voici une solution qui fonctionne pour moi. Une mise en garde est que je connais le format exact de la clé à l'avance, donc je ne liste que le fichier unique

import boto3

# The s3 base class to interact with S3
class S3(object):
  def __init__(self):
    self.s3_client = boto3.client('s3')

  def check_if_object_exists(self, s3_bucket, s3_key):
    response = self.s3_client.list_objects(
      Bucket = s3_bucket,
      Prefix = s3_key
      )
    if 'ETag' in str(response):
      return True
    else:
      return False

if __name__ == '__main__':
  s3  = S3()
  if s3.check_if_object_exists(bucket, key):
    print "Found S3 object."
  else:
    print "No object found."
Rush S
la source
1

vous pouvez utiliser Boto3 pour cela.

import boto3
s3 = boto3.resource('s3')
bucket = s3.Bucket('my-bucket')
objs = list(bucket.objects.filter(Prefix=key))
if(len(objs)>0):
    print("key exists!!")
else:
    print("key doesn't exist!")

Ici, la clé est le chemin que vous souhaitez vérifier existe ou non

AshuGG
la source
D'un simple %timeittest, cela semble l'option la plus rapide
Itamar Katz
1

C'est vraiment simple avec la get()méthode

import botocore
from boto3.session import Session
session = Session(aws_access_key_id='AWS_ACCESS_KEY',
                aws_secret_access_key='AWS_SECRET_ACCESS_KEY')
s3 = session.resource('s3')
bucket_s3 = s3.Bucket('bucket_name')

def not_exist(file_key):
    try:
        file_details = bucket_s3.Object(file_key).get()
        # print(file_details) # This line prints the file details
        return False
    except botocore.exceptions.ClientError as e:
        if e.response['Error']['Code'] == "NoSuchKey": # or you can check with e.reponse['HTTPStatusCode'] == '404'
            return True
        return False # For any other error it's hard to determine whether it exists or not. so based on the requirement feel free to change it to True/ False / raise Exception

print(not_exist('hello_world.txt')) 
isambitd
la source
Pas robuste, une exception pourrait être lancée pour de nombreuses raisons, par exemple HTTP 500 et ce code supposerait un 404.
mickzer
Mais nous avons besoin d'informations pour savoir si le fichier est accessible ou non. Il existe et ne peut pas être accessible alors il équivaut à ne pas exister. droite?
isambitd
@mickzer vérifie les changements maintenant.
isambitd
1
Pour répondre à votre commentaire précédent, Non, le comportement, sur un HTTP 500 pourrait être de réessayer, un 401/403 pour corriger l'authentification, etc. Il est important de vérifier le code d'erreur réel.
mickzer le
0

Il existe un moyen simple de vérifier si le fichier existe ou non dans le compartiment S3. Nous n'avons pas besoin d'utiliser une exception pour cela

sesssion = boto3.Session(aws_access_key_id, aws_secret_access_key)
s3 = session.client('s3')

object_name = 'filename'
bucket = 'bucketname'
obj_status = s3.list_objects(Bucket = bucket, Prefix = object_name)
if obj_status.get('Contents'):
    print("File exists")
else:
    print("File does not exists")
Mahesh Mogal
la source
Ce sera incorrect si un fichier commençant par object_nameexiste dans le compartiment. Par exemple my_file.txt.oldversion, retournera un faux positif si vous vérifiez my_file.txt. Un peu un cas de pointe pour la plupart, mais pour quelque chose d'aussi large que "le fichier existe-t-il" que vous êtes susceptible d'utiliser dans toute votre application mérite probablement d'être pris en considération.
Andrew Schwartz du
0

Si vous recherchez une clé équivalente à un répertoire, vous voudrez peut-être cette approche

session = boto3.session.Session()
resource = session.resource("s3")
bucket = resource.Bucket('mybucket')

key = 'dir-like-or-file-like-key'
objects = [o for o in bucket.objects.filter(Prefix=key).limit(1)]    
has_key = len(objects) > 0

Cela fonctionne pour une clé parente ou une clé qui équivaut à un fichier ou une clé qui n'existe pas. J'ai essayé l'approche préférée ci-dessus et j'ai échoué sur les clés parent.

Peter Kahn
la source
0

J'ai remarqué que juste pour attraper l'exception en utilisant, botocore.exceptions.ClientErrornous devons installer botocore. botocore occupe 36 Mo d'espace disque. Ceci est particulièrement impactant si nous utilisons les fonctions aws lambda. À la place de cela, si nous utilisons simplement l'exception, nous pouvons ignorer l'utilisation de la bibliothèque supplémentaire!

  • Je valide l'extension de fichier pour être «.csv»
  • Cela ne lèvera pas d'exception si le compartiment n'existe pas!
  • Cela ne lèvera pas d'exception si le compartiment existe mais que l'objet n'existe pas!
  • Cela lève une exception si le seau est vide!
  • Cela lève une exception si le compartiment n'a pas d'autorisations!

Le code ressemble à ceci. Veuillez partager vos pensées:

import boto3
import traceback

def download4mS3(s3bucket, s3Path, localPath):
    s3 = boto3.resource('s3')

    print('Looking for the csv data file ending with .csv in bucket: ' + s3bucket + ' path: ' + s3Path)
    if s3Path.endswith('.csv') and s3Path != '':
        try:
            s3.Bucket(s3bucket).download_file(s3Path, localPath)
        except Exception as e:
            print(e)
            print(traceback.format_exc())
            if e.response['Error']['Code'] == "404":
                print("Downloading the file from: [", s3Path, "] failed")
                exit(12)
            else:
                raise
        print("Downloading the file from: [", s3Path, "] succeeded")
    else:
        print("csv file not found in in : [", s3Path, "]")
        exit(12)
utilisateur 923227
la source
AWS indique que les environnements d'exécution python sont livrés avec boto3 préinstallé: docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html
rinat.io
0

En suivant simplement le fil de discussion, quelqu'un peut-il conclure quel est le moyen le plus efficace de vérifier si un objet existe dans S3?

Je pense que head_object pourrait gagner car il vérifie simplement les métadonnées qui sont plus légères que l'objet lui-même

Sai
la source
0

De https://www.peterbe.com/plog/fastest-way-to-find-out-if-a-file-exists-in-s3, il est indiqué que c'est la méthode la plus rapide:

import boto3

boto3_session = boto3.session.Session()
s3_session_client = boto3_session.client("s3")
response = s3_session_client.list_objects_v2(
    Bucket=bc_df_caches_bucket, Prefix=s3_key
)
for obj in response.get("Contents", []):
    if obj["Key"] == s3_key:
        return True
return False
Ivansabik
la source
-1

Check-out

bucket.get_key(
    key_name, 
    headers=None, 
    version_id=None, 
    response_headers=None, 
    validate=True
)

Vérifiez si une clé particulière existe dans le compartiment. Cette méthode utilise une requête HEAD pour vérifier l'existence de la clé. Renvoie: Une instance d'un objet Key ou None

de Boto S3 Docs

Vous pouvez simplement appeler bucket.get_key (keyname) et vérifier si l'objet renvoyé est None.

Alexander Truslow
la source
Cela ne fonctionne pas avec boto3, comme demandé par l'OP
MarkNS
Il existe deux versions de la bibliothèque AWS boto. Cette réponse ne fonctionne pas avec la version demandée par la question.
MarkNS
Ce n'est certainement pas une réponse correcte pour OP, mais cela m'aide car je dois utiliser boto v2. C'est pourquoi j'ai supprimé un vote négatif.
haͣrͬukaͣreͤrͬu