我喜欢收集插图。

小的时候,我会把卡通图画剪下来贴进本子里,做成小手账;再大些有了零花钱,我便攒着买来一本本畅销的画册;等到有了智能手机、接触到广阔的互联网世界,可收藏的东西一下子变得多了起来:从同人插图到概念设定,从商业插画到独立艺术家作品……我乐此不疲地保存这些图片,一张又一张,一个文件夹接着一个文件夹。

这大概是我坚持最久、也最投入的一个爱好。

年轻时的XP
年轻时的XP

可随着时间的推移,收集的作品越来越多,面对电脑中杂乱无章的图库,我才意识到一个问题:该如何为这些收藏进行分类?

分类

相比影音或图书领域有成熟的管理程序和外部数据库可供整理,电子图片往往只是作者在插画网站上的一次更新,或社交平台上的一条动态,本身并没有过于明确的分类依据。我曾考虑使用标签的方式分类,但这么做大概需要一个形似 Yande.re 的图片程序记录标签,并在收集的时候为每张图片打上标签。新增图片尚可如此处理,存量图片就要麻烦得多——以我的技术能力还无法做到自动为每张图片打上标签。而网上搜索到的图片整理软件大多面向专业设计师,对于个人的插画收集而言,又有些大材小用。

况且,在使用 fnOS 相册、immich 这些带 AI 索引功能的相册程序之后,标签可能就没有存在的必要了。想要什么风格(标签)的图片,只需几个形容词简单描述,就可以快速检索到位。

飞牛相册的AI搜索功能
飞牛相册的AI搜索功能

因此,与其折腾复杂的分类系统,不如把精力放在图片本身的保存上。我决定只做最基本的「来源 + 作者」分类:

  1. 从 Pixiv 上下载的图片可以轻松找到作者,因此按作者细分;
  2. Yande、Konachan、Danbooru 等类 Danbooru 图站的图片均为爱好者上传自互联网,可能源自 Pixiv1,也可能是 Twitter,甚至可能是未在网络上公开过的实体画集扫图。没有具体的作者,直接保存在来源文件夹;
  3. Twitter 上右键另存为的图片寻找作者亦较为繁琐,也同 Yande 一样处理。

可是,在准备整理这些图片时,眼前的问题又让我犯了难。

整理

由于没有电脑,最初的收集工作是在手机上完成的,自然没有、也不可能意识到未来会有分类的需求。所有图片都通过 Pixiv APP 保存在固定的文件夹里,再由相册的云服务功能同步到云端。这样做固然方便,更换手机也不怕图片丢失,但缺点同样明显:一旦保存时的命名格式发生变化,就很容易重复保存同一张图片。

例如,Pixiv 官方 APP 当前保存图片的命名格式是 illust_id_save_data.jpg,会生成诸如 illust_112251893_20251123_202020.jpg 这种带没有必要的 illust 前缀和具体下载日期的又臭又长的非常不 Elegant 的文件名;第三方 APP 则可以自行设置不同的命名格式,比如 112251893_p0.jpg 。当同一作品在不同时间、以不同文件名被保存时,就难以仅凭手机相册的时间轴判断是否重复收藏,当时的相册 APP 也缺乏扫描重复图片的功能。

▼ 不同时期的文件命名格式不同,我也是近些年才有所规范。

此外,早期的 Pixiv 还能下载到作者上传的未经去除 Exif 信息的原始图片,且图片格式可能是 jpgpngbmp 2,导致获取的同一作品甚至连文件体积也可能不一致。

所以,我决定先从「去重」这一步入手。针对完全一致的 MD5、相同的文件名,以及相近的文件大小,可以利用重复文件清理工具「Duplicate Cleaner」快速清理掉这部分特征明显的图片。而内容完全一致、只有文件格式或体积有区别的图片,可以在后续通过 immich 的去重功能筛选出来。

利用工具快速去重
利用工具快速去重

移动」方面,由于几乎所有的 Pixiv 图片都包含一串作品 ID,处理起来相对方便。我的思路是利用 PID 拼接链接访问作品页面、解析作者的 UID 后,按 UID 建立作者文件夹移动图片。我把这份工作交给了 AI,编写了脚本批量移动。

▼ 其实最初我是按 UID(NAME) 的格式命名作者文件夹的,但总有作者爱改昵称,于是我又用 AI 糊了个脚本,把文件夹中昵称的部分统统删除了。

至于 PID 异常或是已被删除无法解析链接的作品,可以通过 SauceNao 溯源,再移动到对应文件夹。

最终,仅是 Pixiv 3就整理出了 12172 张(可能)4不重复的图片,这些图片来自 2750 位(可能)5不同的插画师。

