Shineii86/AniNewsAPI
GitHub: Shineii86/AniNewsAPI
AniNewsAPI 是一个实时聚合多个动漫新闻源的 REST API 服务,解决了开发者获取和处理动漫资讯的难题。
Stars: 21 | Forks: 10
一个实时聚合 7 个来源动漫新闻的无服务器 API。
智能缓存、关键词搜索、RSS 订阅源、日期过滤、游标分页与来源健康监控。
为速度、可靠性和动漫社区而构建。
目录 • 功能 • API 文档 • 快速开始 • 部署 • 贡献
## 📖 目录 - [概述](#-overview) - [功能](#-features) - [新闻来源](#-news-sources) - [技术栈](#-tech-stack) - [架构](#-architecture) - [项目结构](#-project-structure) - [快速开始](#-quick-start) - [配置](#-configuration) - [API 端点](#-api-endpoints) - [API 响应模式](#-api-response-schema) - [部署](#-deployment) - [可用脚本](#-available-scripts) - [性能](#-performance) - [更新日志亮点](#-changelog-highlights) - [故障排除](#-troubleshooting) - [常见问题](#-faq) - [路线图](#-roadmap) - [贡献](#-contributing) - [致谢](#-acknowledgements) - [许可证](#-license) - [作者](#-author) - [Star 历史](#-star-history) ## 🌸 概述 **AniNewsAPI** 是一个无服务器的动漫新闻聚合 API,它抓取、去重并通过一个干净的 REST API 提供来自 **7 个主要动漫新闻来源** 的文章——无需任何数据库设置。 ### 为什么选择 AniNewsAPI? - 📰 **7 个来源** — ANN, MAL, Crunchyroll, Anime Corner, Otaku USA, Anime Herald, Comic Book - ⚡ **智能缓存** — 两级缓存(内存+磁盘),10 分钟 TTL,可抵御无服务器冷启动 - 🔍 **全文搜索** — 在标题、摘要、来源和标签中进行相关性评分搜索 - 🗞️ **RSS 订阅源** — 符合标准的 RSS 2.0,适用于任何订阅源阅读器 - 📄 **全文提取** — 通过 slug 获取可读的文章内容 - 🏷️ **标签过滤** — 按标签浏览文章,并带有计数聚合 - 📊 **来源健康** — 实时监控每个来源的状态、延迟和文章计数 - 🔒 **启用 CORS** — 可从任何前端使用,无需代理 - 🚀 **零配置部署** — 一键部署到 Vercel,或使用 Express 独立运行 ### 它是如何工作的 ``` flowchart TD A["🌐 Client Request(Browser / App / curl)"] --> B["🛡️ Vercel Edge / Express Server
CORS · Security Headers · Rate Limiting"] B --> C{"💾 Cache Check
(node-cache + disk)"} C -- HIT --> D["⚡ Return Cached Response
~200ms"] C -- MISS --> E["📰 7 Concurrent Fetchers"] E --> E1["ANN"] E --> E2["MAL"] E --> E3["Crunchyroll"] E --> E4["Anime Corner"] E --> E5["Otaku USA"] E --> E6["Anime Herald"] E --> E7["Comic Book"] E1 & E2 & E3 & E4 & E5 & E6 & E7 --> F["🔄 RSS / Google News RSS / Web Scraping
3 retries · 15s timeout · exponential backoff"] F --> G["🧹 Deduplicate · Enrich · Cache"] G --> H["📤 Respond
JSON · RSS 2.0 XML · SSE"] style A fill:#1e1e2e,stroke:#a78bfa,color:#f1f5f9 style B fill:#1e1e2e,stroke:#6366f1,color:#f1f5f9 style C fill:#1e1e2e,stroke:#f43f8e,color:#f1f5f9 style D fill:#1e1e2e,stroke:#22c55e,color:#f1f5f9 style E fill:#1e1e2e,stroke:#a855f7,color:#f1f5f9 style F fill:#1e1e2e,stroke:#eab308,color:#f1f5f9 style G fill:#1e1e2e,stroke:#06b6d4,color:#f1f5f9 style H fill:#1e1e2e,stroke:#22c55e,color:#f1f5f9 ``` ## ✨ 功能
| ### ⚡ 核心 - **实时抓取** 来自 7 个动漫新闻来源 - **智能缓存**,10 分钟 TTL + 磁盘备份 - **并发获取** — 同时请求所有来源 - **重试逻辑** — 每个来源 3 次尝试,指数退避 - **优雅降级** — 如果一个来源失败,其他继续工作 - **Google News RSS 代理**,用于 Cloudflare 保护的来源 | ### 🔍 数据 - **关键词搜索**,带相关性评分 (`/api/search`) - **日期范围过滤** — `?from=YYYY-MM-DD&to=YYYY-MM-DD` - **游标分页** — 不透明的 `nextCursor`,实现高效分页 - **RSS 2.0 订阅源**,适用于阅读器和集成 (`/api/rss`) - **通过 slug 提取全文** (`/api/news/:slug`) - **标签过滤**,带文章计数 (`/api/news/tags`) |
| ### 🛡️ 可靠性 - 当网页抓取被阻止时,**RSS 回退** - 通过标准化标题进行**跨来源去重** - **超时保护** — 每个来源 15 秒,永不挂起 - **启用 CORS** — 可从任何前端使用 - **安全头** — X-Frame-Options, X-Content-Type-Options - **速率限制** — 每个 IP 每分钟 100 个请求,带头部信息 | ### 📊 监控 - **来源健康** — 每个来源的状态、文章计数、延迟 (`/api/sources`) - **缓存统计** — 命中/未命中指标 (`/api/stats`) - **健康检查** — 正常运行时间、版本、节点信息 (`/api/health`) - **缓存清除授权** — API 密钥保护 (`CACHE_CLEAR_KEY` 环境变量) - **OpenAPI 3.0.3 规范** — 机器可读的 API 定义 |
(node-cache)"} B -- HIT --> C["⚡ Return Cached
~200ms"] B -- MISS --> D{"💾 Disk Cache
(JSON files)"} D -- HIT --> E["🔄 Promote to Memory
Return"] D -- MISS --> F["📰 Fetch from 7 Sources
(concurrent)"] F --> G["💾 Cache Result
(memory + disk)"] G --> H["📤 Return Fresh"] style A fill:#1e1e2e,stroke:#a78bfa,color:#f1f5f9 style B fill:#1e1e2e,stroke:#f43f8e,color:#f1f5f9 style C fill:#1e1e2e,stroke:#22c55e,color:#f1f5f9 style D fill:#1e1e2e,stroke:#6366f1,color:#f1f5f9 style E fill:#1e1e2e,stroke:#06b6d4,color:#f1f5f9 style F fill:#1e1e2e,stroke:#eab308,color:#f1f5f9 style G fill:#1e1e2e,stroke:#a855f7,color:#f1f5f9 style H fill:#1e1e2e,stroke:#22c55e,color:#f1f5f9 ``` ### 来源获取策略 | 来源 | 主要方式 | 回退方式 | 备注 | |:---|:---|:---|:---| | ANN | Google News RSS | 直接抓取 | Cloudflare 阻止直接访问 | | Anime Corner | RSS 订阅源 | 直接抓取 | RSS 包含真实描述 | | MyAnimeList | 直接抓取 | 抓取第二页 | 自定义日期格式解析器 | | Otaku USA | Google News RSS | 直接抓取 | 直接访问时出现 520 错误 | | Crunchyroll | Google News RSS | 直接抓取 | 阻止直接抓取 | | Anime Herald | RSS 订阅源 | 直接抓取 | RSS 包含真实描述 | | Comic Book | 直接抓取 | RSS 订阅源 | 使用副标题选择器 | ## 📁 项目结构 ``` AniNewsAPI/ ├── 📂 api/ # 🌐 Vercel serverless functions │ ├── 📂 cache/ │ │ └── 📄 clear.js # 🔐 Cache management (API key protected) │ ├── 📄 health.js # 💚 Health check endpoint │ ├── 📄 news.js # 📰 Main news endpoint (pagination, filtering) │ ├── 📂 news/ │ │ ├── 📄 [slug].js # 📄 Full article by slug │ │ └── 📄 tags.js # 🏷️ Tag listing & filtering │ ├── 📄 openapi.js # 📋 OpenAPI 3.0.3 specification │ ├── 📄 rss.js # 🗞️ RSS 2.0 XML feed │ ├── 📄 search.js # 🔍 Full-text search with scoring │ ├── 📄 sources.js # 📊 Per-source health & stats │ ├── 📄 stats.js # 📈 Cache hit/miss statistics │ └── 📄 stream.js # 📡 Server-Sent Events │ ├── 📂 public/ │ ├── 📄 index.html # 🏠 Landing page │ ├── 📄 manifest.json # 📱 PWA manifest │ ├── 📄 og-image.png # 🖼️ Open Graph image │ └── 📄 og-image.svg # 🖼️ Open Graph vector │ ├── 📂 utils/ # ⚙️ Core logic │ ├── 📄 cacheHandler.js # 💾 Two-tier cache (memory + disk) │ ├── 📄 constants.js # 📌 Shared config & defaults │ ├── 📄 contentParser.js # 📄 Full-article content extraction │ ├── 📄 dateParser.js # 📅 Multi-format date parsing │ ├── 📄 fetchANN.js # 📰 Anime News Network fetcher │ ├── 📄 fetchAnimeCorner.js # 📰 Anime Corner fetcher │ ├── 📄 fetchAnimeHerald.js # 📰 Anime Herald fetcher │ ├── 📄 fetchComicBook.js # 📰 Comic Book fetcher │ ├── 📄 fetchCrunchyroll.js # 📰 Crunchyroll fetcher │ ├── 📄 fetchMyAnimeList.js # 📰 MyAnimeList fetcher │ ├── 📄 fetchOtakuNews.js # 📰 Otaku USA fetcher │ ├── 📄 generateSlug.js # 🔗 URL-safe slug generator │ └── 📄 sources.js # 📋 Centralized source registry │ ├── 📂 data/ # 💾 Disk cache files (auto-generated) │ ├── 📄 server.js # 🚀 Express server entry point ├── 📄 index.js # ▲ Vercel serverless entry point ├── 📄 test.js # 🧪 Integration test suite ├── 📄 vercel.json # ▲ Vercel routing & headers config ├── 📄 package.json # 📦 Dependencies & scripts ├── 📄 CHANGELOG.md # 📝 Version history ├── 📄 CONTRIBUTING.md # 🤝 Contribution guidelines ├── 📄 LICENSE # 📜 MIT License └── 📄 README.md # 📖 This file ``` ## 🚀 快速开始 ### 前置条件 | 要求 | 最低版本 | 推荐版本 | |:---|:---|:---| | 📦 Node.js | 20.x | 20.x LTS | | 📦 npm | 9.0+ | 10.x | | 💻 操作系统 | Windows, macOS, Linux | 任意 | ### 🔧 安装 ``` # 1️⃣ 克隆仓库 git clone https://github.com/Shineii86/AniNewsAPI.git cd AniNewsAPI # 2️⃣ 安装依赖 npm install # 3️⃣ 启动开发服务器 npm run dev ``` ### 🏗️ 生产环境构建 ``` # 启动生产服务器 npm start # 运行测试 npm test ``` ### 🐳 备选包管理器 ``` # 使用 yarn yarn install yarn dev # 使用 pnpm pnpm install pnpm dev # 使用 bun bun install bun dev ``` ## ⚙️ 配置 ### 环境变量 | 变量 | 默认值 | 描述 | |:---|:---|:---| | `CACHE_TTL` | `600` | 缓存持续时间,单位为秒(10 分钟) | | `PORT` | `3000` | 服务器端口(仅 Express 模式) | | `CACHE_CLEAR_KEY` | — | `POST /api/cache/clear` 的 API 密钥(可选) | | `API_URL` | `http://localhost:3000` | 测试套件的基础 URL | ### Vercel 配置 `vercel.json` 文件处理: - **重写** — 将简洁的 URL 映射到无服务器函数 - **头信息** — CORS、缓存和速率限制头 - **环境** — 为生产环境设置 `CACHE_TTL` ## 📡 API 端点 ### `GET /api/news` 来自所有或特定来源的最新动漫新闻。 | 参数 | 类型 | 默认值 | 描述 | |:---|:---|:---|:---| | `limit` | `1-100` | `20` | 每页最大文章数 | | `offset` | `>=0` | `0` | 分页偏移量 | | `cursor` | `string` | — | 分页游标(来自 `meta.nextCursor`) | | `sort` | `latest\|oldest` | `latest` | 排序顺序 | | `source` | `string` | `all` | 按来源键名过滤 | | `from` | `YYYY-MM-DD` | — | 开始日期过滤 | | `to` | `YYYY-MM-DD` | — | 结束日期过滤 | | `refresh` | `boolean` | `false` | 绕过缓存 | ``` # 基本用法 curl "https://aninews.vercel.app/api/news?limit=10" # 按来源过滤并分页 curl "https://aninews.vercel.app/api/news?source=crunchyroll&limit=10&offset=10" # 日期范围筛选 curl "https://aninews.vercel.app/api/news?from=2026-05-20&to=2026-05-27" # 基于游标的分页(使用上一个响应中的 nextCursor) curl "https://aninews.vercel.app/api/news?limit=20&cursor=eyJvZmZzZXQiOjIwfQ" ```
📄 响应示例
``` { "success": true, "data": [ { "title": "Demon Slayer Season 4 Announced", "slug": "ann-demon-slayer-season-4-announced", "source": "Anime News Network", "excerpt": "The official website confirmed...", "date": "2026-05-27T10:30:00.000Z", "image": "https://example.com/image.jpg", "link": "https://www.animenewsnetwork.com/news/...", "tags": ["news", "anime"] } ], "meta": { "total": 62, "returned": 10, "offset": 0, "limit": 10, "hasMore": true, "nextCursor": "eyJvZmZzZXQiOjEwfQ", "source": "all", "sort": "latest", "from": "2026-05-20", "to": "2026-05-27", "responseTime": "234ms", "timestamp": "2026-05-27T12:00:00.000Z" } } ```📄 响应示例(标签列表)
``` { "success": true, "data": { "tags": [ { "name": "anime", "count": 45 }, { "name": "news", "count": 38 }, { "name": "official", "count": 22 } ], "totalTags": 15, "totalArticles": 62 }, "meta": { "timestamp": "2026-05-27T12:00:00.000Z" } } ```📄 响应示例
``` { "success": true, "data": { "title": "Demon Slayer Season 4 Announced", "slug": "ann-demon-slayer-season-4-announced", "source": "Anime News Network", "excerpt": "The official website confirmed...", "date": "2026-05-27T10:30:00.000Z", "link": "https://www.animenewsnetwork.com/news/...", "content": "Full article HTML content...
", "author": "John Doe", "publishDate": "2026-05-27" }, "meta": { "cached": false, "timestamp": "2026-05-27T12:00:00.000Z" } } ```📄 响应示例
``` { "success": true, "data": [ { "key": "ann", "name": "Anime News Network", "status": "healthy", "articleCount": 15, "latency": "1234ms", "lastFetch": "2026-05-27T12:00:00.000Z", "lastError": null }, { "key": "crunchyroll", "name": "Crunchyroll", "status": "degraded", "articleCount": 0, "latency": "15001ms", "lastFetch": "2026-05-27T11:55:00.000Z", "lastError": { "message": "Timeout", "time": "2026-05-27T11:55:00.000Z" } } ], "meta": { "total": 7, "healthy": 6, "degraded": 1, "responseTime": "2345ms" } } ```📰 如何添加新的新闻来源?
1. 创建
utils/fetchNewSource.js,导出一个异步函数,返回文章对象数组:[{ title, slug, source, excerpt, date, image, link, tags }]2. 在
utils/sources.js 中注册它 — 添加导入和条目到 SOURCES 对象3. 运行
npm test 验证,然后提交 PR
🔄 数据刷新频率是多少?
缓存 TTL 默认为 10 分钟。之后,下一个请求会触发从所有 7 个来源进行全新获取。你可以使用
?refresh=true 强制刷新,或使用 CACHE_TTL 环境变量更改 TTL。
📡 我可以在前端应用中使用这个吗?
可以!CORS 已为所有来源启用(
*)。只需向 API 端点发起 fetch 请求。读取操作无需 API 密钥。示例:fetch('https://aninews.vercel.app/api/news?limit=10')
🗞️ 如何订阅 RSS 订阅源?
将
https://aninews.vercel.app/api/rss 添加到任何订阅源阅读器(Feedly、Inoreader、NetNewsWire 等)。你可以按来源过滤:/api/rss?source=crunchyroll
📊 去重是如何工作的?
文章通过标准化标题进行去重 — 去除标点符号,折叠空白,并且不区分大小写地比较。第一次出现(来自响应最快的来源)的胜出。
🔒 缓存清除端点安全吗?
当设置
CACHE_CLEAR_KEY 时,该端点需要一个 X-Api-Key 头。如果没有设置该环境变量,则端点是开放的 — 因此在生产环境中应设置它。读取端点(/api/news 等)始终是开放的。
⏱️ 为什么第一次请求很慢?
在无服务器环境(Vercel)中,空闲后的第一次请求会触发“冷启动” — 函数初始化并从所有 7 个来源获取数据(~3-6 秒)。后续请求会命中缓存(~200 毫秒)。预热的函数大约存活 5 分钟。
🌐 我可以自托管这个吗?
可以!使用
npm start 在任何 VPS、Docker 容器或 PaaS 上运行 Express 服务器。Vercel 无服务器函数是可选的 — server.js 处理所有事情。
[](./LICENSE)
本项目根据 **MIT 许可证** 授权。
可自由使用、修改和分发 — 详情请见 [LICENSE](LICENSE) 文件。
## 👤 作者
Shinei Nouzen
Full-Stack Developer & Anime Enthusiast
标签:API, DNS解析, Express, GNU通用公共许可证, MITM代理, Node.js, OSV, RSS订阅, Syscall, Vercel, Web开发, 全文提取, 内容聚合, 动漫, 开源项目, 技术栈, 接口服务, 数据服务, 数据聚合, 新闻, 智能缓存, 服务器less, 自定义脚本