Shineii86/AniNewsAPI

GitHub: Shineii86/AniNewsAPI

AniNewsAPI 是一个实时聚合多个动漫新闻源的 REST API 服务,解决了开发者获取和处理动漫资讯的难题。

Stars: 21 | Forks: 10

Stars Forks Issues Pull Requests Last Commit License

Node.js Express Vercel License Version Sources Endpoints

一个实时聚合 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 定义
### 🌟 功能亮点 | 功能 | 描述 | 状态 | |:---|:---|:---:| | 📰 7 个新闻来源 | ANN, MAL, Crunchyroll, Anime Corner, Otaku USA, Anime Herald, Comic Book | ✅ | | ⚡ 智能缓存 | 两级(内存+磁盘),10 分钟 TTL | ✅ | | 🔍 全文搜索 | 相关性评分 — 标题 (10 分) 对比 摘要 (3 分) | ✅ | | 📄 文章提取 | 从原始 URL 解析完整内容 | ✅ | | 🗞️ RSS 2.0 订阅源 | 符合标准的 XML,带 media:thumbnail | ✅ | | 📅 日期过滤 | 新闻和搜索中使用 `?from=YYYY-MM-DD&to=YYYY-MM-DD` | ✅ | | 🔄 游标分页 | 不透明的 base64url 游标,实现稳定分页 | ✅ | | 🏷️ 标签系统 | 带计数的标签列表,按标签过滤 | ✅ | | 📊 来源健康 | 实时获取状态、延迟、文章计数 | ✅ | | 🔒 缓存授权 | API 密钥保护缓存清除端点 | ✅ | | 📡 SSE 流 | 服务器发送事件,用于实时推送 | ✅ | | 📋 OpenAPI 规范 | 机器可读的 3.0.3 规范 | ✅ | | 🚀 一键部署 | Vercel 按钮部署 | ✅ | | 🏗️ Express 模式 | 通过 `npm start` 运行独立服务器 | ✅ | ## 🗞️ 新闻来源 | 来源 | 键名 | 方法 | 文章数 | 网站 | |:---|:---|:---|:---|:---| | **Anime News Network** | `ann` | Google News RSS | ~15 | [animenewsnetwork.com](https://www.animenewsnetwork.com/) | | **Anime Corner** | `animecorner` | RSS 订阅源 | ~12 | [animecorner.me](https://animecorner.me/) | | **MyAnimeList** | `myanimelist` | 直接抓取 | ~15 | [myanimelist.net](https://myanimelist.net/) | | **Otaku USA Magazine** | `otakuusa` | Google News RSS | ~12 | [otakuusamagazine.com](https://otakuusamagazine.com/) | | **Crunchyroll** | `crunchyroll` | Google News RSS | ~15 | [crunchyroll.com/news](https://www.crunchyroll.com/news) | | **Anime Herald** | `animeherald` | RSS 订阅源 | ~10 | [animeherald.com](https://www.animeherald.com/) | | **Comic Book** | `comicbook` | 直接抓取 | ~10 | [comicbook.com/anime](https://comicbook.com/anime/) | ### 添加新来源 1. 创建 `utils/fetchNewSource.js` — 导出一个异步函数,返回 `[{ title, slug, source, excerpt, date, image, link, tags }]` 2. 在 `utils/sources.js` 中注册 → 添加到 `SOURCES` 对象 3. 使用 `npm test` 测试,然后提交 PR ## 🛠️ 技术栈 | 技术 | 用途 | 版本 | 文档 | |:---|:---|:---|:---| | 🟢 [Node.js](https://nodejs.org/) | JavaScript 运行时 | >= 20 | [文档](https://nodejs.org/docs/) | | ⚡ [Express](https://expressjs.com/) | HTTP 服务器框架 | 5.1 | [文档](https://expressjs.com/en/5x/api.html) | | ▲ [Vercel Functions](https://vercel.com/docs/functions) | 无服务器部署 | — | [文档](https://vercel.com/docs/functions) | | 🔍 [Cheerio](https://cheerio.js.org/) | HTML 解析与抓取 | 1.0 | [文档](https://cheerio.js.org/docs/) | | 🌐 [Axios](https://axios-http.com/) | HTTP 客户端 | 1.7 | [文档](https://axios-http.com/docs/intro) | | 📡 [rss-parser](https://github.com/rbren/rss-parser) | RSS/Atom 订阅源解析 | 3.13 | [文档](https://github.com/rbren/rss-parser) | | 💾 [node-cache](https://github.com/ptarjan/node-cache) | 内存缓存 | 5.1 | [文档](https://github.com/ptarjan/node-cache) | | 🔤 [he](https://github.com/mathiasbynens/he) | HTML 实体解码 | 1.2 | [文档](https://github.com/mathiasbynens/he) | ### 📦 关键依赖 ``` { "express": "^5.1.0", // HTTP server "axios": "^1.7.2", // HTTP client for scraping "cheerio": "^1.0.0-rc.12", // HTML parsing "rss-parser": "^3.13.0", // RSS feed parsing "node-cache": "^5.1.2", // In-memory cache "he": "^1.2.0" // HTML entity decoding } ``` ## 🏗️ 架构 ### 请求流程 | 阶段 | 组件 | 描述 | |:-----:|-----------|-------------| | 1 | **客户端** | 浏览器、应用或 `curl` 发送请求 | | 2 | **Vercel Edge / Express** | 路由请求,应用 CORS + 安全头 + 速率限制 | | 3 | **缓存检查** | `node-cache`,10 分钟 TTL — 命中 = 瞬间响应 | | 4 | **获取来源** | 7 个并发抓取器(每个重试 3 次,15 秒超时) | | 5 | **去重** | 通过标准化标题进行跨来源去重 | | 6 | **丰富并响应** | 过滤、分页、排序、格式化 → JSON/RSS/SSE | ### 缓存架构 ``` flowchart TD A["📥 Request"] --> B{"🧠 Memory Cache
(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" } } ```
### `GET /api/search` 全文搜索,带相关性评分。标题匹配的排名高于摘要匹配。 | 参数 | 是否必需 | 描述 | |:---|:---|:---| | `q` | 是 | 搜索查询(最少 2 个字符) | | `source` | 否 | 按来源键名过滤 | | `limit` | 否 | 最大结果数 (1-100) | | `offset` | 否 | 分页偏移量 | | `from` | 否 | 开始日期 (YYYY-MM-DD) | | `to` | 否 | 结束日期 (YYYY-MM-DD) | **评分算法:** - 标题匹配:每个搜索词 **+10 分** - 摘要匹配:每个搜索词 **+3 分** - 平局处理:最新的日期优先 ``` curl "https://aninews.vercel.app/api/search?q=demon+slayer" curl "https://aninews.vercel.app/api/search?q=manga&source=ann&limit=5" curl "https://aninews.vercel.app/api/search?q=crunchyroll&from=2026-05-20&to=2026-05-27" ``` ### `GET /api/news/tags` 列出可用标签及文章数量,或按标签过滤文章。 ``` # 列出所有标签及计数 curl "https://aninews.vercel.app/api/news/tags" # 按标签筛选文章 curl "https://aninews.vercel.app/api/news/tags?tag=official" # 按标签和来源筛选 curl "https://aninews.vercel.app/api/news/tags?tag=news&source=ann" ```
📄 响应示例(标签列表) ``` { "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" } } ```
### `GET /api/news/:slug` 从原始 URL 提取完整文章内容。 ``` curl "https://aninews.vercel.app/api/news/ann-demon-slayer-season-4-announced" ```
📄 响应示例 ``` { "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" } } ```
### `GET /api/rss` 符合标准的 RSS 2.0 XML 订阅源。适用于任何订阅源阅读器。 | 参数 | 默认值 | 描述 | |:---|:---|:---| | `source` | `all` | 按来源过滤 | | `limit` | `20` | 最大条目数 | ``` curl "https://aninews.vercel.app/api/rss" curl "https://aninews.vercel.app/api/rss?source=crunchyroll&limit=10" ``` ### `GET /api/sources` 每个来源的健康监控。返回获取状态、文章计数和延迟。 ``` curl "https://aninews.vercel.app/api/sources" ```
📄 响应示例 ``` { "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" } } ```
### `GET /api/health` · `GET /api/stats` 健康检查和缓存统计。 ``` curl "https://aninews.vercel.app/api/health" curl "https://aninews.vercel.app/api/stats" ``` ### `POST /api/cache/clear` 手动清除缓存。设置 `CACHE_CLEAR_KEY` 时需要 API 密钥。 ``` # 清除所有缓存 curl -X POST "https://aninews.vercel.app/api/cache/clear" \ -H "X-Api-Key: your-secret-key" # 清除特定缓存键 curl -X POST "https://aninews.vercel.app/api/cache/clear" \ -H "Content-Type: application/json" \ -H "X-Api-Key: your-secret-key" \ -d '{"key": "news_all"}' ``` ### `GET /api/stream` 服务器发送事件流。发送初始状态数据然后关闭。 ``` curl -N "https://aninews.vercel.app/api/stream" ``` ### `GET /api/openapi` JSON 格式的 OpenAPI 3.0.3 规范。可用于 Swagger UI、Postman 或任何兼容 OpenAPI 的工具。 ``` curl "https://aninews.vercel.app/api/openapi" ``` ## 📋 API 响应模式 ### 文章对象 | 字段 | 类型 | 描述 | 示例 | |:---|:---|:---|:---| | `title` | `string` | 文章标题 | `"Demon Slayer Season 4"` | | `slug` | `string` | URL 安全的标识符 | `"ann-demon-slayer-season-4"` | | `source` | `string` | 来源的显示名称 | `"Anime News Network"` | | `excerpt` | `string` | 文章描述/摘要 | `"The official website..."` | | `date` | `string` | ISO 8601 发布日期 | `"2026-05-27T10:30:00.000Z"` | | `image` | `string` | 缩略图 URL | `"https://..."` | | `link` | `string` | 原始文章 URL | `"https://..."` | | `tags` | `string[]` | 分类标签 | `["news", "anime"]` | ### 分页元数据 | 字段 | 类型 | 描述 | |:---|:---|:---| | `total` | `number` | 匹配文章总数 | | `returned` | `number` | 此响应中的文章数 | | `offset` | `number` | 当前偏移量 | | `limit` | `number` | 页面大小 | | `hasMore` | `boolean` | 是否还有更多页 | | `nextCursor` | `string\|null` | 下一页的透明游标 | | `source` | `string` | 应用的来源过滤 | | `sort` | `string` | 应用的排序顺序 | | `responseTime` | `string` | 服务器处理时间 | ## 🌐 部署 ### ▲ Vercel(推荐) [![使用 Vercel 部署](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/Shineii86/AniNewsAPI) 1. 点击上面的按钮(或在 vercel.com 手动导入) 2. Vercel 会自动检测项目 — **无需配置** 3. 你的 API 已上线!🎉 ``` # 或使用 Vercel CLI npx vercel --prod ``` ### 🖥️ 独立服务器 ``` # 克隆并安装 git clone https://github.com/Shineii86/AniNewsAPI.git cd AniNewsAPI && npm install # 启动生产服务器 npm start # → http://localhost:3000 ``` ### 🐳 Docker ``` FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --production COPY . . EXPOSE 3000 CMD ["node", "server.js"] ``` ## 📜 可用脚本 | 命令 | 描述 | 详情 | |:---|:---|:---| | `npm run dev` | 🔥 启动开发服务器 | 运行在 `localhost:3000` | | `npm start` | 🚀 启动生产服务器 | `NODE_ENV=production node server.js` | | `npm test` | 🧪 运行集成测试 | 测试所有 12 个端点 | | `npm run build` | 📦 构建(对无服务器无效) | Vercel 处理此步骤 | ## ⚡ 性能 | 指标 | 值 | |:---|:---| | ⚡ 缓存响应 | ~200 毫秒 | | 🔄 全新获取(所有 7 个来源) | ~3-6 秒 | | 💾 缓存 TTL | 10 分钟 | | 🔁 重试次数 | 每个来源 3 次 | | ⏱️ 每个来源超时 | 15 秒 | | 📰 文章总数(平均) | 去重后 60+ | | 📦 总代码库大小 | ~50KB | ### 优化特性 - 💾 **两级缓存** — 内存优先,磁盘回退 - ⚡ **并发获取** — 同时请求所有 7 个来源 - 🔄 **指数退避** — 重试时延迟 1 秒、2 秒、3 秒 - 🧹 **自动清理** — 过期的速率限制桶每 5 分钟清除 - 📁 **磁盘持久化** — 来源指标在无服务器冷启动后保留 - 🗜️ **最小依赖** — 仅 6 个生产依赖 ## 📝 更新日志亮点 | 版本 | 日期 | 关键变更 | |:---|:---|:---| | **4.2.0** | 2026-05-28 | 代码风格大修 — 所有 26 个文件采用 AlisaReactionBot 风格的文档 | | **4.1.6** | 2026-05-27 | 完整摘要,无截断 — 移除了所有 7 个抓取器的 200 字符限制 | | **4.1.5** | 2026-05-27 | Comic Book、Anime Corner、Anime Herald 使用真实摘要 | | **4.1.4** | 2026-05-27 | 来源端点现在执行实时健康检查 | | **4.1.3** | 2026-05-27 | 将来源指标持久化到磁盘,以在无服务器中存活 | | **4.1.0** | 2026-05-26 | 日期范围过滤、游标分页、搜索评分 | ## 🔧 故障排除 | 问题 | 原因 | 解决方案 | |:---|:---|:---| | ❌ `npm install` 失败 | Node.js 版本太旧 | 升级到 Node.js 20+ (`node -v`) | | ❌ 未返回文章 | 所有来源都宕机 | 检查 `/api/sources` 的健康状态 | | ❌ 缓存始终为空 | 无服务器冷启动 | 正常 — 空闲后的第一次请求较慢 | | ❌ 速率受限 (429) | 超过每分钟 100 个请求 | 等待 `X-RateLimit-Reset` 指定的秒数 | | ❌ CORS 错误 | 前端域名被阻止 | CORS 设置为 `*` — 检查浏览器扩展 | | ❌ RSS 订阅源为空 | 没有缓存的文章 | 先访问 `/api/news` 以填充缓存 | | ❌ 文章内容为空 | 来源阻止了解析 | 回退到“查看原始文章”链接 | | ❌ API 路由返回 404 | URL 格式错误 | 使用 `/api/news` 而不是 `/news` | | ❌ Vercel 部署失败 | 构建错误 | 先在本地检查 `npm run build` | | ❌ 测试失败 | 服务器未运行 | 先使用 `npm run dev` 启动服务器 | ### 🐛 调试模式 ``` # 启用详细日志运行 NODE_ENV=development npm run dev # 针对本地服务器运行测试 API_URL=http://localhost:3000 npm test # 检查缓存状态 curl http://localhost:3000/api/stats curl http://localhost:3000/api/health ``` ## ❓ 常见问题
📰 如何添加新的新闻来源?
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 处理所有事情。
## 🗺️ 路线图 ### 🎯 计划功能 - [ ] 🔐 **API 密钥认证** — 每用户的速率限制和使用情况跟踪 - [ ] 📊 **管理面板** — 用于缓存管理和来源监控的 Web UI - [ ] 🌙 **深色/浅色模式** — 落地页的主题切换 - [ ] 📱 **PWA 支持** — 在移动设备上安装为应用 - [ ] 🔔 **Webhook 通知** — 将新文章推送到 Discord/Slack - [ ] 📈 **分析** — 跟踪热门端点和搜索查询 - [ ] 🗄️ **数据库选项** — 可选的 Supabase/Postgres 用于持久化 - [ ] 🌐 **多语言** — 支持日语、韩语新闻来源 - [ ] 🤖 **AI 摘要** — 自动生成文章摘要 - [ ] 📦 **NPM 包** — 用于轻松集成的客户端 SDK ### ✅ 已完成 - [x] 📰 7 个新闻来源,支持并发获取 - [x] 💾 两级缓存(内存+磁盘) - [x] 🔍 全文搜索,带相关性评分 - [x] 📅 日期范围过滤 - [x] 🔄 基于游标的分页 - [x] 🗞️ RSS 2.0 订阅源 - [x] 📄 全文提取 - [x] 🏷️ 带计数的标签过滤 - [x] 📊 来源健康监控 - [x] 📋 OpenAPI 3.0.3 规范 - [x] 📡 SSE 流 - [x] 🔒 缓存清除认证 - [x] 🚀 一键 Vercel 部署 - [x] 📖 全面的文档 ### 🐛 报告 Bug 发现什么问题了吗? [提交一个 Issue](https://github.com/Shineii86/AniNewsAPI/issues) ### 💡 建议功能 对笔记本有什么想法吗? [开始一个讨论]( ### 🔀 提交 PR 准备好贡献代码了吗? [复刻并提交](https://github.com/Shineii86/AniNewsAPI/fork) ### 📋 指南 ### 🐛 报告 Bug 1. 首先检查现有的 [issues](https://github.com/Shineii86/AniNewsAPI/issues) 2. 创建一个新 issue,包含: - 清晰的标题和描述 - 重现步骤 - 预期与实际行为 - 使用的 API 端点和参数 ### 💡 建议功能 1. 检查 [路线图](#-roadmap) 了解计划功能 2. 提交一个 [功能请求](https://github.com/Shineii86/AniNewsAPI/issues/new),包含: - 功能的清晰描述 - 用例/动机 - 如果适用,给出示例 API 用法 ## 🙏 致谢 ### 📰 新闻来源 | 来源 | 简介 | |:---|:---| | [Anime News Network](https://www.animenewsnetwork.com/) | 行业领先的动漫新闻 | | [Anime Corner](https://animecorner.me/) | 社区驱动的动漫新闻与投票 | | [MyAnimeList](https://myanimelist.net/) | 最大的动漫/漫画数据库 | | [Otaku USA Magazine](https://otakuusamagazine.com/) | 英语动漫文化杂志 | | [Crunchyroll](https://www.crunchyroll.com/news) | 官方流媒体平台新闻 | | [Anime Herald](https://www.animeherald.com/) | 动漫新闻、评论与社论 | | [Comic Book](https://comicbook.com/anime/) | ComicBook 上的动漫与漫画报道 | ### 🛠️ 技术 - **[Express](https://expressjs.com/)** — 快速、无偏见的 Web 框架 - **[Cheerio](https://cheerio.js.org/)** — 快速、灵活的 HTML 解析 - **[Axios](https://axios-http.com/)** — 基于 Promise 的 HTTP 客户端 - **[rss-parser](https://github.com/rbren/rss-parser/)** — RSS/Atom 订阅源解析器 - **[node-cache](https://github.com/ptarjan/node-cache/)** — 内存缓存 - **[Vercel](https://vercel.com/)** — 无服务器部署平台 ### 📝 资源 - [Shields.io](https://shields.io/) — README 用的徽章 - [Star History](https://star-history.com/) — GitHub Star 历史图表 ## 📄 许可证
[![许可证:MIT](https://img.shields.io/badge/License-MIT-22c55e?style=for-the-badge&logo=mit&logoColor=white)](./LICENSE) 本项目根据 **MIT 许可证** 授权。 可自由使用、修改和分发 — 详情请见 [LICENSE](LICENSE) 文件。
## 👤 作者
Banner

Shinei Nouzen
Full-Stack Developer & Anime Enthusiast

GitHub Telegram Instagram Email

标签:API, DNS解析, Express, GNU通用公共许可证, MITM代理, Node.js, OSV, RSS订阅, Syscall, Vercel, Web开发, 全文提取, 内容聚合, 动漫, 开源项目, 技术栈, 接口服务, 数据服务, 数据聚合, 新闻, 智能缓存, 服务器less, 自定义脚本