用 AI 一晚上撸了个自托管阅读器,兼容 Legado 5000+ 书源
NAS 上一直跑着 hectorqin/reader 当阅读器,能用,但越用越不爽:
UI 像 2015 年的产物,手机上看书体验很差 没有 PWA,每次都要开浏览器找地址 TTS 朗读?不存在的 想加个暗黑模式都得去改 Java 代码
既然手头有 AI 能写代码,不如自己从零搭一个。一晚上干完,从书源引擎到前端阅读器到 CI/CD 自动部署,全链路跑通。
目标:兼容 Legado 书源,但不复刻 Legado
Legado(阅读 App)的生态里有 5000+ 社区维护的书源,格式是 JSON,里面定义了"去哪搜索、怎么提取书名、怎么拿章节列表、怎么取正文"的规则。
现有方案(hectorqin/reader)的思路是用 Java 复刻 Legado 的规则引擎。我换了个策略:
读 Legado 格式,用 Python 自己执行。
不去复刻 Legado 的 java.* bridge 全集,而是用 Python 生态的解析库直接处理:
@css:div.title | ||
class.name@tag.a@href | ||
//div[@class="content"] | ||
$.data.list[*].title | ||
##作者:(.+) | ||
<js>result.replace(...)</js> |
这样做的好处是:70% 以上的书源用简单选择器就能跑通,不需要完整的 JS bridge。剩下那些复杂的(加密、动态分页)后续再加。
技术栈:一个 Docker 容器搞定
后端:Python + FastAPI + SQLite + httpx前端:React + TypeScript + Tailwind + PWA部署:Docker 单容器,NAS 一键跑
为什么选 Python 不用 Java/Kotlin?
解析库生态好——BS4、lxml、jsonpath-ng 都是成熟工具 异步 I/O 原生支持——搜索时并发请求几十个书源 写起来快——一晚上要干完,不想写模板代码
为什么单容器?NAS 上跑 Docker,一个镜像包含前后端所有东西,docker run 一行命令启动,不搞 docker-compose 多服务那套。
核心能力
1. 搜索:流式返回,不等超时
267 个书源并发搜索,不可能等所有都返回再显示——有些站早就挂了,等它超时要 15 秒。
我用 SSE(Server-Sent Events)做流式推送:每个书源一返回结果就立刻推给前端,页面实时刷新。
用户输入关键词→ 后端并发请求 267 个书源(信号量限制 10 并发)→ 书源 A 返回 → 立即推送 → 前端追加显示→ 书源 B 返回 → 立即推送 → 前端追加显示→ ...→ 全部完成或超时 → 发送 [DONE]
体感差异很大:从"等 15 秒看到一堆结果"变成"1 秒就开始出结果,持续刷新"。
2. 规则引擎:处理 Legado 的奇葩语法
Legado 的规则语法在 JSON 里写得很随意,各种换行、嵌套、组合:
// 先取 JSONPath,再正则替换$.groupID##.*_##// URL 模板引用已解析的字段https://api.example.com/chapters?id={{book.kind}}// 按文本内容查找元素text.在线阅读@href
这些都不是标准语法,需要一个一个适配。我的引擎做了:
##内联正则:$.field##regex##replacement{{book.field}}模板变量:URL 里引用其他已解析字段text.xxx选择器:按文本内容查找 DOM 元素自动 Referer:很多 API 验证来源,自动加上目标域名的 Referer URL 清理:Legado JSON 里的 URL 经常有换行符、相对路径,全部自动修正
3. 前端:响应式 + PWA
一套代码适配手机和桌面:
手机:全屏沉浸阅读,底部导航 桌面:侧边章节栏 + 居中阅读区
PWA 支持意味着可以"添加到主屏幕"当独立 App 用,不需要每次打开浏览器输地址。
阅读设置:字号、行距、主题(亮色/暗色/护眼)都可以调。
CI/CD:push 即部署
整条流水线全自动:
git push main→ GitHub Actions 构建 Docker 镜像→ 推送到 Docker Hub→ SSH 到 NAS(通过 Cloudflare Tunnel)→ docker pull + restart
从提交代码到 NAS 上跑起新版本,大概 30 秒。改一行代码,手机刷新就能看到效果。
# .github/workflows/deploy.yml 核心步骤- name: Build and pushuses: docker/build-push-action@v6with:tags: rio22/reader:latest- name: Deploy to NASrun: |ssh "$NAS_USER@$NAS_HOST" "docker pull rio22/reader:latest && \docker stop reader; docker rm reader; \docker run -d --name reader -p 8880:8080 rio22/reader:latest"
SSH 走的是 Cloudflare Tunnel(没有公网 IP),GitHub Actions 里装了 cloudflared 做代理。
部署:一行命令
docker run -d \--name reader \--restart unless-stopped \-p 8880:8080 \-v /volume1/docker/reader/data:/app/data \rio22/reader:latest
-p 8880:8080 | |
-v .../data:/app/data | |
rio22/reader:latest |
启动后访问 http://NAS-IP:8880,在"书源"页面贴入书源 JSON 地址即可导入。推荐用这个(约 270 个源):
https://cdn.jsdelivr.net/gh/tickmao/Novel@master/sources/legado/full.json实际效果
搜索"十日终焉":
0.5 秒开始出结果 最终 122 条,来自多个书源 点开第一条:1496 章 点击章节:正文 2345 字,排版干净
对比 hectorqin/reader:
踩的坑
1. 书源 URL 里有换行符
Legado 的 JSON 文件里,searchUrl 字段经常有 \n,导致 HTTP 请求直接报错。加了一行 .replace("\n", "") 解决。
2. 相对路径 URL
很多书源的 searchUrl 写的是 /search?q=xxx 而不是完整 URL。需要自动拼接书源域名。
3. Referer 验证
不少 API(QQ 阅读等)会验证 Referer 头。之前没加,返回 "incorrect referer"。现在所有请求自动带上目标域名作为 Referer。
4. 缓存了错误响应
早期请求失败的响应被缓存了,后来修了代码但结果不变。加了"小于 50 字不缓存"的规则防止脏缓存。
5. 通过隧道操作隧道本身
CI 部署时 SSH 走 Cloudflare Tunnel。如果操作不当把隧道搞断了,就连不上 NAS 了。(这个坑我之前单独写过一篇)
功能状态
代码开源在 GitHub:qq148376839/reader
一句话总结
兼容 Legado 书源格式 + Python 自建执行引擎 + React PWA 前端 + Docker 单容器部署 = 一个能用的自托管阅读器,一晚上从零到部署上线。
如果你也在 NAS 上找阅读方案,可以试试。书源直接用 Legado 社区的,不用重新找。
夜雨聆风