总计约30GB
总计约30GB

至此,对存量图片的处理工作算是告一段落。接下来就可以把整理后的图片上传 NAS,并将其挂载到 immich 作为外部图库以便随时浏览和检索。

备份

我的计划是,既然收图的重心正逐渐从手机转移到电脑,而电脑上又有各种现成的工具可以快速从 Pixiv 下载图片(例如:PixivBatchDownloader),那不妨就直接利用这些工具,按自己喜欢的规则生成文件夹和文件名。这样浏览插图时既可以顺手下载,又能通过工具保存下载记录,有效避免了重复下载。

接着,我只需把插图保存在 OneDrive 的同步目录里,就能自动同步到云端,再配置好群晖 DSM 的「CloudSync」套件,就能定时将插图从 OneDrive 拉取更新到 NAS。这样一来,我不仅可以随时在电脑上下载新的插图,还能通过 immich 集中统一管理,可谓一举多得。

唯一的问题是,CloudSync 偶尔会抽风。有时无法及时从云端拉取文件,有时又把我从 immich 中删除的图片同步回来……这些都还算能接受,直到后来出了点小意外。

意外

2025 年初,闲来无事的我准备清理一下 DSM 中无用的数据,决定卸载一个用不到的第三方社群的 Python 套件,并在卸载时并勾选了「清理数据」。万万没想到,这个套件的数据路径竟指向了 NAS 的磁盘根目录,于是它在清理自身残留文件的同时,也顺便删掉了目录下其他的文件。

等我意识到不对劲紧急关机,满满 14T 硬盘的收藏已经被删得只剩下一半。

好在丢的都是些不算特别重要的影视剧和动画,有空再下回来就是。真正让我恼火的是不知何时还会出岔子的 DSM,尽管责任在我,是我自己安装非官方认证的第三方套件导致的。

我本就不太喜欢 DSM,既然事情已经发展到这一步,索性连同 NAS 系统一并更换,试试近期大火的 fnOS。

只是,此时的 fnOS 尚处在测试阶段,还未开发类似 CloudSync 的同步工具,无法直接从 Onedrive 同步文件,原本依赖 OneDrive 的备份方案就这样中道崩殂,插图收藏也因此停滞。我只得另寻其他既能兼顾下载、又能便于同步收藏的方法。

工具

归根结底,我的需求无非就是先获取插图再保存到 NAS,仅此而已。既然终点是 NAS,为何不跳过这些弯弯绕绕,直接一步到位?在互联网上寻找了一番,我决定使用 Nazurin 实现这一思路。

Nazurin 是一个基于 Telegram 的图片收藏工具,支持从 各种网站 浏览和下载图片。在配置好 Telegram bot 和目标 存储源 后,只需给 TG 机器人发送图片源链接,便可直接将图片保存至目的地。

其实前面的内容都是为了引出 Nazurin 的废话,写着写着写跑题了……

使用

在以往的存图流程中,遇到需要保存的图片后,我必须:

浏览 图片 ➡️ 下载 图片 ➡️ 上传 NAS

中间省略了可能需要执行的分类和去重工作。

而在使用 Nazurin、将存储目的地直接设置为 NAS 本地后、并配置好各图片源的存储规则后,这个流程就被简化成了:

浏览 图片 ➡️ 分享(发送) 源链接至 TG 机器人

只需两步,图片便直接被保存到 NAS 中了,可谓一劳永逸!

而在换用 Nazurin 后,我仍可以在 PC 端保持原有的浏览习惯,像平常一样寻找插图,遇到喜欢的图片一键保存。仅仅只是「更换」了一个「下载」按钮:

不仅自动下载了图片,还为这张图片标记了 ❤
不仅自动下载了图片,还为这张图片标记了 ❤

也可以在移动设备上,将图片的源链接分享给 TG 机器人:

这或许相比直接使用 APP 一键保存略微曲折了些,但至少我不用担心图片的分类问题,也不用再烦恼如何把它们备份到 NAS 了。

按来源分类
按来源分类

按作者细分
按作者细分

另外,通过订阅各图站的 RSS 服务,我还可以在繁忙的时候第一时间在 TG 内获取时下热门图片,再择一收入囊中。代价是插图收集也因此失去了些许个性。

安装

虽然官方演示是搭建在 Fly.io 上,但我有 NAS,所以直接部署在本地就好了!

安装 Nazurin 非常简单,使用 docker compose 即可一键部署:

services:
  nazurin:
    image: yyoung01/nazurin:latest
    container_name: nazurin
    user: 1000:1001 # 实测需指定用户运行
    volumes:
      - ./data:/app/data
      - /vol1/1000/photos/Nazurin:/Nazurin # 替换为你的存储路径
    # ports:
    #   - 8080:8080
    environment:
     # ---------- 必填项 ----------
      - TOKEN=123456 # 替换为你的 Telegram 机器人 Token
      - ENV=development
     # - WEBHOOK_URL=http://127.0.0.1:8080
     # - HOST=0.0.0.0
     # - PORT=8080
      - STORAGE=Local,Telegram
      - DATABASE=Local
      - ADMIN_ID=123456 # 替换为你的 Telegram 用户 ID
      - ALBUM_ID=-10012345 # 替换为你的 Telegram 频道 ID
      - STORAGE_DIR=/Nazurin # 需与挂载的内部存储路径一致
     # ---------- 可选项 ----------
      - TZ=Asia/Shanghai
      - IS_PUBLIC=false
      - RETRIES=8
      - MAX_PARALLEL_DOWNLOAD=6
      - HTTP_PROXY=http://mihomo:7890 # 替换为你的代理地址
      - HTTPS_PROXY=http://mihomo:7890 # 替换为你的代理地址
      - CLEANUP_INTERVAL=7
      - FEEDBACK_TYPE=reply
      - BILIBILI_FILE_PATH=Bilibili/{user[mid]}
      - PIXIV_TOKEN=123456 # 替换为你的 Pixiv Token
      - PIXIV_TRANSLATION=zh-CN
      - PIXIV_FILE_PATH=Pixiv/{user[id]}
      - PIXIV_FILE_NAME={filename}
      - TWITTER_AUTH_TOKEN=123456 # 替换为你的 Bearer Token
      - TWITTER_FILE_PATH=Twitter/{user[screen_name]}
    restart: always

这里与官方提供的 docker-compose.yml 不同的是,官方指定了一个 env_file 配置,里面包含了完整的环境变量,即便绝大多数都可以保持默认,但数量仍多得吓人。所以我稍微精简了下,仅保留了自定义的部分。

在这个配置的环境变量中,可选项自定义的功能如下:

  • 同时存储于本地和 Telegram
  • 私有 bot
  • 最大并行下载数量为 6
  • 使用代理。官方文档只写了需要 HTTP_PROXY ,但其实 HTTPS_PROXY 也是必须的
  • 反馈方式设置为在原消息上添加表情回应
  • 可下载 Pixiv、Twitter、Bilibili 图片至指定作者文件夹

    • Pixiv 下载路径设置为 Pixiv/{user[id]},会在 Pixiv 文件夹内建立 作者 ID 文件夹;文件名格式设置为纯数字 ID
    • Twitter 下载路径设置为 Twitter/{user[screen_name]} ,会在 Twitter 文件夹内建立 作者的 @用户名 文件夹6,例如 https://x.com/xinzoruo 里的 xinzoruo
    • Bilibili 下载路径设置为 Bilibili/{user[mid]} ,会在 Bilibili 文件夹内建立作者的 uid 文件夹
  • 临时目录设置为 7 天后自动清理

不过即便如此精简,Nazurin 的配置过程仍较为繁琐。有需要可以具体参考下节的配置说明。

配置

这里详细说明几处需要额外注意的配置。

  • user: 实测需指定用户运行,懒得找可以直接使用 1000:1001,否则登录 SSH 后使用 id 获取你自己的 id

    $ id
    uid=1000(mikusa) gid=1001(Users) groups=1001(Users),994(docker),1000(Administrators)
  • TOKEN:机器人的 API 密钥,可从 @BotFather 获取
  • ADMIN_ID:管理员用户的 Telegram 用户 ID,可从 @userinfobot 获取
  • ALBUM_ID:用于存储图片的频道 ID,可通过网页 TG Web 获取,格式为 -100xxxxxx
  • Env:如果你需要在网页端快捷分享图片至 TG,需要将运行环境设置为 production ,即 Webhook 模式,并指定挂载端口,才能搭配官方的 Nazurin 浏览器扩展 一键分享。否则,使用 development 模式,让机器人轮询访问 TG 即可。
  • 关于 Webhook 模式,官方的说明是:

    发送到 Telegram 服务器的 Webhook URL,机器人的服务器应能通过此 URL 访问,应以 / 结尾,例如 https://xxx.fly.dev/

    也就是说,这个 URL 需要被公开,且必须能被 Telegram 服务器访问,才能正常使用

    这也意味着如果 Nazurin 是运行在家里的 NAS 上,为了正常使用官方的 浏览器扩展必须将 Nazurin 反代至公网

  • HTTP_PROXY:代理,需要一并填写 HTTPS_PROXY ,否则无法正常下载 Pixiv 图片
  • FEEDBACK_TYP:收藏图片成功后的反馈方式,可选值如下:

    • reply:回复原消息
    • reaction: 在原消息上添加表情回应
    • both:回复并添加表情回应

    回复原消息
    回复原消息

