最近真的没怎么发文章,不是太懒更啊,而是有自己的事情要去做。这不,又写了一个小软件,用来备份 Waline 数据的。你可能会以为涉及到了数据库操作之类的,实际上并没有那么复杂。Waline 前端提供了一键导出的功能,直接就是完整的 JSON 数据文件。你当然不需要准备一个云盘或者一个独立的服务器,因为这套脚本只需要运行在 GitHub 仓库即可。我为其添加了 Actions 定时支持和详细的 README,你完全可以按照那套 README 里面的去做。这篇文章主要在代码展示以及技术讲解方面,教程是会包含一些的,以下是代码部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 import argparseimport osimport sysfrom datetime import datefrom pathlib import Pathfrom urllib.parse import urlparsefrom playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutdef parse_args () -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument("--output-dir" , "-o" , type =Path, default=Path.cwd()) return parser.parse_args() def parse_reset_urls (raw: str ) -> list [str ]: urls = [] for line in raw.replace("," , "\n" ).split("\n" ): url = line.strip().rstrip("," ).strip() if url: parsed = urlparse(url) if parsed.scheme and parsed.netloc: urls.append(url) return urls def extract_domain (url: str ) -> str : parsed = urlparse(url) hostname = parsed.hostname or "unknown" parts = hostname.split("." ) if len (parts) >= 2 : return "-" .join(parts[-2 :]) return hostname.replace("." , "-" ) def generate_filename (domain_tag: str , index: int , total: int ) -> str : today = date.today().isoformat() if total > 1 : return f"waline-backup-{today} -{domain_tag} -{index} .json" return f"waline-backup-{today} -{domain_tag} .json" def main () -> None : args = parse_args() output_dir = args.output_dir output_dir.mkdir(parents=True , exist_ok=True ) raw_urls = os.environ.get("WALINE_RESET_URLS" , "" ).strip() if not raw_urls: print ("错误: 未设置 WALINE_RESET_URLS" , file=sys.stderr) sys.exit(1 ) urls = parse_reset_urls(raw_urls) if not urls: print ("错误: 未解析出有效的重置链接" , file=sys.stderr) sys.exit(1 ) ok = True with sync_playwright() as p: browser = p.chromium.launch(headless=True ) context = browser.new_context() page = context.new_page() for i, url in enumerate (urls, 1 ): domain_tag = extract_domain(url) output_path = output_dir / generate_filename(domain_tag, i, len (urls)) page.goto(url, wait_until="domcontentloaded" ) page.wait_for_timeout(3_000 ) parsed = urlparse(url) base = f"{parsed.scheme} ://{parsed.netloc} " page.goto(f"{base} /ui/migration" , wait_until="domcontentloaded" ) page.wait_for_timeout(2_000 ) export_btn = page.locator( 'button.btn:has-text("导出"), ' 'button.btn:has-text("Export")' ) try : export_btn.wait_for(timeout=10_000 ) except PlaywrightTimeout: ok = False context.clear_cookies() continue with page.expect_download(timeout=120_000 ) as download_info: export_btn.click() download = download_info.value download.save_as(str (output_path)) if not output_path.exists() or output_path.stat().st_size == 0 : ok = False context.clear_cookies() browser.close() if ok: print ("备份成功" ) else : print ("备份失败" , file=sys.stderr) sys.exit(1 ) if __name__ == "__main__" : main()
辅助模型使用的是 DeepSeek,它真的很便宜。这套代码的运行逻辑是:第一步,通过你的重置链接访问后台;第二步,通过当前的登录状态跳转到备份页;第三步,导出备份 JSON 到指定位置。为什么需要重置链接?因为 Cookie 我这边折腾不了,新版本的 Waline 貌似是破坏性更新的,老版本的密码直接不能用了;我确实尝试过重置密码,但是下次登录还是用的 QQ 登录。重置链接算是官方提供的一个小门口,别泄露这东西给别人的就行了。我也不确定重置链接会不会失效,有没有保质期之类的。如果会的话,可能就需要定时换什么的。配套的还有一份 Actions,以下是代码部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 name: Waline Backup on: schedule: - cron: "0 2 * * *" workflow_dispatch: jobs: backup: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install dependencies run: | pip install playwright python -m playwright install chromium - name: Run backup env: WALINE_RESET_URLS: ${{ secrets.WALINE_RESET_URLS }} run: python waline_backup.py - name: Push backup to target repository env: BACKUP_REPO: ${{ secrets.WALINE_BACKUP_REPO }} GH_TOKEN: ${{ secrets.WALINE_BACKUP_TOKEN }} run: | CLONE_URL="https://x-access-token:${GH_TOKEN}@${BACKUP_REPO#https://}" git clone --depth=1 "$CLONE_URL" backup-repo cp waline-backup-*.json backup-repo/ cd backup-repo git config user.name "waline-backup-bot" git config user.email "waline-backup-bot@users.noreply.github.com" git add . git diff --cached --quiet || git commit -m "备份评论 $(date +%Y-%m-%d)" git push
值得一提的是,它使用的是仓库密钥,而不是硬编码到仓库的配置文件。仓库方面,你需要额外创建一个私密仓库,用于存储你的备份,这一点我相信大家清楚;还有一点是,千万要保管好这份文件,大家的命根子都在里面。可怕的不是泄露单独的邮箱、名字这些的,怕的是名字、邮箱、网站三个一起泄露,它几乎直接定了你是一个什么样的人。可是你会说,那没啥区别啊!我网站就是这样的,别人一来就能看到联系邮箱。那差别可就大的去了,来你网站看是合法渠道,而通过仓库去看是灰色渠道。并且,来看你网站的几乎是被你吸引过来的,对于他们来说你不是陌生人;但看你仓库的,就是纯纯的陌生人了。最后,附上三个密钥: