添加接口

This commit is contained in:
imsyy 2023-03-14 16:04:10 +08:00
parent e9e156b785
commit 583f7f4b4f
23 changed files with 3186 additions and 0 deletions

5
.env Normal file
View File

@ -0,0 +1,5 @@
# 服务端口
PORT=6688
# 允许的域名
ALLOWED_DOMAIN = '*'

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
test.md

13
.hintrc Normal file
View File

@ -0,0 +1,13 @@
{
"extends": [
"development"
],
"hints": {
"meta-viewport": "off",
"disown-opener": "off",
"button-type": "off",
"axe/aria": "off",
"no-inline-styles": "off",
"axe/text-alternatives": "off"
}
}

154
README.md Normal file
View File

@ -0,0 +1,154 @@
<div align="center">
<img alt="logo" height="120" src="./public/favicon.png" width="120"/>
<h2>今日热榜</h2>
<p>一个聚合热门数据的 API 接口</p>
</div>
## 示例
> 这里是使用该 API 的示例站点
- [今日热榜 - https://hot.imsyy.top/](https://hot.imsyy.top/)
## 总览
> 🟢 状态正常
> 🟠 可能失效
> 🔴 无法使用
| **站点** | **类别** | **调用名称** | **状态** |
|--------|---------|-----------|--------|
| 哔哩哔哩 | 热门榜 | bilibili | 🟢 |
| 知乎 | 热榜 | zhihu | 🟢 |
| 百度 | 热搜榜 | baidu | 🟢 |
| 百度贴吧 | 热议榜 | tieba | 🟢 |
| 少数派 | 热榜 | sspai | 🟢 |
| IT之家 | 热榜 | ithome | 🟠 |
| 澎湃新闻 | 热榜 | thepaper | 🟢 |
| 今日头条 | 热榜 | toutiao | 🟢 |
| 微博热搜 | 热搜榜 | weibo | 🟢 |
| 36氪 | 热榜 | 36kr | 🟢 |
| 腾讯新闻 | 热点榜 | newsqq | 🔴 |
## 调用
### 获取榜单数据
> 获取数据只需在域名后面加上上方列表中的调用名称即可
```http
GET https://api-hot.imsyy.top/bilibili/
```
<details>
<summary>调用示例</summary>
```json
{
"code": 200,
"message": "获取成功",
"title": "哔哩哔哩", // 榜单名称
"subtitle": "热门榜", // 榜单类别
"from": "server", // 此处返回是最新数据还是缓存
"total": 100, // 数据总数
"updateTime": "2023-03-14T07:40:51.846Z", // 数据获取时间
"data": [
{
"id": "BV1E84y1A7z2",
"title": "假如我的校园是一款RPG游戏",
"desc": "所有取景都是在学校里面拍的,都是真实存在的场景哦!",
"pic": "http://i2.hdslb.com/bfs/archive/a24e442d0aae6d488db023c4ddcb450e9f2bf5f3.jpg",
"owner": {
"mid": 424658638,
"name": "四夕小田木_已黑化_",
"face": "https://i1.hdslb.com/bfs/face/afd9ba47933edc4842ccbeba2891a25465d1cf77.jpg"
},
"data": {
"aid": 610872610,
"view": 4178745,
"danmaku": 4229,
"reply": 5317,
"favorite": 91020,
"coin": 133596,
"share": 46227,
"now_rank": 0,
"his_rank": 1,
"like": 616519,
"dislike": 0,
"vt": 0,
"vv": 0
},
"url": "https://b23.tv/BV1E84y1A7z2",
"mobileUrl": "https://m.bilibili.com/video/BV1E84y1A7z2"
},
...
]
}
```
</details>
### 获取榜单最新数据
> 获取最新数据只需在原链接后面加上 `/new`,这样就会直接从服务端拉取最新数据,不会从本地缓存中读取
```http
GET https://api-hot.imsyy.top/bilibili/new
```
<details>
<summary>调用示例</summary>
```json
{
"code": 200,
"message": "获取成功",
"title": "哔哩哔哩", // 榜单名称
"subtitle": "热门榜", // 榜单类别
"total": 100, // 数据总数
"updateTime": "2023-03-14T07:40:51.846Z", // 数据获取时间
"data": [
{
"id": "BV1E84y1A7z2",
"title": "假如我的校园是一款RPG游戏",
"desc": "所有取景都是在学校里面拍的,都是真实存在的场景哦!",
"pic": "http://i2.hdslb.com/bfs/archive/a24e442d0aae6d488db023c4ddcb450e9f2bf5f3.jpg",
"owner": {
"mid": 424658638,
"name": "四夕小田木_已黑化_",
"face": "https://i1.hdslb.com/bfs/face/afd9ba47933edc4842ccbeba2891a25465d1cf77.jpg"
},
"data": {
"aid": 610872610,
"view": 4178745,
"danmaku": 4229,
"reply": 5317,
"favorite": 91020,
"coin": 133596,
"share": 46227,
"now_rank": 0,
"his_rank": 1,
"like": 616519,
"dislike": 0,
"vt": 0,
"vv": 0
},
"url": "https://b23.tv/BV1E84y1A7z2",
"mobileUrl": "https://m.bilibili.com/video/BV1E84y1A7z2"
},
...
]
}
```
</details>
## 其他
- 本项目为了避免频繁请求官方数据,默认对数据做了缓存处理,默认为 `30` 分钟,如需更改,请自行前往 `utils\cacheData.js` 文件修改
- 本项目部分接口使用了 **页面爬虫**,若违反对应页面的相关规则,请 **及时通知我去除该接口**
## 免责声明
- 本项目提供的 `API` 仅供开发者进行技术研究和开发测试使用。使用该 `API` 获取的信息仅供参考,不代表本项目对信息的准确性、可靠性、合法性、完整性作出任何承诺或保证。本项目不对任何因使用该 `API` 获取信息而导致的任何直接或间接损失负责。本项目保留随时更改 `API` 接口地址、接口协议、接口参数及其他相关内容的权利。本项目对使用者使用 `API` 的行为不承担任何直接或间接的法律责任
- 本项目并未与相关信息提供方建立任何关联或合作关系,获取的信息均来自公开渠道,如因使用该 `API` 获取信息而产生的任何法律责任,由使用者自行承担
- 本项目对使用 `API` 获取的信息进行了最大限度的筛选和整理,但不保证信息的准确性和完整性。使用 `API` 获取信息时,请务必自行核实信息的真实性和可靠性,谨慎处理相关事项
- 本项目保留对 `API` 的随时更改、停用、限制使用等措施的权利。任何因使用本 `API` 产生的损失,本项目不负担任何赔偿和责任

89
app.js Normal file
View File

@ -0,0 +1,89 @@
require("dotenv").config();
const Koa = require("koa");
const bodyParser = require("koa-bodyparser");
const cors = require("koa2-cors");
const serve = require("koa-static");
const views = require("koa-views");
const app = new Koa();
const net = require("net");
const router = require("./routes");
// 配置信息
let domain = process.env.ALLOWED_DOMAIN || "*";
let port = process.env.PORT || 6688;
// 解析请求体
app.use(bodyParser());
// 静态文件目录
app.use(serve(__dirname + "/public"));
app.use(views(__dirname + "/public"));
// 跨域
app.use(
cors({
origin: domain,
})
);
app.use(async (ctx, next) => {
if (domain === "*") {
await next();
} else {
if (ctx.headers.referer !== domain) {
ctx.status = 400;
ctx.body = {
code: 400,
message: "请通过正确的域名访问",
};
} else {
await next();
}
}
});
// 使用路由中间件
app.use(router.routes());
app.use(router.allowedMethods());
// 启动应用程序并监听端口
const startApp = (port) => {
app.listen(port, () => {
console.log(`成功在 ${port} 端口上运行`);
});
};
// 检测端口是否被占用
const checkPort = (port) => {
return new Promise((resolve, reject) => {
const server = net
.createServer()
.once("error", (err) => {
if (err.code === "EADDRINUSE") {
console.log(`端口 ${port} 已被占用, 正在尝试其他端口...`);
server.close();
resolve(false);
} else {
reject(err);
}
})
.once("listening", () => {
server.close();
resolve(true);
})
.listen(port);
});
};
// 尝试启动应用程序
const tryStartApp = async (port) => {
let isPortAvailable = await checkPort(port);
while (!isPortAvailable) {
port++;
isPortAvailable = await checkPort(port);
}
startApp(port);
};
tryStartApp(port);

23
package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "dailyhot_api",
"version": "0.0.1",
"description": "一个今日热榜 .",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"author": "imsyy",
"license": "ISC",
"dependencies": {
"axios": "^1.3.4",
"cheerio": "1.0.0-rc.12",
"dotenv": "^16.0.3",
"koa": "^2.14.1",
"koa-bodyparser": "^4.3.0",
"koa-router": "^12.0.0",
"koa-static": "^5.0.0",
"koa-views": "^8.0.0",
"koa2-cors": "^2.0.6",
"node-cache": "^5.1.2"
}
}