至于其余的配置,例如使用外部数据库、云端存储、自定义其他图站的保存路径,如果你有用到,就需要自行参考官方文档后,再额外添加了。

完整的环境变量配置如下,注释部分已使用 AI 进行翻译:

# 这是一个 .env 配置文件示例
# 更多信息请参阅:https://nazurin.readthedocs.io/getting-started/configuration/
# 修改数值并取消相应行的注释后,将文件重命名为 .env

# ---------- 必填项 ----------
# Telegram Bot 的令牌(token)
# TOKEN =

# 运行环境
# production: Webhook 模式;development: 轮询(Polling)模式
ENV = production

# Webhook 地址,例如:https://xxx.fly.dev/,必须以 '/' 结尾
# 使用 Webhook 模式时必填
# WEBHOOK_URL =

# 监听的主机地址,如果使用反向代理请设为 127.0.0.1
# 使用 Webhook 模式时必填
# HOST = 0.0.0.0

# 监听端口,如果部署在 Heroku 或 fly.io 上请注释掉本行
# 使用 Webhook 模式时必填
# PORT =

# 存储类型,用逗号分隔
STORAGE = Local

# 数据库类型
DATABASE = Local

# Telegram 图库频道 ID,可选
# GALLERY_ID =

# 管理员用户 ID
# ADMIN_ID =

# ---------- 可选项 ----------
# 存储目录路径
# STORAGE_DIR = Pictures

# 是否将此 Bot 设置为公开
# IS_PUBLIC = false

# 如果 IS_PUBLIC 为 True,则以下配置项将被忽略
# 允许的用户 ID(可多个)
# ALLOW_ID =

# 允许的用户名(可多个)
# ALLOW_USERNAME =

# 允许的群组 ID(可多个)
# ALLOW_GROUP =

# 重试次数
# RETRIES = 5

# 请求超时时间
# TIMEOUT = 20

# 下载文件时写入的分块大小(字节)
# DOWNLOAD_CHUNK_SIZE = 4096

# 最大并行下载数量
# MAX_PARALLEL_DOWNLOAD = 5

# 最大并行上传数量
# MAX_PARALLEL_UPLOAD = 5

# 网络请求的代理 URL,默认为系统环境设置
# HTTP_PROXY = http://127.0.0.1:7890

# 在图像说明(caption)中忽略的内容
# CAPTION_IGNORE =

# 临时目录清理间隔(天)
# CLEANUP_INTERVAL = 7

# 日志等级,参考:https://docs.python.org/3/howto/logging.html#logging-levels
# LOG_LEVEL = INFO

# 收藏图片成功后的反馈方式
# FEEDBACK_TYPE = reply

# ----- Google 服务 -----
# Firebase & Google Drive 的 API 凭证
# GOOGLE_APPLICATION_CREDENTIALS =

# ---------- 网站相关 ----------
# ----- Artstation -----
# 文件目录
# ARTSTATION_FILE_PATH = Artstation

# 文件名
# ARTSTATION_FILE_NAME = {title} ({hash_id}) - {filename}

# ----- Bilibili -----
# 文件目录
# BILIBILI_FILE_PATH = Bilibili

# 文件名
# BILIBILI_FILE_NAME = {id_str}_{index} - {user[name]}({user[mid]})

# ----- Bluesky -----
# 文件目录
# BLUESKY_FILE_PATH = Bluesky

# 文件名
# BLUESKY_FILE_NAME = {rkey}_{index} - {user[display_name]}({user[handle]})

# ----- Danbooru -----
# 文件目录
# DANBOORU_FILE_PATH = Danbooru

# 文件名
# DANBOORU_FILE_NAME = {id} - {filename}

# ----- DeviantArt -----
# 文件目录
# DEVIANT_ART_FILE_PATH = DeviantArt

# 文件名
# DEVIANT_ART_FILE_NAME = {title} - {deviationId}

# 下载文件的命名
# DEVIANT_ART_DOWNLOAD_NAME = {title} - {deviationId} - {prettyName}

# ----- Gelbooru -----
# 文件目录
# GELBOORU_FILE_PATH = Gelbooru

# 文件名
# GELBOORU_FILE_NAME = {id}

