前言

数据无价,备份无忧。

将其他平台数据备份到不同平台的好处:

  • 增强数据安全性:通过定期备份到GitHub,即使又拍云服务出现问题,你的数据仍然安全。这为你的数据提供了额外的一层保护,尤其是使用国内免费服务的用户。
  • 便于版本管理:GitHub支持Git版本控制系统,这意味着你可以轻松追踪数据的变化历史,这对于需要保存多个版本的数据集或文档非常有用。
  • 自动化操作:使用GitHub Actions,你可以设置自动化的工作流来定期执行备份脚本。这不仅减少了手动操作的需求,还降低了人为错误的风险。
  • 成本效益:对于小规模项目或个人开发者来说,使用GitHub作为备份存储可能比租用额外的云存储空间更经济。特别是当使用GitHub的免费层级时。
  • 易于访问和分享:GitHub上的数据可以通过简单的链接分享给他人,这对于公开数据集或开源项目尤其有益。

预览

又拍云目录: 20241005111856740

github目录: 20241005111856741

操作示例

1. 新增 Workflow YML 文件

upyun_images_sync/.github/workflows/sync-images.yml

name: Sync Images from UpYun to GitHub

on:
  schedule:
    - cron: '0 0 * * 3'  # 每周三凌晨 0 点运行
  workflow_dispatch:  # 允许手动触发

jobs:
  sync-images:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      uses: actions/checkout@v2

    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.x'

    - name: Install dependencies
      run: |
        pip install gitpython

    - name: Run sync script
      env:
        UPYUN_FTP_HOST: $
        UPYUN_FTP_USER: $
        UPYUN_FTP_PASSWORD: $
        UPYUN_FTP_PATH: $
        LOCAL_DOWNLOAD_PATH: ./tmp
        REPO_PATH: ./repo
        GH_TOKEN: $
        REPO_OWNER: $
        REPO_NAME: $
        BRANCH_NAME: $
      run: |
        python3 sync_images.py

2. 新建 Python 同步脚本

upyun_images_sync/sync_images.py

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

import os
from ftplib import FTP
import git
import shutil

class MyFTP:
    def __init__(self, host, port=21):
        self.host = host
        self.port = port
        self.ftp = FTP()
        self.ftp.encoding = 'utf8'
        self.file_list = []

    def login(self, username, password):
        try:
            self.ftp.connect(self.host, self.port)
            self.ftp.login(username, password)
            print(f"成功登录到 {self.host}")
            print(f"当前工作目录: {self.ftp.pwd()}")
        except Exception as err:
            print(f"FTP 连接或登录失败,错误描述为:{err}")
            sys.exit(1)

    def download_file(self, local_file, remote_file):
        if os.path.exists(local_file):
            print(f"{local_file} 已存在,跳过下载")
            return
        try:
            print(f"下载文件 {remote_file} 到 {local_file}")
            buf_size = 1024
            with open(local_file, 'wb') as file_handler:
                self.ftp.retrbinary(f'RETR {remote_file}', file_handler.write, buf_size)
        except Exception as err:
            print(f"下载文件出错,出现异常:{err}")

    def download_file_tree(self, local_path, remote_path):
        try:
            print(f"尝试切换到远程目录: {remote_path}")
            self.ftp.cwd(remote_path)
            print(f"成功切换到远程目录: {remote_path}")
        except Exception as err:
            print(f"远程目录 {remote_path} 不存在,继续... 具体错误描述为:{err}")
            return

        if not os.path.isdir(local_path):
            os.makedirs(local_path)
            print(f"创建本地目录 {local_path}")

        print(f"当前工作目录: {self.ftp.pwd()}")

        # 列出目录内容
        remote_files = self.ftp.nlst()
        print(f"远程目录内容: {remote_files}")

        for remote_file in remote_files:
            local = os.path.join(local_path, remote_file)
            if remote_file in ['.', '..']:
                continue
            try:
                self.ftp.cwd(remote_file)
                print(f"下载目录:{remote_file}")
                self.download_file_tree(local, os.path.join(remote_path, remote_file))
                self.ftp.cwd("..")
            except Exception:
                print(f"下载文件:{remote_file}")
                self.download_file(local, os.path.join(remote_path, remote_file))

    def close(self):
        print("FTP退出")
        self.ftp.quit()

