1125 日 , 2025 14:56:10
把图片存储从r2搬回vps

香港灵车vps意外的坚挺,速度(不抽风的时候)比cloudflare r2快多了,于是把图片搬回vps上。攒了这么多年不过才200多mb而已,确实没有必要用图床。

官网没有提供打包下载的功能,只好写脚本用s3 api解决了。代码by gemini:

import boto3
import os
import logging
from botocore.exceptions import ClientError

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

def download_s3_bucket(bucket_name, local_base_dir="downloaded_s3_objects"):
    """
    下载指定 S3 存储桶中的所有对象到本地目录。
    本地目录结构将与 S3 存储桶中的结构一致。

    Args:
        bucket_name (str): 要下载的 S3 存储桶的名称。
        local_base_dir (str): 本地保存下载对象的根目录。
                              默认情况下,会在这个目录下创建以 bucket_name 命名的子目录。
    """
    try:
        # 创建 S3 资源对象
        s3 = boto3.resource('s3',endpoint_url=CUSTOM_ENDPOINT_URL,aws_access_key_id=S3_UPLOADS_KEY,aws_secret_access_key=S3_UPLOADS_SECRET)
        bucket = s3.Bucket(bucket_name)

        # 构建本地下载目录
        local_download_dir = os.path.join(local_base_dir, bucket_name)
        os.makedirs(local_download_dir, exist_ok=True)
        logger.info(f"将下载 S3 存储桶 '{bucket_name}' 到本地目录: '{local_download_dir}'")

        downloaded_count = 0
        skipped_count = 0
        error_count = 0

        # 遍历存储桶中的所有对象
        for obj in bucket.objects.all():
            s3_key = obj.key  # S3 对象的完整路径,例如 'folder/subfolder/file.txt'

            # 构建本地文件的完整路径
            # 例如:'downloaded_s3_objects/my-bucket/folder/subfolder/file.txt'
            local_file_path = os.path.join(local_download_dir, s3_key)

            # 确保本地目录结构与 S3 保持一致
            # 如果 S3 对象在某个子目录中,需要先创建这些本地子目录
            directory = os.path.dirname(local_file_path)
            if not os.path.exists(directory):
                os.makedirs(directory, exist_ok=True)
                logger.debug(f"创建本地目录: {directory}")

            # 检查文件是否已存在,并跳过或覆盖
            # 如果你想要增量下载(只下载新文件或更新文件),可以在这里添加逻辑
            # 例如,比较文件大小或ETag,这里默认会覆盖
            if os.path.exists(local_file_path):
                logger.debug(f"文件 '{local_file_path}' 已存在,将覆盖。")
                # 或者选择跳过:
                # logger.info(f"文件 '{local_file_path}' 已存在,跳过。")
                # skipped_count += 1
                # continue

            try:
                # 下载文件
                # S3Resource.Object.download_file 默认会处理多部分下载等优化
                bucket.download_file(s3_key, local_file_path)
                downloaded_count += 1
                logger.info(f"成功下载: s3://{bucket_name}/{s3_key} -> {local_file_path}")
            except ClientError as e:
                error_count += 1
                if e.response['Error']['Code'] == '404':
                    logger.warning(f"S3对象 '{s3_key}' 未找到 (404)。可能已被删除或权限问题。")
                else:
                    logger.error(f"下载 '{s3_key}' 时发生 AWS 客户端错误: {e}")
            except Exception as e:
                error_count += 1
                logger.error(f"下载 '{s3_key}' 时发生未知错误: {e}")

        logger.info("-" * 50)
        logger.info(f"下载完成概要:")
        logger.info(f"总计下载文件数: {downloaded_count}")
        logger.info(f"总计跳过文件数: {skipped_count}")
        logger.info(f"总计错误文件数: {error_count}")
        logger.info("-" * 50)

    except ClientError as e:
        logger.error(f"AWS 客户端错误: {e}")
        logger.error("请检查存储桶名称、区域以及 AWS 凭证和权限。")
    except Exception as e:
        logger.error(f"发生未知错误: {e}")

if __name__ == "__main__":
    # --- 配置你的存储桶名称 ---
    TARGET_BUCKET_NAME = "blog-pics"  # <-- 将此替换为你的 S3 存储桶名称
    CUSTOM_ENDPOINT_URL = ''
    S3_UPLOADS_KEY = ''
    S3_UPLOADS_SECRET = ''
    
    download_s3_bucket(TARGET_BUCKET_NAME)
暂无评论

发送评论 编辑评论