# ----- Kemono -----
# 文件目录
# KEMONO_FILE_PATH = Kemono

# 文件名
# KEMONO_FILE_NAME = {pretty_name}

# ----- Lofter -----
# 文件目录
# LOFTER_FILE_PATH = Lofter

# 文件名
# LOFTER_FILE_NAME = {id}_{index} - {nickName}({blogName})

# ----- Moebooru -----
# 文件目录
# {site_name} -> Yandere,{site_url} -> 'yande.re'
# MOEBOORU_FILE_PATH = {site_name}

# 文件名
# MOEBOORU_FILE_NAME = {filename}

# ----- Pixiv -----
# Refresh Token
# PIXIV_TOKEN =

# 图片镜像,可选
# PIXIV_MIRROR = i.pximg.net

# 标签翻译,可选
# PIXIV_TRANSLATION =

# 收藏隐私(public/private),可选
# PIXIV_BOOKMARK_PRIVACY = public

# 文件目录
# PIXIV_FILE_PATH = Pixiv

# 文件名
# PIXIV_FILE_NAME = {filename} - {title} - {user[name]}({user[id]})

# ----- Twitter -----
# API 选择,可选
# TWITTER_API = web

# Web API 的 Auth Token,可选
# TWITTER_AUTH_TOKEN =

# 文件目录
# TWITTER_FILE_PATH = Twitter

# 文件名
# TWITTER_FILE_NAME = {id_str}_{index} - {user[name]}({user[id_str]})

# ----- Wallhaven -----
# API Key,可选
# WALLHAVEN_API_KEY =

# 文件目录
# WALLHAVEN_FILE_PATH = Wallhaven

# 文件名
# WALLHAVEN_FILE_NAME = {id}

# ----- Weibo -----
# 文件目录
# WEIBO_FILE_PATH = Weibo

# 文件名
# WEIBO_FILE_NAME = {mid}_{index} - {user[screen_name]}({user[id]})

# ----- Zerochan -----
# 文件目录
# ZEROCHAN_FILE_PATH = Zerochan

# 文件名
# ZEROCHAN_FILE_NAME = {id} - {name}

# ---------- 数据库 ----------
# ----- MongoDB -----
# MONGO_URI = mongodb://localhost:27017/nazurin

# ----- Cloudant -----
# CLOUDANT_USER =
# CLOUDANT_APIKEY =
# CLOUDANT_DB = nazurin

# ---------- 存储 ----------
# ----- Telegram -----
# 图库频道 ID
# ALBUM_ID =

# ----- MEGA -----
# MEGA_USER =
# MEGA_PASS =

# ----- Google Drive -----
# 文件夹 ID
# GD_FOLDER =

# ----- OneDrive -----
# 应用(客户端)ID
# OD_CLIENT =

# Refresh Token
# OD_RF_TOKEN =

# 客户端密钥
# OD_SECRET =

# ----- S3 -----
# 终端节点(Endpoint)
# S3_ENDPOINT = s3.amazonaws.com

# Access Key
# S3_ACCESS_KEY =

# Secret Key
# S3_SECRET_KEY =

# 是否使用 SSL
# S3_SECURE = True

# 区域
# S3_REGION =

# Bucket 名称
# S3_BUCKET = nazurin

其他

到这里,这篇文章就算彻底结束了。本来只是想简单写写 Nazurin 的用法,结果在开头卡了很久……于是就写成这样了。

总之,如果你同我一样有收集插图的爱好,且拥有一台 NAS,或是一台小服务器,希望能随时随地地保存那些美好的图片,那么 Nazurin 会是个很不错的小助手。

参考


  1. 如果是源自 Pixiv 的图片,我也会优先从 Pixiv 下载,除非这些图片已经被作者从 Pixiv 上删除了。所以 Yande 上下载的图片一般不会出现与 Pixiv 重复的情况。
  2. bmp 格式不确定是不是 Pixiv 自带的,只是我整理时发现有保存这个格式的图片。
  3. Pixiv 以外的图片,由于下载时有注意区分,几乎不用处理。
  4. 毕竟数量巨大,难免会有漏网之鱼,还需未来进一步整理。
  5. 存在有些画师账号被封另起炉灶、或是起了个小号偷偷画涩图的情况。
  6. 理论上,将 Twitter 的作者文件夹设置为 id_str 最为稳妥,但这会生成一条无法直接与 https://x.com/ 拼接的数字,例如 1507705239507329034。除非使用一些转换 userid 为 user name 的工具,例如 这个 ,否则无法直接找到作者。因此还是使用 screen_name 最为稳妥。赌的就是作者不会轻易更换这个 id