七十二家房客-观看平台v1.5

- 更改播放器
 - 优化自动播放
This commit is contained in:
云上贵猪 2026-03-11 07:56:01 +08:00
parent 139e1a2135
commit c7f6d6b2b1
3 changed files with 308 additions and 199 deletions

450
README.md
View File

@ -1,265 +1,321 @@
# 72QishierPlayer
# 72QishierPlayer 技术栈说明
一个基于 **Vue3 + Vite + Node.js + Playwright + HLS.js** 的《七十二家房客》在线播放项目。
一个基于 **Vue3 + Vite + Node.js + Playwright + HLS.js + Artplayer**
的《七十二家房客》在线播放项目。
本项目通过 **浏览器自动采集官网数据 → 本地缓存 JSON → Node API 提供 → Vue 前端播放** 的方式,解决官网接口 **签名、401、分页限制** 等问题,实现稳定播放。
本项目通过 **浏览器自动采集官网数据 → 本地缓存 JSON → Node API 提供 →
Vue 前端播放** 的方式,解决官网接口 **签名、401、分页限制**
等问题,实现稳定播放。
---
------------------------------------------------------------------------
# 运行与安装指南
# 项目特点
本文档说明 **72QishierPlayer** 项目的安装与运行步骤。\
项目由 **Node.js 后端 + Vue3 前端** 两部分组成,需要分别启动。
- 自动采集《七十二家房客》节目列表
- 自动滚动加载更多节目
- 自动去重并缓存到 JSON
- Node API 提供数据
- Vue3 播放器展示
- HLS.js 播放 m3u8 视频
- Cookie 记录上次观看进度
- 前端分页加载,避免一次加载过多图片
------------------------------------------------------------------------
---
# 运行&安装
## 一、后端
安装后端依赖
```shell
npm i
```
# 一、后端服务Node.js
开启后端服务
```shell
node server.js
```
后端主要负责:
如需更新数据:
```shell
node capture.js
```
- 提供节目 API
- 读取本地 JSON 数据
- 提供给前端播放列表
## 二、前端
## 1 安装前端依赖
```
npm i
```
运行前端服务:
```shell
npm run dev
```
---
## 2 安装后端依赖
## 1. 安装依赖
进入后端目录:
```
``` bash
cd backServer
```
安装依赖:
```
``` bash
npm install
```
安装 Playwright 浏览器:
------------------------------------------------------------------------
```
npx playwright install chromium
## 2. 启动后端服务
``` bash
node server.js
```
---
默认接口地址:
# 运行项目
http://localhost:3000
## 1 采集节目数据
例如:
```
npm run capture
http://localhost:3000/api/qishier/list
------------------------------------------------------------------------
## 3. 更新节目数据(可选)
如果需要重新采集节目数据:
``` bash
node capture.js
```
程序会自动:
该脚本会
1. 启动浏览器
2. 打开页面
1. 启动浏览器自动采集
2. 打开节目页面
3. 自动加载更多节目
4. 捕获接口数据
5. 更新本地缓存
```
https://www1.gdtv.cn/tvColumn/768
数据保存位置:
backServer/data/qishier-cache.json
------------------------------------------------------------------------
# 二、前端服务Vue3
前端负责:
- 视频播放器
- 节目列表展示
- 自动下一集
- 播放进度记录
------------------------------------------------------------------------
## 1. 安装前端依赖
进入前端目录:
``` bash
cd 72QishierPlayer
```
3. 自动滚动页面
4. 自动点击 **加载更多**
5. 捕获接口数据
6. 写入缓存
安装依赖:
缓存文件:
```
backServer/data/qishier-cache.json
``` bash
npm install
```
---
------------------------------------------------------------------------
## 2 启动 Node API
## 2. 启动前端开发服务器
`backServer` 目录运行:
```
npm run server
```
API 地址:
```
http://localhost:3000/api/qishier/all
```
返回示例:
```
{
"total": 1141,
"list": []
}
```
---
## 3 启动 Vue 前端
回到项目根目录地址http://localhost:5173
```
``` bash
npm run dev
```
默认访问地址:
---
http://localhost:5173
# 项目架构
------------------------------------------------------------------------
```
官网接口
gdtv-api.gdtv.cn
Playwright 浏览器采集
本地 JSON 缓存
backServer/data/qishier-cache.json
Node API
http://localhost:3000/api/qishier/all
Vue 播放器
# 三、开发模式运行流程
建议的启动顺序:
``` text
1. 启动 Node 后端
node server.js
2. 启动 Vue 前端
npm run dev
3. 浏览器访问
http://localhost:5173
```
---
------------------------------------------------------------------------
# 项目目录
# 四、生产环境部署
构建前端:
``` bash
npm run build
```
构建完成后生成:
dist/
`dist` 部署到 Nginx 或静态服务器即可。
后端服务建议使用 **PM2** 运行:
``` bash
pm2 start server.js --name qishier-api
pm2 save
```
------------------------------------------------------------------------
# 五、常见问题
## 1. 播放器无法播放
确认:
- 后端服务已启动
- API 地址正常
- m3u8 视频链接有效
------------------------------------------------------------------------
## 2. 没有节目数据
执行:
``` bash
node capture.js
```
重新采集节目列表。
------------------------------------------------------------------------
# 六、默认端口
服务 端口
---------- ------
Vue 前端 5173
Node API 3000
# 技术栈Tech Stack
## 前端
技术 说明
------------- -----------------------
Vue3 前端框架
Vite 前端构建工具
TypeScript 类型安全开发
Axios HTTP 请求库
Artplayer 视频播放器 UI
hls.js m3u8 / HLS 视频流播放
HTML5 Video 浏览器视频播放能力
CSS3 页面样式
------------------------------------------------------------------------
## 后端
技术 说明
------------ ------------------
Node.js 后端运行环境
Express API 服务
Playwright 浏览器自动化采集
JSON 数据缓存存储
------------------------------------------------------------------------
## 数据采集
技术 说明
---------------- ------------------
Playwright 自动打开官网页面
浏览器接口拦截 捕获节目 API
自动滚动 加载更多节目
JSON缓存 本地节目数据存储
------------------------------------------------------------------------
## 视频播放技术
技术 说明
--------------------------- --------------------
HLS (HTTP Live Streaming) 视频流协议
hls.js 浏览器 HLS 解码
Artplayer 视频播放器 UI 控件
------------------------------------------------------------------------
# 项目架构
``` text
官网接口
gdtv-api.gdtv.cn
Playwright 浏览器采集
本地 JSON 缓存
backServer/data/qishier-cache.json
Node API
/api/qishier/*
Vue3 前端
Artplayer + HLS
```
------------------------------------------------------------------------
# 核心功能
功能 技术实现
----------------- --------------------
自动采集节目 Playwright
数据缓存 JSON
API 服务 Express
视频播放 Artplayer + hls.js
自动下一集 Vue 状态管理
顺序 / 倒序播放 列表排序
播放进度记录 Cookie
懒加载节目列表 前端分页
组件化架构 Vue Components
------------------------------------------------------------------------
# 运行环境
环境 版本
---------- -------------------------
Node.js ≥ 18
npm ≥ 9
浏览器 Chrome / Edge / Safari
操作系统 macOS / Linux / Windows
------------------------------------------------------------------------
# 项目目录结构
``` text
72QishierPlayer
├─ src
│ ├─ api
│ │ └─ qishier.ts
│ │
│ ├─ components
│ │ └─ qishier
│ │ ├─ QishierVideoPlayer.vue
│ │ └─ QishierEpisodeList.vue
│ │
│ ├─ views
│ │ └─ QishierPlayer.vue
│ │
│ ├─ App.vue
│ ├─ main.ts
│ └─ assets
│ └─ utils
│ └─ playHistory.ts
├─ backServer
│ │
│ ├─ data
│ │ └─ qishier-cache.json
│ │
│ ├─ capture.js
│ ├─ server.js
│ └─ package.json
│ └─ server.js
├─ 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 本地缓存
---
# 免责声明
本项目仅用于:
- 技术学习
- 数据采集研究
- 视频播放技术演示
请勿用于商业用途。

BIN
dist.zip

Binary file not shown.

View File

@ -1,8 +1,9 @@
<template>
<div class="list-panel">
<div ref="listPanelRef" class="list-panel">
<div
v-for="item in episodes"
:key="item.id"
:data-episode-id="item.id"
class="episode-item"
:class="{ active: currentEpisodeId === item.id }"
@click="$emit('select', item)"
@ -23,9 +24,10 @@
</template>
<script setup lang="ts">
import { nextTick, ref, watch } from 'vue'
import type { EpisodeData } from '@/api/qishier'
defineProps<{
const props = defineProps<{
episodes: EpisodeData[]
currentEpisodeId: string
hasMore: boolean
@ -37,6 +39,8 @@ defineEmits<{
loadMore: []
}>()
const listPanelRef = ref<HTMLElement | null>(null)
function formatDate(timestamp: number): string {
if (!timestamp) return '--'
const d = new Date(timestamp)
@ -45,6 +49,47 @@ function formatDate(timestamp: number): string {
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
}
async function scrollToActiveEpisode() {
await nextTick()
const panel = listPanelRef.value
if (!panel || !props.currentEpisodeId) return
const activeEl = panel.querySelector(
`[data-episode-id="${props.currentEpisodeId}"]`
) as HTMLElement | null
if (!activeEl) return
const panelRect = panel.getBoundingClientRect()
const itemRect = activeEl.getBoundingClientRect()
const isAbove = itemRect.top < panelRect.top
const isBelow = itemRect.bottom > panelRect.bottom
if (isAbove || isBelow) {
activeEl.scrollIntoView({
behavior: 'smooth',
block: 'center'
})
}
}
watch(
() => props.currentEpisodeId,
async () => {
await scrollToActiveEpisode()
},
{ immediate: true }
)
watch(
() => props.episodes.length,
async () => {
await scrollToActiveEpisode()
}
)
</script>
<style scoped>
@ -54,6 +99,7 @@ function formatDate(timestamp: number): string {
padding: 12px;
background: #1b1b1b;
border-radius: 12px;
scroll-behavior: smooth;
}
.episode-item {
@ -64,6 +110,7 @@ function formatDate(timestamp: number): string {
cursor: pointer;
margin-bottom: 10px;
transition: 0.2s;
border: 1px solid transparent;
}
.episode-item:hover {
@ -72,6 +119,8 @@ function formatDate(timestamp: number): string {
.episode-item.active {
background: rgba(255, 193, 7, 0.16);
border-color: rgba(255, 193, 7, 0.45);
box-shadow: 0 0 0 1px rgba(255, 193, 7, 0.12) inset;
}
.episode-item img {
@ -95,6 +144,10 @@ function formatDate(timestamp: number): string {
color: #fff;
}
.episode-item.active .title {
color: #ffd666;
}
.meta {
font-size: 13px;
color: #999;