打造一条干净的 RSS 订阅流:RSSHub + 透明代理图床 + Reeder 体验优化,彻底搞定 RSSHub 防盗链与图片超时

截屏2026-08-08 09.32.11

自建 RSSHub 最膈应的痛点莫过于文章刷出来了,图片却遇到微信、知乎等平台的防盗链拦截,或是 Telegram 等海外 CDN 直连报错 504 Timeout。为了让图床稳定加载,同时不污染 NAS 宿主机的网络环境、不影响内网穿透与 PT 下载,本文记录了一套基于 Nginx (imgproxy) 伪装 Referer + tun2socks 伴生透明网关 (Sidecar) 的图床架构。不改动主路由、不开主机的全局代理,让图床容器独享 SOCKS5 转发链路,搭配最新订阅制 Reeder 客户端,实现全量 RSS 图文零裂图、内网级秒开的阅读体验。

由于现在的互联网环境高度封闭,用自建 RSSHub 抓取回来的正文虽然整洁,但图片通常会面临三重拦路虎:

  1. 防盗链拦截:微信公众号(qpic.cn)、知乎(zhimg.com)、微博(sinaimg.cn)、少数派(sspai.com)等平台,一旦检测到 HTTP 请求头里的 Referer 不来自自家主站,直接返回 403 或占位坏图。
  2. 海外被封阻图床:Telegram 频道(telesco.pe / t.me)、X/Twitter 等平台的 CDN,在国内直连直接报错超时。
  3. 连接池与请求速度:每次在客户端打开一篇文章,几十张图片并发发往不同的源站,极易触发反爬限制,且移动端蜂窝网络下的加载体验极差。

为了解决这三个问题,并在买断版本 Reeder 5中获得秒开无坏图的终极体验,一整套实践架构的思路由三部分串联而成:RSSHub 生产源内容 → Nginx (imgproxy) 解决防盗链与磁盘缓存 → tun2socks (Sidecar 网关) 打通海外封锁 → Reeder 稳定消费

整体网络架构与链路隔离

在 NAS 上搞容器代理,最忌讳就是直接把主机网络改得面目全非。QNAP NAS 上往往运行着 PT/BT 下载、cloudflared tunnel 内网穿透、本地虚拟交换机以及系统内置服务。如果直接在宿主机层面开启 Mihomo/Clash 的全局 TUN 增强模式,会出现明显的破坏性副作用:

  • cloudflared 的 UDP/QUIC 隧道会被强制截取或丢包,导致连接持续中断或断崖式降级。
  • PT 下载可能会误走海外节点,一方面耗尽机场流量,另一方面立马被机场规则封号。

所以,这套配置坚持使用 Sidecar(伴生容器)透明网络隔离:让 Nginx 图床服务直接寄生在一个极轻量的 tun2socks 容器的网络空间下。只有它自己抓图时才走代理,全机其余网络干净如初。

+-------------------------------------------------------------------------------+
| QNAP NAS (Host 宿主机)                                                        |
|   代理监听端口:Mihomo/Clash -> 172.29.8.1:7891 (SOCKS5/HTTP 混合)              |
|                                                                               |
|  +-------------------------------------------------------------------------+  |
|  | Container Station (Docker Compose)                                      |  |
|  |                                                                         |  |
|  |  +-------------------------------------------------------------------+  |  |
|  |  | tun2socks-gateway (网关容器, 对外映射 8888:80)                   |  |  |
|  |  |   [虚拟网卡 tun0] <-- SOCKS5 转发 --> 172.29.8.1:7891             |  |  |
|  |  |         ^                                                         |  |  |
|  |  |         | (共享同一个命名空间与虚拟网卡 network_mode: service:...)  |  |  |
|  |  |  imgproxy (Nginx 缓存与防盗链伪装)                                 |  |  |
|  |  +-------------------------------------------------------------------+  |  |
|  |                                                                         |  |
|  |  +-------------------------------------------------------------------+  |  |
|  |  | rsshub (独立的 RSS 源生产容器, 输出正文与图片链接)                  |  |  |
|  |  +-------------------------------------------------------------------+  |  |
|  +-------------------------------------------------------------------------+  |
+-------------------------------------------------------------------------------+
       ^
       | HTTP/HTTPS 请求