def sync_to_github(repo_path, branch_name):
    if os.path.exists(repo_path):
        shutil.rmtree(repo_path)
    os.makedirs(repo_path)

    repo_url = f"https://{GH_TOKEN}@github.com/{REPO_OWNER}/{REPO_NAME}.git"
    print(f"克隆仓库: {repo_url}")
    try:
        repo = git.Repo.clone_from(repo_url, repo_path, branch=branch_name)
        print(f"克隆仓库到 {repo_path}")
    except git.exc.GitCommandError as e:
        print(f"克隆仓库失败: {e}")
        return

    images_dir = os.path.join(repo_path, 'images')
    if not os.path.exists(images_dir):
        os.makedirs(images_dir)

    shutil.copytree(LOCAL_DOWNLOAD_PATH, images_dir, dirs_exist_ok=True)
    print(f"复制文件到 {images_dir}")

    repo.git.add(all=True)
    if repo.is_dirty():
        repo.index.commit("Sync images from UpYun")
        origin = repo.remote(name='origin')
        origin.push()
        print("推送更改到 GitHub 仓库")
    else:
        print("没有更改需要提交")

if __name__ == "__main__":
    # 从环境变量中读取配置
    UPYUN_FTP_HOST = os.getenv('UPYUN_FTP_HOST')
    UPYUN_FTP_USER = os.getenv('UPYUN_FTP_USER')
    UPYUN_FTP_PASSWORD = os.getenv('UPYUN_FTP_PASSWORD')
    UPYUN_FTP_PATH = os.getenv('UPYUN_FTP_PATH', '/')
    LOCAL_DOWNLOAD_PATH = os.getenv('LOCAL_DOWNLOAD_PATH', './tmp')
    REPO_PATH = os.getenv('REPO_PATH', './repo')
    GH_TOKEN = os.getenv('GH_TOKEN')
    REPO_OWNER = os.getenv('REPO_OWNER')
    REPO_NAME = os.getenv('REPO_NAME')
    BRANCH_NAME = os.getenv('BRANCH_NAME', 'master')

    # 创建 FTP 对象
    my_ftp = MyFTP(UPYUN_FTP_HOST)
    # 登录 FTP 服务器
    my_ftp.login(UPYUN_FTP_USER, UPYUN_FTP_PASSWORD)
    
    # 下载目录
    my_ftp.download_file_tree(LOCAL_DOWNLOAD_PATH, UPYUN_FTP_PATH)
    
    # 关闭 FTP 连接
    my_ftp.close()

    # 同步到 GitHub
    sync_to_github(REPO_PATH, BRANCH_NAME)

3. 授予 Workflow 读写权限

将本地的项目提交到github,然后设置Workflow 读写权限

20241005111856742

4. 新增 Secrets 变量

在github上设置Secrets 变量

20241005111856743

序号 变量名 释义
1 UPYUN_FTP_HOST 又拍云 FTP 主机地址
2 UPYUN_FTP_USER 又拍云 FTP 用户名
3 UPYUN_FTP_PASSWORD 又拍云 FTP 密码
4 UPYUN_FTP_PATH 又拍云 FTP 路径
5 GH_TOKEN GitHub 个人访问令牌
6 REPO_OWNER 仓库所有者
7 REPO_NAME 仓库名称
8 BRANCH_NAME 分支名称(默认为 master)

变量详解:

  1. UPYUN_FTP_HOST:参考又拍云文档,直接使用v0.ftp.upyun.com就行了
  2. UPYUN_FTP_USER:格式为 operator/bucket,在又拍云 - 云存储 - 选择对应的bucket点击配置 - 存储管理 - 操作员授权 - 自己添加操作用户名和密码,假设你的又拍云云储存bucket的名称为upai-img,自定义的用户名为user,密码为123456,则UPYUN_FTP_USER就是 user/upai-img,如果你看不懂我说的,参考 又拍云视频教程
  3. UPYUN_FTP_PASSWORD:就是上面你自己设置的 123456图片示例
  4. UPYUN_FTP_PATH:直接使用 / (根目录)就行了
  5. GH_TOKEN:选择github的 Settings - 点击右侧列表最下面的 Developer Settings - Personal access tokens - tokens (classic) - Generated new token (classic),Note名字可以随意,Expiration时间选择 no Expiration,下面权限全选了,点击Generated token 就生成了想要的token,记得保存,他只显示一次,示例:abc_123456789pUS123454321WmJkE987654321图片示例
  6. REPO_OWNER:就是你的github的用户名,我在github给这个项目创建仓库为https://github.com/gorpeln/upyun_images_sync,示例:gorpeln
  7. REPO_NAME:仓库链接后面的就是仓库名,示例:upyun_images_sync图片示例
  8. BRANCH_NAME:项目分支名,一般为master/main,示例:master