七十二家房客-观看平台v1.1
This commit is contained in:
parent
b77ff62c58
commit
a20c6ad819
261
README.md
261
README.md
@ -1,42 +1,259 @@
|
||||
# 72QishierPlayer
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
一个基于 **Vue3 + Vite + Node.js + Playwright + HLS.js** 的《七十二家房客》在线播放项目。
|
||||
|
||||
## Recommended IDE Setup
|
||||
本项目通过 **浏览器自动采集官网数据 → 本地缓存 JSON → Node API 提供 → Vue 前端播放** 的方式,解决官网接口 **签名、401、分页限制** 等问题,实现稳定播放。
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
---
|
||||
|
||||
## Recommended Browser Setup
|
||||
# 项目特点
|
||||
|
||||
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||
- Firefox:
|
||||
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||
- 自动采集《七十二家房客》节目列表
|
||||
- 自动滚动加载更多节目
|
||||
- 自动去重并缓存到 JSON
|
||||
- Node API 提供数据
|
||||
- Vue3 播放器展示
|
||||
- HLS.js 播放 m3u8 视频
|
||||
- Cookie 记录上次观看进度
|
||||
- 前端分页加载,避免一次加载过多图片
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
---
|
||||
获取的空数据
|
||||
```json
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
{
|
||||
"updatedAt": "2026-03-10T18:00:00Z",
|
||||
"name": "七十二家房客",
|
||||
"items": []
|
||||
}
|
||||
```
|
||||
|
||||
## Customize configuration
|
||||
# 安装
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
## 1 安装前端依赖
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
安装播放器依赖:
|
||||
|
||||
```sh
|
||||
```
|
||||
npm install axios hls.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2 安装后端依赖
|
||||
|
||||
进入后端目录:
|
||||
|
||||
```
|
||||
cd backServer
|
||||
```
|
||||
|
||||
安装依赖:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
安装 Playwright 浏览器:
|
||||
|
||||
```
|
||||
npx playwright install chromium
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 运行项目
|
||||
|
||||
## 1 采集节目数据
|
||||
|
||||
```
|
||||
npm run capture
|
||||
```
|
||||
|
||||
程序会自动:
|
||||
|
||||
1. 启动浏览器
|
||||
2. 打开页面
|
||||
|
||||
```
|
||||
https://www1.gdtv.cn/tvColumn/768
|
||||
```
|
||||
|
||||
3. 自动滚动页面
|
||||
4. 自动点击 **加载更多**
|
||||
5. 捕获接口数据
|
||||
6. 写入缓存
|
||||
|
||||
缓存文件:
|
||||
|
||||
```
|
||||
backServer/data/qishier-cache.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2 启动 Node API
|
||||
|
||||
在 `backServer` 目录运行:
|
||||
|
||||
```
|
||||
npm run server
|
||||
```
|
||||
|
||||
API 地址:
|
||||
|
||||
```
|
||||
http://localhost:3000/api/qishier/all
|
||||
```
|
||||
|
||||
返回示例:
|
||||
|
||||
```
|
||||
{
|
||||
"total": 1141,
|
||||
"list": []
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3 启动 Vue 前端
|
||||
|
||||
回到项目根目录:地址:http://localhost:5173
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
---
|
||||
|
||||
# 项目架构
|
||||
|
||||
```
|
||||
官网接口
|
||||
gdtv-api.gdtv.cn
|
||||
│
|
||||
▼
|
||||
Playwright 浏览器采集
|
||||
│
|
||||
▼
|
||||
本地 JSON 缓存
|
||||
backServer/data/qishier-cache.json
|
||||
│
|
||||
▼
|
||||
Node API
|
||||
http://localhost:3000/api/qishier/all
|
||||
│
|
||||
▼
|
||||
Vue 播放器
|
||||
http://localhost:5173
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 项目目录
|
||||
|
||||
```
|
||||
72QishierPlayer
|
||||
│
|
||||
├─ src
|
||||
│ ├─ api
|
||||
│ │ └─ qishier.ts
|
||||
│ │
|
||||
│ ├─ views
|
||||
│ │ └─ QishierPlayer.vue
|
||||
│ │
|
||||
│ ├─ App.vue
|
||||
│ ├─ main.ts
|
||||
│ └─ assets
|
||||
│
|
||||
├─ backServer
|
||||
│ │
|
||||
│ ├─ data
|
||||
│ │ └─ qishier-cache.json
|
||||
│ │
|
||||
│ ├─ capture.js
|
||||
│ ├─ server.js
|
||||
│ └─ package.json
|
||||
│
|
||||
├─ vite.config.ts
|
||||
└─ package.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
# 播放器功能
|
||||
|
||||
播放器支持:
|
||||
|
||||
- m3u8 视频播放
|
||||
- HLS.js 播放器
|
||||
- 节目列表分页加载
|
||||
- 懒加载封面图
|
||||
- 自动播放
|
||||
- 自动恢复观看进度
|
||||
|
||||
观看记录存储在 Cookie:
|
||||
|
||||
```
|
||||
qishier_last_episode
|
||||
```
|
||||
|
||||
刷新页面会自动恢复上次观看。
|
||||
|
||||
---
|
||||
|
||||
# 数据缓存机制
|
||||
|
||||
缓存文件:
|
||||
|
||||
```
|
||||
backServer/data/qishier-cache.json
|
||||
```
|
||||
|
||||
缓存策略:
|
||||
|
||||
- 新数据自动追加
|
||||
- 根据 `id` 自动去重
|
||||
- 避免重复节目
|
||||
|
||||
---
|
||||
|
||||
# 技术栈
|
||||
|
||||
前端:
|
||||
|
||||
- Vue 3
|
||||
- Vite
|
||||
- TypeScript
|
||||
- Axios
|
||||
- HLS.js
|
||||
|
||||
后端:
|
||||
|
||||
- Node.js
|
||||
- Express
|
||||
- Playwright
|
||||
|
||||
数据:
|
||||
|
||||
- JSON 本地缓存
|
||||
|
||||
---
|
||||
|
||||
# 免责声明
|
||||
|
||||
本项目仅用于:
|
||||
|
||||
- 技术学习
|
||||
- 数据采集研究
|
||||
- 视频播放技术演示
|
||||
|
||||
请勿用于商业用途。
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import { chromium } from 'playwright'
|
||||
import readlineSync from 'readline-sync'
|
||||
import { ensureDataFiles, readCache, writeCache, writeStatus } from './lib/cache.js'
|
||||
import {
|
||||
ensureDataFiles,
|
||||
readYearData,
|
||||
writeYearData,
|
||||
writeStatus
|
||||
} from './lib/cache.js'
|
||||
|
||||
const PAGE_URL = 'https://www1.gdtv.cn/tvColumn/768'
|
||||
|
||||
@ -25,41 +30,45 @@ function parseEpisodeItem(rawItem) {
|
||||
}
|
||||
}
|
||||
|
||||
function mergeCache(oldCache, responseUrl, data) {
|
||||
const url = new URL(responseUrl)
|
||||
const currentPage = Number(url.searchParams.get('currentPage') || 1)
|
||||
const beginScore = Number(url.searchParams.get('beginScore') || 0)
|
||||
function getYearByTimestamp(timestamp) {
|
||||
if (!timestamp) return new Date().getFullYear()
|
||||
return new Date(timestamp).getFullYear()
|
||||
}
|
||||
|
||||
const parsedItems = (data.list || [])
|
||||
.map(parseEpisodeItem)
|
||||
.filter(Boolean)
|
||||
function saveItemsByYear(items) {
|
||||
const yearGroups = new Map()
|
||||
|
||||
const itemMap = new Map((oldCache.items || []).map(item => [item.id, item]))
|
||||
for (const item of parsedItems) {
|
||||
itemMap.set(item.id, item)
|
||||
for (const item of items) {
|
||||
const year = getYearByTimestamp(item.releasedAt)
|
||||
if (!yearGroups.has(year)) {
|
||||
yearGroups.set(year, [])
|
||||
}
|
||||
yearGroups.get(year).push(item)
|
||||
}
|
||||
|
||||
return {
|
||||
...oldCache,
|
||||
updatedAt: new Date().toISOString(),
|
||||
name: data.name || oldCache.name || '七十二家房客',
|
||||
coverUrl: data.coverUrl || oldCache.coverUrl || '',
|
||||
displayType: data.displayType ?? oldCache.displayType ?? 0,
|
||||
beginScoreMap: {
|
||||
...(oldCache.beginScoreMap || {}),
|
||||
[currentPage]: beginScore
|
||||
},
|
||||
pages: {
|
||||
...(oldCache.pages || {}),
|
||||
[currentPage]: {
|
||||
currentPage,
|
||||
beginScore,
|
||||
count: parsedItems.length,
|
||||
capturedAt: new Date().toISOString()
|
||||
}
|
||||
},
|
||||
items: Array.from(itemMap.values()).sort((a, b) => (b.releasedAt || 0) - (a.releasedAt || 0))
|
||||
let totalUniqueAdded = 0
|
||||
|
||||
for (const [year, groupItems] of yearGroups.entries()) {
|
||||
const oldData = readYearData(year)
|
||||
const itemMap = new Map((oldData.items || []).map(item => [item.id, item]))
|
||||
const beforeCount = itemMap.size
|
||||
|
||||
for (const item of groupItems) {
|
||||
itemMap.set(item.id, item)
|
||||
}
|
||||
|
||||
const nextItems = Array.from(itemMap.values()).sort((a, b) => (b.releasedAt || 0) - (a.releasedAt || 0))
|
||||
const afterCount = nextItems.length
|
||||
totalUniqueAdded += afterCount - beforeCount
|
||||
|
||||
writeYearData(year, {
|
||||
year,
|
||||
updatedAt: new Date().toISOString(),
|
||||
items: nextItems
|
||||
})
|
||||
}
|
||||
|
||||
return totalUniqueAdded
|
||||
}
|
||||
|
||||
function log(...args) {
|
||||
@ -270,23 +279,25 @@ async function main() {
|
||||
|
||||
try {
|
||||
const data = await response.json()
|
||||
const oldCache = readCache()
|
||||
const nextCache = mergeCache(oldCache, url, data)
|
||||
writeCache(nextCache)
|
||||
const parsedItems = (data.list || [])
|
||||
.map(parseEpisodeItem)
|
||||
.filter(Boolean)
|
||||
|
||||
const addedCount = saveItemsByYear(parsedItems)
|
||||
|
||||
const totalItems = parsedItems.length
|
||||
const currentPage = new URL(url).searchParams.get('currentPage')
|
||||
const capturedPages = Object.keys(nextCache.pages || {}).length
|
||||
const totalItems = nextCache.items?.length || 0
|
||||
|
||||
writeStatus({
|
||||
running: true,
|
||||
lastMessage: `已采集第 ${currentPage} 页`,
|
||||
updatedAt: new Date().toISOString(),
|
||||
capturedPages,
|
||||
totalItems
|
||||
currentPage,
|
||||
pageItems: totalItems,
|
||||
addedCount
|
||||
})
|
||||
|
||||
log(`采集成功: page=${currentPage} totalItems=${totalItems}`)
|
||||
log(`采集成功: page=${currentPage} 本页=${totalItems} 新增唯一=${addedCount}`)
|
||||
} catch (error) {
|
||||
console.error('[capture] 解析响应失败:', error)
|
||||
}
|
||||
@ -322,7 +333,7 @@ async function main() {
|
||||
updatedAt: new Date().toISOString()
|
||||
})
|
||||
|
||||
log('自动采集完成,数据已写入 data/qishier-cache.json')
|
||||
log('自动采集完成,数据已按年份写入 data/*.json')
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"running": true,
|
||||
"lastMessage": "已采集第 2 页",
|
||||
"updatedAt": "2026-03-10T19:14:36.004Z",
|
||||
"capturedPages": 26,
|
||||
"totalItems": 1141
|
||||
}
|
||||
0
backServer/data/.gitkeep
Normal file
0
backServer/data/.gitkeep
Normal file
45607
backServer/data/2022.json
Normal file
45607
backServer/data/2022.json
Normal file
File diff suppressed because it is too large
Load Diff
49730
backServer/data/2023.json
Normal file
49730
backServer/data/2023.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
12561
backServer/data/2025.json
Normal file
12561
backServer/data/2025.json
Normal file
File diff suppressed because it is too large
Load Diff
347
backServer/data/2026.json
Normal file
347
backServer/data/2026.json
Normal file
@ -0,0 +1,347 @@
|
||||
{
|
||||
"year": 2026,
|
||||
"updatedAt": "2026-03-10T21:02:47.172Z",
|
||||
"items": [
|
||||
{
|
||||
"id": "5abbc88ea530b5db6438a4e584e80281",
|
||||
"title": "七十二家房客:疍家盘粉(上)20260223",
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/679165294b806fc4ac81fc17994e3d52.jpg",
|
||||
"releasedAt": 1771863480000,
|
||||
"timeLength": 1255,
|
||||
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192283084.m3u8",
|
||||
"raw": {
|
||||
"id": "5abbc88ea530b5db6438a4e584e80281",
|
||||
"title": "七十二家房客:疍家盘粉(上)20260223",
|
||||
"titleStyle": null,
|
||||
"titleColor": null,
|
||||
"subTitle": "",
|
||||
"preSubTitle": null,
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/679165294b806fc4ac81fc17994e3d52.jpg",
|
||||
"contentType": 3,
|
||||
"releasedAt": 1771863480000,
|
||||
"readCount": 0,
|
||||
"author": null,
|
||||
"objectType": 0,
|
||||
"memberNum": 0,
|
||||
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192283084.m3u8\"}",
|
||||
"timeLength": 1255,
|
||||
"imageCount": 0,
|
||||
"recentVisitTime": 0,
|
||||
"keyword": null,
|
||||
"serialsInfo": null,
|
||||
"crawlUrl": null,
|
||||
"newsList": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "322b2856a22948d27637c11292cae81f",
|
||||
"title": "七十二家房客:二五八遇险记(下)20260223",
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/9d93bc4b040f63bfb58e452e18701b99.jpg",
|
||||
"releasedAt": 1771862220000,
|
||||
"timeLength": 1258,
|
||||
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192274092.m3u8",
|
||||
"raw": {
|
||||
"id": "322b2856a22948d27637c11292cae81f",
|
||||
"title": "七十二家房客:二五八遇险记(下)20260223",
|
||||
"titleStyle": null,
|
||||
"titleColor": null,
|
||||
"subTitle": "",
|
||||
"preSubTitle": null,
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/9d93bc4b040f63bfb58e452e18701b99.jpg",
|
||||
"contentType": 3,
|
||||
"releasedAt": 1771862220000,
|
||||
"readCount": 0,
|
||||
"author": null,
|
||||
"objectType": 0,
|
||||
"memberNum": 0,
|
||||
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192274092.m3u8\"}",
|
||||
"timeLength": 1258,
|
||||
"imageCount": 0,
|
||||
"recentVisitTime": 0,
|
||||
"keyword": null,
|
||||
"serialsInfo": null,
|
||||
"crawlUrl": null,
|
||||
"newsList": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "173f132bd10460b2bc87518a749da720",
|
||||
"title": "七十二家房客:二五八遇险记(上)20260223",
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/af20658e9ee1fc2426e5eb26fd27b3fa.jpg",
|
||||
"releasedAt": 1771860960000,
|
||||
"timeLength": 1250,
|
||||
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192240974.m3u8",
|
||||
"raw": {
|
||||
"id": "173f132bd10460b2bc87518a749da720",
|
||||
"title": "七十二家房客:二五八遇险记(上)20260223",
|
||||
"titleStyle": null,
|
||||
"titleColor": null,
|
||||
"subTitle": "",
|
||||
"preSubTitle": null,
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/af20658e9ee1fc2426e5eb26fd27b3fa.jpg",
|
||||
"contentType": 3,
|
||||
"releasedAt": 1771860960000,
|
||||
"readCount": 0,
|
||||
"author": null,
|
||||
"objectType": 0,
|
||||
"memberNum": 0,
|
||||
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192240974.m3u8\"}",
|
||||
"timeLength": 1250,
|
||||
"imageCount": 0,
|
||||
"recentVisitTime": 0,
|
||||
"keyword": null,
|
||||
"serialsInfo": null,
|
||||
"crawlUrl": null,
|
||||
"newsList": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ae071bfb70913eafff08119e47e7d2bf",
|
||||
"title": "七十二家房客:残疾的阿香(下)20260223",
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/2850e51e6a4909b45cd6e6492d0fa31c.jpg",
|
||||
"releasedAt": 1771859700000,
|
||||
"timeLength": 1248,
|
||||
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192228993.m3u8",
|
||||
"raw": {
|
||||
"id": "ae071bfb70913eafff08119e47e7d2bf",
|
||||
"title": "七十二家房客:残疾的阿香(下)20260223",
|
||||
"titleStyle": null,
|
||||
"titleColor": null,
|
||||
"subTitle": "",
|
||||
"preSubTitle": null,
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/2850e51e6a4909b45cd6e6492d0fa31c.jpg",
|
||||
"contentType": 3,
|
||||
"releasedAt": 1771859700000,
|
||||
"readCount": 0,
|
||||
"author": null,
|
||||
"objectType": 0,
|
||||
"memberNum": 0,
|
||||
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192228993.m3u8\"}",
|
||||
"timeLength": 1248,
|
||||
"imageCount": 0,
|
||||
"recentVisitTime": 0,
|
||||
"keyword": null,
|
||||
"serialsInfo": null,
|
||||
"crawlUrl": null,
|
||||
"newsList": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "6741c8888b69e273bc2fc9e402806bc3",
|
||||
"title": "七十二家房客:残疾的阿香(上)20260223",
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/f628992ecb2e497c43499ac1194a14aa.jpg",
|
||||
"releasedAt": 1771858380000,
|
||||
"timeLength": 1254,
|
||||
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192199043.m3u8",
|
||||
"raw": {
|
||||
"id": "6741c8888b69e273bc2fc9e402806bc3",
|
||||
"title": "七十二家房客:残疾的阿香(上)20260223",
|
||||
"titleStyle": null,
|
||||
"titleColor": null,
|
||||
"subTitle": "",
|
||||
"preSubTitle": null,
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/f628992ecb2e497c43499ac1194a14aa.jpg",
|
||||
"contentType": 3,
|
||||
"releasedAt": 1771858380000,
|
||||
"readCount": 0,
|
||||
"author": null,
|
||||
"objectType": 0,
|
||||
"memberNum": 0,
|
||||
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192199043.m3u8\"}",
|
||||
"timeLength": 1254,
|
||||
"imageCount": 0,
|
||||
"recentVisitTime": 0,
|
||||
"keyword": null,
|
||||
"serialsInfo": null,
|
||||
"crawlUrl": null,
|
||||
"newsList": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "fc2d7f23efac7c804113b0b5e651a0e1",
|
||||
"title": "七十二家房客:乱世飘萍(下)20260223",
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/3ffb96c45b375a82f81da4a7fcc30e16.jpg",
|
||||
"releasedAt": 1771857120000,
|
||||
"timeLength": 1257,
|
||||
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192187034.m3u8",
|
||||
"raw": {
|
||||
"id": "fc2d7f23efac7c804113b0b5e651a0e1",
|
||||
"title": "七十二家房客:乱世飘萍(下)20260223",
|
||||
"titleStyle": null,
|
||||
"titleColor": null,
|
||||
"subTitle": "",
|
||||
"preSubTitle": null,
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/3ffb96c45b375a82f81da4a7fcc30e16.jpg",
|
||||
"contentType": 3,
|
||||
"releasedAt": 1771857120000,
|
||||
"readCount": 0,
|
||||
"author": null,
|
||||
"objectType": 0,
|
||||
"memberNum": 0,
|
||||
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192187034.m3u8\"}",
|
||||
"timeLength": 1257,
|
||||
"imageCount": 0,
|
||||
"recentVisitTime": 0,
|
||||
"keyword": null,
|
||||
"serialsInfo": null,
|
||||
"crawlUrl": null,
|
||||
"newsList": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "fd2f79033153dd6b9e35d9f549a2875c",
|
||||
"title": "七十二家房客:乱世飘萍(上)20260223",
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/0e80bd756f4d0102056aff562dae1db9.jpg",
|
||||
"releasedAt": 1771855800000,
|
||||
"timeLength": 1258,
|
||||
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192156995.m3u8",
|
||||
"raw": {
|
||||
"id": "fd2f79033153dd6b9e35d9f549a2875c",
|
||||
"title": "七十二家房客:乱世飘萍(上)20260223",
|
||||
"titleStyle": null,
|
||||
"titleColor": null,
|
||||
"subTitle": "",
|
||||
"preSubTitle": null,
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/0e80bd756f4d0102056aff562dae1db9.jpg",
|
||||
"contentType": 3,
|
||||
"releasedAt": 1771855800000,
|
||||
"readCount": 0,
|
||||
"author": null,
|
||||
"objectType": 0,
|
||||
"memberNum": 0,
|
||||
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192156995.m3u8\"}",
|
||||
"timeLength": 1258,
|
||||
"imageCount": 0,
|
||||
"recentVisitTime": 0,
|
||||
"keyword": null,
|
||||
"serialsInfo": null,
|
||||
"crawlUrl": null,
|
||||
"newsList": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "46373fb5eaf3ec143b1f1c71d347ed3d",
|
||||
"title": "七十二家房客:打掩护(下)20260223",
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/51388f66e6851aa9109dfdc7d683fe89.jpg",
|
||||
"releasedAt": 1771854540000,
|
||||
"timeLength": 1243,
|
||||
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192144981.m3u8",
|
||||
"raw": {
|
||||
"id": "46373fb5eaf3ec143b1f1c71d347ed3d",
|
||||
"title": "七十二家房客:打掩护(下)20260223",
|
||||
"titleStyle": null,
|
||||
"titleColor": null,
|
||||
"subTitle": "",
|
||||
"preSubTitle": null,
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/51388f66e6851aa9109dfdc7d683fe89.jpg",
|
||||
"contentType": 3,
|
||||
"releasedAt": 1771854540000,
|
||||
"readCount": 0,
|
||||
"author": null,
|
||||
"objectType": 0,
|
||||
"memberNum": 0,
|
||||
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192144981.m3u8\"}",
|
||||
"timeLength": 1243,
|
||||
"imageCount": 0,
|
||||
"recentVisitTime": 0,
|
||||
"keyword": null,
|
||||
"serialsInfo": null,
|
||||
"crawlUrl": null,
|
||||
"newsList": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "6aa2c98312b92526dd5d725c8d08cc21",
|
||||
"title": "七十二家房客:她来了(下)20260223",
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/005d61b7b991d89fc176a2a0fd2df635.jpg",
|
||||
"releasedAt": 1771853100000,
|
||||
"timeLength": 1386,
|
||||
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192111957.m3u8",
|
||||
"raw": {
|
||||
"id": "6aa2c98312b92526dd5d725c8d08cc21",
|
||||
"title": "七十二家房客:她来了(下)20260223",
|
||||
"titleStyle": null,
|
||||
"titleColor": null,
|
||||
"subTitle": "",
|
||||
"preSubTitle": null,
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/005d61b7b991d89fc176a2a0fd2df635.jpg",
|
||||
"contentType": 3,
|
||||
"releasedAt": 1771853100000,
|
||||
"readCount": 0,
|
||||
"author": null,
|
||||
"objectType": 0,
|
||||
"memberNum": 0,
|
||||
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192111957.m3u8\"}",
|
||||
"timeLength": 1386,
|
||||
"imageCount": 0,
|
||||
"recentVisitTime": 0,
|
||||
"keyword": null,
|
||||
"serialsInfo": null,
|
||||
"crawlUrl": null,
|
||||
"newsList": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ffa1e619d83eda52253b5ece8734ff13",
|
||||
"title": "七十二家房客:她来了(上)20260223",
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/96b6576501867170847372b111f3752f.jpg",
|
||||
"releasedAt": 1771851540000,
|
||||
"timeLength": 1359,
|
||||
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192102933.m3u8",
|
||||
"raw": {
|
||||
"id": "ffa1e619d83eda52253b5ece8734ff13",
|
||||
"title": "七十二家房客:她来了(上)20260223",
|
||||
"titleStyle": null,
|
||||
"titleColor": null,
|
||||
"subTitle": "",
|
||||
"preSubTitle": null,
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/96b6576501867170847372b111f3752f.jpg",
|
||||
"contentType": 3,
|
||||
"releasedAt": 1771851540000,
|
||||
"readCount": 0,
|
||||
"author": null,
|
||||
"objectType": 0,
|
||||
"memberNum": 0,
|
||||
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192102933.m3u8\"}",
|
||||
"timeLength": 1359,
|
||||
"imageCount": 0,
|
||||
"recentVisitTime": 0,
|
||||
"keyword": null,
|
||||
"serialsInfo": null,
|
||||
"crawlUrl": null,
|
||||
"newsList": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "805f300979f10859b525b2917208d668",
|
||||
"title": "2026-01-03 七十二家房客:爱情万岁(下)",
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/01/716c83304d268942740c4b210a0a8cc0.jpg",
|
||||
"releasedAt": 1767368100000,
|
||||
"timeLength": 1228,
|
||||
"videoUrl": "https://vod.gdtv.cn/m3u8/202601/176743509593.m3u8",
|
||||
"raw": {
|
||||
"id": "805f300979f10859b525b2917208d668",
|
||||
"title": "2026-01-03 七十二家房客:爱情万岁(下)",
|
||||
"titleStyle": null,
|
||||
"titleColor": null,
|
||||
"subTitle": "",
|
||||
"preSubTitle": null,
|
||||
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/01/716c83304d268942740c4b210a0a8cc0.jpg",
|
||||
"contentType": 3,
|
||||
"releasedAt": 1767368100000,
|
||||
"readCount": 0,
|
||||
"author": null,
|
||||
"objectType": 0,
|
||||
"memberNum": 0,
|
||||
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202601/176743509593.m3u8\"}",
|
||||
"timeLength": 1228,
|
||||
"imageCount": 0,
|
||||
"recentVisitTime": 0,
|
||||
"keyword": null,
|
||||
"serialsInfo": null,
|
||||
"crawlUrl": null,
|
||||
"newsList": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,5 +1,8 @@
|
||||
{
|
||||
"running": false,
|
||||
"lastMessage": "自动采集完成",
|
||||
"updatedAt": "2026-03-10T19:27:40.184Z"
|
||||
"running": true,
|
||||
"lastMessage": "已采集第 1 页",
|
||||
"updatedAt": "2026-03-10T21:11:58.820Z",
|
||||
"currentPage": "1",
|
||||
"pageItems": 0,
|
||||
"addedCount": 0
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,6 @@ const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
const dataDir = path.resolve(__dirname, '../data')
|
||||
const cacheFile = path.join(dataDir, 'qishier-cache.json')
|
||||
const statusFile = path.join(dataDir, 'capture-status.json')
|
||||
|
||||
function ensureDir() {
|
||||
@ -18,26 +17,6 @@ function ensureDir() {
|
||||
export function ensureDataFiles() {
|
||||
ensureDir()
|
||||
|
||||
if (!fs.existsSync(cacheFile)) {
|
||||
fs.writeFileSync(
|
||||
cacheFile,
|
||||
JSON.stringify(
|
||||
{
|
||||
updatedAt: null,
|
||||
name: '七十二家房客',
|
||||
coverUrl: '',
|
||||
displayType: 0,
|
||||
pages: {},
|
||||
beginScoreMap: {},
|
||||
items: []
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
'utf-8'
|
||||
)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(statusFile)) {
|
||||
fs.writeFileSync(
|
||||
statusFile,
|
||||
@ -55,14 +34,69 @@ export function ensureDataFiles() {
|
||||
}
|
||||
}
|
||||
|
||||
export function readCache() {
|
||||
ensureDataFiles()
|
||||
return JSON.parse(fs.readFileSync(cacheFile, 'utf-8'))
|
||||
export function getYearFile(year) {
|
||||
ensureDir()
|
||||
return path.join(dataDir, `${year}.json`)
|
||||
}
|
||||
|
||||
export function writeCache(data) {
|
||||
ensureDataFiles()
|
||||
fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2), 'utf-8')
|
||||
export function readYearData(year) {
|
||||
ensureDir()
|
||||
const file = getYearFile(year)
|
||||
|
||||
if (!fs.existsSync(file)) {
|
||||
return {
|
||||
year,
|
||||
updatedAt: null,
|
||||
items: []
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.parse(fs.readFileSync(file, 'utf-8'))
|
||||
}
|
||||
|
||||
export function writeYearData(year, data) {
|
||||
ensureDir()
|
||||
const file = getYearFile(year)
|
||||
fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf-8')
|
||||
}
|
||||
|
||||
export function getAllYearFiles() {
|
||||
ensureDir()
|
||||
|
||||
return fs
|
||||
.readdirSync(dataDir)
|
||||
.filter((file) => /^\d{4}\.json$/.test(file))
|
||||
.sort()
|
||||
}
|
||||
|
||||
export function readAllYearsData() {
|
||||
ensureDir()
|
||||
|
||||
const files = getAllYearFiles()
|
||||
const allItems = []
|
||||
const years = []
|
||||
|
||||
for (const file of files) {
|
||||
const year = Number(file.replace('.json', ''))
|
||||
const data = readYearData(year)
|
||||
|
||||
years.push(year)
|
||||
|
||||
if (Array.isArray(data.items)) {
|
||||
allItems.push(...data.items)
|
||||
}
|
||||
}
|
||||
|
||||
allItems.sort((a, b) => (b.releasedAt || 0) - (a.releasedAt || 0))
|
||||
|
||||
return {
|
||||
updatedAt: new Date().toISOString(),
|
||||
name: '七十二家房客',
|
||||
coverUrl: '',
|
||||
displayType: 0,
|
||||
years,
|
||||
items: allItems
|
||||
}
|
||||
}
|
||||
|
||||
export function readStatus() {
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import { ensureDataFiles, readCache, readStatus } from './lib/cache.js'
|
||||
import {
|
||||
ensureDataFiles,
|
||||
readAllYearsData,
|
||||
readYearData,
|
||||
readStatus
|
||||
} from './lib/cache.js'
|
||||
|
||||
const app = express()
|
||||
app.use(cors())
|
||||
@ -10,15 +15,15 @@ ensureDataFiles()
|
||||
|
||||
app.get('/api/qishier/all', (_req, res) => {
|
||||
try {
|
||||
const cache = readCache()
|
||||
const cache = readAllYearsData()
|
||||
|
||||
res.json({
|
||||
updatedAt: cache.updatedAt,
|
||||
name: cache.name || '七十二家房客',
|
||||
coverUrl: cache.coverUrl || '',
|
||||
displayType: cache.displayType || 0,
|
||||
total: cache.items?.length || 0,
|
||||
beginScoreMap: cache.beginScoreMap || {},
|
||||
pages: cache.pages || {},
|
||||
years: cache.years || [],
|
||||
list: cache.items || []
|
||||
})
|
||||
} catch (error) {
|
||||
@ -29,6 +34,28 @@ app.get('/api/qishier/all', (_req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/api/qishier/year/:year', (req, res) => {
|
||||
try {
|
||||
const year = Number(req.params.year)
|
||||
const data = readYearData(year)
|
||||
|
||||
res.json({
|
||||
updatedAt: data.updatedAt,
|
||||
name: '七十二家房客',
|
||||
coverUrl: '',
|
||||
displayType: 0,
|
||||
total: data.items?.length || 0,
|
||||
years: [year],
|
||||
list: data.items || []
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
message: '读取年度缓存失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/api/qishier/status', (_req, res) => {
|
||||
try {
|
||||
const status = readStatus()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user