comprobar si existe una clave en un cubo en s3 usando boto3


165

Me gustaría saber si existe una clave en boto3. Puedo recorrer el contenido del cubo y verificar la clave si coincide.

Pero eso parece más largo y una exageración. Los documentos oficiales de Boto3 indican explícitamente cómo hacer esto.

Puede ser que me falta lo obvio. ¿Alguien puede señalarme cómo puedo lograr esto?

Respuestas:


196

El boto.s3.key.Keyobjeto de Boto 2 solía tener un existsmétodo que verificaba si la clave existía en S3 haciendo una solicitud HEAD y mirando el resultado, pero parece que ya no existe. Tienes que hacerlo tú mismo:

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() realiza una solicitud HEAD para una sola clave, que es rápida, incluso si el objeto en cuestión es grande o si tiene muchos objetos en su depósito.

Por supuesto, es posible que esté verificando si el objeto existe porque planea usarlo. Si ese es el caso, puede olvidarse del load()y hacer un get()o download_file()directamente, luego manejar el caso de error allí.


Gracias por la rápida respuesta Wander. Solo necesito lo mismo para boto3.
Prabhakar Shanmugam

12
Porque boto3, parece que lo mejor que puede hacer en este momento es llamar head_objectpara intentar obtener los metadatos de la clave, luego manejar el error resultante si no existe.
Wander Nauta el

1
@Leonid Ciertamente podrías, pero solo si envuelves esto en una función o método, que depende de ti. He modificado un poco el código de ejemplo para que el existsbooleano desaparezca, y es más claro (¡espero!) Que se supone que las personas deben adaptar esto a su situación.
Wander Nauta

2
-1; no funciona para mi En la versión 1.5.26 de boto3 veo que e.response['Error']['Code']tiene un valor como "NoSuchKey", no "404". No he comprobado si esto se debe a una diferencia en las versiones de la biblioteca o un cambio en la API en sí desde que se escribió esta respuesta. De cualquier manera, en mi versión de boto3, un enfoque más corto que verificar e.response['Error']['Code']es atrapar solo s3.meta.client.exceptions.NoSuchKeyen primer lugar.
Mark Amery

2
si está utilizando un s3 client(en lugar de a resource), hágalo en s3.head_object(Bucket='my_bucket', Key='my_key')lugar des3.Object(...).load()
user2426679

127

No soy un gran fanático del uso de excepciones para el flujo de control. Este es un enfoque alternativo que funciona en 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")

Gracias por la actualización EvilPuppetMaster. Desafortunadamente, cuando lo verifiqué por última vez, no tenía derechos de acceso a la lista. Su respuesta es apta para mi pregunta, así que lo he votado. Pero ya había marcado la primera respuesta como respuesta mucho antes. Gracias por tu ayuda.
Prabhakar Shanmugam

27
¿No cuenta esto como una solicitud de listado (12,5 veces más caro que obtener)? Si haces esto por 100 millones de objetos, eso podría ser un poco caro ... Tengo la sensación de que el método de captura de excepción es, lamentablemente, el mejor hasta ahora.
Pierre D

21
La lista puede ser 12.5x más costosa por solicitud, pero una sola solicitud también puede devolver 100 millones de objetos donde un solo get solo puede devolver uno. Por lo tanto, en su caso hipotético, sería más barato obtener los 100 millones con una lista y luego compararlos localmente, que obtener 100 millones de copias individuales. Sin mencionar 1000 veces más rápido ya que no necesitaría el http ida y vuelta para cada objeto.
EvilPuppetMaster

No funciona cuando mi archivo está dentro de carpetas dentro de un depósito de s3
user3186866

2
@ user3186866 Eso se debe a que S3 en realidad no tiene "carpetas". Todos los objetos existen como archivos en sus rutas dadas. Las carpetas son una herramienta que nos ayuda a organizar y comprender la estructura de nuestro almacenamiento, pero en realidad, los cubos S3 son solo eso, cubos.
ibtokin

114

La forma más fácil que encontré (y probablemente la más eficiente) es esta:

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

2
Nota: No tiene que pasar aws_access_key_id / aws_secret_access_key, etc. si usa un rol o tiene las claves en su configuración .aws, simplemente puede hacerlos3 = boto3.client('s3')
Andy Hayden

20
Creo que la adición de esta prueba le da un poco de confianza más el objeto no existe realmente, en lugar de algún otro error lanzar la excepción - nota que 'e' es la instancia de excepción ClientError:if e.response['ResponseMetadata']['HTTPStatusCode'] == 404:
Richard

@AndyHayden ¿Qué trataría cada intento en términos de costo de aws?
loop

2
@ Taylor es una solicitud de obtención pero sin transferencia de datos.
Andy Hayden

1
ClientError es una trampa para 400, no solo 404, por lo tanto, no es robusto.
mickzer

21

En Boto3, si está buscando una carpeta (prefijo) o un archivo usando list_objects. Puede usar la existencia de 'Contenido' en la respuesta dict como una verificación de si el objeto existe. Es otra forma de evitar el intento / excepto las capturas como sugiere @EvilPuppetMaster

import boto3
client = boto3.client('s3')
results = client.list_objects(Bucket='my-bucket', Prefix='dootdoot.jpg')
return 'Contents' in results

2
Tuve un problema en esto. list_objects ("2000") devolverá claves como "2000-01", "2000-02"
Gunnar Cheng

3

Esta es la solución más eficiente, ya que no requiere s3:GetObjectpermisos solo los s3:ListBucketpermisos
Vishrant

11

No solo clientsino buckettambién:

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