+-------------------------------------------------------------------------------+
| 终端客户端:Reeder 5                                        |
| 读取 RSSHub 源 -> 遇到图片标签访问 http://NAS_IP:8888/imgproxy?url=...        |
+-------------------------------------------------------------------------------+

第一模块:RSSHub 本地自建配置

RSSHub 作为最上流的源生产单元,在容器编排中保持独立即可,无需接入 SOCKS5 代理网关(如果有需要抓取被墙的 RSS 源,可通过 RSSHub 自身的 HTTP 代理环境变量单发代理)。

docker-compose.yml(RSSHub 单独服务段)

version: '3.8'

services:
  rsshub:
    image: diygod/rsshub:latest
    container_name: rsshub
    restart: always
    environment:
      - NODE_ENV=production
      - CACHE_TYPE=memory
      - CACHE_EXPIRE=3600
      - PUPPETEER_WS_ENDPOINT=
      # 如果想让 RSSHub 也能正常解析外网源,配置单独的代理即可:
      - PROXY_URI=http://172.29.8.1:7891
    ports:
      - "1200:1200"

第二模块:Nginx 缓存图床(imgproxy)的核心防盗链规则

这是整套系统处理图片最基础的第一道关:解决防盗链

大部分基于 Referer 的反防盗链逻辑都很简陋:服务器判断你请求图片时的 Referer 头是否符合自己主站域名。不符合就拒绝。用 Nginx 作为前置反向代理时,我们可以利用 map 功能,精准匹配 URL 中的目标主站,强制伪装对应的 Referer

Nginx 防盗链伪装匹配字典

