verifique se existe uma chave em um bucket no s3 usando boto3


165

Gostaria de saber se existe uma chave no boto3. Posso fazer um loop do conteúdo do balde e verificar a chave se ela corresponder.

Mas isso parece mais longo e um exagero. Os documentos oficiais do Boto3 afirmam explicitamente como fazer isso.

Pode estar faltando o óbvio. Alguém pode me apontar como posso conseguir isso.

Respostas:


196

O boto.s3.key.Keyobjeto Boto 2 costumava ter um existsmétodo que verificava se a chave existia no S3 fazendo uma solicitação HEAD e olhando o resultado, mas parece que isso não existe mais. Você precisa fazer isso sozinho:

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() faz uma solicitação HEAD para uma única chave, que é rápida, mesmo se o objeto em questão for grande ou você tiver muitos objetos em seu bucket.

Obviamente, você pode verificar se o objeto existe porque planeja usá-lo. Se for esse o caso, você pode simplesmente esquecer oe load()fazer um get()ou download_file()diretamente, e então lidar com o caso de erro.


Obrigado pela resposta rápida Wander. Eu só preciso do mesmo para o boto3.
Prabhakar Shanmugam

12
Pois boto3, parece que o melhor que você pode fazer no momento é ligar head_objectpara tentar buscar os metadados da chave e manipular o erro resultante, se ele não existir.
Wander Nauta

1
@ Leonid Você certamente poderia, mas apenas se você envolver isso em uma função ou método, que é com você. Modifiquei um pouco o código de exemplo para que o existsbooleano acabe, e é mais claro (espero!) Que as pessoas devam adaptar isso à sua situação.
Wander Nauta

2
-1; não funciona para mim. No boto3 versão 1.5.26, vejo e.response['Error']['Code']ter um valor como "NoSuchKey", não "404". Não verifiquei se isso se deve a uma diferença nas versões da biblioteca ou a uma alteração na própria API desde que esta resposta foi escrita. De qualquer maneira, na minha versão do boto3, uma abordagem mais curta do que a verificação e.response['Error']['Code']é capturar apenas s3.meta.client.exceptions.NoSuchKeyem primeiro lugar.
Mark Amery

2
se você estiver usando um s3 client(em oposição a resource), faça-o em s3.head_object(Bucket='my_bucket', Key='my_key')vez des3.Object(...).load()
user2426679 27/04

127

Não sou muito fã de usar exceções para controlar o fluxo. Esta é uma abordagem alternativa que funciona no 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")

Obrigado pela atualização EvilPuppetMaster. Infelizmente, quando verifiquei pela última vez, não tinha direitos de acesso ao intervalo da lista. Sua resposta é adequada para minha pergunta, então votei em você. Mas eu já tinha marcado a primeira resposta como resposta muito antes. Obrigado pela ajuda.
Prabhakar Shanmugam

27
Isso não conta como um pedido de listagem (12,5x mais caro que o get)? Se você fizer isso para 100 milhões de objetos, isso pode ficar um pouco caro ... Sinto que o método de exceção de captura é, infelizmente, o melhor até agora.
Pierre D

21
A lista pode ser 12,5x mais cara por solicitação, mas uma única solicitação também pode retornar 100 milhões de objetos, onde uma única obtenção pode retornar apenas um. Portanto, no seu caso hipotético, seria mais barato buscar todos os 100 milhões com a lista e depois comparar localmente, do que fazer 100 milhões de ganhos individuais. Sem mencionar 1000x mais rápido, pois você não precisaria da ida e volta http para cada objeto.
EvilPuppetMaster

Ele não está funcionando quando meu arquivo é pastas dentro dentro de um balde S3
user3186866

2
@ user3186866 Isso ocorre porque o S3 não possui realmente "pastas". Todos os objetos existem como arquivos nos respectivos caminhos. As pastas são uma ferramenta para nos ajudar a organizar e entender a estrutura de nosso armazenamento, mas, na realidade, os baldes S3 são exatamente isso, baldes.
Ibtokin

114

A maneira mais fácil que encontrei (e provavelmente a mais eficiente) é 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: Você não precisa passar aws_access_key_id / aws_secret_access_key etc. se estiver usando uma função ou tiver as chaves na sua configuração de .aws, você pode simplesmente fazê-los3 = boto3.client('s3')
Andy Hayden

20
Eu acho que adicionar esse teste dá a você um pouco mais de confiança de que o objeto realmente não existe, em vez de algum outro erro ao gerar a exceção - observe que 'e' é a instância de exceção ClientError:if e.response['ResponseMetadata']['HTTPStatusCode'] == 404:
Richard

@AndyHayden O que cada uma delas tentaria contar em termos de custo de aws?
circuito

2
@ Taylor é um pedido de obtenção, mas sem transferência de dados.
Andy Hayden

1
ClientError é um problema para 400, não apenas 404, portanto, não é robusto.
mickzer 7/01

21

No Boto3, se você estiver procurando por uma pasta (prefixo) ou um arquivo usando list_objects. Você pode usar a existência de 'Conteúdo' no ditado de resposta como uma verificação para verificar se o objeto existe. É outra maneira de evitar a tentativa / exceto capturas, como sugere @EvilPuppetMaster

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

2
Teve um problema nisso. list_objects ("2000") retornará chaves como "2000-01", "2000-02"
Gunnar Cheng


Esta é a solução mais eficiente, pois isso não requer s3:GetObjectpermissões, apenas as s3:ListBucketpermissões
Vishrant

11