3
Es posible que no desee obtener el objeto, pero solo vea si está allí. Podría usar un método que encabeza el objeto como otros ejemplos aquí, como bucket.Object(key).last_modified.
ryanjdillon

10

Puede usar S3F , que es esencialmente un envoltorio alrededor de boto3 que expone las operaciones típicas de estilo de sistema de archivos:

import s3fs
s3 = s3fs.S3FileSystem()
s3.exists('myfile.txt')

Aunque creo que esto funcionaría, la pregunta se refiere a cómo hacer esto con boto3; en este caso, es práctico resolver el problema sin instalar una biblioteca adicional.
Paulkernfeld

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)

5

FWIW, aquí están las funciones muy simples que estoy usando

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

1
Esta es la única respuesta que vi que abordaba la comprobación de la existencia de una 'carpeta' en comparación con un 'archivo'. eso es muy importante para las rutinas que necesitan saber si existe una carpeta específica, no los archivos específicos en una carpeta.
Dave Campbell

Si bien esta es una respuesta cuidadosa, solo es útil si el usuario comprende que la noción de una carpeta es engañosa en este caso. Puede existir una 'carpeta' vacía en S3 dentro de un cubo y, de ser así, el isdir_s3 regresará False, me tomó un par de minutos resolverlo. Estaba pensando en editar la respuesta como si la expresión se cambiara a> 0, obtendría el resultado que estás esperando
PyNEwbie

5

Suponiendo que solo desea verificar si existe una clave (en lugar de sobreescribirla silenciosamente), primero haga esta verificación:

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...

4

Esto podría verificar tanto el prefijo como la clave, y obtiene como máximo 1 clave.

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

3

Prueba esto 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")


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

1

Para boto3, ObjectSummary se puede usar para verificar si existe un objeto.

Contiene el resumen de un objeto almacenado en un bucket de Amazon S3. Este objeto no contiene los metadatos completos del objeto ni ninguno de sus contenidos.

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

En ObjectSummary.load

Llama a s3.Client.head_object para actualizar los atributos del recurso ObjectSummary.

Esto muestra que puede usar en ObjectSummarylugar de Objectsi planea no usar get(). La load()función no recupera el objeto, solo obtiene el resumen.


1

Aquí hay una solución que funciona para mí. Una advertencia es que conozco el formato exacto de la clave con anticipación, por lo que solo enumero el archivo único

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."

1

puedes usar Boto3 para esto.

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!")

Aquí la clave es la ruta que desea verificar existe o no


Desde una simple %timeitprueba, esta parece ser la opción más rápida
Itamar Katz el

1

Es muy simple con el get()método.

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

No es robusto, se puede
generar

Pero necesitamos información sobre si el archivo es accesible o no. Existe y no puede ser accesible, entonces es equivalente a no existir. ¿Derecha?
isambitd

@mickzer verifique los cambios ahora.
isambitd

1
Para responder a su comentario anterior, No, el comportamiento, en un HTTP 500 podría ser volver a intentarlo, un 401/403 para corregir la autenticación, etc. Es importante verificar el código de error real.
mickzer

0

Hay una manera simple por la cual podemos verificar si el archivo existe o no en el depósito S3. No necesitamos usar excepciones para esto

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

Esto será incorrecto si un archivo que comienza con object_nameexiste en el depósito. Por ejemplo my_file.txt.oldversion, devolverá un falso positivo si verifica my_file.txt. Es un caso marginal para la mayoría, pero para algo tan amplio como "existe el archivo" que probablemente usará en toda su aplicación, probablemente valga la pena tenerlo en cuenta.
Andrew Schwartz

0

Si busca una clave que sea equivalente a un directorio, es posible que desee este enfoque

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

Esto funciona para una clave principal o una clave que equivale a un archivo o una clave que no existe. Probé el enfoque favorito anterior y fallé en las claves principales.


0

Noté que solo para detectar la excepción usando botocore.exceptions.ClientErrornecesitamos instalar botocore. botocore ocupa 36 millones de espacio en disco. Esto es particularmente impactante si usamos funciones aws lambda. En lugar de eso, si solo usamos la excepción, ¡podemos omitir el uso de la biblioteca adicional!

  • Estoy validando que la extensión del archivo sea '.csv'
  • ¡Esto no arrojará una excepción si el cubo no existe!
  • ¡Esto no arrojará una excepción si el depósito existe pero el objeto no existe!
  • ¡Esto arroja una excepción si el cubo está vacío!
  • ¡Esto arroja una excepción si el cubo no tiene permisos!

El código se ve así. Por favor comparte tus pensamientos:

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)

AWS dice que los tiempos de ejecución de Python vienen con boto3 preinstalado: docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html
rinat.io

0

Simplemente siguiendo el hilo, ¿alguien puede concluir cuál es la forma más eficiente de verificar si un objeto existe en S3?

Creo que head_object podría ganar ya que solo verifica los metadatos que son más ligeros que el objeto en sí



-1

Revisa

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

Verifique si existe una clave en particular dentro del cubo. Este método utiliza una solicitud HEAD para verificar la existencia de la clave. Devuelve: una instancia de un objeto clave o ninguno

de Boto S3 Docs

Simplemente puede llamar a bucket.get_key (keyname) y verificar si el objeto devuelto es None.


Esto no funciona con boto3, como lo solicitó el OP
MarkNS

Hay dos versiones de la biblioteca de bots de AWS. Esta respuesta no funciona con la versión solicitada por la pregunta.
MarkNS

Es seguro que no es una respuesta correcta para OP, pero me ayuda porque necesito usar boto v2. Por eso eliminé un voto negativo.
haͣrͬukaͣreͤrͬu
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.