目标 CDN 域名特征命中平台Nginx 强行注入的 Referer
~*sspai\.com少数派[https://sspai.com/](https://sspai.com/)
~*zhimg\.com知乎[https://www.zhihu.com/](https://www.zhihu.com/)
~*yuque\.com语雀[https://www.yuque.com/](https://www.yuque.com/)
~*doubanio\.com豆瓣图片[https://www.douban.com/](https://www.douban.com/)
~*douban\.com豆瓣[https://www.douban.com/](https://www.douban.com/)
~*chongdiantou\.com充电头网[https://www.chongdiantou.com/](https://www.chongdiantou.com/)
~*qpic\.cn微信公众号[https://mp.weixin.qq.com/](https://mp.weixin.qq.com/)
~*sinaimg\.cn新浪微博[https://weibo.com/](https://weibo.com/)

nginx.conf 完整生产级配置

将以下内容保存为 /share/Container/imgproxy/nginx.conf

events {
    worker_connections 1024;
}

http {
    # 使用国内外通用公共 DNS,配合 SOCKS5 代理确保抗污染和干净解析
    resolver 223.5.5.5 119.29.29.29 8.8.8.8 valid=300s ipv6=off;
    resolver_timeout 5s;

    # 磁盘缓存设置:最多占用 1G,最长保存 7 天
    proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=img_cache:100m inactive=7d max_size=1g;

    map $arg_url $matched_referer {
        default                         "";
        "~*sspai\.com"                  "https://sspai.com/";
        "~*zhimg\.com"                  "https://www.zhihu.com/";
        "~*yuque\.com"                  "https://www.yuque.com/";
        "~*doubanio\.com"               "https://www.douban.com/";
        "~*douban\.com"                 "https://www.douban.com/";
        "~*chongdiantou\.com"           "https://www.chongdiantou.com/";
        "~*qpic\.cn"                    "https://mp.weixin.qq.com/";
        "~*sinaimg\.cn"                 "https://weibo.com/";
    }

    server {
        listen 80;
        server_name _;

        location /imgproxy {
            if ($arg_url !~* "^https?://") {
                return 400 "Invalid URL";
            }

            proxy_cache img_cache;
            proxy_cache_valid 200 7d;
            # 当源站网络抖动或超时时,允许使用陈旧的本地过期缓存先给阅读器展示
            proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;

            proxy_pass $arg_url;
            
            proxy_set_header Referer $matched_referer;
            proxy_set_header User-Agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
            proxy_set_header Accept "image/avif,image/webp,image/apng,image/*,*/*;q=0.8";
            
            proxy_ssl_server_name on;
            proxy_ssl_protocols TLSv1.2 TLSv1.3;
            
            proxy_hide_header Access-Control-Allow-Origin;
            add_header Access-Control-Allow-Origin *;
            
            proxy_hide_header Cache-Control;
            add_header Cache-Control "public, max-age=604800";
        }
    }
}

第三模块:Sidecar 伴生透明网关(tun2socks)的进阶融合

常规图床可以用上面的 Nginx 解决,但如果抓取 Telegram(如 cdn1.telesco.pe)等被墙图片,Nginx 会抛出 504 Gateway Timeout。如果简单粗暴地在配置里写 HTTP 代理转发,又会遇到各类协议不适配现象。

核心组网配置(图床代理模块 docker-compose.yml

version: '3.8'

services:
  # 1. 透明代理网关容器:创建虚拟网卡,接管同网络栈下所有的 TCP/UDP 流量
  tun2socks-gateway:
    image: xjasonlyu/tun2socks:v2.5.2
    container_name: tun2socks-gateway
    restart: always
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    environment:
      # 必须指向 NAS 上支持 SOCKS5 协议的监听端口 (默认 mixed 端口可写 7890/7891)
      - PROXY=socks5://172.29.8.1:7891
    ports:
      # Nginx 对外提供服务的 8888 端口在这里声明
      - "8888:80"

  # 2. 寄生于网关的 Nginx 中转图床容器
  imgproxy:
    image: nginx:alpine
    container_name: imgproxy
    restart: always
    # 核心:复用伴生容器的网络栈,自动具备透明抓墙能力
    network_mode: "service:tun2socks-gateway"
    depends_on:
      - tun2socks-gateway
    volumes:
      - /share/Container/imgproxy/nginx.conf:/etc/nginx/nginx.conf:ro
      - /share/Container/imgproxy/cache:/tmp/nginx_cache

关键网络参数对比解析

配置/做法具体配置代码底层原理与为什么要这么配
错误代理方式PROXY=[http://172.29.8.1:7891](http://172.29.8.1:7891)HTTP 代理无法转发 UDP 报文。Nginx 向 DNS 服务器发出的域名解析请求是 UDP 53 端口,会被直接丢弃,在日志报错 could not be resolved (110: Operation timed out) 继而导致 502 Bad Gateway
正确代理方式PROXY=socks5://172.29.8.1:7891SOCKS5 天然兼容 UDP Associate 转发。可以完整承载 Nginx 对外的 UDP 域名查询,避开代理通道丢包的问题。
正确容器网关network_mode: "service:tun2socks-gateway"Docker 原生特性。让 imgproxy 容器没有单独的以太网卡,直接用 tun2socks 初始化好的虚拟网卡 tun0。出站流量无感知进入 SOCKS5 代理。

折腾过程中的核心报错排查指南

Nginx 报错现象根源排查说明最佳解决手段
504 Gateway Timeout直连被墙 CDN(如 telesco.pe)长时间无响应加上 tun2socks-gateway 容器,引入 Sidecar 转发逻辑。
502 Bad Gateway (且带有 110: Operation timed out)tun2socks 中配置的 http:// 代理协议把 Nginx 的 UDP DNS 解析包给吞了将网关环境变量修改为 socks5://172.29.8.1:7891
502 Bad Gateway (且带有 2: Server failure)配置了 Docker 内部 DNS 127.0.0.11,但流量最终走了运营商有污染的 DNSnginx.conf 中填入纯净组合公网 DNS:223.5.5.5 119.29.29.29 8.8.8.8
HTTP 200 OK (正常抓取,但 Reeder 里显示裂图)目标源为微信公众号或少数派等强防盗链系统,未正确返回伪装 Referer检查 map $arg_url $matched_referer 是否覆盖该主站,如果有漏,按正则规则补上一行即可。

第四模块:终端阅读器(Reeder)的联动配置

一切后置设施配置完成、执行 docker compose up -d 启动后,你的图片缓存转发接口已经确定为:

http://NAS_IP:18888/imgproxy?url=真实图片URL

在终端设备(iPhone / Mac)上,使用最新订阅制版本的 Reeder(Reeder 2024+ / Version 2024.9.1 及后续系列)。这个版本比起老版的 Reeder 5 在界面流式交互和多设备云同步上做了大幅重构,对于有高频刷图需求的订阅流特别有效。我还是习惯使用老版本的 Reeder 5。

在 RSS 源中应用 imgproxy 转换的两种策略

策略 A:直接在 RSSHub 生产源时完成替换(最推荐)

如果你的 RSS 抓取源是自建或自己能够修改模板的内容,建议在生成 RSS 报文时,直接将其中的 <img src="https://..."> 全部利用脚本或中间件重定向:

原始:<img src="https://cdn1.telesco.pe/file/xxx.jpg">
替换后:<img src="http://NAS_IP:8888/imgproxy?url=https://cdn1.telesco.pe/file/xxx.jpg">

此时你在 Reeder 中订阅这个改过 URL 的 RSS 地址即可,没有任何额外适配。

策略 B:用 Nginx 前置给 RSSHub 做响应体改写(进阶方案)

可以通过 Nginx 的 sub_filter 模块,再开一个反向代理端口套在 RSSHub 前面。每当 Reeder 去请求 RSS 列表时,Nginx 自动把文本里的 https:// 匹配并前缀补上你的 http://NAS_IP:8888/imgproxy?url=。这能彻底免去修改源脚本的成本。

买断版 Reeder 5 的实际体验提升

接入这一套自建缓存链条之后,在 Reeder 客户端里能直观地看到体验跃迁:

  • 微信 / 知乎 / 微博长文秒开:通过伪装 Referer 和服务器本地千兆网络代理,客户端再无空白图和破损图。
  • Telegram 图文推送即刷即看:即便手机处于直连或非分流网络,依然可以直接借由 NAS 宿主机的透明网关顺利将图片抓回。
  • 极速本地预载:由于 Nginx 配置了 /tmp/nginx_cache 磁盘快取,第一次某设备加载完成后,本地其他设备的 Reeder 再次刷到这篇内容,完全由 NAS 内网直接吐出,省去了源站二次解析以及带宽抖动等待。

总结与检查清单

整个自建图床系统的优化核心在于:让专业的工具在隔离的空间做专业的事

  • RSSHub 只管去各大目标站点摘取结构化内容,不做无意义的数据脏转换;
  • Nginx (imgproxy) 只专注搞定 HTTP 头部重写、协议校验与磁盘缓存,不需要关心理底层的网络隧道如何建立;
  • tun2socks 作为最小化权限的 Sidecar,只收容这一个 Nginx 图床容器发出的流量,把最困难的国内外网络路由静默卸载到主机的代理端口。

如果你需要对新的服务快速建立同款无缝抓图代理,不用改写主机的路由表,只需新建一个需要翻墙的容器,在其 Docker Compose 里挂载一条 network_mode: "service:tun2socks-gateway" 即可直接把链路接通。

截屏2026-08-08 09.32.42

现在我的 Reeder 5 大部分内容都是有图的包括豆瓣,体验好很多。

flowchart TD
    subgraph Client ["终端设备 (iPhone / Mac)"]
        R["Reeder 5"]
    end

    subgraph NAS_Docker ["QNAP NAS : Container Station (Docker Bridge)"]
        subgraph Sidecar_Pod ["共享网络空间 (network_mode: service:tun2socks-gateway)"]
            GW["tun2socks-gateway<br/>监听: 8888:80<br/>虚拟网卡: tun0"]
            NG["imgproxy (Nginx)<br/>防盗链 Referer + 7天磁盘缓存"]
        end
        RH["rsshub<br/>监听: 1200<br/>生成带 /imgproxy 前缀的图片 URL"]
    end

    subgraph NAS_Host ["QNAP NAS : 宿主机主机网络"]
        MI["Mihomo / Clash 代理服务<br/>监听: 172.29.8.1:7891 (SOCKS5/HTTP)"]
    end

    subgraph CDN ["目标图片源站"]
        CN["国内图床 (微信/知乎/少数派)<br/>校验 Referer 正常放行"]
        TG["海外被墙图床 (Telegram cdn1.telesco.pe)<br/>通过代理出境"]
    end

    %% 数据流向
    R -- "1. 订阅文本请求" --> RH
    R -- "2. 图片请求 http://NAS_IP:8888/imgproxy?url=" --> GW
    GW <-->|网络命名空间共享| NG
    NG -- "3. 命中缓存直接高速返回" --> R
    NG -- "4. 未命中缓存 (TCP抓取 / UDP 53 DNS)" --> GW
    GW -- "5. SOCKS5 代理穿透" --> MI
    MI -- "国内规则" --> CN
    MI -- "代理规则" --> TG