1131
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

195
public/404.html Normal file
View File

@ -0,0 +1,195 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>404 | Linkbook API</title>
<link
rel="shortcut icon"
href="https://img.imsyy.top/logo/imsyy.png"
type="image/x-icon"
/>
<link
rel="stylesheet"
href="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/font-awesome/6.0.0/css/all.min.css"
/>
<style>
:root {
--text-color: #000;
--text-color-gray: #888;
--text-color-hover: #fff;
--icon-color: #444;
}
html.dark-mode {
--text-color: #fff;
--text-color-gray: #888;
--text-color-hover: #3c3c3c;
--icon-color: #cbcbcb;
}
* {
margin: 0;
padding: 0;
-webkit-user-select: none;
user-select: none;
}
html {
height: 100%;
}
body {
background-color: var(--text-color-hover);
color: var(--text-color);
font-family: "PingFang SC", "Open Sans", "Microsoft YaHei", sans-serif;
transition: background-color 0.5s, color 0.5s;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
height: 100%;
}
.dark-mode body {
background-color: #2a2a2a;
}
main {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
a {
text-decoration: none;
color: var(--text-color-gray);
transition: all 0.5s ease;
}
a:hover {
color: var(--text-color);
}
.ico {
margin: 4rem 2rem;
font-size: 6rem;
color: var(--text-color-gray);
}
.title {
font-size: 2rem;
font-weight: bold;
}
.text {
margin: 1rem;
}
.control button {
background-color: var(--text-color-hover);
border: var(--text-color) solid;
border-radius: 4px;
padding: 0.5rem 1rem;
transition: all 0.5s ease;
margin: 1rem 0.2rem;
color: var(--text-color);
cursor: pointer;
}
.control button:hover {
border: var(--text-color) solid;
background: var(--text-color);
color: var(--text-color-hover);
}
.control button i {
margin-right: 6px;
}
footer {
line-height: 1.75rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 140px;
}
footer .social {
color: var(--icon-color);
font-size: 15px;
display: flex;
align-items: center;
cursor: pointer;
}
footer .social i {
margin: 0 12px;
}
footer .point::before {
content: "·";
color: var(--text-color-gray);
}
.power,
.icp {
font-size: 14px;
}
</style>
</head>
<body>
<main>
<div class="ico"><i class="fa-solid fa-circle-exclamation"></i></div>
<div class="title">404 Not Found</div>
<div class="text">请检查您的路径</div>
<div class="control">
<button onclick="window.location.href = '/'">
<i class="fa-solid fa-house"></i>
<span>回到首页</span>
</button>
</div>
</main>
<footer>
<div class="social">
<i class="fa-brands fa-github" onclick="socialJump('github')"></i>
<div class="point"></div>
<i class="fa-solid fa-house" onclick="socialJump('home')"></i>
<div class="point"></div>
<i class="fa-solid fa-envelope" onclick="socialJump('email')"></i>
</div>
<div class="power">
Copyright © 2020
<script>
document.write(" - " + new Date().getFullYear());
</script>
<a href="https://imsyy.top/" target="_blank">無名</a>
</div>
<div class="icp">
<a href="https://beian.miit.gov.cn/" target="_blank"
>豫ICP备2022018134号-1</a
>
</div>
</footer>
<script>
// 跟随系统主题
const darkModeMediaQuery = window.matchMedia(
"(prefers-color-scheme: dark)"
);
const toggleDarkMode = (darkModeMediaQuery) => {
if (darkModeMediaQuery.matches) {
document.documentElement.classList.add("dark-mode");
} else {
document.documentElement.classList.remove("dark-mode");
}
};
darkModeMediaQuery.addListener(toggleDarkMode);
toggleDarkMode(darkModeMediaQuery);
// 按钮事件
const clickFunction = () => {
window.location.href = "/api/links";
};
// 社交链接跳转
const socialJump = (type) => {
switch (type) {
case "github":
window.location.href = "https://github.com/imsyy/";
break;
case "home":
window.location.href = "https://www.imsyy.top/";
break;
case "email":
window.location.href = "mailto:one@imsyy.top";
break;
default:
break;
}
};
</script>
</body>
</html>

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

11
public/favicon.svg Normal file
View File

@ -0,0 +1,11 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024">
<title>favicon-svg</title>
<defs>
<image width="1024" height="1024" id="img1" href="data:image/png;base64,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"/>
</defs>
<style>
.s0 { fill: #ffffff }
</style>
<use id="Layer" href="#img1" x="0" y="0"/>
<path id="Layer" class="s0" d="m408.6 448.2v-0.5q0-0.8-0.1-1.7 0-0.9 0-1.7 0-0.9 0-1.7-0.1-0.9-0.1-1.8v0.3-1.1q0-0.3 0-0.5 0-0.3 0-0.6 0-0.3-0.1-0.6 0-0.2 0-0.5 0.1-1 0.1-2 0-0.9 0-1.9 0.1-1 0.1-1.9 0.1-1 0.2-1.9l-0.2 1.7 0.2-2.7c1.2-14 5-31.4 10.8-49.2 9.6-27.6 26.1-57.3 52.3-79.2 35.1-29.4 85.5-44.7 135.9-44.7 5.7 0 11 2.9 13.8 7.5 2.9 4.6 2.9 10.3 0.1 15-2 3.2-86.3 81.7 11.5 137.3 38.8 22.1 96.5 79.9 96.5 148.8 0 54-22.6 105-63.7 143.5-41.2 38.6-95.9 59.9-153.9 59.9-57.8 0-112.5-21.6-153.9-60.7q-7.3-6.9-14-14.4-6.6-7.6-12.4-15.7-5.9-8.2-10.9-16.8-5.1-8.7-9.3-17.8-4.2-9.2-7.4-18.7-3.2-9.6-5.3-19.4-2.2-9.8-3.3-19.8-1.1-10-1.1-20.1c0-52.3 26.3-110.3 62.9-119.4l0.2 11c0.1 3.4 0.2 6.7 0.5 10.1l0.5 6.8c0.4 3.5 0.8 7 1.3 10.7l1.2 7.8 1.4 8.5 0.8 4.6 1.8 10c1.6 8.8 4.2 18.8 7.7 30.1 2 6.5 6.5 12 12.5 15.1 6 3.2 13 3.9 19.5 1.9 6.5-2 11.9-6.5 15.1-12.5 3.2-6 3.8-13 1.8-19.5q-0.9-3-1.8-6-0.9-3.1-1.7-6.2-0.7-3-1.4-6.1-0.7-3.1-1.4-6.2l-2.3-13-1.3-7.5-1-6.8q0-0.4-0.1-0.8-0.1-0.4-0.1-0.8-0.1-0.4-0.1-0.8-0.1-0.4-0.1-0.8l-0.7-6-0.5-5.7q0-0.3-0.1-0.6 0-0.3 0-0.6 0-0.3 0-0.6-0.1-0.3-0.1-0.6l-0.2-5-0.1-6.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

201
public/index.html Normal file
View File

@ -0,0 +1,201 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>DailyHot API</title>
<link
rel="shortcut icon"
href="./favicon.svg"
type="image/x-icon"
/>
<link
rel="stylesheet"
href="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/font-awesome/6.0.0/css/all.min.css"
/>
<style>
:root {
--text-color: #000;
--text-color-gray: #888;
--text-color-hover: #fff;
--icon-color: #444;
}
html.dark-mode {
--text-color: #fff;
--text-color-gray: #888;
--text-color-hover: #3c3c3c;
--icon-color: #cbcbcb;
}
* {
margin: 0;
padding: 0;
-webkit-user-select: none;
user-select: none;
}
html {
height: 100%;
}
body {
background-color: var(--text-color-hover);
color: var(--text-color);
font-family: "PingFang SC", "Open Sans", "Microsoft YaHei", sans-serif;
transition: background-color 0.5s, color 0.5s;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
height: 100%;
}
.dark-mode body {
background-color: #2a2a2a;
}
main {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
a {
text-decoration: none;
color: var(--text-color-gray);
transition: all 0.5s ease;
}
a:hover {
color: var(--text-color);
}
.ico {
margin: 4rem 2rem;
font-size: 6rem;
color: var(--text-color-gray);
}
.title {
font-size: 2rem;
font-weight: bold;
}
.text {
margin: 1rem;
}
.control button {
background-color: var(--text-color-hover);
border: var(--text-color) solid;
border-radius: 4px;
padding: 0.5rem 1rem;
transition: all 0.5s ease;
margin: 1rem 0.2rem;
color: var(--text-color);
cursor: pointer;
}
.control button:hover {
border: var(--text-color) solid;
background: var(--text-color);
color: var(--text-color-hover);
}
.control button i {
margin-right: 6px;
}
footer {
line-height: 1.75rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 140px;
}
footer .social {
color: var(--icon-color);
font-size: 15px;
display: flex;
align-items: center;
cursor: pointer;
}
footer .social i {
margin: 0 12px;
}
footer .point::before {
content: "·";
color: var(--text-color-gray);
}
.power,
.icp {
font-size: 14px;
}
</style>
</head>
<body>
<main>
<div class="ico"><i class="fa-solid fa-code"></i></div>
<div class="title">HotDaily API</div>
<div class="text">服务已正常运行</div>
<div class="control">
<button onclick="clickFunction()">
<i class="fa-solid fa-vial"></i>
<span>测试接口</span>
</button>
<button
onclick="window.open(`https://www.apifox.cn/apidoc/shared-152aff39-4070-4ef7-8a53-d03940c5e2f3`)"
>
<i class="fa-solid fa-book"></i>
<span>接口文档</span>
</button>
</div>
</main>
<footer>
<div class="social">
<i class="fa-brands fa-github" onclick="socialJump('github')"></i>
<div class="point"></div>
<i class="fa-solid fa-house" onclick="socialJump('home')"></i>
<div class="point"></div>
<i class="fa-solid fa-envelope" onclick="socialJump('email')"></i>
</div>
<div class="power">
Copyright © 2020
<script>
document.write(" - " + new Date().getFullYear());
</script>
<a href="https://imsyy.top/" target="_blank">無名</a>
</div>
<div class="icp">
<a href="https://beian.miit.gov.cn/" target="_blank"
>豫ICP备2022018134号-1</a
>
</div>
</footer>
<script>
// 跟随系统主题
const darkModeMediaQuery = window.matchMedia(
"(prefers-color-scheme: dark)"
);
const toggleDarkMode = (darkModeMediaQuery) => {
if (darkModeMediaQuery.matches) {
document.documentElement.classList.add("dark-mode");
} else {
document.documentElement.classList.remove("dark-mode");
}
};
darkModeMediaQuery.addListener(toggleDarkMode);
toggleDarkMode(darkModeMediaQuery);
// 按钮事件
const clickFunction = () => {
window.location.href = "/bilibili";
};
// 社交链接跳转
const socialJump = (type) => {
switch (type) {
case "github":
window.location.href = "https://github.com/imsyy/";
break;
case "home":
window.location.href = "https://www.imsyy.top/";
break;
case "email":
window.location.href = "mailto:one@imsyy.top";
break;
default:
break;
}
};
</script>
</body>
</html>

129
routes/36kr.js Normal file
View File

@ -0,0 +1,129 @@
const Router = require("koa-router");
const krRouter = new Router();
const axios = require("axios");
const { get, set, del } = require("../utils/cacheData");
// 缓存键名
const cacheKey = "krData";
// 调用时间
let updateTime = new Date().toISOString();
// 调用路径
const url = "https://gateway.36kr.com/api/mis/nav/home/nav/rank/hot";
// 数据处理
const getData = (data) => {
if (!data) return [];
return data.map((v) => {
return {
id: v.itemId,
title: v.templateMaterial.widgetTitle,
pic: v.templateMaterial.widgetImage,
owner: v.templateMaterial.authorName,
hot: v.templateMaterial.statRead,
data: v.templateMaterial,
url: `https://www.36kr.com/p/${v.itemId}`,
mobileUrl: `https://www.36kr.com/p/${v.itemId}`,
};
});
};
// 36氪热榜
krRouter.get("/36kr", async (ctx) => {
console.log("获取36氪热榜");
try {
// 从缓存中获取数据
let data = await get(cacheKey);
const from = data ? "cache" : "server";
if (!data) {
// 如果缓存中不存在数据
console.log("从服务端重新获取36氪热榜");
// 从服务器拉取数据
const response = await axios.post(url, {
partner_id: "wap",
param: {
siteId: 1,
platformId: 2,
},
});
data = getData(response.data.data.hotRankList);
updateTime = new Date().toISOString();
// 将数据写入缓存
await set(cacheKey, data);
}
ctx.body = {
code: 200,
message: "获取成功",
title: "36氪",
subtitle: "热榜",
from,
total: data.length,
updateTime,
data,
};
} catch (error) {
console.error(error);
ctx.body = {
code: 500,
message: "获取失败",
};
}
});
// 36氪热榜 - 获取最新数据
krRouter.get("/36kr/new", async (ctx) => {
console.log("获取36氪热榜 - 最新数据");
try {
// 从服务器拉取最新数据
const response = await axios.post(url, {
partner_id: "wap",
param: {
siteId: 1,
platformId: 2,
},
});
const newData = getData(response.data.data.hotRankList);
updateTime = new Date().toISOString();
console.log("从服务端重新获取36氪热榜");
// 返回最新数据
ctx.body = {
code: 200,
message: "获取成功",
title: "36氪",
subtitle: "热榜",
total: newData.length,
updateTime,
data: newData,
};
// 删除旧数据
await del(cacheKey);
// 将最新数据写入缓存
await set(cacheKey, newData);
} catch (error) {
// 如果拉取最新数据失败,尝试从缓存中获取数据
console.error(error);
const cachedData = await get(cacheKey);
if (cachedData) {
ctx.body = {
code: 200,
message: "获取成功",
title: "36氪",
subtitle: "热榜",
total: cachedData.length,
updateTime,
data: cachedData,
};
} else {
// 如果缓存中也没有数据,则返回错误信息
ctx.body = {
code: 500,
message: "获取失败",
};
}
}
});
module.exports = krRouter;

132
routes/baidu.js Normal file
View File

@ -0,0 +1,132 @@
const Router = require("koa-router");
const baiduRouter = new Router();
const axios = require("axios");
const { get, set, del } = require("../utils/cacheData");
// 缓存键名
const cacheKey = "baiduData";
// 调用时间
let updateTime = new Date().toISOString();
// 调用路径
const url = "https://top.baidu.com/board?tab=realtime";
// 数据处理
const getData = (data) => {
if (!data) return [];
const dataList = [];
try {
const pattern = /<\!--s-data:(.*?)-->/s;
const matchResult = data.match(pattern);
const jsonObject = JSON.parse(matchResult[1]).cards[0].content;
jsonObject.forEach((v) => {
dataList.push({
title: v.query,
desc: v.desc,
pic: v.img,
hot: v.hotScore,
url: `https://www.baidu.com/s?wd=${encodeURIComponent(v.query)}`,
mobileUrl: v.url,
});
});
return dataList;
} catch (error) {
console.error("数据处理出错" + error);
return false;
}
};
// 百度热搜
baiduRouter.get("/baidu", async (ctx) => {
console.log("获取百度热搜");
try {
// 从缓存中获取数据
let data = await get(cacheKey);
const from = data ? "cache" : "server";
if (!data) {
// 如果缓存中不存在数据
console.log("从服务端重新获取百度热搜");
// 从服务器拉取数据
const response = await axios.get(url);
data = getData(response.data);
updateTime = new Date().toISOString();
if (!data) {
ctx.body = {
code: 500,
message: "获取失败",
};
return false;
}
// 将数据写入缓存
await set(cacheKey, data);
}
ctx.body = {
code: 200,
message: "获取成功",
title: "百度",
subtitle: "热搜榜",
from,
total: data.length,
updateTime,
data,
};
} catch (error) {
console.error(error);
ctx.body = {
code: 500,
message: "获取失败",
};
}
});
// 百度热搜 - 获取最新数据
baiduRouter.get("/baidu/new", async (ctx) => {
console.log("获取百度热搜 - 最新数据");
try {
// 从服务器拉取最新数据
const response = await axios.get(url);
const newData = getData(response.data);
updateTime = new Date().toISOString();
console.log("从服务端重新获取百度热搜");
// 返回最新数据
ctx.body = {
code: 200,
message: "获取成功",
title: "百度",
subtitle: "热搜榜",
total: newData.length,
updateTime,
data: newData,
};
// 删除旧数据
await del(cacheKey);
// 将最新数据写入缓存
await set(cacheKey, newData);
} catch (error) {
// 如果拉取最新数据失败,尝试从缓存中获取数据
console.error(error);
const cachedData = await get(cacheKey);
if (cachedData) {
ctx.body = {
code: 200,
message: "获取成功",
title: "百度",
subtitle: "热搜榜",
total: cachedData.length,
updateTime,
data: cachedData,
};
} else {
// 如果缓存中也没有数据,则返回错误信息
ctx.body = {
code: 500,
message: "获取失败",
};
}
}
});
module.exports = baiduRouter;

117
routes/bilibili.js Normal file
View File

@ -0,0 +1,117 @@
const Router = require("koa-router");
const bilibiliRouter = new Router();
const axios = require("axios");
const { get, set, del } = require("../utils/cacheData");
// 缓存键名
const cacheKey = "bilibiliData";
// 调用时间
let updateTime = new Date().toISOString();
// 调用路径
const url = "https://api.bilibili.com/x/web-interface/ranking/v2";
// 数据处理
const getData = (data) => {
if (!data) return [];
return data.map((v) => {
return {
id: v.bvid,
title: v.title,
desc: v.desc,
pic: v.pic,
owner: v.owner,
data: v.stat,
url: v.short_link,
mobileUrl: `https://m.bilibili.com/video/${v.bvid}`,
};
});
};
// 哔哩哔哩热门榜
bilibiliRouter.get("/bilibili", async (ctx) => {
console.log("获取哔哩哔哩热门榜");
try {
// 从缓存中获取数据
let data = await get(cacheKey);
const from = data ? "cache" : "server";
if (!data) {
// 如果缓存中不存在数据
console.log("从服务端重新获取哔哩哔哩热门榜");
// 从服务器拉取数据
const response = await axios.get(url);
data = getData(response.data.data.list);
updateTime = new Date().toISOString();
// 将数据写入缓存
await set(cacheKey, data);
}
ctx.body = {
code: 200,
message: "获取成功",
title: "哔哩哔哩",
subtitle: "热门榜",
from,
total: data.length,
updateTime,
data,
};
} catch (error) {
console.error(error);
ctx.body = {
code: 500,
message: "哔哩哔哩热门榜获取失败",
};
}
});
// 哔哩哔哩热门榜 - 获取最新数据
bilibiliRouter.get("/bilibili/new", async (ctx) => {
console.log("获取哔哩哔哩热门榜 - 最新数据");
try {
// 从服务器拉取最新数据
const response = await axios.get(url);
const newData = getData(response.data.data.list);
updateTime = new Date().toISOString();
console.log("从服务端重新获取哔哩哔哩热门榜");
// 返回最新数据
ctx.body = {
code: 200,
message: "获取成功",
title: "哔哩哔哩",
subtitle: "热门榜",
total: newData.length,
updateTime,
data: newData,
};
// 删除旧数据
await del(cacheKey);
// 将最新数据写入缓存
await set(cacheKey, newData);
} catch (error) {
// 如果拉取最新数据失败,尝试从缓存中获取数据
console.error(error);
const cachedData = await get(cacheKey);
if (cachedData) {
ctx.body = {
code: 200,
message: "获取成功",
title: "哔哩哔哩",
subtitle: "热门榜",
total: cachedData.length,
updateTime,
data: cachedData,
};
} else {
// 如果缓存中也没有数据,则返回错误信息
ctx.body = {
code: 500,
message: "哔哩哔哩热门榜获取失败",
};
}
}
});
module.exports = bilibiliRouter;

37
routes/index.js Normal file
View File

@ -0,0 +1,37 @@
const Router = require("koa-router");
const bilibiliRouter = require("./bilibili");
const zhihuRouter = require("./zhihu");
const baiduRouter = require("./baidu");
const weiboRouter = require("./weibo");
const itHomeRouter = require("./ithome");
const krRouter = require("./36kr");
const sspaiRouter = require("./sspai");
const tiebaRouter = require("./tieba");
const toutiaoRouter = require("./toutiao");
const thepaperRouter = require("./thepaper");
const router = new Router();
// 根目录
router.get("/", async (ctx) => {
await ctx.render("index");
});
router.use(bilibiliRouter.routes());
router.use(zhihuRouter.routes());
router.use(baiduRouter.routes());
router.use(weiboRouter.routes());
router.use(itHomeRouter.routes());
router.use(krRouter.routes());
router.use(sspaiRouter.routes());
router.use(tiebaRouter.routes());
router.use(toutiaoRouter.routes());
router.use(thepaperRouter.routes());
// 404 路由
router.use(async (ctx) => {
await ctx.render("404");
});
module.exports = router;

150
routes/ithome.js Normal file
View File

@ -0,0 +1,150 @@
const Router = require("koa-router");
const itHomeRouter = new Router();
const axios = require("axios");
const cheerio = require("cheerio");
const { get, set, del } = require("../utils/cacheData");
// 缓存键名
const cacheKey = "itHomeData";
// 调用时间
let updateTime = new Date().toISOString();
// 调用路径
const url = "https://m.ithome.com/rankm/";
const headers = {
"User-Agent":
"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1",
};
// it之家特殊处理 - url
const replaceLink = (url) => {
const match = url.match(/html\/(\d+)\.htm/)[1];
return `https://www.ithome.com/0/${match.slice(0, 3)}/${match.slice(3)}.htm`;
};
// 数据处理
const getData = (data) => {
if (!data) return false;
const dataList = {};
const $ = cheerio.load(data);
try {
$(".rank-name").each(function () {
const type = $(this).data("rank-type");
const newListHtml = $(this).next(".rank-box").html();
const newsList = cheerio
.load(newListHtml)(".placeholder")
.get()
.map((v) => {
return {
title: $(v).find(".plc-title").text(),
img: $(v).find("img").attr("data-original"),
time: $(v).find(".post-time").text(),
hot: $(v).find(".review-num").text(),
url: replaceLink($(v).find("a").attr("href")),
mobileUrl: $(v).find("a").attr("href"),
};
});
dataList[type] = {
name: $(this).text(),
total: newsList.length,
list: newsList,
};
});
return dataList;
} catch (error) {
console.error("数据处理出错" + error);
return false;
}
};
// IT之家热榜
itHomeRouter.get("/ithome", async (ctx) => {
console.log("获取IT之家热榜");
try {
// 从缓存中获取数据
let data = await get(cacheKey);
const from = data ? "cache" : "server";
if (!data) {
// 如果缓存中不存在数据
console.log("从服务端重新获取IT之家热榜");
// 从服务器拉取数据
const response = await axios.get(url, { headers });
data = getData(response.data);
updateTime = new Date().toISOString();
if (!data) {
ctx.body = {
code: 500,
message: "获取失败",
};
return false;
}
// 将数据写入缓存
await set(cacheKey, data);
}
ctx.body = {
code: 200,
message: "获取成功",
title: "IT之家",
subtitle: "热榜",
from,
updateTime,
data,
};
} catch (error) {
console.error(error);
ctx.body = {
code: 500,
message: "获取失败",
};
}
});
// IT之家热榜 - 获取最新数据
itHomeRouter.get("/ithome/new", async (ctx) => {
console.log("获取IT之家热榜 - 最新数据");
try {
// 从服务器拉取最新数据
const response = await axios.get(url, { headers });
const newData = getData(response.data);
updateTime = new Date().toISOString();
console.log("从服务端重新获取IT之家热榜");
// 返回最新数据
ctx.body = {
code: 200,
message: "获取成功",
title: "IT之家",
subtitle: "热榜",
updateTime,
data: newData,
};
// 删除旧数据
await del(cacheKey);
// 将最新数据写入缓存
await set(cacheKey, newData);
} catch (error) {
// 如果拉取最新数据失败,尝试从缓存中获取数据
console.error(error);
const cachedData = await get(cacheKey);
if (cachedData) {
ctx.body = {
code: 200,
message: "获取成功",
title: "IT之家",
subtitle: "热榜",
updateTime,
data: cachedData,
};
} else {
// 如果缓存中也没有数据,则返回错误信息
ctx.body = {
code: 500,
message: "获取失败",
};
}
}
});
module.exports = itHomeRouter;

117
routes/sspai.js Normal file
View File

@ -0,0 +1,117 @@
const Router = require("koa-router");
const sspaiRouter = new Router();
const axios = require("axios");
const { get, set, del } = require("../utils/cacheData");
// 缓存键名
const cacheKey = "krData";
// 调用时间
let updateTime = new Date().toISOString();
// 调用路径
const url = `https://sspai.com/api/v1/article/tag/page/get?limit=40&tag=热门文章`;
// 数据处理
const getData = (data) => {
if (!data) return [];
return data.map((v) => {
return {
id: v.id,
title: v.title,
desc: v.summary,
pic: `https://cdn.sspai.com/${v.banner}`,
owner: v.author,
hot: v.like_count,
url: `https://sspai.com/post/${v.id}`,
mobileUrl: `https://sspai.com/post/${v.itemId}`,
};
});
};
// 少数派热榜
sspaiRouter.get("/sspai", async (ctx) => {
console.log("获取少数派热榜");
try {
// 从缓存中获取数据
let data = await get(cacheKey);
const from = data ? "cache" : "server";
if (!data) {
// 如果缓存中不存在数据
console.log("从服务端重新获取少数派热榜");
// 从服务器拉取数据
const response = await axios.get(url);
data = getData(response.data.data);
updateTime = new Date().toISOString();
// 将数据写入缓存
await set(cacheKey, data);
}
ctx.body = {
code: 200,
message: "获取成功",
title: "少数派",
subtitle: "最热",
from,
total: data.length,
updateTime,
data,
};
} catch (error) {
console.error(error);
ctx.body = {
code: 500,
message: "获取失败",
};
}
});
// 少数派热榜 - 获取最新数据
sspaiRouter.get("/sspai/new", async (ctx) => {
console.log("获取少数派热榜 - 最新数据");
try {
// 从服务器拉取最新数据
const response = await axios.get(url);
const newData = getData(response.data.data);
updateTime = new Date().toISOString();
console.log("从服务端重新获取少数派热榜");
// 返回最新数据
ctx.body = {
code: 200,
message: "获取成功",
title: "少数派",
subtitle: "最热",
total: newData.length,
updateTime,
data: newData,
};
// 删除旧数据
await del(cacheKey);
// 将最新数据写入缓存
await set(cacheKey, newData);
} catch (error) {
// 如果拉取最新数据失败,尝试从缓存中获取数据
console.error(error);
const cachedData = await get(cacheKey);
if (cachedData) {
ctx.body = {
code: 200,
message: "获取成功",
title: "少数派",
subtitle: "最热",
total: cachedData.length,
updateTime,
data: cachedData,
};
} else {
// 如果缓存中也没有数据,则返回错误信息
ctx.body = {
code: 500,
message: "获取失败",
};
}
}
});
module.exports = sspaiRouter;

115
routes/thepaper.js Normal file
View File

@ -0,0 +1,115 @@
const Router = require("koa-router");
const thepaperRouter = new Router();
const axios = require("axios");
const { get, set, del } = require("../utils/cacheData");
// 缓存键名
const cacheKey = "thepaperData";
// 调用时间
let updateTime = new Date().toISOString();
// 调用路径
const url = "https://cache.thepaper.cn/contentapi/wwwIndex/rightSidebar";
// 数据处理
const getData = (data) => {
if (!data) return [];
return data.map((v) => {
return {
id: v.contId,
title: v.name,
pic: v.pic,
time: v.pubTime,
url: `https://www.thepaper.cn/newsDetail_forward_${v.contId}`,
mobileUrl: `https://m.thepaper.cn/newsDetail_forward_${v.contId}`,
};
});
};
// 澎湃热榜
thepaperRouter.get("/thepaper", async (ctx) => {
console.log("获取澎湃热榜");
try {
// 从缓存中获取数据
let data = await get(cacheKey);
const from = data ? "cache" : "server";
if (!data) {
// 如果缓存中不存在数据
console.log("从服务端重新获取澎湃热榜");
// 从服务器拉取数据
const response = await axios.get(url);
data = getData(response.data.data.hotNews);
updateTime = new Date().toISOString();
// 将数据写入缓存
await set(cacheKey, data);
}
ctx.body = {
code: 200,
message: "获取成功",
title: "澎湃新闻",
subtitle: "热榜",
from,
total: data.length,
updateTime,
data,
};
} catch (error) {
console.error(error);
ctx.body = {
code: 500,
message: "获取失败",
};
}
});
// 澎湃热榜 - 获取最新数据
thepaperRouter.get("/thepaper/new", async (ctx) => {
console.log("获取澎湃热榜 - 最新数据");
try {
// 从服务器拉取最新数据
const response = await axios.get(url);
const newData = getData(response.data.data.hotNews);
updateTime = new Date().toISOString();
console.log("从服务端重新获取澎湃热榜");
// 返回最新数据
ctx.body = {
code: 200,
message: "获取成功",
title: "澎湃新闻",
subtitle: "热榜",
total: newData.length,
updateTime,
data: newData,
};
// 删除旧数据
await del(cacheKey);
// 将最新数据写入缓存
await set(cacheKey, newData);
} catch (error) {
// 如果拉取最新数据失败,尝试从缓存中获取数据
console.error(error);
const cachedData = await get(cacheKey);
if (cachedData) {
ctx.body = {
code: 200,
message: "获取成功",
title: "澎湃新闻",
subtitle: "热榜",
total: cachedData.length,
updateTime,
data: cachedData,
};
} else {
// 如果缓存中也没有数据,则返回错误信息
ctx.body = {
code: 500,
message: "获取失败",
};
}
}
});
module.exports = thepaperRouter;

116
routes/tieba.js Normal file
View File

@ -0,0 +1,116 @@
const Router = require("koa-router");
const tiebaRouter = new Router();
const axios = require("axios");
const { get, set, del } = require("../utils/cacheData");
// 缓存键名
const cacheKey = "tiebaData";
// 调用时间
let updateTime = new Date().toISOString();
// 调用路径
const url = "https://tieba.baidu.com/hottopic/browse/topicList";
// 数据处理
const getData = (data) => {
if (!data) return [];
return data.map((v) => {
return {
id: v.topic_id,
title: v.topic_name,
desc: v.topic_desc,
pic: v.topic_pic,
hot: v.discuss_num,
url: v.topic_url,
mobileUrl: v.topic_url,
};
});
};
// 贴吧热议榜
tiebaRouter.get("/tieba", async (ctx) => {
console.log("获取贴吧热议榜");
try {
// 从缓存中获取数据
let data = await get(cacheKey);
const from = data ? "cache" : "server";
if (!data) {
// 如果缓存中不存在数据
console.log("从服务端重新获取贴吧热议榜");
// 从服务器拉取数据
const response = await axios.get(url);
data = getData(response.data.data.bang_topic.topic_list);
updateTime = new Date().toISOString();
// 将数据写入缓存
await set(cacheKey, data);
}
ctx.body = {
code: 200,
message: "获取成功",
title: "百度贴吧",
subtitle: "热议榜",
from,
total: data.length,
updateTime,
data,
};
} catch (error) {
console.error(error);
ctx.body = {
code: 500,
message: "获取失败",
};
}
});
// 贴吧热议榜 - 获取最新数据
tiebaRouter.get("/tieba/new", async (ctx) => {
console.log("获取贴吧热议榜 - 最新数据");
try {
// 从服务器拉取最新数据
const response = await axios.get(url);
const newData = getData(response.data.data.bang_topic.topic_list);
updateTime = new Date().toISOString();
console.log("从服务端重新获取贴吧热议榜");
// 返回最新数据
ctx.body = {
code: 200,
message: "获取成功",
title: "百度贴吧",
subtitle: "热议榜",
total: newData.length,
updateTime,
data: newData,
};
// 删除旧数据
await del(cacheKey);
// 将最新数据写入缓存
await set(cacheKey, newData);
} catch (error) {
// 如果拉取最新数据失败,尝试从缓存中获取数据
console.error(error);
const cachedData = await get(cacheKey);
if (cachedData) {
ctx.body = {
code: 200,
message: "获取成功",
title: "百度贴吧",
subtitle: "热议榜",
total: cachedData.length,
updateTime,
data: cachedData,
};
} else {
// 如果缓存中也没有数据,则返回错误信息
ctx.body = {
code: 500,
message: "获取失败",
};
}
}
});
module.exports = tiebaRouter;

115
routes/toutiao.js Normal file
View File

@ -0,0 +1,115 @@
const Router = require("koa-router");
const toutiaoRouter = new Router();
const axios = require("axios");
const { get, set, del } = require("../utils/cacheData");
// 缓存键名
const cacheKey = "toutiaoData";
// 调用时间
let updateTime = new Date().toISOString();
// 调用路径
const url = "https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc";
// 数据处理
const getData = (data) => {
if (!data) return [];
return data.map((v) => {
return {
id: v.ClusterId,
title: v.Title,
pic: v.Image.url,
hot: v.HotValue,
url: `https://www.toutiao.com/trending/${v.ClusterIdStr}/`,
mobileUrl: `https://api.toutiaoapi.com/feoffline/amos_land/new/html/main/index.html?topic_id=${v.ClusterIdStr}`,
};
});
};
// 头条热榜
toutiaoRouter.get("/toutiao", async (ctx) => {
console.log("获取头条热榜");
try {
// 从缓存中获取数据
let data = await get(cacheKey);
const from = data ? "cache" : "server";
if (!data) {
// 如果缓存中不存在数据
console.log("从服务端重新获取头条热榜");
// 从服务器拉取数据
const response = await axios.get(url);
data = getData(response.data.data);
updateTime = new Date().toISOString();
// 将数据写入缓存
await set(cacheKey, data);
}
ctx.body = {
code: 200,
message: "获取成功",
title: "今日头条",
subtitle: "热榜",
from,
total: data.length,
updateTime,
data,
};
} catch (error) {
console.error(error);
ctx.body = {
code: 500,
message: "获取失败",
};
}
});
// 头条热榜 - 获取最新数据
toutiaoRouter.get("/toutiao/new", async (ctx) => {
console.log("获取头条热榜 - 最新数据");
try {
// 从服务器拉取最新数据
const response = await axios.get(url);
const newData = getData(response.data.data);
updateTime = new Date().toISOString();
console.log("从服务端重新获取头条热榜");
// 返回最新数据
ctx.body = {
code: 200,
message: "获取成功",
title: "今日头条",
subtitle: "热榜",
total: newData.length,
updateTime,
data: newData,
};
// 删除旧数据
await del(cacheKey);
// 将最新数据写入缓存
await set(cacheKey, newData);
} catch (error) {
// 如果拉取最新数据失败,尝试从缓存中获取数据
console.error(error);
const cachedData = await get(cacheKey);
if (cachedData) {
ctx.body = {
code: 200,
message: "获取成功",
title: "今日头条",
subtitle: "热榜",
total: cachedData.length,
updateTime,
data: cachedData,
};
} else {
// 如果缓存中也没有数据,则返回错误信息
ctx.body = {
code: 500,
message: "获取失败",
};
}
}
});
module.exports = toutiaoRouter;

128
routes/weibo.js Normal file
View File

@ -0,0 +1,128 @@
const Router = require("koa-router");
const weiboRouter = new Router();
const axios = require("axios");
// const cheerio = require("cheerio");
const { get, set, del } = require("../utils/cacheData");
// 缓存键名
const cacheKey = "weiboData";
// 调用时间
let updateTime = new Date().toISOString();
// 调用路径
const url = "https://weibo.com/ajax/side/hotSearch";
// 数据处理
const getData = (data) => {
if (!data) return [];
// return data;
return data.map((v) => {
const key = v.word_scheme ? v.word_scheme : `#${v.word}`;
return {
title: v.word,
desc: key,
hot: v.raw_hot,
url: `https://s.weibo.com/weibo?q=${encodeURIComponent(
key
)}&t=31&band_rank=1&Refer=top`,
mobileUrl: `https://s.weibo.com/weibo?q=${encodeURIComponent(
key
)}&t=31&band_rank=1&Refer=top`,
};
});
};
// 微博热搜
weiboRouter.get("/weibo", async (ctx) => {
console.log("获取微博热搜");
try {
// 从缓存中获取数据
let data = await get(cacheKey);
const from = data ? "cache" : "server";
if (!data) {
// 如果缓存中不存在数据
console.log("从服务端重新获取微博热搜");
// 从服务器拉取数据
const response = await axios.get(url);
data = getData(response.data.data.realtime);
updateTime = new Date().toISOString();
if (!data) {
ctx.body = {
code: 500,
message: "获取失败",
};
return false;
}
// 将数据写入缓存
await set(cacheKey, data);
}
ctx.body = {
code: 200,
message: "获取成功",
title: "微博",
subtitle: "热搜榜",
from,
total: data.length,
updateTime,
data,
};
} catch (error) {
console.error(error);
ctx.body = {
code: 500,
message: "获取失败",
};
}
});
// 微博热搜 - 获取最新数据
weiboRouter.get("/weibo/new", async (ctx) => {
console.log("获取微博热搜 - 最新数据");
try {
// 从服务器拉取最新数据
const response = await axios.get(url);
const newData = getData(response.data.data.realtime);
updateTime = new Date().toISOString();
console.log("从服务端重新获取微博热搜");
// 返回最新数据
ctx.body = {
code: 200,
message: "获取成功",
title: "微博",
subtitle: "热搜榜",
total: newData.length,
updateTime,
data: newData,
};
// 删除旧数据
await del(cacheKey);
// 将最新数据写入缓存
await set(cacheKey, newData);
} catch (error) {
// 如果拉取最新数据失败,尝试从缓存中获取数据
console.error(error);
const cachedData = await get(cacheKey);
if (cachedData) {
ctx.body = {
code: 200,
message: "获取成功",
title: "微博",
subtitle: "热搜榜",
total: cachedData.length,
updateTime,
data: cachedData,
};
} else {
// 如果缓存中也没有数据,则返回错误信息
ctx.body = {
code: 500,
message: "获取失败",
};
}
}
});
module.exports = weiboRouter;

137
routes/zhihu.js Normal file
View File

@ -0,0 +1,137 @@
const Router = require("koa-router");
const zhihuRouter = new Router();
const axios = require("axios");
const { get, set, del } = require("../utils/cacheData");
// 缓存键名
const cacheKey = "zhihuData";
// 调用时间
let updateTime = new Date().toISOString();
// 调用路径
const url = "https://www.zhihu.com/hot";
const headers = {
"User-Agent":
"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1",
};
// 数据处理
const getData = (data) => {
if (!data) return [];
const dataList = [];
try {
const pattern =
/<script id="js-initialData" type="text\/json">(.*?)<\/script>/;
const matchResult = data.match(pattern);
const jsonObject = JSON.parse(matchResult[1]).initialState.topstory.hotList;
jsonObject.forEach((v) => {
dataList.push({
title: v.target.titleArea.text,
desc: v.target.excerptArea.text,
pic: v.target.imageArea.url,
hot: v.target.metricsArea.text,
url: v.target.link.url,
mobileUrl: v.target.link.url,
});
});
return dataList;
} catch (error) {
console.error("数据处理出错" + error);
return false;
}
};
// 知乎热榜
zhihuRouter.get("/zhihu", async (ctx) => {
console.log("获取知乎热榜");
try {
// 从缓存中获取数据
let data = await get(cacheKey);
const from = data ? "cache" : "server";
if (!data) {
// 如果缓存中不存在数据
console.log("从服务端重新获取知乎热榜");
// 从服务器拉取数据
const response = await axios.get(url, { headers });
data = getData(response.data);
updateTime = new Date().toISOString();
if (!data) {
ctx.body = {
code: 500,
message: "获取失败",
};
return false;
}
// 将数据写入缓存
await set(cacheKey, data);
}
ctx.body = {
code: 200,
message: "获取成功",
title: "知乎",
subtitle: "热榜",
from,
total: data.length,
updateTime,
data,
};
} catch (error) {
console.error(error);
ctx.body = {
code: 500,
message: "获取失败",
};
}
});
// 知乎热榜 - 获取最新数据
zhihuRouter.get("/zhihu/new", async (ctx) => {
console.log("获取知乎热榜 - 最新数据");
try {
// 从服务器拉取最新数据
const response = await axios.get(url, { headers });
const newData = getData(response.data);
updateTime = new Date().toISOString();
console.log("从服务端重新获取知乎热榜");
// 返回最新数据
ctx.body = {
code: 200,
message: "获取成功",
title: "知乎",
subtitle: "热榜",
total: newData.length,
updateTime,
data: newData,
};
// 删除旧数据
await del(cacheKey);
// 将最新数据写入缓存
await set(cacheKey, newData);
} catch (error) {
// 如果拉取最新数据失败,尝试从缓存中获取数据
console.error(error);
const cachedData = await get(cacheKey);
if (cachedData) {
ctx.body = {
code: 200,
message: "获取成功",
title: "知乎",
subtitle: "热榜",
total: cachedData.length,
updateTime,
data: cachedData,
};
} else {
// 如果缓存中也没有数据,则返回错误信息
ctx.body = {
code: 500,
message: "获取失败",
};
}
}
});
module.exports = zhihuRouter;

41
utils/cacheData.js Normal file
View File

@ -0,0 +1,41 @@
const NodeCache = require("node-cache");
const cache = new NodeCache({
stdTTL: 1800, // 缓存默认过期时间(单位秒)
checkperiod: 60, // 定期检查过期缓存的时间(单位秒)
});
/**
* 从缓存中获取数据
* @param {string} key 缓存键值
* @return {Promise<any>} 数据
*/
const get = async (key) => {
return cache.get(key);
};
/**
* 将数据写入缓存
* @param {string} key 缓存键值
* @param {any} value 数据
* @param {number} ttl 有效期单位秒默认为300秒
* @return {Promise<void>} 无返回值
*/
const set = async (key, value, ttl = 300) => {
return cache.set(key, value, ttl);
};
/**
* 从缓存中删除数据
* @param {string} key 缓存键值
* @return {Promise<void>} 无返回值
*/
const del = async (key) => {
return cache.del(key);
};
module.exports = {
get,
set,
del,
};