data engineering

download s3 folder recursively

qkqhxla1 2019. 2. 14. 19:17

현재 s3구조다.


python boto3로 디렉터리를 다운받는 코드를 짰다.


https://stackoverflow.com/questions/8659382/downloading-an-entire-s3-bucket 를 보면 콘솔로 자동으로 다운로드하는 방법이 나와있다. 구조가 위처럼 좀 복잡하고, 파일 하나당 크기가 크고, 하나 다운받고 하나 처리하는 방식으로 갈거여서 (사실 boto3를 이용하는게 조금 더 많이 익숙해서, aws configure를 서버에 설정하기 싫어서, 다운로드시 어떻게 동작할지 테스트하기 귀찮아서..) 짰다. 


https://stackoverflow.com/questions/31918960/boto3-to-download-all-files-from-a-s3-bucket/31929277 에 보면 예시가 잘 나와있다. 

아래에 좋은 지식적 답변도 하나있는데, s3에는 폴더라는게 없다고 한다. 파일 하나의 구조인데 파일 이름이 /로 구분되어서 폴더처럼 보이는거라고 한다. s3에서 보여줄때도 폴더처럼 보여주는게 더 좋아서 그렇게 보여주는듯싶고.


기본적으로 s3에서 폴더를 다운받으려면 아래 메소드를 쓴다.

s3 = boto3.client('s3', 
                  aws_access_key_id=access_key,
                  aws_secret_access_key=secret_access_key
                 )
s3.download_file(bucket_name, _from, _to)
# _from은 위 s3사진의 구조로 예시를 들면 '폴더구조1/폴더구조2/폴더구조3/_SUCCESS',
# _to는 내 로컬에서 저장할 위치이다.

그리고 s3에서 디렉터리 리스팅을 하려면 아래와 같다. 

s3.get_paginator('list_objects').paginate(Bucket=self.bucket_name, 
Delimiter='/', Prefix='s3에서 시작할 파일 위치')
# 나는 폴더구조2에서 시작할거였으므로 '폴더구조1/폴더구조2/' 를 입력해줬다.
# Prefix작성시 끝에 /붙이는걸 잊지말자.

for dir_list in self.s3.get_paginator('list_objects').paginate(Bucket=
self.bucket_name, Delimiter='/', Prefix=s3_dir_path):
            print dir_list

디렉터리 리스팅을 하면 PageIterator 객체가 리턴되는데 결과를 보려면 하나하나 빼서 봐야 한다. dir_list를 출력해보면 사전이 하나 나오는데, 여기서 'CommonPrefixes'키가 있으면 디렉터리 안에 또다른 디렉터리가 있는거고, 'Contents'키가 있으면 디렉터리가 아니라 파일이라는 뜻인것 같다.(내가 테스트해본 결과)


내 구조는 폴더 안에 폴더가 있고, leaf에는 폴더가 없고 파일만 있다. 위의 특성을 이용하여 아래와 같이 짰다.


# -*- coding: utf-8 -*-
import boto3
import os

class AwsS3Manager:
    def __init__(self):
        self.access_key = '엑세스 키'
        self.secret_access_key = '시크릿 엑세스 키'
        self.s3 = boto3.client('s3',
                               aws_access_key_id=self.access_key,
                               aws_secret_access_key=self.secret_access_key
                               )

        self.bucket_name = '버킷 네임'
        
    
    def make_dir(self, path):
        if not os.path.exists(path):
            os.mkdir('./' + path)

    def download_file_from_s3(self, _from, _to):
        print "download file from s3 '{}' to local '{}'".format(_from, _to)
        if not os.path.exists(_to):
            self.s3.download_file(self.bucket_name, _from, _to)
    
    def download_folder_from_s3_recursively(self, s3_dir_path):
        for dir_list in self.s3.get_paginator('list_objects').paginate(Bucket=self.bucket_name, Delimiter='/', Prefix=s3_dir_path):  # s3_dir_path에서 폴더나 파일을 찾는다.
            if 'CommonPrefixes' in dir_list:  # 폴더가있으면..
                for i, each_dir in enumerate(dir_list['CommonPrefixes']):  # 폴더를 iteration한다.
                    print 'start dir {}/{} ing..'.format(i+1, len(dir_list['CommonPrefixes']))
                    # print 'dir =',each_dir['Prefix']
                    self.download_folder_from_s3_recursively(each_dir['Prefix'])  # 폴더들의 Prefix를 이용하여 다시 recursive하게 함수를 호출한다.
            if 'Contents' in dir_list:  # 폴더가 아니라 파일이 있다.
                for i, each_file in enumerate(dir_list['Contents']):  # 파일을 iteration한다.
                    if i % 50 == 0:
                        print 'download file {}/{} ing..'.format(i+1, len(dir_list['Contents']))
                    myfolder, filename = each_file['Key'].split('/')[-2:]  # 내 로컬에 저장할 폴더 이름은 s3의 폴더 이름과 같게 한다. 파일 이름도 그대로.
                    myfolder = './' + myfolder
                    self.make_dir(myfolder)
                    self.download_file_from_s3(each_file['Key'], myfolder + '/' + filename)  # 저장한다...

awss3manager = AwsS3Manager()
awss3manager.download_folder_from_s3_recursively('폴더구조1/폴더구조2/')