Não apenas clientmas buckettambém:

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
Você pode não querer obter o objeto, mas apenas veja se ele está lá. Você pode usar um método que encabeça o objeto como outros exemplos aqui, como bucket.Object(key).last_modified.
Ryanjdillon

10

Você pode usar S3Fs , que é essencialmente um invólucro do boto3 que expõe operações típicas do estilo do sistema de arquivos:

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

Embora eu ache que isso funcione, a pergunta é sobre como fazer isso com o boto3; nesse caso, é prático resolver o problema sem instalar uma 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, aqui estão as funções muito simples que estou 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 é a única resposta que vi que abordava a verificação da existência de uma 'pasta' em comparação com um 'arquivo'. isso é super importante para rotinas que precisam saber se existe uma pasta específica, não os arquivos específicos em uma pasta.
Dave campbell

Embora esta seja uma resposta cuidadosa, só é útil se o usuário entender que a noção de uma pasta é enganosa nesse caso. Uma 'pasta' vazia pode existir no S3 dentro de um balde e, se assim for, o isdir_s3 retornará Falso, levei alguns minutos para resolver isso. Eu estava pensando em editar a resposta, como se a expressão fosse alterada para> 0, você receberá o resultado que você espera
PyNEwbie 19/10/19

5

Supondo que você queira apenas verificar se existe uma chave (em vez de sobrescrevê-la), faça essa verificação primeiro:

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

Isso pode verificar o prefixo e a chave e buscar no máximo 1 chave.

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

Experimente este simples

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, o ObjectSummary pode ser usado para verificar se existe um objeto.

Contém o resumo de um objeto armazenado em um bucket do Amazon S3. Este objeto não contém os metadados completos do objeto ou qualquer conteúdo

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

Em ObjectSummary.load

Chama s3.Client.head_object para atualizar os atributos do recurso ObjectSummary.

Isso mostra que você pode usar em ObjectSummaryvez de Objectse planeja não usar get(). A load()função não recupera o objeto, apenas obtém o resumo.


1

Aqui está uma solução que funciona para mim. Uma ressalva é que eu sei o formato exato da chave antes do tempo, portanto, estou listando apenas o único arquivo

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

você pode usar o Boto3 para isso.

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

Aqui, a chave é o caminho que você deseja verificar se existe ou não


A partir de um simples %timeitteste esta parece ser a opção mais rápida
Itamar Katz

1

É realmente simples com o 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')) 

Não robusta, a exceção poderia ser lançada por vários motivos, por exemplo, HTTP 500 e esse código assumiria um 404.
mickzer 07/01

Mas precisamos de informações sobre se o arquivo está acessível ou não. Ele existe e não pode ser acessível, então é equivalente a não existir. certo?
isambitd 7/01

@mickzer verifique as alterações agora.
isambitd 10/01

1
Para responder ao seu comentário anterior, Não, o comportamento em um HTTP 500 pode ser tentar novamente, um 401/403 para corrigir a autenticação etc. É importante verificar o código de erro real.
mickzer 13/01

0

Existe uma maneira simples de verificar se o arquivo existe ou não no bucket do S3. Não precisamos usar exceção para isso

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

Isso estará incorreto se um arquivo iniciado com object_nameexistir no intervalo. Por exemplo my_file.txt.oldversion, retornará um falso positivo se você verificar my_file.txt. Um pouco de argumento para a maioria, mas para algo tão amplo quanto "existe o arquivo" que você provavelmente usará em todo o aplicativo, provavelmente vale a pena levar em consideração.
Andrew Schwartz

0

Se você procurar uma chave equivalente a um diretório, poderá querer essa abordagem

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

Isso funciona para uma chave pai ou uma chave que equivale a arquivo ou uma chave que não existe. Tentei a abordagem preferida acima e falhei nas chaves dos pais.


0

Notei que apenas para capturar a exceção usando botocore.exceptions.ClientErrorprecisamos instalar o botocore. O botocore ocupa 36M de espaço em disco. Isso é particularmente impactante se usarmos as funções aws lambda. Em vez disso, se usarmos exceção, podemos pular usando a biblioteca extra!

  • Estou validando para que a extensão do arquivo seja '.csv'
  • Isso não emitirá uma exceção se o bucket não existir!
  • Isso não emitirá uma exceção se o bucket existir, mas o objeto não existir!
  • Isso gera uma exceção se o balde estiver vazio!
  • Isso lança uma exceção se o bucket não tiver permissões!

O código fica assim. Por favor, compartilhe seus pensamentos:

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)

A AWS diz que os tempos de execução do python vêm com o boto3 pré-instalado: docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html
rinat.io

0

Apenas seguindo o thread, alguém pode concluir qual é a maneira mais eficiente de verificar se existe um objeto no S3?

Eu acho que o head_object pode ganhar, pois apenas verifica os metadados que são mais leves que o próprio objeto real



-1

Verificação de saída

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

Verifique se existe uma chave específica no balde. Este método usa uma solicitação HEAD para verificar a existência da chave. Retorna: uma instância de um objeto Key ou None

de Boto S3 Docs

Você pode simplesmente chamar bucket.get_key (keyname) e verificar se o objeto retornado é None.


Isso não funciona com o boto3, conforme solicitado pelo OP #
MarkNS

Existem duas versões da biblioteca boto da AWS. Esta resposta não funciona com a versão solicitada pela pergunta.
MarkNS

Certamente não é uma resposta correta para o OP, mas isso me ajuda porque eu preciso usar o boto v2. Foi por isso que removi um voto negativo.
haͣrͬukaͣreͤrͬu
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.