七十二家房客-观看平台v1.1
This commit is contained in:
parent
b77ff62c58
commit
a20c6ad819
261
README.md
261
README.md
@ -1,42 +1,259 @@
|
|||||||
# 72QishierPlayer
|
# 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)
|
- 自动去重并缓存到 JSON
|
||||||
- Firefox:
|
- Node API 提供数据
|
||||||
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
- Vue3 播放器展示
|
||||||
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
- 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
|
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
|
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 { chromium } from 'playwright'
|
||||||
import readlineSync from 'readline-sync'
|
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'
|
const PAGE_URL = 'https://www1.gdtv.cn/tvColumn/768'
|
||||||
|
|
||||||
@ -25,41 +30,45 @@ function parseEpisodeItem(rawItem) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeCache(oldCache, responseUrl, data) {
|
function getYearByTimestamp(timestamp) {
|
||||||
const url = new URL(responseUrl)
|
if (!timestamp) return new Date().getFullYear()
|
||||||
const currentPage = Number(url.searchParams.get('currentPage') || 1)
|
return new Date(timestamp).getFullYear()
|
||||||
const beginScore = Number(url.searchParams.get('beginScore') || 0)
|
}
|
||||||
|
|
||||||
const parsedItems = (data.list || [])
|
function saveItemsByYear(items) {
|
||||||
.map(parseEpisodeItem)
|
const yearGroups = new Map()
|
||||||
.filter(Boolean)
|
|
||||||
|
|
||||||
const itemMap = new Map((oldCache.items || []).map(item => [item.id, item]))
|
for (const item of items) {
|
||||||
for (const item of parsedItems) {
|
const year = getYearByTimestamp(item.releasedAt)
|
||||||
itemMap.set(item.id, item)
|
if (!yearGroups.has(year)) {
|
||||||
|
yearGroups.set(year, [])
|
||||||
|
}
|
||||||
|
yearGroups.get(year).push(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
let totalUniqueAdded = 0
|
||||||
...oldCache,
|
|
||||||
updatedAt: new Date().toISOString(),
|
for (const [year, groupItems] of yearGroups.entries()) {
|
||||||
name: data.name || oldCache.name || '七十二家房客',
|
const oldData = readYearData(year)
|
||||||
coverUrl: data.coverUrl || oldCache.coverUrl || '',
|
const itemMap = new Map((oldData.items || []).map(item => [item.id, item]))
|
||||||
displayType: data.displayType ?? oldCache.displayType ?? 0,
|
const beforeCount = itemMap.size
|
||||||
beginScoreMap: {
|
|
||||||
...(oldCache.beginScoreMap || {}),
|
for (const item of groupItems) {
|
||||||
[currentPage]: beginScore
|
itemMap.set(item.id, item)
|
||||||
},
|
}
|
||||||
pages: {
|
|
||||||
...(oldCache.pages || {}),
|
const nextItems = Array.from(itemMap.values()).sort((a, b) => (b.releasedAt || 0) - (a.releasedAt || 0))
|
||||||
[currentPage]: {
|
const afterCount = nextItems.length
|
||||||
currentPage,
|
totalUniqueAdded += afterCount - beforeCount
|
||||||
beginScore,
|
|
||||||
count: parsedItems.length,
|
writeYearData(year, {
|
||||||
capturedAt: new Date().toISOString()
|
year,
|
||||||
}
|
updatedAt: new Date().toISOString(),
|
||||||
},
|
items: nextItems
|
||||||
items: Array.from(itemMap.values()).sort((a, b) => (b.releasedAt || 0) - (a.releasedAt || 0))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return totalUniqueAdded
|
||||||
}
|
}
|
||||||
|
|
||||||
function log(...args) {
|
function log(...args) {
|
||||||
@ -270,23 +279,25 @@ async function main() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
const oldCache = readCache()
|
const parsedItems = (data.list || [])
|
||||||
const nextCache = mergeCache(oldCache, url, data)
|
.map(parseEpisodeItem)
|
||||||
writeCache(nextCache)
|
.filter(Boolean)
|
||||||
|
|
||||||
|
const addedCount = saveItemsByYear(parsedItems)
|
||||||
|
|
||||||
|
const totalItems = parsedItems.length
|
||||||
const currentPage = new URL(url).searchParams.get('currentPage')
|
const currentPage = new URL(url).searchParams.get('currentPage')
|
||||||
const capturedPages = Object.keys(nextCache.pages || {}).length
|
|
||||||
const totalItems = nextCache.items?.length || 0
|
|
||||||
|
|
||||||
writeStatus({
|
writeStatus({
|
||||||
running: true,
|
running: true,
|
||||||
lastMessage: `已采集第 ${currentPage} 页`,
|
lastMessage: `已采集第 ${currentPage} 页`,
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
capturedPages,
|
currentPage,
|
||||||
totalItems
|
pageItems: totalItems,
|
||||||
|
addedCount
|
||||||
})
|
})
|
||||||
|
|
||||||
log(`采集成功: page=${currentPage} totalItems=${totalItems}`)
|
log(`采集成功: page=${currentPage} 本页=${totalItems} 新增唯一=${addedCount}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[capture] 解析响应失败:', error)
|
console.error('[capture] 解析响应失败:', error)
|
||||||
}
|
}
|
||||||
@ -322,7 +333,7 @@ async function main() {
|
|||||||
updatedAt: new Date().toISOString()
|
updatedAt: new Date().toISOString()
|
||||||
})
|
})
|
||||||
|
|
||||||
log('自动采集完成,数据已写入 data/qishier-cache.json')
|
log('自动采集完成,数据已按年份写入 data/*.json')
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((error) => {
|
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,
|
"running": true,
|
||||||
"lastMessage": "自动采集完成",
|
"lastMessage": "已采集第 1 页",
|
||||||
"updatedAt": "2026-03-10T19:27:40.184Z"
|
"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 __dirname = path.dirname(__filename)
|
||||||
|
|
||||||
const dataDir = path.resolve(__dirname, '../data')
|
const dataDir = path.resolve(__dirname, '../data')
|
||||||
const cacheFile = path.join(dataDir, 'qishier-cache.json')
|
|
||||||
const statusFile = path.join(dataDir, 'capture-status.json')
|
const statusFile = path.join(dataDir, 'capture-status.json')
|
||||||
|
|
||||||
function ensureDir() {
|
function ensureDir() {
|
||||||
@ -18,26 +17,6 @@ function ensureDir() {
|
|||||||
export function ensureDataFiles() {
|
export function ensureDataFiles() {
|
||||||
ensureDir()
|
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)) {
|
if (!fs.existsSync(statusFile)) {
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
statusFile,
|
statusFile,
|
||||||
@ -55,14 +34,69 @@ export function ensureDataFiles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readCache() {
|
export function getYearFile(year) {
|
||||||
ensureDataFiles()
|
ensureDir()
|
||||||
return JSON.parse(fs.readFileSync(cacheFile, 'utf-8'))
|
return path.join(dataDir, `${year}.json`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function writeCache(data) {
|
export function readYearData(year) {
|
||||||
ensureDataFiles()
|
ensureDir()
|
||||||
fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2), 'utf-8')
|
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() {
|
export function readStatus() {
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
import { ensureDataFiles, readCache, readStatus } from './lib/cache.js'
|
import {
|
||||||
|
ensureDataFiles,
|
||||||
|
readAllYearsData,
|
||||||
|
readYearData,
|
||||||
|
readStatus
|
||||||
|
} from './lib/cache.js'
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
app.use(cors())
|
app.use(cors())
|
||||||
@ -10,15 +15,15 @@ ensureDataFiles()
|
|||||||
|
|
||||||
app.get('/api/qishier/all', (_req, res) => {
|
app.get('/api/qishier/all', (_req, res) => {
|
||||||
try {
|
try {
|
||||||
const cache = readCache()
|
const cache = readAllYearsData()
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
updatedAt: cache.updatedAt,
|
updatedAt: cache.updatedAt,
|
||||||
name: cache.name || '七十二家房客',
|
name: cache.name || '七十二家房客',
|
||||||
coverUrl: cache.coverUrl || '',
|
coverUrl: cache.coverUrl || '',
|
||||||
displayType: cache.displayType || 0,
|
displayType: cache.displayType || 0,
|
||||||
total: cache.items?.length || 0,
|
total: cache.items?.length || 0,
|
||||||
beginScoreMap: cache.beginScoreMap || {},
|
years: cache.years || [],
|
||||||
pages: cache.pages || {},
|
|
||||||
list: cache.items || []
|
list: cache.items || []
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} 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) => {
|
app.get('/api/qishier/status', (_req, res) => {
|
||||||
try {
|
try {
|
||||||
const status = readStatus()
|
const status = readStatus()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user