Compare commits

..

61 Commits
master ... main

Author SHA1 Message Date
00b1e31291 Merge branch 'main' of http://git.xintiao100.com/Qyir/rank_backend 2025-11-18 11:43:35 +08:00
9017cea25d 管理后台连接大模型API支持重新生成评论总结
添加锁定评论列表字段
2025-11-18 11:42:47 +08:00
xbh
5292ce8f82 url地址添加到.env文件中 2025-11-13 22:53:20 +08:00
xbh
ba85036a42 Merge commit '91761b6754b2ca55b5f1a5317c1fc603a76794aa' 2025-11-13 22:29:51 +08:00
xbh
4261d75f22 修改 .gitignore 文件,添加 .figma/ 目录 2025-11-13 22:28:30 +08:00
91761b6754 添加短剧版权方认证页面
添加申请管理页面上传至TOS
2025-11-13 17:49:48 +08:00
5a1c14a080 优化前端详情页面 2025-11-13 10:00:39 +08:00
6dfcda492e 添加前端详情页面,优化评论格式 2025-11-12 18:36:14 +08:00
59bc601b9a 合并 xbh 分支的昨天修改 2025-11-10 09:53:14 +08:00
a35077363d 更新 .gitignore 2025-11-10 09:51:31 +08:00
868163d84e 去除了多余的日期过滤条件,添加前端准确使用mix_id来同步找短剧 2025-11-10 21:34:27 +08:00
xbh
54fc7ed68a 修改样式 2025-11-09 23:41:19 +08:00
xbh
d3929f2e35 修改更新剧信息的bug 2025-11-09 21:19:10 +08:00
xbh
79c8a66c80 Merge commit '8f231a7c8efe8d5901b2e63d8bde1dfb43971373' into xbh 2025-11-09 19:42:06 +08:00
8f231a7c8e 模拟用户在收藏合集滚动 2025-11-09 19:03:04 +08:00
xbh
d04bd85546 Merge commit '13b05ae252a12aa05bd63c7000020426c04b19f9' into xbh 2025-11-08 17:40:43 +08:00
13b05ae252 修改 2025-11-08 16:29:33 +08:00
ad54ff0398 优化后的管理后台 2025-11-07 17:36:53 +08:00
xbh
089d055a18 前端显示bug 2025-11-07 17:06:09 +08:00
xbh
74cde01f1b 更改端口为8443 2025-11-07 16:53:52 +08:00
xbh
112a41d372 Merge commit '18efb2513331ca236984fceea85dc36dd00598dc' into xbh 2025-11-07 15:33:21 +08:00
18efb25133 测试完成,现在的代码运行都是比较稳定的。 2025-11-06 18:29:36 +08:00
d4d555cdb1 目前后端和前端:
1.添加后台管理页面,使用网址进入后台管理页面:http://localhost:5174/admin
2.剧种分类完成,只要用户再后台管理页面选择类型之后会一直显示
3.在两个脚本运行的时候可以同时启动两个浏览器页面不受影响(原因是:必须要管理数据库有了数据之后前端点赞才可以显示,但是主代码运行慢,为了不浪费时间
每次定时器运行的时候都会通过视频ID来同步短剧的详细信息)
前端可以稳定的显示数据。
2025-11-06 18:28:43 +08:00
3b95c52fcb 修复bug,修复优化代码时出现的异常,和新短剧不能正确的计算播放量插值 2025-11-06 10:08:10 +08:00
39239c3e85 修改数据库存储的问题,优化时出现的小问题 2025-11-04 21:32:40 +08:00
4057620cf4 优化锁定字段逻辑 2025-11-06 18:13:31 +08:00
a726e4d8b3 优化后台管理代码
后台管理 : http://localhost:5174/admin (直接URL访问)
2025-11-03 16:08:14 +08:00
9b5b430997 优化后台管理代码 2025-11-03 16:00:29 +08:00
36be77948f 1.添加前端后台管理,并且成功分类
2.数据库整理清晰
2025-11-04 21:37:23 +08:00
xbh
fafb0aee4f 优化了界面 2025-10-30 16:47:10 +08:00
xbh
d15eb6997c Merge commit 'acad6baadeaae0d5c5aa6836a32e7129f62c3d5e' 2025-10-30 12:04:18 +08:00
xbh
54c9f95e29 前端页面调整 2025-10-30 12:03:54 +08:00
acad6baade 补丁机制优化 2025-10-30 10:20:04 +08:00
xbh
560581c7ee Merge commit 'c2bd4d3b2c890787bce353344028c1e270c2c671' 2025-10-30 10:19:31 +08:00
xbh
15f593f082 figma 2025-10-30 10:19:09 +08:00
c2bd4d3b2c 把内部数据使用print输出给终端 2025-10-29 22:29:17 +08:00
b26852b95f 优化增强了获取评论内容机制,
现在抓取评论内容的方法是网络日志拦截抖音API的方法,
这个获取的评论内容是随机的,但是里面添加了去重的机制,
针对于过少的评论和无评论做了优化,
添加监控超时限制,爬取评论内容一条视频监控超时时间为5小时(因为有的视频评论数量太大)
2025-10-29 18:49:19 +08:00
64455034bb 修改了Chrome配置文件清理功能
每次运行的时候都会查看配置文件中文件的大小,超过50MB自动清理
暴露最重要的用户登录信息。
2025-10-29 16:24:12 +08:00
149ea00f1e 优化了实时保存的功能 2025-10-28 17:46:45 +08:00
9295e77cf1 主代码可以实时更新
定时器由于要进行播放量插值计算,所以要有固定的时间戳,还是统一保存。
2025-10-27 18:55:54 +08:00
e8baaa4ce9 优化定时器功能 2025-10-27 13:38:16 +08:00
97c5fbe4df 解决合并冲突 - 接受远程版本 2025-10-27 09:55:45 +08:00
5b601356d4 添加episode_video_ids文件 2025-10-27 09:54:20 +08:00
xbh
74494013f9 Merge commit 'bba47d2fe95f318d35a986a17aff2bdbc97ed5e5' 2025-10-26 22:42:27 +08:00
xbh
278f007b5e 排行榜前端 2025-10-26 22:41:53 +08:00
bba47d2fe9 修正错误代码 2025-10-25 19:41:24 +08:00
43ec2c397e Merge branch 'main' of http://git.xintiao100.com/Qyir/rank_backend 2025-10-25 19:28:01 +08:00
3591f5bdc2 获取评论内容 2025-10-25 19:27:07 +08:00
2a32b2a8c0 1.添加判断代码,启动定时器时不调用主代码的某几个函数,确保定时器正常计算播放量差值
2.新增功能:获取点赞,收藏,转发数量+评论内容列表(不完整,正在继续优化)
3.增加数据库文件夹,当启动定时器时存储到Ranking_storage_list中,
按照Ranking_storage_list中的数据进行计算播放量差值,计算结果存入Ranking_storage中
单独运行rank_data_scraper.py的时候存入Rankings_list

原因:
Rankings_list里面存储的数据结构较多
Ranking_storage_list里面存储的主要是播放量
Rankings_list里面存入的是播放量差值
2025-10-23 10:04:44 +08:00
8b1149da56 添加了episode_video_ids字段,在运行rank_data_scraper.py
或定时器代码的时候不仅保证了原来的数据库存入数据库还保存了
每一部短剧的每一集的视频ID

提示:
拉取代码在您运行脚本之后会主动创建一个episode_video_ids文件夹
里面存入的是您第一次运行脚本的每一集的视频ID(作为缓存)
判断的方法是:在运行脚本之后检查每一集的缓存数量是否与本剧的集数相同,相同则使用缓存的视频ID
不相同则重新获取

获取视频ID的时间不长
2025-10-21 17:56:00 +08:00
8b607f6e24 在Rankings_list数据库里面添加三个字段:合集作者,合集描述,合集总集数 2025-10-21 15:12:18 +08:00
be44334960 解决封面图片过期情况,存入TOS中media/rank/目录中
热播总榜选择日期之后永久显示图片。
2025-10-20 18:47:49 +08:00
xbh
06996967ca 增加前端的代码,改编文件结构 2025-10-19 15:13:01 +08:00
xbh
c1bfa0de21 小调整 2025-10-18 22:58:19 +08:00
xbh
feb4d83b8d 改成了每小时整点执行抖音播放量抓取 2025-10-18 20:17:28 +08:00
xbh
fe6c867579 改成了每小时执行抖音播放量抓取 2025-10-18 20:09:13 +08:00
bd58c1dc8f 榜单列表数据库储存 2025-10-21 16:17:52 +08:00
xbh
2c0f891a89 整理了路由,添加了前缀/api/rank 2025-10-18 00:04:53 +08:00
xbh
93928b8a66 修改数据库名字 2025-10-17 22:01:33 +08:00
xbh
2af34bd0dc 整理了一下代码 2025-10-17 21:58:19 +08:00
xbh
ab8fd3131d doc文件夹往上移动一级 2025-10-17 18:50:02 +08:00
126 changed files with 24277 additions and 2041 deletions

237
.figma/0_2/index.jsx Normal file
View File

@ -0,0 +1,237 @@
import React from 'react';
import styles from './index.module.scss';
const Component = () => {
return (
<div className={styles.lunacyBackground}>
<div className={styles.autoWrapper}>
<img src="../image/mhcw5e6e-krjj7v6.svg" className={styles.frame} />
<p className={styles.text}>AI棒榜</p>
<img src="../image/mhcw5e6d-58eu18x.svg" className={styles.frame2} />
</div>
<div className={styles.frame3}>
<p className={styles.text2}>微短剧爆火</p>
<p className={styles.text3}>中国血统的ReelShort征服美国</p>
</div>
<img src="../image/mhcw5e6d-8fwj6ae.svg" className={styles.frame4} />
<div className={styles.autoWrapper2}>
<p className={styles.a2}>日榜 2025年10月19日/周日</p>
<img src="../image/mhcw5e6e-zupmsq9.svg" className={styles.frame5} />
</div>
<div className={styles.autoWrapper3}>
<div className={styles.frame6}>
<p className={styles.a22}>全部</p>
</div>
<div className={styles.frame7}>
<p className={styles.a23}>小说</p>
</div>
<div className={styles.frame7}>
<p className={styles.a23}>动漫</p>
</div>
<div className={styles.frame7}>
<p className={styles.a23}>短剧</p>
</div>
</div>
<div className={styles.frame39}>
<div className={styles.frame17}>
<div className={styles.frame16}>
<img src="../image/mhcw5e6e-u278byr.png" className={styles.frame8} />
<div className={styles.autoWrapper7}>
<p className={styles.a24}>奶团</p>
<div className={styles.autoWrapper5}>
<div className={styles.autoWrapper4}>
<img
src="../image/mhcw5e6e-5gc00qy.svg"
className={styles.frame9}
/>
<img
src="../image/mhcw5e6e-19alacf.svg"
className={styles.frame10}
/>
<img
src="../image/mhcw5e6e-7kk4jjy.svg"
className={styles.frame11}
/>
</div>
<p className={styles.a25}>
剧场名爱微剧场&nbsp;&nbsp;&nbsp;
<br />
承制 妙想制片厂&nbsp;&nbsp;&nbsp;&nbsp;
<br />
版权可梦&nbsp;&nbsp;
</p>
</div>
<div className={styles.autoWrapper6}>
<img
src="../image/mhcw5e6e-6bvf0dd.svg"
className={styles.frame12}
/>
<p className={styles.a26}>9999W</p>
<img
src="../image/mhcw5e6e-5hzy6ee.svg"
className={styles.frame13}
/>
<p className={styles.a26}>374W</p>
</div>
</div>
<img src="../image/mhcw5e6e-5x5ujui.svg" className={styles.frame14} />
<p className={styles.a27}>300W</p>
<div className={styles.frame15}>
<p className={styles.a1}>1</p>
</div>
</div>
<p className={styles.a28}>用户评论总结</p>
</div>
<img src="../image/mhcw5e6e-epiv9as.svg" className={styles.frame18} />
<div className={styles.frame23}>
<img src="../image/mhcw5e6e-kdsx4vj.png" className={styles.frame8} />
<div className={styles.autoWrapper9}>
<p className={styles.a24}>奶团</p>
<div className={styles.frame20}>
<img src="../image/mhcw5e6e-5gc00qy.svg" className={styles.frame19} />
<p className={styles.a25}>剧场名爱微剧场&nbsp;&nbsp;&nbsp;</p>
</div>
<div className={styles.autoWrapper8}>
<img src="../image/mhcw5e6e-6kmpbld.svg" className={styles.frame12} />
<p className={styles.a26}>9999W</p>
<img src="../image/mhcw5e6e-l10msdd.svg" className={styles.frame13} />
<p className={styles.a26}>374W</p>
</div>
</div>
<div className={styles.autoWrapper11}>
<div className={styles.autoWrapper10}>
<img src="../image/mhcw5e6e-5x5ujui.svg" className={styles.frame21} />
<p className={styles.a29}>300W</p>
</div>
<p className={styles.a210}>用户评论总结</p>
</div>
<div className={styles.frame22}>
<p className={styles.a1}>2</p>
</div>
</div>
<img src="../image/mhcw5e6e-epiv9as.svg" className={styles.frame24} />
<div className={styles.frame28}>
<div className={styles.frame27}>
<img src="../image/mhcw5e6e-myt1pbz.png" className={styles.frame8} />
<div className={styles.autoWrapper15}>
<p className={styles.a24}>奶团</p>
<div className={styles.autoWrapper13}>
<div className={styles.autoWrapper12}>
<img
src="../image/mhcw5e6e-5gc00qy.svg"
className={styles.frame9}
/>
<img
src="../image/mhcw5e6e-19alacf.svg"
className={styles.frame10}
/>
</div>
<p className={styles.a25}>
剧场名爱微剧场&nbsp;&nbsp;&nbsp;
<br />
承制 妙想制片厂&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</p>
</div>
<div className={styles.autoWrapper14}>
<img
src="../image/mhcw5e6e-6kmpbld.svg"
className={styles.frame12}
/>
<p className={styles.a26}>9999W</p>
<img
src="../image/mhcw5e6e-l10msdd.svg"
className={styles.frame13}
/>
<p className={styles.a26}>374W</p>
</div>
</div>
<img src="../image/mhcw5e6e-5x5ujui.svg" className={styles.frame25} />
<p className={styles.a27}>300W</p>
<div className={styles.frame26}>
<p className={styles.a1}>3</p>
</div>
</div>
<p className={styles.a211}>用户评论总结</p>
</div>
<img src="../image/mhcw5e6e-epiv9as.svg" className={styles.frame24} />
<div className={styles.frame33}>
<div className={styles.frame30}>
<div className={styles.frame29}>
<p className={styles.a1}>4</p>
</div>
</div>
<div className={styles.autoWrapper17}>
<p className={styles.a24}>奶团</p>
<div className={styles.frame32}>
<img src="../image/mhcw5e6e-cpzpf35.svg" className={styles.frame31} />
<p className={styles.a25}>
剧场名爱微剧场&nbsp;&nbsp;&nbsp;
<br />
承制 妙想制片厂&nbsp;&nbsp;&nbsp;&nbsp;
<br />
版权可梦&nbsp;&nbsp;
</p>
</div>
<div className={styles.autoWrapper16}>
<img src="../image/mhcw5e6e-6kmpbld.svg" className={styles.frame12} />
<p className={styles.a26}>9999W</p>
<img src="../image/mhcw5e6e-l10msdd.svg" className={styles.frame13} />
<p className={styles.a26}>374W</p>
</div>
</div>
<div className={styles.autoWrapper19}>
<div className={styles.autoWrapper18}>
<img src="../image/mhcw5e6e-5x5ujui.svg" className={styles.frame21} />
<p className={styles.a29}>300W</p>
</div>
<p className={styles.a210}>用户评论总结</p>
</div>
</div>
<img src="../image/mhcw5e6e-epiv9as.svg" className={styles.frame24} />
<div className={styles.frame38}>
<div className={styles.frame37}>
<div className={styles.frame35}>
<div className={styles.frame34}>
<p className={styles.a1}>5</p>
</div>
</div>
<div className={styles.autoWrapper17}>
<p className={styles.a24}>奶团</p>
<div className={styles.frame32}>
<img
src="../image/mhcw5e6e-cpzpf35.svg"
className={styles.frame31}
/>
<p className={styles.a25}>
剧场名爱微剧场&nbsp;&nbsp;&nbsp;
<br />
承制 妙想制片厂&nbsp;&nbsp;&nbsp;&nbsp;
<br />
版权可梦&nbsp;&nbsp;
</p>
</div>
<div className={styles.autoWrapper16}>
<img
src="../image/mhcw5e6e-6kmpbld.svg"
className={styles.frame12}
/>
<p className={styles.a26}>9999W</p>
<img
src="../image/mhcw5e6e-l10msdd.svg"
className={styles.frame13}
/>
<p className={styles.a26}>374W</p>
</div>
</div>
<img src="../image/mhcw5e6e-5x5ujui.svg" className={styles.frame36} />
<p className={styles.a27}>300W</p>
</div>
<p className={styles.a212}>用户评论总结</p>
</div>
</div>
</div>
);
}
export default Component;

View File

@ -0,0 +1,659 @@
* {
box-sizing: border-box;
}
.lunacyBackground {
display: flex;
flex-direction: column;
align-items: flex-start;
border-radius: 38px;
background: #ebedf2;
padding: 46px 13px 36px 30px;
width: 856px;
height: 2013px;
.autoWrapper {
display: flex;
align-items: flex-start;
align-self: stretch;
padding-right: 0;
padding-left: 304px;
.frame {
margin-top: 9px;
width: 50px;
height: 38px;
}
.text {
margin: 0px 0px 0px 12px;
line-height: 51px;
letter-spacing: 1.9px;
color: #272727;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 40px;
}
.frame2 {
margin-top: 10px;
margin-left: 68px;
width: 259px;
height: 69px;
}
}
.frame3 {
display: flex;
flex-direction: column;
align-items: flex-start;
align-self: stretch;
margin-top: 13px;
margin-right: 17px;
padding: 53px 58px 64px 35px;
background-image: url(../image/mhcw5e6d-c7qpzc4.png);
background-position: center;
background-size: cover;
background-repeat: no-repeat;
.text2 {
margin: 0px 0px 0px 156px;
width: 414px;
text-align: center;
line-height: 60px;
letter-spacing: 0;
color: #ffffff;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 37px;
text-shadow: 0px 5px 11px #000000;
}
.text3 {
width: 703px;
text-align: center;
line-height: 50px;
letter-spacing: 0;
color: #ffffff;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 48px;
text-shadow: 0px 0px 11px #000000;
}
}
.frame4 {
margin-top: 30px;
margin-left: 355px;
width: 85px;
height: 12px;
}
.autoWrapper2 {
display: flex;
align-items: flex-start;
align-self: stretch;
justify-content: space-between;
margin-top: 50px;
padding-right: 226px;
padding-left: 220px;
.a2 {
line-height: 36px;
letter-spacing: 0;
color: #222222;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 28px;
}
.frame5 {
margin-top: 13px;
width: 21px;
height: 12px;
}
}
.autoWrapper3 {
display: flex;
align-items: center;
align-self: stretch;
justify-content: space-between;
margin-top: 47px;
padding-right: 35px;
padding-left: 0;
.frame6 {
display: flex;
align-items: flex-start;
border-radius: 28px;
background: #41a4fd;
padding: 16px 62px 18px;
.a22 {
line-height: 36px;
letter-spacing: 0;
color: #ffffff;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 28px;
}
}
.frame7 {
display: flex;
align-items: flex-start;
border-radius: 28px;
background: #d7dbdf;
padding: 16px 62px 18px;
.a23 {
line-height: 36px;
letter-spacing: 0;
color: #6d6f74;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 28px;
}
}
}
.frame39 {
display: flex;
flex-direction: column;
align-items: flex-start;
align-self: stretch;
margin-top: 30px;
margin-right: 17px;
border-radius: 35px;
background: #ffffff;
padding: 29px 29px 36px 28px;
.a210 {
margin: 130px 0px 0px;
line-height: 26px;
letter-spacing: 0;
color: #6d6f74;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 28px;
}
.a29 {
line-height: 41px;
letter-spacing: 0;
color: #ff3419;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 32px;
}
.frame21 {
margin-top: 6px;
width: 26px;
height: 32px;
}
.a1 {
line-height: 25px;
letter-spacing: 0;
color: #ffffff;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 32px;
}
.a27 {
margin: 0px 0px 0px 7px;
line-height: 41px;
letter-spacing: 0;
color: #ff3419;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 32px;
}
.frame10 {
margin-top: 18px;
width: 20px;
height: 18px;
}
.frame9 {
width: 22px;
height: 21px;
}
.frame8 {
width: 150px;
height: 204px;
}
.frame13 {
margin-top: 7px;
margin-left: 45px;
width: 26px;
height: 23px;
}
.a26 {
margin: 0px 0px 0px 10px;
line-height: 33px;
letter-spacing: 0;
color: #6d6f74;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 26px;
}
.frame12 {
margin-top: 5px;
width: 26px;
height: 26px;
}
.a25 {
line-height: 33px;
letter-spacing: 0;
color: #a5a8b1;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 26px;
}
.a24 {
line-height: 41px;
letter-spacing: 0;
color: #222222;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 32px;
}
.autoWrapper17 {
display: flex;
flex-direction: column;
align-items: flex-start;
align-self: stretch;
margin-left: 25px;
.frame32 {
display: flex;
align-items: flex-start;
align-self: stretch;
justify-content: space-between;
margin-top: 7px;
min-width: 267px;
.frame31 {
margin-top: 11px;
width: 22px;
height: 94px;
}
}
.autoWrapper16 {
display: flex;
align-items: flex-start;
align-self: stretch;
margin-top: 14px;
padding-right: 1px;
padding-left: 0;
}
}
.frame17 {
position: relative;
width: 738px;
height: 205px;
.frame16 {
display: flex;
position: absolute;
top: 0;
left: 0;
align-items: flex-start;
padding-top: 1px;
padding-left: 1px;
width: 737px;
height: 205px;
.autoWrapper7 {
display: flex;
flex-direction: column;
align-items: flex-start;
align-self: stretch;
margin-left: 25px;
.autoWrapper5 {
display: flex;
align-items: flex-start;
align-self: stretch;
justify-content: space-between;
margin-top: 7px;
padding-right: 0;
padding-left: 2px;
.autoWrapper4 {
display: flex;
flex-direction: column;
align-items: center;
align-self: stretch;
margin-top: 8px;
.frame11 {
margin-top: 16px;
width: 22px;
height: 22px;
}
}
}
.autoWrapper6 {
display: flex;
align-items: flex-start;
align-self: stretch;
margin-top: 16px;
padding-right: 3px;
padding-left: 0;
}
}
.frame14 {
margin-top: 6px;
margin-left: 177px;
width: 26px;
height: 32px;
}
.frame15 {
display: flex;
position: absolute;
top: 0;
left: 0;
align-items: center;
border-radius: 9px;
padding: 12px 19px 12px 10px;
width: 39px;
height: 49px;
background-image: linear-gradient(180deg, #ffe100 0%, #ffb200 100%);
}
}
.a28 {
position: absolute;
top: 173px;
left: 570px;
width: 168px;
height: 26px;
line-height: 26px;
letter-spacing: 0;
color: #6d6f74;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 28px;
}
}
.frame18 {
margin-top: 31px;
width: 738px;
height: 2px;
}
.frame23 {
display: flex;
position: relative;
align-items: flex-start;
align-self: stretch;
margin: 30px 1px 0px;
padding-top: 1px;
padding-left: 1px;
.autoWrapper9 {
display: flex;
flex-direction: column;
align-items: flex-start;
align-self: stretch;
margin-left: 25px;
.frame20 {
display: flex;
align-items: flex-start;
align-self: stretch;
justify-content: space-between;
margin-top: 7px;
margin-right: 24px;
min-width: 242px;
.frame19 {
margin-top: 8px;
width: 22px;
height: 21px;
}
}
.autoWrapper8 {
display: flex;
align-items: flex-start;
align-self: stretch;
margin-top: 86px;
padding-right: 0;
padding-left: 0;
}
}
.autoWrapper11 {
display: flex;
flex-direction: column;
align-items: flex-start;
align-self: stretch;
margin-left: 125px;
.autoWrapper10 {
display: flex;
align-items: flex-start;
align-self: stretch;
justify-content: space-between;
padding-right: 0;
padding-left: 55px;
}
}
.frame22 {
display: flex;
position: absolute;
top: 0;
left: 0;
align-items: center;
border-radius: 9px;
padding: 12px 12px 12px 10px;
width: 39px;
height: 49px;
background-image: linear-gradient(180deg, #c9d8dc 0%, #9ca6b2 100%);
}
}
.frame24 {
margin-top: 30px;
width: 738px;
height: 2px;
}
.frame28 {
position: relative;
margin-top: 30px;
width: 738px;
height: 205px;
.frame27 {
display: flex;
position: absolute;
top: 0;
left: 0;
align-items: flex-start;
padding-top: 1px;
padding-left: 1px;
width: 737px;
height: 205px;
.autoWrapper15 {
display: flex;
flex-direction: column;
align-items: flex-start;
align-self: stretch;
margin-left: 25px;
.autoWrapper13 {
display: flex;
align-items: flex-start;
align-self: stretch;
justify-content: space-between;
margin-top: 7px;
padding-right: 9px;
padding-left: 0;
.autoWrapper12 {
display: flex;
flex-direction: column;
align-items: center;
align-self: stretch;
margin-top: 8px;
}
}
.autoWrapper14 {
display: flex;
align-items: flex-start;
align-self: stretch;
margin-top: 53px;
padding-right: 0;
padding-left: 0;
}
}
.frame25 {
margin-top: 6px;
margin-left: 180px;
width: 26px;
height: 32px;
}
.frame26 {
display: flex;
position: absolute;
top: 0;
left: 0;
align-items: center;
border-radius: 9px;
padding: 12px 12px 12px 10px;
width: 39px;
height: 49px;
background-image: linear-gradient(180deg, #de8260 0%, #ae3829 100%);
}
}
.a211 {
position: absolute;
top: 172px;
left: 570px;
width: 168px;
height: 26px;
line-height: 26px;
letter-spacing: 0;
color: #6d6f74;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 28px;
}
}
.frame33 {
display: flex;
align-items: flex-start;
align-self: stretch;
margin-top: 30px;
margin-right: 3px;
.frame30 {
display: flex;
align-items: flex-start;
padding-right: 111px;
padding-bottom: 155px;
background-image: url(../image/mhcw5e6e-824j2v2.png);
background-position: center;
background-size: cover;
background-repeat: no-repeat;
.frame29 {
display: flex;
flex-grow: 1;
align-items: center;
border-radius: 9px;
background: #000000a6;
padding: 12px 10px;
}
}
.autoWrapper19 {
display: flex;
flex-direction: column;
align-items: flex-start;
align-self: stretch;
margin-left: 126px;
.autoWrapper18 {
display: flex;
align-items: flex-start;
align-self: stretch;
justify-content: space-between;
padding-right: 0;
padding-left: 53px;
}
}
}
.frame38 {
position: relative;
margin-top: 30px;
margin-left: 2px;
width: 737px;
height: 204px;
.frame37 {
display: flex;
position: absolute;
top: 0;
left: 0;
align-items: flex-start;
width: 736px;
height: 204px;
.frame35 {
display: flex;
align-items: flex-start;
padding-right: 111px;
padding-bottom: 155px;
background-image: url(../image/mhcw5e6e-7s1hyk3.png);
background-position: center;
background-size: cover;
background-repeat: no-repeat;
.frame34 {
display: flex;
flex-grow: 1;
align-items: center;
border-radius: 9px;
background: #000000a6;
padding: 12px 12px 12px 10px;
}
}
.frame36 {
margin-top: 6px;
margin-left: 179px;
width: 26px;
height: 32px;
}
}
.a212 {
position: absolute;
top: 171px;
left: 569px;
width: 168px;
height: 26px;
line-height: 26px;
letter-spacing: 0;
color: #6d6f74;
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
font-size: 28px;
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -0,0 +1,24 @@
<svg width="259" height="69" viewBox="0 0 259 69" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_0_7)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 34.5C0 15.4462 15.4462 0 34.5 0H224.5C243.554 0 259 15.4462 259 34.5C259 53.5538 243.554 69 224.5 69H34.5C15.4462 69 0 53.5538 0 34.5Z" fill="white" fill-opacity="0.74902"/>
</g>
<circle cx="51.5" cy="34.5" r="7.5" fill="black"/>
<circle cx="209.5" cy="34.5" r="18.5" fill="black"/>
<circle cx="209.5" cy="34.5" r="5.5" fill="black"/>
<circle cx="35.5" cy="34.5" r="4.5" fill="black"/>
<circle cx="66.5" cy="34.5" r="4.5" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M115 34.5C115 32.567 116.567 31 118.5 31H141.5C143.433 31 145 32.567 145 34.5C145 36.433 143.433 38 141.5 38H118.5C116.567 38 115 36.433 115 34.5Z" fill="black"/>
<rect x="87" y="15" width="4" height="39" fill="#959595" fill-opacity="0.74902"/>
<rect x="169" y="15" width="4" height="39" fill="#959595" fill-opacity="0.74902"/>
<defs>
<filter id="filter0_d_0_7" x="-4" y="-4" width="267" height="77" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.392157 0 0 0 0 0.392157 0 0 0 0 0.392157 0 0 0 0.2 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_0_7"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_0_7" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,7 @@
<svg width="85" height="12" viewBox="0 0 85 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="4.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 6C15 2.68629 17.6863 0 21 0H34C37.3137 0 40 2.68629 40 6C40 9.31371 37.3137 12 34 12H21C17.6863 12 15 9.31371 15 6Z" fill="#41A4FD"/>
<circle cx="50.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
<circle cx="65.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
<circle cx="80.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
</svg>

After

Width:  |  Height:  |  Size: 497 B

View File

@ -0,0 +1,3 @@
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2008 12.9985L10.4014 18.1985V7.79852L18.2008 12.9985L18.2008 12.9985ZM12.9998 23.3985C18.7447 23.3985 23.401 18.7427 23.401 12.9985C23.401 7.25429 18.7447 2.59851 12.9998 2.59851C7.25495 2.59851 2.59863 7.25728 2.59863 12.9985C2.59863 18.7397 7.25495 23.3985 12.9998 23.3985V23.3985ZM13 26C5.81965 26 0 20.181 0 13.0015C0 5.82197 5.81965 0 13 0C20.1804 0 26 5.81898 26 12.9985C26 20.178 20.1804 26 13 26V26Z" fill="#6D6F74"/>
</svg>

After

Width:  |  Height:  |  Size: 582 B

View File

@ -0,0 +1,3 @@
<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.0559 0H0.945609C0.423363 0 0 0.447715 0 1V17C0 17.552 0.423633 18 0.945609 18H19.0559C19.5764 18 20 17.552 20 17V1C19.9992 0.448095 19.5763 0.000888824 19.0544 0H19.0559ZM4.50451 16H1.8916V13.9968H4.51359V11.9968H1.8916V9.9968H4.51359V7.9968H1.8916V5.9968H4.51359V3.9968H1.8916V2H4.50451V16V16ZM18.1093 3.99519L16.0637 3.99519V5.99519L18.1093 5.99519V7.99519L16.0637 7.99519V9.99519L18.1093 9.99519V11.9952H16.0637V13.9952L18.1093 13.9952V15.9968H16.041V1.9968H18.1093V3.9952V3.99519Z" fill="#AFB1B7"/>
</svg>

After

Width:  |  Height:  |  Size: 659 B

View File

@ -0,0 +1,3 @@
<svg width="738" height="2" viewBox="0 0 738 2" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 1H738" stroke="#E1E3E5" stroke-linecap="square"/>
</svg>

After

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

View File

@ -0,0 +1,3 @@
<svg width="21" height="12" viewBox="0 0 21 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.2205 0C19.6854 0 20.0621 0.0872106 20.3507 0.261632C20.6393 0.436053 20.8317 0.665972 20.9279 0.951388C21.024 1.2368 21.024 1.5579 20.9279 1.91467C20.8317 2.27144 20.6233 2.62424 20.3026 2.97309C19.0041 4.40017 17.7536 5.79553 16.5512 7.15919C15.3488 8.52284 14.1865 9.79929 13.0643 10.9885C12.7597 11.3057 12.403 11.5514 11.9942 11.7259C11.5854 11.9003 11.1646 11.9914 10.7317 11.9994C10.2989 12.0073 9.88205 11.9399 9.48126 11.7972C9.08047 11.6545 8.7438 11.4325 8.47127 11.1312C7.84603 10.4494 7.19675 9.75172 6.52342 9.03818L4.45534 6.89756C3.78201 6.18402 3.11269 5.48237 2.44738 4.79262C1.78206 4.10286 1.16084 3.43293 0.583698 2.78281C0.311159 2.48154 0.134811 2.16837 0.0546533 1.84332C-0.0255049 1.51826 -0.0174891 1.22095 0.0787007 0.95139C0.174891 0.681831 0.359255 0.45984 0.631792 0.285419C0.90433 0.110998 1.25703 0.0237872 1.68988 0.0237872C2.25099 0.0237872 3.02452 0.019823 4.01046 0.011895C4.99641 0.00396702 6.07454 3.18977e-06 7.24485 2.83536e-06H10.852H14.3869H17.3448H19.2205V0Z" fill="#41A4FD"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,5 @@
<svg width="23" height="94" viewBox="0 0 23 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 7.6475C0 8.16531 0 14.2805 0 14.2805C0 16.59 1.5961 18.2743 2.9357 19.2803C4.49022 20.4491 6.05075 21.0036 6.34432 21.0597C6.6374 21.0036 8.19793 20.4491 9.75294 19.2803C11.092 18.2743 12.6881 16.59 12.6881 14.2805V13.7094C12.0797 13.3309 11.5066 12.8983 10.9758 12.4169C9.98786 13.0481 8.71463 13.012 7.76407 12.3257C7.56166 12.1719 7.51867 11.8848 7.66715 11.6785C7.81563 11.4721 8.1015 11.4217 8.31164 11.5647C8.89103 11.9827 9.65433 12.0452 10.294 11.727C10.1828 11.6028 10.0761 11.4765 9.97387 11.3483C9.4759 10.7211 9.10268 10.0648 8.85671 9.38445L8.6007 8.44662L8.58267 8.34643L8.58067 8.33541C8.57466 8.29922 8.56899 8.26299 8.56363 8.22671L8.56163 8.21319L8.54661 8.10548L8.5451 8.08944C8.5401 8.05437 8.53608 8.0183 8.53258 7.98274L8.53157 7.97272C8.52026 7.86458 8.5119 7.75616 8.50653 7.64757C8.50653 7.63454 8.50552 7.62202 8.50502 7.60899L8.50302 7.53885L8.50101 7.48124C6.62838 7.62753 4.55635 7.57593 2.85755 7.33295C1.58909 7.1511 1.57376 7.10999 1.07139 6.96036C0.518555 6.79431 0 7.12969 0 7.6475ZM6.35453 15.4708H6.33449C5.40904 15.4719 4.60439 16.1058 4.38672 17.0053H8.3023C8.08447 16.1059 7.27991 15.4721 6.35453 15.4708H6.35453ZM21.0654 0.431909C19.7504 0.192045 18.9529 -0.000125216 15.781 2.63281e-08C12.6091 0.000125268 11.8126 0.236747 10.5555 0.450036C9.8584 0.572662 9.4375 1.21924 9.4375 2C9.4375 3.93591 9.4375 7.35551 9.4375 7.35551C9.4375 7.41963 9.439 7.48375 9.44151 7.54688L9.44301 7.58796C9.44602 7.64908 9.45002 7.7097 9.45503 7.76981L9.45854 7.80638C9.46305 7.85848 9.46856 7.91009 9.47507 7.96119L9.48008 8.00126C9.4871 8.05637 9.49561 8.11198 9.50513 8.16709L9.51515 8.2247C9.52763 8.29497 9.54166 8.36497 9.55723 8.43462L9.58328 8.54483L9.60332 8.62248C9.62771 8.71476 9.65478 8.80632 9.68448 8.89712C10.3478 10.9279 12.2014 12.3251 13.3987 13.0456L13.4072 13.0516C14.5645 13.748 15.5539 14.0911 15.7813 14.1347C16.0744 14.0786 17.6348 13.524 19.1905 12.3557C20.5291 11.3492 22.1251 9.66562 22.1251 7.35552C22.1251 7.35552 22.1248 2.85222 22.1249 2.00018C22.125 1.14813 21.6113 0.536591 21.0654 0.431909ZM1.47105 11.6714C1.62246 11.4615 1.91529 11.414 2.12531 11.5652C2.79752 12.0504 3.70497 12.0504 4.37717 11.5652C4.58732 11.4222 4.87319 11.4726 5.02166 11.679C5.17014 11.8853 5.12715 12.1724 4.92474 12.3262C3.9256 13.0473 2.57689 13.0473 1.57775 12.3262C1.3677 12.1749 1.31993 11.8821 1.47105 11.6719V11.6714ZM17.7388 9.01526H13.8232C14.0411 9.91465 14.8456 10.5485 15.771 10.5497H15.7911C16.7165 10.5487 17.5212 9.91475 17.7388 9.01526L17.7388 9.01526ZM17.2011 4.6227C18.2003 3.90213 19.5485 3.90213 20.5476 4.6227C20.691 4.7176 20.7709 4.88334 20.7559 5.05461C20.7409 5.22588 20.6334 5.37521 20.4757 5.44375C20.3181 5.51229 20.1355 5.48905 20.0001 5.38318C19.328 4.89819 18.4208 4.89819 17.7487 5.38318C17.613 5.4863 17.432 5.50766 17.276 5.43897C17.1201 5.37028 17.0136 5.22237 16.9981 5.05264C16.9825 4.88292 17.0602 4.71811 17.2011 4.6222L17.2011 4.6227ZM11.0141 4.6227C12.0133 3.90189 13.3618 3.90189 14.3611 4.6227C14.5044 4.7176 14.5844 4.88334 14.5694 5.05461C14.5544 5.22588 14.4468 5.37521 14.2892 5.44374C14.1315 5.51228 13.949 5.48904 13.8135 5.38318C13.1414 4.89819 12.2342 4.89819 11.5621 5.38318C11.4263 5.48256 11.2475 5.50148 11.0939 5.43274C10.9402 5.36401 10.8352 5.21817 10.8187 5.05063C10.8022 4.8831 10.8768 4.71958 11.0141 4.6222L11.0141 4.6227Z" fill="#AFB1B7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.1184 36H2.00811C1.48586 36 1.0625 36.4477 1.0625 37V53C1.0625 53.552 1.48613 54 2.00811 54H20.1184C20.6389 54 21.0625 53.552 21.0625 53V37C21.0617 36.4481 20.6388 36.0009 20.1169 36H20.1184ZM5.56701 52H2.9541V49.9968H5.57609V47.9968H2.9541V45.9968H5.57609V43.9968H2.9541V41.9968H5.57609V39.9968H2.9541V38H5.56701V52V52ZM19.1718 39.9952L17.1262 39.9952V41.9952L19.1718 41.9952V43.9952L17.1262 43.9952V45.9952L19.1718 45.9952V47.9952H17.1262V49.9952L19.1718 49.9952V51.9968H17.1035V37.9968H19.1718V39.9952V39.9952Z" fill="#AFB1B7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0625 72C17.1345 72 22.0625 76.928 22.0625 83C22.0625 89.072 17.1345 94 11.0625 94C4.9905 94 0.0625 89.072 0.0625 83C0.0625 76.928 4.9905 72 11.0625 72V72ZM11.0625 77.5C8.0265 77.5 5.5625 79.964 5.5625 83C5.56591 85.4706 7.21552 87.636 9.59643 88.2952C11.9773 88.9545 14.5058 87.946 15.7794 85.8291L13.8929 84.6983C13.1293 85.97 11.6111 86.5759 10.1818 86.1795C8.75244 85.7831 7.76322 84.4817 7.7637 82.9985C7.76418 81.5152 8.75425 80.2145 10.1838 79.819C11.6134 79.4235 13.1312 80.0304 13.894 81.3026L15.7794 80.1696C14.7853 78.5129 12.9946 77.4994 11.0625 77.5H11.0625Z" fill="#AFB1B7"/>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

View File

@ -0,0 +1,3 @@
<svg width="26" height="23" viewBox="0 0 26 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8674 2.08791C22.524 0.748704 20.698 0 18.7465 0C16.7951 0 14.9691 0.748704 13.6048 2.10824L13.0099 2.69215L12.4121 2.10137C11.0409 0.746233 9.21431 0 7.26929 0C5.32428 0 3.49764 0.746232 2.12649 2.10137C0.757832 3.44305 0 5.2478 0 7.17641C0 9.10503 0.757832 10.9098 2.13343 12.258L4.41082 14.5091L11.3269 21.3447L13.0018 23L14.6787 21.3466L21.6106 14.511L23.8871 12.2457C25.2227 10.9477 26 9.09294 26 7.17641C25.9997 5.25988 25.2227 3.40515 23.8674 2.08791L23.8674 2.08791ZM22.2037 10.5942L19.935 12.8521L13.003 19.6874L6.08694 12.8518L3.80261 10.5939C1.89344 8.72267 1.89344 5.62981 3.80261 3.75832C4.75719 2.81489 6.01274 2.34331 7.26857 2.34331C8.5244 2.34331 9.77995 2.81489 10.7345 3.75832L12.2374 5.24392H13.7745L15.2879 3.75832C16.2347 2.81489 17.4903 2.34331 18.7461 2.34331C20.0019 2.34331 21.2575 2.81489 22.204 3.75832C23.1586 4.68637 23.628 5.93879 23.628 7.1761C23.628 8.41342 23.1583 9.66611 22.2037 10.5942V10.5942Z" fill="#6D6F74"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="23" height="22" viewBox="0 0 23 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 7.6475C0 8.16531 0 14.2805 0 14.2805C0 16.59 1.5961 18.2743 2.9357 19.2803C4.49022 20.4491 6.05075 21.0036 6.34432 21.0597C6.6374 21.0036 8.19793 20.4491 9.75294 19.2803C11.092 18.2743 12.6881 16.59 12.6881 14.2805V13.7094C12.0797 13.3309 11.5066 12.8983 10.9758 12.4169C9.98786 13.0481 8.71463 13.012 7.76407 12.3257C7.56166 12.1719 7.51867 11.8848 7.66715 11.6785C7.81563 11.4721 8.1015 11.4217 8.31164 11.5647C8.89103 11.9827 9.65433 12.0452 10.294 11.727C10.1828 11.6028 10.0761 11.4765 9.97387 11.3483C9.4759 10.7211 9.10268 10.0648 8.85671 9.38445L8.6007 8.44662L8.58267 8.34643L8.58067 8.33541C8.57466 8.29922 8.56899 8.26299 8.56363 8.22671L8.56163 8.21319L8.54661 8.10548L8.5451 8.08944C8.5401 8.05437 8.53608 8.0183 8.53258 7.98274L8.53157 7.97272C8.52026 7.86458 8.5119 7.75616 8.50653 7.64757C8.50653 7.63454 8.50552 7.62202 8.50502 7.60899L8.50302 7.53885L8.50101 7.48124C6.62838 7.62753 4.55635 7.57593 2.85755 7.33295C1.58909 7.1511 1.57376 7.10999 1.07139 6.96036C0.518555 6.79431 0 7.12969 0 7.6475ZM6.35453 15.4708H6.33449C5.40904 15.4719 4.60439 16.1058 4.38672 17.0053H8.3023C8.08447 16.1059 7.27991 15.4721 6.35453 15.4708H6.35453ZM21.0654 0.431909C19.7504 0.192045 18.9529 -0.000125216 15.781 2.63279e-08C12.6091 0.000125268 11.8126 0.236747 10.5555 0.450036C9.8584 0.572662 9.4375 1.21924 9.4375 2C9.4375 3.93591 9.4375 7.35551 9.4375 7.35551C9.4375 7.41963 9.439 7.48375 9.44151 7.54688L9.44301 7.58796C9.44602 7.64908 9.45002 7.7097 9.45503 7.76981L9.45854 7.80638C9.46305 7.85848 9.46856 7.91009 9.47507 7.96119L9.48008 8.00126C9.4871 8.05637 9.49561 8.11198 9.50513 8.16709L9.51515 8.2247C9.52763 8.29497 9.54166 8.36497 9.55723 8.43462L9.58328 8.54483L9.60332 8.62248C9.62771 8.71476 9.65478 8.80632 9.68448 8.89712C10.3478 10.9279 12.2014 12.3251 13.3987 13.0456L13.4072 13.0516C14.5645 13.748 15.5539 14.0911 15.7813 14.1347C16.0744 14.0786 17.6348 13.524 19.1905 12.3557C20.5291 11.3492 22.1251 9.66562 22.1251 7.35552C22.1251 7.35552 22.1248 2.85222 22.1249 2.00018C22.125 1.14813 21.6113 0.536591 21.0654 0.431909ZM1.47105 11.6714C1.62246 11.4615 1.91529 11.414 2.12531 11.5652C2.79752 12.0504 3.70497 12.0504 4.37717 11.5652C4.58732 11.4222 4.87319 11.4726 5.02166 11.679C5.17014 11.8853 5.12715 12.1724 4.92474 12.3262C3.9256 13.0473 2.57689 13.0473 1.57775 12.3262C1.3677 12.1749 1.31993 11.8821 1.47105 11.6719V11.6714ZM17.7388 9.01526H13.8232C14.0411 9.91465 14.8456 10.5485 15.771 10.5497H15.7911C16.7165 10.5487 17.5212 9.91475 17.7388 9.01526L17.7388 9.01526ZM17.2011 4.6227C18.2003 3.90213 19.5485 3.90213 20.5476 4.6227C20.691 4.7176 20.7709 4.88334 20.7559 5.05461C20.7409 5.22588 20.6334 5.37521 20.4757 5.44375C20.3181 5.51229 20.1355 5.48905 20.0001 5.38318C19.328 4.89819 18.4208 4.89819 17.7487 5.38318C17.613 5.4863 17.432 5.50766 17.276 5.43897C17.1201 5.37028 17.0136 5.22237 16.9981 5.05264C16.9825 4.88292 17.0602 4.71811 17.2011 4.6222L17.2011 4.6227ZM11.0141 4.6227C12.0133 3.90189 13.3618 3.90189 14.3611 4.6227C14.5044 4.7176 14.5844 4.88334 14.5694 5.05461C14.5544 5.22588 14.4468 5.37521 14.2892 5.44374C14.1315 5.51228 13.949 5.48904 13.8135 5.38318C13.1414 4.89819 12.2342 4.89819 11.5621 5.38318C11.4263 5.48256 11.2475 5.50148 11.0939 5.43274C10.9402 5.36401 10.8352 5.21817 10.8187 5.05063C10.8022 4.8831 10.8768 4.71958 11.0141 4.6222L11.0141 4.6227Z" fill="#AFB1B7"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

View File

@ -0,0 +1,3 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 0C17.072 0 22 4.928 22 11C22 17.072 17.072 22 11 22C4.928 22 0 17.072 0 11C0 4.928 4.928 0 11 0V0ZM11 5.5C7.964 5.5 5.5 7.964 5.5 11C5.50341 13.4706 7.15302 15.636 9.53393 16.2952C11.9148 16.9545 14.4433 15.946 15.7169 13.8291L13.8304 12.6983C13.0668 13.97 11.5486 14.5759 10.1193 14.1795C8.68994 13.7831 7.70072 12.4817 7.7012 10.9985C7.70168 9.51517 8.69175 8.21446 10.1213 7.81898C11.5509 7.4235 13.0687 8.03044 13.8315 9.3026L15.7169 8.1696C14.7228 6.51286 12.9321 5.49941 11 5.5H11Z" fill="#AFB1B7"/>
</svg>

After

Width:  |  Height:  |  Size: 662 B

View File

@ -0,0 +1,24 @@
<svg width="259" height="69" viewBox="0 0 259 69" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_0_7)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 34.5C0 15.4462 15.4462 0 34.5 0H224.5C243.554 0 259 15.4462 259 34.5C259 53.5538 243.554 69 224.5 69H34.5C15.4462 69 0 53.5538 0 34.5Z" fill="white" fill-opacity="0.74902"/>
</g>
<circle cx="51.5" cy="34.5" r="7.5" fill="black"/>
<circle cx="209.5" cy="34.5" r="18.5" fill="black"/>
<circle cx="209.5" cy="34.5" r="5.5" fill="black"/>
<circle cx="35.5" cy="34.5" r="4.5" fill="black"/>
<circle cx="66.5" cy="34.5" r="4.5" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M115 34.5C115 32.567 116.567 31 118.5 31H141.5C143.433 31 145 32.567 145 34.5C145 36.433 143.433 38 141.5 38H118.5C116.567 38 115 36.433 115 34.5Z" fill="black"/>
<rect x="87" y="15" width="4" height="39" fill="#959595" fill-opacity="0.74902"/>
<rect x="169" y="15" width="4" height="39" fill="#959595" fill-opacity="0.74902"/>
<defs>
<filter id="filter0_d_0_7" x="-4" y="-4" width="267" height="77" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.392157 0 0 0 0 0.392157 0 0 0 0 0.392157 0 0 0 0.2 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_0_7"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_0_7" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,7 @@
<svg width="85" height="12" viewBox="0 0 85 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="4.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 6C15 2.68629 17.6863 0 21 0H34C37.3137 0 40 2.68629 40 6C40 9.31371 37.3137 12 34 12H21C17.6863 12 15 9.31371 15 6Z" fill="#41A4FD"/>
<circle cx="50.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
<circle cx="65.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
<circle cx="80.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
</svg>

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -0,0 +1,3 @@
<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.0559 0H0.945609C0.423363 0 0 0.447715 0 1V17C0 17.552 0.423633 18 0.945609 18H19.0559C19.5764 18 20 17.552 20 17V1C19.9992 0.448095 19.5763 0.000888824 19.0544 0H19.0559ZM4.50451 16H1.8916V13.9968H4.51359V11.9968H1.8916V9.9968H4.51359V7.9968H1.8916V5.9968H4.51359V3.9968H1.8916V2H4.50451V16V16ZM18.1093 3.99519L16.0637 3.99519V5.99519L18.1093 5.99519V7.99519L16.0637 7.99519V9.99519L18.1093 9.99519V11.9952H16.0637V13.9952L18.1093 13.9952V15.9968H16.041V1.9968H18.1093V3.9952V3.99519Z" fill="#AFB1B7"/>
</svg>

After

Width:  |  Height:  |  Size: 659 B

View File

@ -0,0 +1,3 @@
<svg width="23" height="22" viewBox="0 0 23 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 7.6475C0 8.16531 0 14.2805 0 14.2805C0 16.59 1.5961 18.2743 2.9357 19.2803C4.49022 20.4491 6.05075 21.0036 6.34432 21.0597C6.6374 21.0036 8.19793 20.4491 9.75294 19.2803C11.092 18.2743 12.6881 16.59 12.6881 14.2805V13.7094C12.0797 13.3309 11.5066 12.8983 10.9758 12.4169C9.98786 13.0481 8.71463 13.012 7.76407 12.3257C7.56166 12.1719 7.51867 11.8848 7.66715 11.6785C7.81563 11.4721 8.1015 11.4217 8.31164 11.5647C8.89103 11.9827 9.65433 12.0452 10.294 11.727C10.1828 11.6028 10.0761 11.4765 9.97387 11.3483C9.4759 10.7211 9.10268 10.0648 8.85671 9.38445L8.6007 8.44662L8.58267 8.34643L8.58067 8.33541C8.57466 8.29922 8.56899 8.26299 8.56363 8.22671L8.56163 8.21319L8.54661 8.10548L8.5451 8.08944C8.5401 8.05437 8.53608 8.0183 8.53258 7.98274L8.53157 7.97272C8.52026 7.86458 8.5119 7.75616 8.50653 7.64757C8.50653 7.63454 8.50552 7.62202 8.50502 7.60899L8.50302 7.53885L8.50101 7.48124C6.62838 7.62753 4.55635 7.57593 2.85755 7.33295C1.58909 7.1511 1.57376 7.10999 1.07139 6.96036C0.518555 6.79431 0 7.12969 0 7.6475ZM6.35453 15.4708H6.33449C5.40904 15.4719 4.60439 16.1058 4.38672 17.0053H8.3023C8.08447 16.1059 7.27991 15.4721 6.35453 15.4708H6.35453ZM21.0654 0.431909C19.7504 0.192045 18.9529 -0.000125216 15.781 2.63279e-08C12.6091 0.000125268 11.8126 0.236747 10.5555 0.450036C9.8584 0.572662 9.4375 1.21924 9.4375 2C9.4375 3.93591 9.4375 7.35551 9.4375 7.35551C9.4375 7.41963 9.439 7.48375 9.44151 7.54688L9.44301 7.58796C9.44602 7.64908 9.45002 7.7097 9.45503 7.76981L9.45854 7.80638C9.46305 7.85848 9.46856 7.91009 9.47507 7.96119L9.48008 8.00126C9.4871 8.05637 9.49561 8.11198 9.50513 8.16709L9.51515 8.2247C9.52763 8.29497 9.54166 8.36497 9.55723 8.43462L9.58328 8.54483L9.60332 8.62248C9.62771 8.71476 9.65478 8.80632 9.68448 8.89712C10.3478 10.9279 12.2014 12.3251 13.3987 13.0456L13.4072 13.0516C14.5645 13.748 15.5539 14.0911 15.7813 14.1347C16.0744 14.0786 17.6348 13.524 19.1905 12.3557C20.5291 11.3492 22.1251 9.66562 22.1251 7.35552C22.1251 7.35552 22.1248 2.85222 22.1249 2.00018C22.125 1.14813 21.6113 0.536591 21.0654 0.431909ZM1.47105 11.6714C1.62246 11.4615 1.91529 11.414 2.12531 11.5652C2.79752 12.0504 3.70497 12.0504 4.37717 11.5652C4.58732 11.4222 4.87319 11.4726 5.02166 11.679C5.17014 11.8853 5.12715 12.1724 4.92474 12.3262C3.9256 13.0473 2.57689 13.0473 1.57775 12.3262C1.3677 12.1749 1.31993 11.8821 1.47105 11.6719V11.6714ZM17.7388 9.01526H13.8232C14.0411 9.91465 14.8456 10.5485 15.771 10.5497H15.7911C16.7165 10.5487 17.5212 9.91475 17.7388 9.01526L17.7388 9.01526ZM17.2011 4.6227C18.2003 3.90213 19.5485 3.90213 20.5476 4.6227C20.691 4.7176 20.7709 4.88334 20.7559 5.05461C20.7409 5.22588 20.6334 5.37521 20.4757 5.44375C20.3181 5.51229 20.1355 5.48905 20.0001 5.38318C19.328 4.89819 18.4208 4.89819 17.7487 5.38318C17.613 5.4863 17.432 5.50766 17.276 5.43897C17.1201 5.37028 17.0136 5.22237 16.9981 5.05264C16.9825 4.88292 17.0602 4.71811 17.2011 4.6222L17.2011 4.6227ZM11.0141 4.6227C12.0133 3.90189 13.3618 3.90189 14.3611 4.6227C14.5044 4.7176 14.5844 4.88334 14.5694 5.05461C14.5544 5.22588 14.4468 5.37521 14.2892 5.44374C14.1315 5.51228 13.949 5.48904 13.8135 5.38318C13.1414 4.89819 12.2342 4.89819 11.5621 5.38318C11.4263 5.48256 11.2475 5.50148 11.0939 5.43274C10.9402 5.36401 10.8352 5.21817 10.8187 5.05063C10.8022 4.8831 10.8768 4.71958 11.0141 4.6222L11.0141 4.6227Z" fill="#AFB1B7"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,3 @@
<svg width="26" height="23" viewBox="0 0 26 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8674 2.08791C22.524 0.748704 20.698 0 18.7465 0C16.7951 0 14.9691 0.748704 13.6048 2.10824L13.0099 2.69215L12.4121 2.10137C11.0409 0.746233 9.21431 0 7.26929 0C5.32428 0 3.49764 0.746232 2.12649 2.10137C0.757832 3.44305 0 5.2478 0 7.17641C0 9.10503 0.757832 10.9098 2.13343 12.258L4.41082 14.5091L11.3269 21.3447L13.0018 23L14.6787 21.3466L21.6106 14.511L23.8871 12.2457C25.2227 10.9477 26 9.09294 26 7.17641C25.9997 5.25988 25.2227 3.40515 23.8674 2.08791L23.8674 2.08791ZM22.2041 10.5942L19.9353 12.8521L13.0034 19.6874L6.0873 12.8518L3.80297 10.5939C1.8938 8.72269 1.8938 5.62982 3.80297 3.75834C4.75756 2.8149 6.01311 2.34332 7.26893 2.34332C8.52476 2.34332 9.78031 2.8149 10.7349 3.75834L12.2378 5.24393H13.7748L15.2883 3.75834C16.2351 2.8149 17.4906 2.34332 18.7465 2.34332C20.0023 2.34332 21.2578 2.8149 22.2044 3.75834C23.159 4.68639 23.6283 5.93881 23.6283 7.17612C23.6283 8.41343 23.1587 9.66612 22.2041 10.5942V10.5942Z" fill="#6D6F74"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="26" height="32" viewBox="0 0 26 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.0631 10.399L18.6311 12.799C18.6311 12.799 18.6311 3.199 10.5181 0C10.5181 0 9.7081 8.8 5.65111 11.998C1.59513 15.2 -6.51884 24.797 9.7041 31.997C9.7041 31.997 1.59113 23.197 12.1361 16.8C12.1361 16.8 11.3261 19.998 15.3821 23.2C19.4391 26.399 15.3821 32 15.3821 32C15.3821 32 34.851 27.199 21.0631 10.399V10.399Z" fill="#FF3419"/>
</svg>

After

Width:  |  Height:  |  Size: 447 B

View File

@ -0,0 +1,3 @@
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2008 12.9985L10.4014 18.1985V7.79852L18.2008 12.9985L18.2008 12.9985ZM12.9998 23.3985C18.7447 23.3985 23.401 18.7427 23.401 12.9985C23.401 7.25429 18.7447 2.59851 12.9998 2.59851C7.25495 2.59851 2.59863 7.25728 2.59863 12.9985C2.59863 18.7397 7.25495 23.3985 12.9998 23.3985V23.3985ZM13 26C5.81965 26 0 20.181 0 13.0015C0 5.82197 5.81965 0 13 0C20.1804 0 26 5.81898 26 12.9985C26 20.178 20.1804 26 13 26V26Z" fill="#6D6F74"/>
</svg>

After

Width:  |  Height:  |  Size: 582 B

View File

@ -0,0 +1,3 @@
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2006 12.9985L10.4012 18.1985V7.7985L18.2006 12.9985L18.2006 12.9985ZM13 23.3985C18.7449 23.3985 23.4012 18.7427 23.4012 12.9985C23.4012 7.25429 18.7449 2.5985 13 2.5985C7.25512 2.5985 2.5988 7.25728 2.5988 12.9985C2.5988 18.7397 7.25512 23.3985 13 23.3985V23.3985ZM13 26C5.81965 26 0 20.181 0 13.0015C0 5.82197 5.81965 0 13 0C20.1804 0 26 5.81898 26 12.9985C26 20.178 20.1804 26 13 26V26Z" fill="#6D6F74"/>
</svg>

After

Width:  |  Height:  |  Size: 563 B

View File

@ -0,0 +1,3 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 0C17.072 0 22 4.928 22 11C22 17.072 17.072 22 11 22C4.928 22 0 17.072 0 11C0 4.928 4.928 0 11 0V0ZM11 5.5C7.964 5.5 5.5 7.964 5.5 11C5.50341 13.4706 7.15302 15.636 9.53393 16.2952C11.9148 16.9545 14.4433 15.946 15.7169 13.8291L13.8304 12.6983C13.0668 13.97 11.5486 14.5759 10.1193 14.1795C8.68994 13.7831 7.70072 12.4817 7.7012 10.9985C7.70168 9.51517 8.69175 8.21446 10.1213 7.81898C11.5509 7.4235 13.0687 8.03044 13.8315 9.3026L15.7169 8.1696C14.7228 6.51286 12.9321 5.49941 11 5.5H11Z" fill="#AFB1B7"/>
</svg>

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

View File

@ -0,0 +1,5 @@
<svg width="23" height="94" viewBox="0 0 23 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 7.6475C0 8.16531 0 14.2805 0 14.2805C0 16.59 1.5961 18.2743 2.9357 19.2803C4.49022 20.4491 6.05075 21.0036 6.34432 21.0597C6.6374 21.0036 8.19793 20.4491 9.75294 19.2803C11.092 18.2743 12.6881 16.59 12.6881 14.2805V13.7094C12.0797 13.3309 11.5066 12.8983 10.9758 12.4169C9.98786 13.0481 8.71463 13.012 7.76407 12.3257C7.56166 12.1719 7.51867 11.8848 7.66715 11.6785C7.81563 11.4721 8.1015 11.4217 8.31164 11.5647C8.89103 11.9827 9.65433 12.0452 10.294 11.727C10.1828 11.6028 10.0761 11.4765 9.97387 11.3483C9.4759 10.7211 9.10268 10.0648 8.85671 9.38445L8.6007 8.44662L8.58267 8.34643L8.58067 8.33541C8.57466 8.29922 8.56899 8.26299 8.56363 8.22671L8.56163 8.21319L8.54661 8.10548L8.5451 8.08944C8.5401 8.05437 8.53608 8.0183 8.53258 7.98274L8.53157 7.97272C8.52026 7.86458 8.5119 7.75616 8.50653 7.64757C8.50653 7.63454 8.50552 7.62202 8.50502 7.60899L8.50302 7.53885L8.50101 7.48124C6.62838 7.62753 4.55635 7.57593 2.85755 7.33295C1.58909 7.1511 1.57376 7.10999 1.07139 6.96036C0.518555 6.79431 0 7.12969 0 7.6475ZM6.35453 15.4708H6.33449C5.40904 15.4719 4.60439 16.1058 4.38672 17.0053H8.3023C8.08447 16.1059 7.27991 15.4721 6.35453 15.4708H6.35453ZM21.0654 0.431909C19.7504 0.192045 18.9529 -0.000125216 15.781 2.63281e-08C12.6091 0.000125268 11.8126 0.236747 10.5555 0.450036C9.8584 0.572662 9.4375 1.21924 9.4375 2C9.4375 3.93591 9.4375 7.35551 9.4375 7.35551C9.4375 7.41963 9.439 7.48375 9.44151 7.54688L9.44301 7.58796C9.44602 7.64908 9.45002 7.7097 9.45503 7.76981L9.45854 7.80638C9.46305 7.85848 9.46856 7.91009 9.47507 7.96119L9.48008 8.00126C9.4871 8.05637 9.49561 8.11198 9.50513 8.16709L9.51515 8.2247C9.52763 8.29497 9.54166 8.36497 9.55723 8.43462L9.58328 8.54483L9.60332 8.62248C9.62771 8.71476 9.65478 8.80632 9.68448 8.89712C10.3478 10.9279 12.2014 12.3251 13.3987 13.0456L13.4072 13.0516C14.5645 13.748 15.5539 14.0911 15.7813 14.1347C16.0744 14.0786 17.6348 13.524 19.1905 12.3557C20.5291 11.3492 22.1251 9.66562 22.1251 7.35552C22.1251 7.35552 22.1248 2.85222 22.1249 2.00018C22.125 1.14813 21.6113 0.536591 21.0654 0.431909ZM1.47105 11.6714C1.62246 11.4615 1.91529 11.414 2.12531 11.5652C2.79752 12.0504 3.70497 12.0504 4.37717 11.5652C4.58732 11.4222 4.87319 11.4726 5.02166 11.679C5.17014 11.8853 5.12715 12.1724 4.92474 12.3262C3.9256 13.0473 2.57689 13.0473 1.57775 12.3262C1.3677 12.1749 1.31993 11.8821 1.47105 11.6719V11.6714ZM17.7388 9.01526H13.8232C14.0411 9.91465 14.8456 10.5485 15.771 10.5497H15.7911C16.7165 10.5487 17.5212 9.91475 17.7388 9.01526L17.7388 9.01526ZM17.2011 4.6227C18.2003 3.90213 19.5485 3.90213 20.5476 4.6227C20.691 4.7176 20.7709 4.88334 20.7559 5.05461C20.7409 5.22588 20.6334 5.37521 20.4757 5.44375C20.3181 5.51229 20.1355 5.48905 20.0001 5.38318C19.328 4.89819 18.4208 4.89819 17.7487 5.38318C17.613 5.4863 17.432 5.50766 17.276 5.43897C17.1201 5.37028 17.0136 5.22237 16.9981 5.05264C16.9825 4.88292 17.0602 4.71811 17.2011 4.6222L17.2011 4.6227ZM11.0141 4.6227C12.0133 3.90189 13.3618 3.90189 14.3611 4.6227C14.5044 4.7176 14.5844 4.88334 14.5694 5.05461C14.5544 5.22588 14.4468 5.37521 14.2892 5.44374C14.1315 5.51228 13.949 5.48904 13.8135 5.38318C13.1414 4.89819 12.2342 4.89819 11.5621 5.38318C11.4263 5.48256 11.2475 5.50148 11.0939 5.43274C10.9402 5.36401 10.8352 5.21817 10.8187 5.05063C10.8022 4.8831 10.8768 4.71958 11.0141 4.6222L11.0141 4.6227Z" fill="#AFB1B7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.1184 36H2.00811C1.48586 36 1.0625 36.4477 1.0625 37V53C1.0625 53.552 1.48613 54 2.00811 54H20.1184C20.6389 54 21.0625 53.552 21.0625 53V37C21.0617 36.4481 20.6388 36.0009 20.1169 36H20.1184ZM5.56701 52H2.9541V49.9968H5.57609V47.9968H2.9541V45.9968H5.57609V43.9968H2.9541V41.9968H5.57609V39.9968H2.9541V38H5.56701V52V52ZM19.1718 39.9952L17.1262 39.9952V41.9952L19.1718 41.9952V43.9952L17.1262 43.9952V45.9952L19.1718 45.9952V47.9952H17.1262V49.9952L19.1718 49.9952V51.9968H17.1035V37.9968H19.1718V39.9952V39.9952Z" fill="#AFB1B7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0625 72C17.1345 72 22.0625 76.928 22.0625 83C22.0625 89.072 17.1345 94 11.0625 94C4.9905 94 0.0625 89.072 0.0625 83C0.0625 76.928 4.9905 72 11.0625 72V72ZM11.0625 77.5C8.0265 77.5 5.5625 79.964 5.5625 83C5.56591 85.4706 7.21552 87.636 9.59643 88.2952C11.9773 88.9545 14.5058 87.946 15.7794 85.8291L13.8929 84.6983C13.1293 85.97 11.6111 86.5759 10.1818 86.1795C8.75244 85.7831 7.76322 84.4817 7.7637 82.9985C7.76418 81.5152 8.75425 80.2145 10.1838 79.819C11.6134 79.4235 13.1312 80.0304 13.894 81.3026L15.7794 80.1696C14.7853 78.5129 12.9946 77.4994 11.0625 77.5H11.0625Z" fill="#AFB1B7"/>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -0,0 +1,3 @@
<svg width="738" height="2" viewBox="0 0 738 2" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 1H738" stroke="#E1E3E5" stroke-linecap="square"/>
</svg>

After

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

View File

@ -0,0 +1,3 @@
<svg width="50" height="38" viewBox="0 0 50 38" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.96056 15.1535C2.5462 14.37 0 11.3188 0 7.67434C0 3.43592 3.44369 0 7.6917 0C10.2552 0 12.5257 1.25119 13.9234 3.17489C15.2911 4.88748 15.6419 6.72398 15.9632 8.40619C16.3281 10.3168 16.6551 12.0285 18.3908 13.1334C21.6289 15.1948 24.5167 13.9049 27.7946 12.4407C30.752 11.1197 34.027 9.65684 38.1634 10.3851C44.8629 11.3863 50.0005 17.1524 50.0005 24.1157C50.0005 31.7838 43.7702 38 36.0847 38C28.7341 38 22.7146 32.3137 22.204 25.1093C22.0847 24.2802 22.0005 23.4596 21.9182 22.657C21.5515 19.0836 21.2212 15.8647 17.9913 13.8281C16.4778 12.8737 14.8768 13.5133 13.0203 14.255C11.0298 15.0502 8.74562 15.9628 5.96056 15.1535Z" fill="#272727"/>
</svg>

After

Width:  |  Height:  |  Size: 800 B

View File

@ -0,0 +1,3 @@
<svg width="26" height="23" viewBox="0 0 26 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8674 2.08791C22.524 0.748704 20.698 0 18.7465 0C16.7951 0 14.9691 0.748704 13.6048 2.10824L13.0099 2.69215L12.4121 2.10137C11.0409 0.746233 9.21431 0 7.26929 0C5.32428 0 3.49764 0.746232 2.12649 2.10137C0.757832 3.44305 0 5.2478 0 7.17641C0 9.10503 0.757832 10.9098 2.13343 12.258L4.41082 14.5091L11.3269 21.3447L13.0018 23L14.6787 21.3466L21.6106 14.511L23.8871 12.2457C25.2227 10.9477 26 9.09294 26 7.17641C25.9997 5.25988 25.2227 3.40515 23.8674 2.08791L23.8674 2.08791ZM22.2037 10.5942L19.935 12.8521L13.003 19.6874L6.08694 12.8518L3.80261 10.5939C1.89344 8.72267 1.89344 5.62981 3.80261 3.75832C4.75719 2.81489 6.01274 2.34331 7.26857 2.34331C8.5244 2.34331 9.77995 2.81489 10.7345 3.75832L12.2374 5.24392H13.7745L15.2879 3.75832C16.2347 2.81489 17.4903 2.34331 18.7461 2.34331C20.0019 2.34331 21.2575 2.81489 22.204 3.75832C23.1586 4.68637 23.628 5.93879 23.628 7.1761C23.628 8.41342 23.1583 9.66611 22.2037 10.5942V10.5942Z" fill="#6D6F74"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

View File

@ -0,0 +1,3 @@
<svg width="21" height="12" viewBox="0 0 21 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.2205 0C19.6854 0 20.0621 0.0872106 20.3507 0.261632C20.6393 0.436053 20.8317 0.665972 20.9279 0.951388C21.024 1.2368 21.024 1.5579 20.9279 1.91467C20.8317 2.27144 20.6233 2.62424 20.3026 2.97309C19.0041 4.40017 17.7536 5.79553 16.5512 7.15919C15.3488 8.52284 14.1865 9.79929 13.0643 10.9885C12.7597 11.3057 12.403 11.5514 11.9942 11.7259C11.5854 11.9003 11.1646 11.9914 10.7317 11.9994C10.2989 12.0073 9.88205 11.9399 9.48126 11.7972C9.08047 11.6545 8.7438 11.4325 8.47127 11.1312C7.84603 10.4494 7.19675 9.75172 6.52342 9.03818L4.45534 6.89756C3.78201 6.18402 3.11269 5.48237 2.44738 4.79262C1.78206 4.10286 1.16084 3.43293 0.583698 2.78281C0.311159 2.48154 0.134811 2.16837 0.0546533 1.84332C-0.0255049 1.51826 -0.0174891 1.22095 0.0787007 0.95139C0.174891 0.681831 0.359255 0.45984 0.631792 0.285419C0.90433 0.110998 1.25703 0.0237872 1.68988 0.0237872C2.25099 0.0237872 3.02452 0.019823 4.01046 0.011895C4.99641 0.00396702 6.07454 3.18977e-06 7.24485 2.83536e-06H10.852H14.3869H17.3448H19.2205V0Z" fill="#41A4FD"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 KiB

24
.gitignore vendored
View File

@ -32,9 +32,12 @@ douyin_cdp_play_vv_*.txt
# Chrome profiles and drivers # Chrome profiles and drivers
# 注意Chrome profile 包含大量缓存文件不应加入Git # 注意Chrome profile 包含大量缓存文件不应加入Git
scripts/config/chrome_profile/ backend/scripts/config/chrome_profile/
drivers/* backend/drivers/*
!drivers/chromedriver.exe !backend/drivers/chromedriver.exe
# Rankings config directory
backend/handlers/Rankings/config/
# Environment variables # Environment variables
.env .env
@ -45,6 +48,16 @@ ENV/
env.bak/ env.bak/
venv.bak/ venv.bak/
# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.npm
.yarn-integrity
.pnp
.pnp.js
# IDE # IDE
.vscode/ .vscode/
.idea/ .idea/
@ -53,4 +66,7 @@ venv.bak/
# OS # OS
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Figma 设计文件目录(无需纳入版本控制)
.figma/

48
README.md Normal file
View File

@ -0,0 +1,48 @@
# 榜单系统
这是一个全栈榜单系统项目包含后端API服务和前端Vue3应用。
## 项目结构
```
rank_backend/
├── backend/ # 后端代码
│ ├── app.py # Flask应用主文件
│ ├── config.py # 配置文件
│ ├── database.py # 数据库连接
│ ├── Timer_worker.py # 定时任务
│ ├── handlers/ # 业务处理器
│ └── routers/ # 路由定义
├── frontend/ # 前端Vue3应用
│ ├── src/ # 源代码
│ ├── public/ # 静态资源
│ └── package.json # 依赖配置
└── docs/ # 文档
├── API接口文档.md
└── requirements.txt
```
## 快速开始
### 后端服务
```bash
cd backend
python3 -m pip install -r ../docs/requirements.txt
python3 app.py
```
### 前端应用
```bash
cd frontend
npm install
npm run dev
```
## 开发说明
- 后端使用Flask框架提供RESTful API
- 前端使用Vue3 + Vite构建
- 数据库配置在backend/config.py中
- API文档位于docs/API接口文档.md

View File

@ -1,154 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
抖音播放量自动抓取定时器 - 跨平台版本
功能
- 每晚24:00自动执行抖音播放量抓取任务
- 支持WindowsmacOSLinux
- 自动保存数据到MongoDB
"""
import schedule
import time
import subprocess
import sys
import os
import logging
from pathlib import Path
from datetime import datetime
# 配置日志的函数
def setup_logging():
"""设置日志配置"""
# 确保logs目录存在
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
logs_dir = os.path.join(script_dir, 'handlers', 'Rankings', 'logs')
os.makedirs(logs_dir, exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(logs_dir, 'scheduler.log'), encoding='utf-8'),
logging.StreamHandler()
]
)
class DouyinAutoScheduler:
def __init__(self):
self.is_running = False
def run_douyin_scraper(self):
"""执行抖音播放量抓取任务"""
try:
logging.info("🚀 开始执行抖音播放量抓取任务...")
# 设置环境变量,确保自动模式
os.environ['AUTO_CONTINUE'] = '1'
# 构建脚本路径 - 指向Rankings目录中的脚本
script_path = Path(__file__).parent / 'handlers' / 'Rankings' / 'rank_data_scraper.py'
if not script_path.exists():
logging.error(f"❌ 脚本文件不存在: {script_path}")
return
logging.info(f"📁 执行脚本: {script_path}")
# 使用subprocess执行脚本
result = subprocess.run([
sys.executable,
str(script_path),
'--auto',
'--duration', '60'
], capture_output=True, text=True, encoding='utf-8', errors='ignore')
if result.returncode == 0:
logging.info("✅ 抖音播放量抓取任务执行成功")
if result.stdout:
logging.info(f"📄 输出: {result.stdout.strip()}")
else:
logging.error(f"❌ 任务执行失败,返回码: {result.returncode}")
if result.stderr:
logging.error(f"💥 错误信息: {result.stderr.strip()}")
if result.stdout:
logging.info(f"📄 输出: {result.stdout.strip()}")
except Exception as e:
logging.error(f"💥 执行任务时发生异常: {e}")
def setup_schedule(self):
"""设置定时任务"""
# 主执行时间每晚24:00午夜
schedule.every().day.at("00:00").do(self.run_douyin_scraper)
logging.info("⏰ 定时器已设置每晚24:00执行抖音播放量抓取")
def show_next_run(self):
"""显示下次执行时间"""
jobs = schedule.get_jobs()
if jobs:
next_run = jobs[0].next_run
logging.info(f"⏰ 下次执行时间: {next_run}")
def run_once(self):
"""立即执行一次"""
logging.info("🔧 立即执行模式...")
self.run_douyin_scraper()
def run_test(self):
"""测试模式 - 立即执行一次"""
logging.info("🧪 测试模式 - 立即执行抖音播放量抓取任务...")
self.run_douyin_scraper()
def start_scheduler(self):
"""启动定时器"""
self.is_running = True
logging.info("🚀 抖音播放量自动抓取定时器已启动")
logging.info("⏰ 执行时间每晚24:00")
logging.info("📁 目标脚本rank_data_scraper.py")
logging.info("💾 数据保存MongoDB")
logging.info("⏹️ 按 Ctrl+C 停止定时器")
self.show_next_run()
try:
while self.is_running:
schedule.run_pending()
time.sleep(1)
# 每分钟显示一次状态
if int(time.time()) % 60 == 0:
self.show_next_run()
except KeyboardInterrupt:
logging.info("\n⏹️ 定时器已停止")
self.is_running = False
def main():
"""主函数"""
import argparse
parser = argparse.ArgumentParser(description='抖音播放量自动抓取定时器')
parser.add_argument('--test', action='store_true', help='测试模式 - 立即执行一次')
parser.add_argument('--once', action='store_true', help='立即执行一次并退出')
args = parser.parse_args()
# 设置日志配置
setup_logging()
scheduler = DouyinAutoScheduler()
if args.test:
scheduler.run_test()
elif args.once:
scheduler.run_once()
else:
scheduler.setup_schedule()
scheduler.start_scheduler()
if __name__ == '__main__':
main()

93
app.py
View File

@ -1,93 +0,0 @@
from flask import Flask
from flask_cors import CORS
import logging
import os
app = Flask(__name__)
CORS(app) # 允许跨域访问
# 配置日志
# 确保logs目录存在
logs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'handlers', 'Rankings', 'logs')
os.makedirs(logs_dir, exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(logs_dir, 'app.log'), encoding='utf-8'),
logging.StreamHandler()
]
)
# 导入路由
from routers.rank_api_routes import api
# 注册路由
@app.route('/')
def index():
"""API首页"""
from flask import jsonify
return jsonify({
"name": "抖音播放量数据API服务",
"version": "2.0",
"description": "主程序服务 - 整合小程序API功能",
"endpoints": {
"/api/videos": "获取视频列表 (支持分页和排序)",
"/api/top": "获取热门视频榜单",
"/api/search": "搜索视频",
"/api/detail": "获取视频详情",
"/api/stats": "获取统计信息",
"/api/health": "健康检查"
},
"features": [
"分页支持",
"多种排序方式",
"搜索功能",
"详情查看",
"统计分析",
"小程序优化"
]
})
# 注册小程序API路由
@app.route('/api/videos')
def get_videos():
return api.get_videos()
@app.route('/api/top')
def get_top():
return api.get_top()
@app.route('/api/search')
def search():
return api.search()
@app.route('/api/detail')
def get_detail():
return api.get_detail()
@app.route('/api/stats')
def get_stats():
return api.get_stats()
@app.route('/api/health')
def health_check():
return api.health_check()
if __name__ == '__main__':
print("启动主程序服务...")
print("服务地址: http://localhost:5000")
print("API接口列表:")
print(" - GET / 显示API信息")
print(" - GET /api/videos?page=1&limit=20&sort=playcount 获取视频列表(总播放量排序)")
print(" - GET /api/videos?page=1&limit=20&sort=growth 获取视频列表(增长排序,默认昨天到今天的差值)")
print(" - GET /api/videos?page=1&limit=20&sort=growth&start_date=2025-10-16&end_date=2025-10-17 获取视频列表(自定义日期范围增长排序)")
print(" - GET /api/top?limit=10 获取热门榜单")
print(" - GET /api/search?q=关键词&page=1&limit=10 搜索视频")
print(" - GET /api/detail?id=视频ID 获取视频详情")
print(" - GET /api/stats 获取统计信息")
print(" - GET /api/health 健康检查")
print("专为小程序优化:分页、搜索、详情、统计、增长排序、自定义日期范围")
app.run(host='0.0.0.0', port=5000, debug=True)

831
backend/Timer_worker.py Normal file
View File

@ -0,0 +1,831 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
抖音播放量自动抓取定时器 - 跨平台版本
功能
- 每晚自动执行抖音播放量抓取任务
- 数据抓取完成后自动生类榜单
- 支持WindowsmacOSLinux
- 自动保存数据到MongoDB
使用方法
- 正常模式python Timer_worker.py启动定时器
- 测试模式python Timer_worker.py --test立即执行一次
- 单次执行python Timer_worker.py --once立即执行一次并退出
- 仅生成榜单python Timer_worker.py --ranking-only仅生成榜单不抓取数据
"""
import schedule
import time
import sys
import os
import logging
import argparse
from datetime import datetime, date, timedelta
import config
# 添加项目路径到 Python 路径
sys.path.append(os.path.join(os.path.dirname(__file__), 'handlers', 'Rankings'))
from handlers.Rankings.rank_data_scraper import DouyinPlayVVScraper
# 配置日志的函数
def setup_timer_environment():
"""设置定时器相关的环境变量"""
config.apply_timer_environment()
for key, value in config.TIMER_ENV_CONFIG.items():
logging.info(f"设置环境变量: {key}={value}")
def setup_logging(quiet_mode=False):
"""设置日志配置"""
# 确保logs目录存在
script_dir = os.path.dirname(os.path.abspath(__file__))
logs_dir = os.path.join(script_dir, 'handlers', 'Rankings', 'logs')
os.makedirs(logs_dir, exist_ok=True)
# 在安静模式下只记录WARNING及以上级别的日志到控制台
console_level = logging.WARNING if quiet_mode else logging.INFO
logging.basicConfig(
level=logging.INFO, # 文件日志仍然记录所有INFO级别
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(logs_dir, 'scheduler.log'), encoding='utf-8'),
logging.StreamHandler()
]
)
# 如果是安静模式,调整控制台处理器的级别
if quiet_mode:
for handler in logging.getLogger().handlers:
if isinstance(handler, logging.StreamHandler) and not isinstance(handler, logging.FileHandler):
handler.setLevel(console_level)
class DouyinAutoScheduler:
def __init__(self):
self.is_running = False
# 创建logger实例
self.logger = logging.getLogger(__name__)
def _sync_episode_details_with_lock(self, episode_details, comments_summary):
"""
同步 episode_details 时处理评论锁定逻辑
如果有 comments_summary则保留评论内容只更新互动数据
Args:
episode_details: 管理数据库中的 episode_details
comments_summary: 评论总结字段
Returns:
处理后的 episode_details
"""
# 如果没有 comments_summary 或没有 episode_details直接返回原数据
if not comments_summary or not episode_details:
return episode_details
# 如果有 comments_summary说明评论内容已锁定直接返回管理数据库的数据
# 因为管理数据库中已经保存了锁定的评论内容
logging.info(f'🔒 检测到 comments_summaryepisode_details 将保持锁定状态(包含评论内容)')
return episode_details
def _normalize_play_vv(self, play_vv):
"""标准化播放量数据类型,将字符串转换为数字"""
if isinstance(play_vv, str):
try:
return int(play_vv.replace(',', '').replace('', '0000').replace('亿', '00000000'))
except:
return 0
elif not isinstance(play_vv, (int, float)):
return 0
return play_vv
def check_browser_login_status(self):
"""检查浏览器登录状态,如果没有登录则提示用户登录"""
try:
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
profile_dir = os.path.join(script_dir, 'config', 'chrome_profile_timer', 'douyin_persistent')
# 检查配置文件是否为空(可能未登录)
import glob
profile_files = glob.glob(os.path.join(profile_dir, "*"))
if len(profile_files) < 5: # 如果文件太少,可能未登录
print("⚠️ 检测到定时器浏览器可能未登录")
print(" 请在浏览器中完成抖音登录,并导航到【我的】→【收藏】→【合集】页面")
print(" 完成后按回车键继续...")
input()
else:
print("✅ 定时器浏览器已配置,继续执行...")
except Exception as e:
logging.warning(f"检查浏览器登录状态时出错: {e}")
print("⚠️ 检查浏览器状态失败,请确保浏览器已正确配置")
print(" 完成后按回车键继续...")
input()
def _cleanup_chrome_processes(self):
"""清理可能占用配置文件的Chrome进程"""
try:
import psutil
import os
# 获取当前配置文件路径
script_dir = os.path.dirname(os.path.abspath(__file__))
profile_dir = os.path.join(script_dir, 'config', 'chrome_profile_timer', 'douyin_persistent')
# 查找使用该配置文件的Chrome进程
killed_processes = []
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
try:
if proc.info['name'] and 'chrome' in proc.info['name'].lower():
cmdline = proc.info['cmdline']
if cmdline and any(profile_dir in arg for arg in cmdline):
proc.terminate()
killed_processes.append(proc.info['pid'])
logging.info(f'终止占用配置文件的Chrome进程: PID {proc.info["pid"]}')
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
continue
# 等待进程终止
if killed_processes:
import time
time.sleep(2)
return len(killed_processes) > 0
except ImportError:
# 如果没有psutil跳过清理以避免影响其他脚本实例
logging.warning('psutil 不可用,跳过进程清理(避免全局终止 Chrome')
return False
except Exception as e:
logging.warning(f'清理Chrome进程时出错: {e}')
return False
def run_douyin_scraper(self):
"""执行抖音播放量抓取任务"""
try:
logging.warning("🚀 开始执行抖音播放量抓取任务...")
# 设置环境变量,确保定时器模式和自动模式
setup_timer_environment()
# 直接创建并运行 DouyinPlayVVScraper 实例
scraper = DouyinPlayVVScraper(
start_url="https://www.douyin.com/user/self?showTab=favorite_collection&showSubTab=compilation",
auto_continue=True,
duration_s=60 # 增加到60秒给更多时间收集数据
)
print("开始执行抓取任务...")
logging.info("📁 开始执行抓取任务...")
scraper.run()
print("抖音播放量抓取任务执行成功")
logging.info("✅ 抖音播放量抓取任务执行成功")
# 数据抓取完成后,自动生成当日榜单
self.generate_daily_rankings()
# 任务完成后立即显示下次执行时间
print("🎯 任务完成,准备下次执行...")
self.show_next_run()
print("💡 定时器正在等待中,将在整点自动执行任务...")
logging.info("🎯 任务完成,准备下次执行...")
logging.info("💡 定时器正在等待中,将在整点自动执行任务...")
except Exception as e:
logging.error(f"💥 执行任务时发生异常: {e}")
import traceback
logging.error(f"详细错误信息: {traceback.format_exc()}")
def generate_daily_rankings(self):
"""生成每日榜单数据(基于时间轴对比)"""
try:
from database import db
from datetime import timedelta
# 获取集合
douyin_collection = db['Ranking_storage_list'] # 使用定时器抓取的数据
rankings_collection = db['Ranking_storage']
today = date.today()
yesterday = today - timedelta(days=1)
today_str = today.strftime('%Y-%m-%d')
yesterday_str = yesterday.strftime('%Y-%m-%d')
logging.info(f"📅 正在生成 {today_str} 的榜单(对比 {yesterday_str}...")
# 删除当天已有的榜单数据
rankings_collection.delete_many({"date": today_str})
print(f"🗑️ 已清理 {today_str} 的旧榜单数据")
logging.info(f"🗑️ 已清理 {today_str} 的旧榜单数据")
# 获取今天和昨天的榜单数据进行对比
try:
print("🔄 正在生成时间轴对比榜单...")
logging.info("🔄 正在生成时间轴对比榜单...")
# 获取最新批次的数据
latest_batch = douyin_collection.find_one(sort=[("batch_time", -1)])
if not latest_batch:
logging.warning("⚠️ 未找到任何数据")
return False
latest_batch_time = latest_batch.get("batch_time")
logging.info(f"📊 找到最新批次时间: {latest_batch_time}")
# 只获取最新批次的数据
today_videos_raw = list(douyin_collection.find({"batch_time": latest_batch_time}).sort("play_vv", -1))
logging.info(f"📊 最新批次数据数量: {len(today_videos_raw)}")
# 按短剧ID去重每个短剧只保留播放量最高的一条
# 🚫 过滤掉空的或无效的mix_id和播放量为0的记录
unique_videos = {}
for video in today_videos_raw:
mix_id = video.get("mix_id", "").strip()
mix_name = video.get("mix_name", "").strip()
play_vv = video.get("play_vv", 0)
# 过滤掉空的或无效的mix_id
if not mix_id or mix_id == "" or mix_id.lower() == "null":
continue
# 注意播放量为0的数据也会被保留可能是新发布的短剧
if play_vv <= 0:
logging.warning(f"⚠️ 发现播放量为0的数据: mix_name={mix_name}, play_vv={play_vv},仍会保留")
if mix_id not in unique_videos or play_vv > unique_videos[mix_id].get("play_vv", 0):
unique_videos[mix_id] = video
today_videos = list(unique_videos.values())
logging.info(f"📊 今日数据去重后:{len(today_videos)} 个独特短剧(原始数据:{len(today_videos_raw)} 条)")
# 从Ranking_storage_list中获取昨天最后一次抓取的数据
yesterday_start = datetime.combine(yesterday, datetime.min.time())
yesterday_end = datetime.combine(yesterday, datetime.max.time())
# 获取昨天的最后一次抓取数据按batch_time排序取最新的
yesterday_latest_batch = douyin_collection.find_one({
"batch_time": {
"$gte": yesterday_start,
"$lte": yesterday_end
}
}, sort=[("batch_time", -1)])
yesterday_data = {}
if yesterday_latest_batch:
yesterday_batch_time = yesterday_latest_batch.get("batch_time")
logging.info(f"📊 找到昨天最后一次抓取时间: {yesterday_batch_time}")
# 获取昨天最后一次抓取的所有数据
yesterday_videos_raw = list(douyin_collection.find({
"batch_time": yesterday_batch_time
}).sort("play_vv", -1))
# 按短剧ID去重每个短剧只保留播放量最高的一条
# 🚫 过滤掉空的或无效的mix_id
unique_yesterday_videos = {}
for video in yesterday_videos_raw:
mix_id = video.get("mix_id", "").strip()
mix_name = video.get("mix_name", "").strip()
play_vv = video.get("play_vv", 0)
# 过滤掉空的或无效的mix_id
if not mix_id or mix_id == "" or mix_id.lower() == "null":
continue
# 注意播放量为0的数据也会被保留可能是新发布的短剧
if play_vv <= 0:
logging.warning(f"⚠️ 昨天数据中发现播放量为0: mix_name={mix_name}, play_vv={play_vv},仍会保留")
if mix_id not in unique_yesterday_videos or play_vv > unique_yesterday_videos[mix_id].get("play_vv", 0):
unique_yesterday_videos[mix_id] = video
# 将昨天的数据转换为字典以短剧ID为键
for mix_id, video in unique_yesterday_videos.items():
yesterday_data[mix_id] = {
"rank": 0, # 原始数据没有排名设为0
"play_vv": video.get("play_vv", 0),
"video_id": str(video.get("_id", ""))
}
logging.info(f"📊 找到昨天的原始数据,共 {len(yesterday_data)} 个短剧(原始数据:{len(yesterday_videos_raw)} 条)")
else:
logging.info("📊 未找到昨天的原始数据,将作为首次生成")
if today_videos:
# 先计算所有视频的播放量差值
videos_with_growth = []
for video in today_videos:
video_id = str(video.get("_id", ""))
current_play_vv = video.get("play_vv", 0)
# 计算与昨天的对比数据
play_vv_change = 0
play_vv_change_rate = 0
is_new = True
mix_id = video.get("mix_id", "")
if mix_id in yesterday_data:
is_new = False
yesterday_play_vv = yesterday_data[mix_id]["play_vv"]
# 计算播放量变化
play_vv_change = current_play_vv - yesterday_play_vv
if yesterday_play_vv > 0:
play_vv_change_rate = round((play_vv_change / yesterday_play_vv) * 100, 2)
# 创建包含增长数据的视频项
video_with_growth = {
"video": video,
"play_vv_change": play_vv_change,
"play_vv_change_rate": play_vv_change_rate,
"is_new": is_new,
"yesterday_data": yesterday_data.get(mix_id, {})
}
videos_with_growth.append(video_with_growth)
# 按播放量差值降序排序(差值越大排名越靠前)
videos_with_growth.sort(key=lambda x: x["play_vv_change"], reverse=True)
comprehensive_ranking = {
"date": today_str,
"type": "comprehensive",
"name": "播放量增长榜单",
"description": f"基于 {yesterday_str}{today_str} 播放量差值排序的榜单(差值越大排名越靠前)",
"comparison_date": yesterday_str,
"total_videos": len(videos_with_growth),
"data": []
}
# 获取Rankings_management集合用于补充详细信息
rankings_management_collection = db['Rankings_management']
# 生成排序后的榜单数据
rank = 1 # 使用独立的排名计数器
for item in videos_with_growth:
video = item["video"]
video_id = str(video.get("_id", ""))
current_play_vv = video.get("play_vv", 0)
mix_name = video.get("mix_name", "").strip()
# 🚫 跳过无效数据确保mix_name不为空
# 注意播放量为0的数据也会被保留可能是新发布的短剧
if not mix_name or mix_name == "" or mix_name.lower() == "null":
self.logger.warning(f"跳过空的mix_name记录video_id: {video_id}")
continue
if current_play_vv <= 0:
self.logger.warning(f"⚠️ 榜单中发现播放量为0的记录: mix_name={mix_name}, play_vv={current_play_vv},仍会保留")
# 计算排名变化(基于昨天的排名)
rank_change = 0
if not item["is_new"] and item["yesterday_data"]:
yesterday_rank = item["yesterday_data"].get("rank", 0)
rank_change = yesterday_rank - rank # 使用当前排名计数器
# 🔍 从Rankings_management获取详细信息按mix_id查询因为管理数据库每个短剧只有一条记录
mix_id = video.get("mix_id", "").strip()
management_data = None
if mix_id:
# 直接按mix_id查询不需要按日期查询
management_data = rankings_management_collection.find_one({"mix_id": mix_id})
if management_data:
logging.info(f"📋 从 Rankings_management 获取数据: {mix_name} (mix_id: {mix_id})")
else:
logging.warning(f"⚠️ 未找到管理数据: {mix_name} (mix_id: {mix_id})")
else:
logging.warning(f"⚠️ mix_id 为空: {mix_name}")
ranking_item = {
# 🎯 核心榜单字段
"rank": rank, # 使用排名计数器
"title": mix_name,
"mix_name": mix_name,
"play_vv": current_play_vv,
"series_author": video.get("series_author", ""),
"video_id": video_id,
"video_url": video.get("video_url", ""),
"cover_image_url": video.get("cover_image_url", ""),
"playcount_str": video.get("playcount", ""),
# 📋 从Rankings_management获取的详细字段
"batch_id": management_data.get("batch_id", "") if management_data else "",
"batch_time": management_data.get("batch_time") if management_data else None,
"item_sequence": management_data.get("item_sequence", 0) if management_data else 0,
"mix_id": video.get("mix_id", ""), # 直接从原始数据获取mix_id
"playcount": management_data.get("playcount", "") if management_data else "",
"request_id": management_data.get("request_id", "") if management_data else "",
"cover_image_url_original": management_data.get("cover_image_url_original", "") if management_data else "",
"cover_upload_success": management_data.get("cover_upload_success", True) if management_data else True,
"cover_backup_urls": management_data.get("cover_backup_urls", []) if management_data else [],
"desc": management_data.get("desc", "") if management_data else "",
"updated_to_episode": management_data.get("updated_to_episode", 0) if management_data else 0,
"episode_video_ids": management_data.get("episode_video_ids", []) if management_data else [],
# 🔒 episode_details 同步逻辑:如果有 comments_summary保留评论内容但更新互动数据
"episode_details": self._sync_episode_details_with_lock(
management_data.get("episode_details", []) if management_data else [],
management_data.get("comments_summary", "") if management_data else ""
),
"data_status": management_data.get("data_status", "") if management_data else "",
"realtime_saved": management_data.get("realtime_saved", True) if management_data else True,
"created_at": management_data.get("created_at") if management_data else None,
"last_updated": management_data.get("last_updated") if management_data else None,
# 🎬 评论总结字段直接从管理数据库获取按mix_id查询
"comments_summary": management_data.get("comments_summary", "") if management_data else "",
# 🔑 分类字段直接从管理数据库获取按mix_id查询每个短剧只有一条记录
"Manufacturing_Field": management_data.get("Manufacturing_Field", "") if management_data else "",
"Copyright_field": management_data.get("Copyright_field", "") if management_data else "",
"classification_type": management_data.get("classification_type", "") if management_data else "",
"release_date": management_data.get("release_date", "") if management_data else "",
"Novel_IDs": management_data.get("Novel_IDs", []) if management_data else [],
"Anime_IDs": management_data.get("Anime_IDs", []) if management_data else [],
"Drama_IDs": management_data.get("Drama_IDs", []) if management_data else [],
# 🔒 锁定状态:直接从管理数据库获取
"field_lock_status": management_data.get("field_lock_status", {}) if management_data else {},
# 📊 时间轴对比数据(重要:包含播放量差值)
"timeline_data": {
"is_new": item["is_new"],
"rank_change": rank_change,
"play_vv_change": item["play_vv_change"],
"play_vv_change_rate": item["play_vv_change_rate"],
"yesterday_rank": item["yesterday_data"].get("rank", 0) if not item["is_new"] else 0,
"yesterday_play_vv": item["yesterday_data"].get("play_vv", 0) if not item["is_new"] else 0
}
}
comprehensive_ranking["data"].append(ranking_item)
rank += 1 # 递增排名计数器
# 为每次计算添加唯一的时间戳,确保数据唯一性
current_timestamp = datetime.now()
comprehensive_ranking["created_at"] = current_timestamp
comprehensive_ranking["calculation_id"] = f"{today_str}_{current_timestamp.strftime('%H%M%S')}"
# 检查今天已有多少次计算
existing_count = rankings_collection.count_documents({
"date": today_str,
"type": "comprehensive"
})
comprehensive_ranking["calculation_sequence"] = existing_count + 1
# 总是插入新的榜单记录,保留所有历史计算数据
rankings_collection.insert_one(comprehensive_ranking)
logging.info(f"📝 创建了新的今日榜单数据(第{existing_count + 1}次计算,包含最新差值)")
logging.info(f"🔖 计算ID: {comprehensive_ranking['calculation_id']}")
# 📊 检查数据完整性统计从Rankings_management成功获取详细信息的项目数量
total_items = len(comprehensive_ranking["data"])
items_with_management_data = 0
items_with_manufacturing = 0
items_with_copyright = 0
for item in comprehensive_ranking["data"]:
# 检查是否从Rankings_management获取到了数据
if item.get("batch_id") or item.get("desc") or item.get("Manufacturing_Field") or item.get("Copyright_field"):
items_with_management_data += 1
if item.get("Manufacturing_Field"):
items_with_manufacturing += 1
if item.get("Copyright_field"):
items_with_copyright += 1
print(f"数据完整性统计:")
print(f" 总项目数: {total_items}")
print(f" 从Rankings_management获取到详细信息: {items_with_management_data}")
print(f" 包含Manufacturing_Field: {items_with_manufacturing}")
print(f" 包含Copyright_field: {items_with_copyright}")
logging.info(f"📊 数据完整性: 总{total_items}项,获取详细信息{items_with_management_data}Manufacturing_Field: {items_with_manufacturing}Copyright_field: {items_with_copyright}")
# 统计信息
new_count = sum(1 for item in comprehensive_ranking["data"] if item["timeline_data"]["is_new"])
print(f"✅ 时间轴对比榜单生成成功")
print(f"📊 总计 {len(comprehensive_ranking['data'])} 条记录")
print(f"🆕 新上榜 {new_count}")
print(f"🔄 对比基准日期: {yesterday_str}")
logging.info(f"✅ 时间轴对比榜单生成成功")
logging.info(f"📊 总计 {len(comprehensive_ranking['data'])} 条记录")
logging.info(f"🆕 新上榜 {new_count}")
logging.info(f"🔄 对比基准日期: {yesterday_str}")
return True
else:
logging.warning("⚠️ 榜单生成失败:无今日数据")
return False
except Exception as e:
logging.error(f"💥 生成时间轴对比榜单时发生异常: {e}")
import traceback
logging.error(f"详细错误信息: {traceback.format_exc()}")
return False
except Exception as e:
logging.error(f"💥 生成榜单时发生异常: {e}")
import traceback
logging.error(f"详细错误信息: {traceback.format_exc()}")
def check_and_sync_missing_fields(self):
"""实时检查并同步当天缺失字段"""
try:
from database import db
# 只检查当天的数据
today = date.today()
today_str = today.strftime('%Y-%m-%d')
# 首先检查 Rankings_management 是否有当天的数据
rankings_management_collection = db['Rankings_management']
management_count = rankings_management_collection.count_documents({})
if management_count == 0:
# Rankings_management 没有数据,说明还没有抓取,直接返回
return
rankings_collection = db['Ranking_storage']
key_fields = ['Manufacturing_Field', 'Copyright_field', 'desc', 'series_author']
# 检查今天是否有缺失字段的数据
missing_conditions = []
for field in key_fields:
missing_conditions.extend([
{field: {"$exists": False}},
{field: None},
{field: ""}
])
today_missing_count = rankings_collection.count_documents({
"date": today_str,
"$or": missing_conditions
})
# 如果今天没有缺失数据,静默返回
if today_missing_count == 0:
return
logging.info(f"🔍 检测到今天有 {today_missing_count} 条缺失字段Rankings_management有 {management_count} 条数据,开始实时同步...")
# 只处理当天的数据
dates_to_check = [today_str]
total_missing = 0
total_synced = 0
for check_date in dates_to_check:
# 查询该日期缺失字段的数据
rankings_collection = db['Ranking_storage']
# 检查多个关键字段(包括新增的分类字段)
key_fields = ['Manufacturing_Field', 'Copyright_field', 'desc', 'series_author', 'Novel_IDs', 'Anime_IDs', 'Drama_IDs']
missing_conditions = []
for field in key_fields:
missing_conditions.extend([
{field: {"$exists": False}},
{field: None},
{field: ""}
])
missing_query = {
"date": check_date,
"$or": missing_conditions
}
missing_count = rankings_collection.count_documents(missing_query)
# 详细统计每个字段的缺失情况
field_stats = {}
total_items = rankings_collection.count_documents({"date": check_date})
for field in key_fields:
missing_field_count = rankings_collection.count_documents({
"date": check_date,
"$or": [
{field: {"$exists": False}},
{field: None},
{field: ""}
]
})
field_stats[field] = {
"missing": missing_field_count,
"completion_rate": ((total_items - missing_field_count) / total_items * 100) if total_items > 0 else 0
}
if missing_count > 0:
logging.info(f"📅 今日({check_date}): 发现 {missing_count} 条记录缺失字段(总计 {total_items} 条)")
# 输出详细的字段统计
for field, stats in field_stats.items():
if stats["missing"] > 0:
logging.info(f" - {field}: 缺失 {stats['missing']} 条 ({stats['completion_rate']:.1f}% 完整)")
total_missing += missing_count
# 尝试同步
try:
from routers.rank_api_routes import sync_ranking_storage_fields
# 使用改进的重试机制
sync_result = sync_ranking_storage_fields(
target_date=check_date,
force_update=False,
max_retries=2, # 定期检查时重试2次
retry_delay=15 # 15秒重试间隔
)
if sync_result.get("success", False):
stats = sync_result.get("stats", {})
synced = stats.get("updated_items", 0)
retry_count = stats.get("retry_count", 0)
pending_final = stats.get("pending_items_final", 0)
total_synced += synced
if synced > 0:
logging.info(f"✅ 今日({check_date}): 成功同步 {synced} 条记录")
if retry_count > 0:
logging.info(f"🔄 今日({check_date}): 使用了 {retry_count} 次重试")
if pending_final > 0:
logging.warning(f"⚠️ 今日({check_date}): {pending_final} 条记录在 Rankings_management 中仍未找到")
else:
logging.warning(f"⚠️ 今日({check_date}): 同步失败 - {sync_result.get('message', '')}")
except Exception as sync_error:
logging.error(f"💥 今日({check_date}): 同步过程出错 - {sync_error}")
else:
if total_items > 0:
logging.info(f"📅 {check_date}: 所有字段完整(总计 {total_items} 条记录)")
# 显示完整性统计
for field, stats in field_stats.items():
logging.info(f" - {field}: {stats['completion_rate']:.1f}% 完整")
else:
logging.info(f"📅 {check_date}: 无数据")
if total_missing > 0:
logging.info(f"🔍 当天同步完成:发现 {total_missing} 条缺失记录,成功同步 {total_synced}")
print(f"🔍 当天字段同步:发现 {total_missing} 条缺失,同步 {total_synced}")
else:
# 当天没有缺失数据时,不输出日志(静默模式)
pass
except Exception as e:
logging.error(f"💥 检查缺失字段时发生异常: {e}")
import traceback
logging.error(f"详细错误信息: {traceback.format_exc()}")
def setup_schedule(self):
"""设置定时任务"""
# 每小时的整点执行抖音播放量抓取
schedule.every().hour.at(":00").do(self.run_douyin_scraper)
# 每1分钟检查一次缺失字段并尝试同步实时同步
schedule.every(1).minutes.do(self.check_and_sync_missing_fields)
logging.info(f"⏰ 定时器已设置:每小时整点执行抖音播放量抓取")
logging.info(f"⏰ 定时器已设置每1分钟检查缺失字段并同步实时模式")
def show_next_run(self):
"""显示下次执行时间"""
jobs = schedule.get_jobs()
if jobs:
next_run = jobs[0].next_run
current_time = datetime.now()
wait_seconds = (next_run - current_time).total_seconds()
wait_minutes = int(wait_seconds // 60)
wait_hours = int(wait_minutes // 60)
remaining_minutes = wait_minutes % 60
print(f"💡 定时器运行中,下次执行:{next_run.strftime('%Y-%m-%d %H:%M:%S')} (还有{wait_hours}h{remaining_minutes}m)")
print(f"⏳ 距离下次执行:{wait_minutes} 分钟 ({int(wait_seconds)} 秒)")
logging.info(f"💡 定时器运行中,下次执行:{next_run.strftime('%Y-%m-%d %H:%M:%S')} (还有{wait_hours}h{remaining_minutes}m)")
logging.info(f"⏳ 距离下次执行:{wait_minutes} 分钟 ({int(wait_seconds)} 秒)")
def run_once(self):
"""立即执行一次"""
logging.info("🔧 立即执行模式...")
self.run_douyin_scraper()
def run_test(self):
"""测试模式 - 立即执行一次"""
logging.info("🧪 测试模式 - 立即执行抖音播放量抓取任务...")
self.run_douyin_scraper()
def run_ranking_only(self):
"""仅生成榜单(不抓取数据)"""
logging.info("📊 仅生成榜单模式...")
self.generate_daily_rankings()
def start_scheduler(self):
"""启动定时器"""
self.is_running = True
last_status_time = int(time.time()) # 设置为当前时间1分钟后开始显示状态
print("🚀 抖音播放量自动抓取定时器已启动")
print("⏰ 执行时间:每小时整点执行抖音播放量抓取")
print("⏹️ 按 Ctrl+C 停止定时器")
logging.info("🚀 抖音播放量自动抓取定时器已启动")
logging.info(f"⏰ 执行时间:每小时整点执行抖音播放量抓取")
logging.info("⏹️ 按 Ctrl+C 停止定时器")
# 启动时显示一次下次执行时间
self.show_next_run()
try:
while self.is_running:
schedule.run_pending()
time.sleep(1)
# 每1分钟显示一次状态
current_time = int(time.time())
if current_time - last_status_time >= 60: # 60秒 = 1分钟
self.show_next_run()
last_status_time = current_time
except KeyboardInterrupt:
print("\n⏹️ 定时器已停止")
logging.info("\n⏹️ 定时器已停止")
self.is_running = False
def main():
"""主函数"""
try:
parser = argparse.ArgumentParser(description='抖音播放量自动抓取定时器')
parser.add_argument('--test', action='store_true', help='测试模式 - 立即执行一次')
parser.add_argument('--once', action='store_true', help='立即执行一次并退出')
parser.add_argument('--ranking-only', action='store_true', help='仅生成榜单(不抓取数据)')
args = parser.parse_args()
# 设置日志配置 - 只在定时器模式下启用静默模式
quiet_mode = not (args.test or args.once or args.ranking_only)
setup_logging(quiet_mode=quiet_mode)
print("正在初始化定时器...")
scheduler = DouyinAutoScheduler()
if args.test:
scheduler._is_timer_mode = False
print("执行测试模式...")
scheduler.run_test()
elif args.once:
scheduler._is_timer_mode = False
print("执行单次模式...")
scheduler.run_once()
elif args.ranking_only:
scheduler._is_timer_mode = False
print("执行榜单生成模式...")
scheduler.run_ranking_only()
else:
scheduler._is_timer_mode = True
print("启动定时器模式...")
# 显示定时器信息使用print确保能看到
from datetime import datetime
current_time = datetime.now()
print(f"🕐 当前时间:{current_time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"⏰ 执行规则:每小时整点执行抖音播放量抓取")
# 计算下次执行时间
next_hour = current_time.replace(minute=0, second=0, microsecond=0)
if current_time.minute > 0 or current_time.second > 0:
next_hour = next_hour.replace(hour=next_hour.hour + 1)
if next_hour.hour >= 24:
from datetime import timedelta
next_hour = next_hour.replace(hour=0) + timedelta(days=1)
wait_seconds = (next_hour - current_time).total_seconds()
wait_minutes = int(wait_seconds // 60)
print(f"⏰ 下次执行时间:{next_hour.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"⏳ 距离下次执行:{wait_minutes} 分钟 ({int(wait_seconds)} 秒)")
print("💡 定时器正在等待中,将在整点自动执行任务...")
print("⏹️ 按 Ctrl+C 停止定时器")
scheduler.setup_schedule()
scheduler.start_scheduler()
print("程序执行完成")
except Exception as e:
print(f"程序执行出错: {e}")
import traceback
traceback.print_exc()
return 1
return 0
if __name__ == '__main__':
main()

52
backend/app.py Normal file
View File

@ -0,0 +1,52 @@
from flask import Flask, jsonify, send_from_directory
from flask_cors import CORS
import logging
import os
app = Flask(__name__)
# 配置静态文件目录为dist
# 说明:这里指向后端目录中的 dist前端构建产物应复制或输出到此
dist_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'dist'))
app.static_folder = dist_dir
# 为 SPA 提供静态文件与回退到 index.html 的路由
@app.route('/')
def serve_index():
# 返回构建后的前端入口文件
return send_from_directory(app.static_folder, 'index.html')
@app.route('/<path:path>')
def serve_static_or_fallback(path):
# 如果请求的文件存在则直接返回,否则回退到 index.html用于前端路由
file_path = os.path.join(app.static_folder, path)
if os.path.isfile(file_path):
return send_from_directory(app.static_folder, path)
return send_from_directory(app.static_folder, 'index.html')
CORS(app) # 允许跨域访问
# 配置日志
# 确保logs目录存在
logs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'handlers', 'Rankings', 'logs')
os.makedirs(logs_dir, exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(logs_dir, 'app.log'), encoding='utf-8'),
logging.StreamHandler()
]
)
# 导入并注册蓝图
from routers.rank_api_routes import rank_bp
app.register_blueprint(rank_bp)
if __name__ == '__main__':
print("启动主程序服务...")
print("服务地址: http://localhost:8443")
app.run(host='0.0.0.0', port=8443, debug=True)

76
backend/config.py Normal file
View File

@ -0,0 +1,76 @@
import os
import importlib
# 数据库配置
MONGO_URI = "mongodb://localhost:27017"
MONGO_DB_NAME = "Rankings"
# MONGO_URI = "mongodb://mongouser:Jdei2243afN@172.16.0.6:27017,172.16.0.4:27017/test?replicaSet=cmgo-r6qkaern_0&authSource=admin"
# MONGO_DB_NAME = "kemeng_media"
# 应用配置
APP_ENV = os.getenv('APP_ENV', 'development')
DEBUG = APP_ENV == 'development'
# 日志配置
LOG_LEVEL = 'INFO'
LOG_DIR = 'logs'
# 定时器配置
SCHEDULER_TIME = "24:00" # 定时器执行时间,格式为 HH:MM (24小时制)
# 定时器环境变量配置
TIMER_ENV_CONFIG = {
'TIMER_MODE': '1', # 启用定时器模式,使数据保存到 Ranking_storage_list 集合
'AUTO_CONTINUE': '1' # 启用自动模式,跳过详细数据获取以提高性能
}
# 自动模式跳过函数配置
AUTO_CONTINUE_SKIP_FUNCTIONS = [
'get_collection_video_details', # 跳过合集视频详细数据获取
'scroll_comments', # 跳过评论滚动
# 可以在这里添加更多需要跳过的函数名
]
# TOS/火山云对象存储配置
TOS_CONFIG = {
'access_key_id': os.getenv('TOS_ACCESS_KEY_ID', 'AKLTYjQyYmE1ZDAwZTY5NGZiOWI3ODZkZDhhOWE4MzVjODE'),
'access_key_secret': os.getenv('TOS_ACCESS_KEY_SECRET', 'WlRKa05EbGhZVEUyTXpjNU5ESmpPRGt5T0RJNFl6QmhPR0pqTVRjMVpUWQ=='),
'endpoint': 'https://tos-cn-beijing.volces.com',
'region': 'cn-beijing',
'bucket_name': os.getenv('TOS_BUCKET_NAME', 'km1'),
'self_domain': os.getenv('TOS_SELF_DOMAIN', 'oss.xintiao85.com'),
'disable_ssl_warnings': True
}
# API配置兼容现有代码
API_CONFIG = {
'huoshan': {
'AccessKey': TOS_CONFIG['access_key_id'],
'SecretKey': TOS_CONFIG['access_key_secret']
},
'OSS_BUCKET_NAME': TOS_CONFIG['bucket_name'],
'OSS_HOST': TOS_CONFIG['self_domain']
}
# DeepSeek API 配置(用于评论总结功能)
DEEPSEEK_CONFIG = {
'api_key': 'sk-7b47e34bdcb549e6b00115a99b9b5c4c', # DeepSeek API密钥
'api_base': 'https://api.deepseek.com/v1', # API基础URL
'model': 'deepseek-chat', # 使用的模型
'max_retries': 3, # 最大重试次数
'retry_delays': [2, 5, 10], # 重试延迟(秒)
'batch_size': 800, # 每批评论数量
'max_tokens': 15000, # 每批最大token数
'summary_max_length': 200 # 最终总结最大字数
}
def apply_timer_environment():
"""应用定时器环境变量配置"""
for key, value in TIMER_ENV_CONFIG.items():
os.environ[key] = value
def get_skip_functions():
"""获取自动模式下需要跳过的函数列表"""
return AUTO_CONTINUE_SKIP_FUNCTIONS.copy()
print(f"Successfully loaded configuration for environment: {APP_ENV}")

View File

@ -0,0 +1,43 @@
{
"episodes": [
{
"video_id": "7389531100718107954",
"episode_num": 0
},
{
"video_id": "7393209049208130851",
"episode_num": 0
},
{
"video_id": "7398121035452763432",
"episode_num": 0
},
{
"video_id": "7404808266888252698",
"episode_num": 0
},
{
"video_id": "7410761253204905235",
"episode_num": 0
},
{
"video_id": "7416596680776158515",
"episode_num": 0
},
{
"video_id": "7426956763892665654",
"episode_num": 0
},
{
"video_id": "7429208304389328180",
"episode_num": 0
},
{
"video_id": "7429519648518966555",
"episode_num": 0
}
],
"total_count": 9,
"last_update": "2025-10-22T09:55:41.380145",
"mix_name": "觉醒"
}

View File

@ -0,0 +1,123 @@
{
"episodes": [
{
"video_id": "7421044063190699303",
"episode_num": 0
},
{
"video_id": "7425556502057897270",
"episode_num": 0
},
{
"video_id": "7428086785537821963",
"episode_num": 0
},
{
"video_id": "7438273827555134774",
"episode_num": 0
},
{
"video_id": "7431047488955632907",
"episode_num": 0
},
{
"video_id": "7434029345796066623",
"episode_num": 0
},
{
"video_id": "7438157166382550313",
"episode_num": 0
},
{
"video_id": "7445146231225077003",
"episode_num": 0
},
{
"video_id": "7448086473322106152",
"episode_num": 0
},
{
"video_id": "7451825341225585954",
"episode_num": 0
},
{
"video_id": "7463783714389085476",
"episode_num": 0
},
{
"video_id": "7461502376516586752",
"episode_num": 0
},
{
"video_id": "7471109492738952483",
"episode_num": 0
},
{
"video_id": "7473376886383512884",
"episode_num": 0
},
{
"video_id": "7476035323416415542",
"episode_num": 0
},
{
"video_id": "7477826100815514889",
"episode_num": 0
},
{
"video_id": "7480121799733202186",
"episode_num": 0
},
{
"video_id": "7482669047008611610",
"episode_num": 0
},
{
"video_id": "7483737890845920549",
"episode_num": 0
},
{
"video_id": "7486742756103130394",
"episode_num": 0
},
{
"video_id": "7487446983901269274",
"episode_num": 0
},
{
"video_id": "7488238211139996978",
"episode_num": 0
},
{
"video_id": "7489006391865216265",
"episode_num": 0
},
{
"video_id": "7493926858061352202",
"episode_num": 0
},
{
"video_id": "7498645852182203700",
"episode_num": 0
},
{
"video_id": "7502686428054244660",
"episode_num": 0
},
{
"video_id": "7504263612656078116",
"episode_num": 0
},
{
"video_id": "7509429118845340982",
"episode_num": 0
},
{
"video_id": "7509743276300291340",
"episode_num": 0
}
],
"total_count": 29,
"last_update": "2025-10-22T09:56:01.930652",
"mix_name": "《田螺姑娘》系列短剧"
}

View File

@ -0,0 +1,51 @@
{
"episodes": [
{
"video_id": "7472763101620751626",
"episode_num": 0
},
{
"video_id": "7472763403182886156",
"episode_num": 0
},
{
"video_id": "7472763009627065619",
"episode_num": 0
},
{
"video_id": "7472764867905195327",
"episode_num": 0
},
{
"video_id": "7472763523450293516",
"episode_num": 0
},
{
"video_id": "7472763290104450315",
"episode_num": 0
},
{
"video_id": "7472762896833908031",
"episode_num": 0
},
{
"video_id": "7472763068376780071",
"episode_num": 0
},
{
"video_id": "7472763556648193334",
"episode_num": 0
},
{
"video_id": "7472763183409777939",
"episode_num": 0
},
{
"video_id": "7472763426813529380",
"episode_num": 0
}
],
"total_count": 11,
"last_update": "2025-10-22T09:56:08.613429",
"mix_name": "兴安岭诡事"
}

View File

@ -0,0 +1,31 @@
{
"episodes": [
{
"video_id": "7462779038784933158",
"episode_num": 0
},
{
"video_id": "7469001162868444443",
"episode_num": 0
},
{
"video_id": "7469998771519130889",
"episode_num": 0
},
{
"video_id": "7470709002284862720",
"episode_num": 0
},
{
"video_id": "7471924777410645283",
"episode_num": 0
},
{
"video_id": "7472791705268325641",
"episode_num": 0
}
],
"total_count": 6,
"last_update": "2025-11-06T17:43:54.929209",
"mix_name": "《青蛇传》"
}

View File

@ -0,0 +1,123 @@
{
"episodes": [
{
"video_id": "7438832661914717475",
"episode_num": 0
},
{
"video_id": "7441055420505918760",
"episode_num": 0
},
{
"video_id": "7441797752599711010",
"episode_num": 0
},
{
"video_id": "7444025334355201314",
"episode_num": 0
},
{
"video_id": "7444767906782514432",
"episode_num": 0
},
{
"video_id": "7445511144686587170",
"episode_num": 0
},
{
"video_id": "7448480282623216934",
"episode_num": 0
},
{
"video_id": "7449224455496256777",
"episode_num": 0
},
{
"video_id": "7449966534602018074",
"episode_num": 0
},
{
"video_id": "7452190854900370715",
"episode_num": 0
},
{
"video_id": "7452932931934342437",
"episode_num": 0
},
{
"video_id": "7453673782092303666",
"episode_num": 0
},
{
"video_id": "7454416566910864650",
"episode_num": 0
},
{
"video_id": "7458870546697080101",
"episode_num": 0
},
{
"video_id": "7459612621348572443",
"episode_num": 0
},
{
"video_id": "7460354830322699571",
"episode_num": 0
},
{
"video_id": "7461097063850249499",
"episode_num": 0
},
{
"video_id": "7461840320993463590",
"episode_num": 0
},
{
"video_id": "7462583778557349147",
"episode_num": 0
},
{
"video_id": "7463322906186091813",
"episode_num": 0
},
{
"video_id": "7467405224320322854",
"episode_num": 0
},
{
"video_id": "7468147493620960549",
"episode_num": 0
},
{
"video_id": "7468887846011653402",
"episode_num": 0
},
{
"video_id": "7469630119456361779",
"episode_num": 0
},
{
"video_id": "7471114379258318106",
"episode_num": 0
},
{
"video_id": "7471856794772491547",
"episode_num": 0
},
{
"video_id": "7472598861073157403",
"episode_num": 0
},
{
"video_id": "7473341167099186441",
"episode_num": 0
},
{
"video_id": "7474085833558052159",
"episode_num": 0
}
],
"total_count": 29,
"last_update": "2025-10-22T09:55:53.324636",
"mix_name": "我在地府开当铺"
}

View File

@ -0,0 +1,87 @@
{
"episodes": [
{
"video_id": "7472623062152695074",
"episode_num": 0
},
{
"video_id": "7457112388907666727",
"episode_num": 0
},
{
"video_id": "7457505550188743973",
"episode_num": 0
},
{
"video_id": "7457874864729345318",
"episode_num": 0
},
{
"video_id": "7458965679874542858",
"episode_num": 0
},
{
"video_id": "7459348765586001178",
"episode_num": 0
},
{
"video_id": "7459760717202345254",
"episode_num": 0
},
{
"video_id": "7460119944940883227",
"episode_num": 0
},
{
"video_id": "7460847335602490675",
"episode_num": 0
},
{
"video_id": "7461235696414559514",
"episode_num": 0
},
{
"video_id": "7461939199000825151",
"episode_num": 0
},
{
"video_id": "7462718793974910271",
"episode_num": 0
},
{
"video_id": "7463090318058081555",
"episode_num": 0
},
{
"video_id": "7463442602558246207",
"episode_num": 0
},
{
"video_id": "7464950323786861874",
"episode_num": 0
},
{
"video_id": "7466027689028193574",
"episode_num": 0
},
{
"video_id": "7467918209505676585",
"episode_num": 0
},
{
"video_id": "7469002478999096610",
"episode_num": 0
},
{
"video_id": "7471609858727447808",
"episode_num": 0
},
{
"video_id": "7472348174464650536",
"episode_num": 0
}
],
"total_count": 20,
"last_update": "2025-10-22T09:56:14.396524",
"mix_name": "《鲛人珠》"
}

View File

@ -0,0 +1,47 @@
{
"episodes": [
{
"video_id": "7445594903796518201",
"episode_num": 0
},
{
"video_id": "7446432501297384763",
"episode_num": 0
},
{
"video_id": "7454551962659376443",
"episode_num": 0
},
{
"video_id": "7461234093703875898",
"episode_num": 0
},
{
"video_id": "7466059018256026940",
"episode_num": 0
},
{
"video_id": "7471994773054819641",
"episode_num": 0
},
{
"video_id": "7474591461695147324",
"episode_num": 0
},
{
"video_id": "7477189231987772729",
"episode_num": 0
},
{
"video_id": "7478673086884187449",
"episode_num": 0
},
{
"video_id": "7482757832807386426",
"episode_num": 0
}
],
"total_count": 10,
"last_update": "2025-10-22T09:55:40.776573",
"mix_name": "中式百妖集·白骨夫人"
}

View File

@ -0,0 +1,79 @@
{
"episodes": [
{
"video_id": "7484932173439536421",
"episode_num": 0
},
{
"video_id": "7485664110688898343",
"episode_num": 0
},
{
"video_id": "7486419411969068314",
"episode_num": 0
},
{
"video_id": "7487121293100305718",
"episode_num": 0
},
{
"video_id": "7487526141658467620",
"episode_num": 0
},
{
"video_id": "7488271435824123173",
"episode_num": 0
},
{
"video_id": "7488650316636917044",
"episode_num": 0
},
{
"video_id": "7489021106041392418",
"episode_num": 0
},
{
"video_id": "7489378816629001510",
"episode_num": 0
},
{
"video_id": "7489762229294730530",
"episode_num": 0
},
{
"video_id": "7490506362162580771",
"episode_num": 0
},
{
"video_id": "7491612455748128063",
"episode_num": 0
},
{
"video_id": "7492356791385214249",
"episode_num": 0
},
{
"video_id": "7493098513320938764",
"episode_num": 0
},
{
"video_id": "7493470039182445843",
"episode_num": 0
},
{
"video_id": "7494213861059579170",
"episode_num": 0
},
{
"video_id": "7494607366281809179",
"episode_num": 0
},
{
"video_id": "7496444061780299017",
"episode_num": 0
}
],
"total_count": 18,
"last_update": "2025-10-22T09:56:10.891927",
"mix_name": "忘川引"
}

View File

@ -0,0 +1,163 @@
{
"episodes": [
{
"video_id": "7508787409220308287",
"episode_num": 0
},
{
"video_id": "7510681348156214582",
"episode_num": 0
},
{
"video_id": "7511991741650701607",
"episode_num": 0
},
{
"video_id": "7513617213908159783",
"episode_num": 0
},
{
"video_id": "7514504440183885067",
"episode_num": 0
},
{
"video_id": "7515713983450451211",
"episode_num": 0
},
{
"video_id": "7516611820073930020",
"episode_num": 0
},
{
"video_id": "7517661121336184127",
"episode_num": 0
},
{
"video_id": "7518746065160424743",
"episode_num": 0
},
{
"video_id": "7519749002439429417",
"episode_num": 0
},
{
"video_id": "7520472849899932964",
"episode_num": 0
},
{
"video_id": "7521204206330547497",
"episode_num": 0
},
{
"video_id": "7524573752886054196",
"episode_num": 0
},
{
"video_id": "7530578815811128603",
"episode_num": 0
},
{
"video_id": "7531758004685901065",
"episode_num": 0
},
{
"video_id": "7532765269714816306",
"episode_num": 0
},
{
"video_id": "7535386089935129871",
"episode_num": 0
},
{
"video_id": "7537427761216998708",
"episode_num": 0
},
{
"video_id": "7538524602385878324",
"episode_num": 0
},
{
"video_id": "7540051891523177737",
"episode_num": 0
},
{
"video_id": "7541107806611475752",
"episode_num": 0
},
{
"video_id": "7541853195853712640",
"episode_num": 0
},
{
"video_id": "7542682704836332835",
"episode_num": 0
},
{
"video_id": "7543822135823830287",
"episode_num": 0
},
{
"video_id": "7544261185663798555",
"episode_num": 0
},
{
"video_id": "7544908029926313225",
"episode_num": 0
},
{
"video_id": "7545999867550747914",
"episode_num": 0
},
{
"video_id": "7548507331529657626",
"episode_num": 0
},
{
"video_id": "7550700558206635274",
"episode_num": 0
},
{
"video_id": "7551695429147086126",
"episode_num": 0
},
{
"video_id": "7552568123484900646",
"episode_num": 0
},
{
"video_id": "7552961927568313609",
"episode_num": 0
},
{
"video_id": "7553727704906943771",
"episode_num": 0
},
{
"video_id": "7555956738030177562",
"episode_num": 0
},
{
"video_id": "7557016037955325194",
"episode_num": 0
},
{
"video_id": "7558378239337467174",
"episode_num": 0
},
{
"video_id": "7567050545257516331",
"episode_num": 0
},
{
"video_id": "7568152326477942022",
"episode_num": 0
},
{
"video_id": "7569217928420183332",
"episode_num": 0
}
],
"total_count": 39,
"last_update": "2025-11-06T11:06:44.598400",
"mix_name": "末世系列"
}

View File

@ -0,0 +1,47 @@
{
"episodes": [
{
"video_id": "7488147760089287973",
"episode_num": 0
},
{
"video_id": "7488147616237096219",
"episode_num": 0
},
{
"video_id": "7488146990686014757",
"episode_num": 0
},
{
"video_id": "7488146612695485706",
"episode_num": 0
},
{
"video_id": "7488145907737775369",
"episode_num": 0
},
{
"video_id": "7488510699275029810",
"episode_num": 0
},
{
"video_id": "7488511105959071013",
"episode_num": 0
},
{
"video_id": "7488511733053607177",
"episode_num": 0
},
{
"video_id": "7488885747047468315",
"episode_num": 0
},
{
"video_id": "7488885905084714249",
"episode_num": 0
}
],
"total_count": 10,
"last_update": "2025-10-22T09:56:06.856199",
"mix_name": "全国首部奇幻探秘Al微短剧"
}

View File

@ -0,0 +1,143 @@
{
"episodes": [
{
"video_id": "7486805231804681530",
"episode_num": 0
},
{
"video_id": "7487578743611985210",
"episode_num": 0
},
{
"video_id": "7488320388464774460",
"episode_num": 0
},
{
"video_id": "7489343145566555449",
"episode_num": 0
},
{
"video_id": "7490919031881469241",
"episode_num": 0
},
{
"video_id": "7491659592909327675",
"episode_num": 0
},
{
"video_id": "7492774825606483258",
"episode_num": 0
},
{
"video_id": "7495283384688594233",
"episode_num": 0
},
{
"video_id": "7496485765267754300",
"episode_num": 0
},
{
"video_id": "7497545408492014905",
"episode_num": 0
},
{
"video_id": "7499453865651014971",
"episode_num": 0
},
{
"video_id": "7500569260890099004",
"episode_num": 0
},
{
"video_id": "7502050015763189050",
"episode_num": 0
},
{
"video_id": "7502796678514642233",
"episode_num": 0
},
{
"video_id": "7505018729597963577",
"episode_num": 0
},
{
"video_id": "7505763063816670521",
"episode_num": 0
},
{
"video_id": "7507631537329458490",
"episode_num": 0
},
{
"video_id": "7508730844816280890",
"episode_num": 0
},
{
"video_id": "7513187407958150460",
"episode_num": 0
},
{
"video_id": "7516524242646125884",
"episode_num": 0
},
{
"video_id": "7518011787930520889",
"episode_num": 0
},
{
"video_id": "7520976901600447801",
"episode_num": 0
},
{
"video_id": "7537305227914546490",
"episode_num": 0
},
{
"video_id": "7546210488913595705",
"episode_num": 0
},
{
"video_id": "7546922516716539196",
"episode_num": 0
},
{
"video_id": "7547625203083185468",
"episode_num": 0
},
{
"video_id": "7549924545768295739",
"episode_num": 0
},
{
"video_id": "7553215200825134394",
"episode_num": 0
},
{
"video_id": "7554737168394374459",
"episode_num": 0
},
{
"video_id": "7555479694759284027",
"episode_num": 0
},
{
"video_id": "7556555317988724026",
"episode_num": 0
},
{
"video_id": "7562121519012285755",
"episode_num": 0
},
{
"video_id": "7563238756892757307",
"episode_num": 0
},
{
"video_id": "7564756828878753061",
"episode_num": 0
}
],
"total_count": 34,
"last_update": "2025-10-26T14:48:56.017802",
"mix_name": "【中式百妖集·阴医】"
}

View File

@ -0,0 +1,67 @@
{
"episodes": [
{
"video_id": "7498696558876331318",
"episode_num": 0
},
{
"video_id": "7498696627905940747",
"episode_num": 0
},
{
"video_id": "7498696682746514751",
"episode_num": 0
},
{
"video_id": "7498696464852471051",
"episode_num": 0
},
{
"video_id": "7498991164272479551",
"episode_num": 0
},
{
"video_id": "7499073383687114024",
"episode_num": 0
},
{
"video_id": "7499073497541528867",
"episode_num": 0
},
{
"video_id": "7499073601295944975",
"episode_num": 0
},
{
"video_id": "7499073974123498804",
"episode_num": 0
},
{
"video_id": "7499074026569059599",
"episode_num": 0
},
{
"video_id": "7499074347429006626",
"episode_num": 0
},
{
"video_id": "7499074195519802658",
"episode_num": 0
},
{
"video_id": "7501976370621287718",
"episode_num": 0
},
{
"video_id": "7502295085464227082",
"episode_num": 0
},
{
"video_id": "7502419858789911846",
"episode_num": 0
}
],
"total_count": 15,
"last_update": "2025-10-22T09:55:36.327209",
"mix_name": "盲盒千岁"
}

View File

@ -0,0 +1,47 @@
{
"episodes": [
{
"video_id": "7504244447165615360",
"episode_num": 0
},
{
"video_id": "7504244610508524834",
"episode_num": 0
},
{
"video_id": "7504244482838138147",
"episode_num": 0
},
{
"video_id": "7504244233549761844",
"episode_num": 0
},
{
"video_id": "7504244628758007040",
"episode_num": 0
},
{
"video_id": "7504244597929889076",
"episode_num": 0
},
{
"video_id": "7504244625457024296",
"episode_num": 0
},
{
"video_id": "7504244460637654307",
"episode_num": 0
},
{
"video_id": "7504244631295528227",
"episode_num": 0
},
{
"video_id": "7504244436004654371",
"episode_num": 0
}
],
"total_count": 10,
"last_update": "2025-10-22T09:55:42.069016",
"mix_name": "白骨精传奇"
}

View File

@ -0,0 +1,155 @@
{
"episodes": [
{
"video_id": "7506067244128963892",
"episode_num": 0
},
{
"video_id": "7506450698897296692",
"episode_num": 0
},
{
"video_id": "7506711648292375846",
"episode_num": 0
},
{
"video_id": "7508950346337815817",
"episode_num": 0
},
{
"video_id": "7509774388443499803",
"episode_num": 0
},
{
"video_id": "7510136494552550697",
"episode_num": 0
},
{
"video_id": "7511916836351511862",
"episode_num": 0
},
{
"video_id": "7514267874987363618",
"episode_num": 0
},
{
"video_id": "7514880725972225319",
"episode_num": 0
},
{
"video_id": "7516734442199682339",
"episode_num": 0
},
{
"video_id": "7517445470608690447",
"episode_num": 0
},
{
"video_id": "7517854243118730536",
"episode_num": 0
},
{
"video_id": "7519774016958172443",
"episode_num": 0
},
{
"video_id": "7520504060600929571",
"episode_num": 0
},
{
"video_id": "7521579659352395062",
"episode_num": 0
},
{
"video_id": "7522327810527055143",
"episode_num": 0
},
{
"video_id": "7524266961459481891",
"episode_num": 0
},
{
"video_id": "7524619540563512616",
"episode_num": 0
},
{
"video_id": "7525332357025402151",
"episode_num": 0
},
{
"video_id": "7527197348515171584",
"episode_num": 0
},
{
"video_id": "7527580712619363618",
"episode_num": 0
},
{
"video_id": "7527875102319316258",
"episode_num": 0
},
{
"video_id": "7529722113029147944",
"episode_num": 0
},
{
"video_id": "7529741695441833268",
"episode_num": 0
},
{
"video_id": "7530556459571039507",
"episode_num": 0
},
{
"video_id": "7532003677117091107",
"episode_num": 0
},
{
"video_id": "7533520661294239012",
"episode_num": 0
},
{
"video_id": "7534591926373190912",
"episode_num": 0
},
{
"video_id": "7535986779401325843",
"episode_num": 0
},
{
"video_id": "7538632635627703606",
"episode_num": 0
},
{
"video_id": "7538634729889877290",
"episode_num": 0
},
{
"video_id": "7540121743873035555",
"episode_num": 0
},
{
"video_id": "7540870569168784655",
"episode_num": 0
},
{
"video_id": "7542402995653283112",
"episode_num": 0
},
{
"video_id": "7546532809460976948",
"episode_num": 0
},
{
"video_id": "7547533101929467151",
"episode_num": 0
},
{
"video_id": "7547910476743609652",
"episode_num": 0
}
],
"total_count": 37,
"last_update": "2025-10-22T09:56:06.222702",
"mix_name": "九尾狐男妖爱上我"
}

View File

@ -0,0 +1,235 @@
{
"episodes": [
{
"video_id": "7506725945898847515",
"episode_num": 0
},
{
"video_id": "7506726188392402186",
"episode_num": 0
},
{
"video_id": "7506726062122929417",
"episode_num": 0
},
{
"video_id": "7506725983026777354",
"episode_num": 0
},
{
"video_id": "7506726108860124443",
"episode_num": 0
},
{
"video_id": "7506726665033223433",
"episode_num": 0
},
{
"video_id": "7506726346563800370",
"episode_num": 0
},
{
"video_id": "7506726016790940965",
"episode_num": 0
},
{
"video_id": "7506726671379221787",
"episode_num": 0
},
{
"video_id": "7506726090564586761",
"episode_num": 0
},
{
"video_id": "7506726187318725898",
"episode_num": 0
},
{
"video_id": "7506726296039230730",
"episode_num": 0
},
{
"video_id": "7506726309150592294",
"episode_num": 0
},
{
"video_id": "7506726284358094107",
"episode_num": 0
},
{
"video_id": "7506726237205777714",
"episode_num": 0
},
{
"video_id": "7506726182524685605",
"episode_num": 0
},
{
"video_id": "7506726228850674954",
"episode_num": 0
},
{
"video_id": "7506726166506605862",
"episode_num": 0
},
{
"video_id": "7506726208801901851",
"episode_num": 0
},
{
"video_id": "7506726298337758501",
"episode_num": 0
},
{
"video_id": "7506726295435218213",
"episode_num": 0
},
{
"video_id": "7506726377392016650",
"episode_num": 0
},
{
"video_id": "7506726273427754278",
"episode_num": 0
},
{
"video_id": "7506726355774524682",
"episode_num": 0
},
{
"video_id": "7506726266536594726",
"episode_num": 0
},
{
"video_id": "7506726314951462181",
"episode_num": 0
},
{
"video_id": "7506726196588203290",
"episode_num": 0
},
{
"video_id": "7506726170638028070",
"episode_num": 0
},
{
"video_id": "7506726491531660594",
"episode_num": 0
},
{
"video_id": "7506726608372239643",
"episode_num": 0
},
{
"video_id": "7506726485236141362",
"episode_num": 0
},
{
"video_id": "7506726498439564553",
"episode_num": 0
},
{
"video_id": "7506726478663486758",
"episode_num": 0
},
{
"video_id": "7506726546317659419",
"episode_num": 0
},
{
"video_id": "7506726541062130982",
"episode_num": 0
},
{
"video_id": "7506726526709157147",
"episode_num": 0
},
{
"video_id": "7506726570220883251",
"episode_num": 0
},
{
"video_id": "7506726627510897958",
"episode_num": 0
},
{
"video_id": "7506726589883747622",
"episode_num": 0
},
{
"video_id": "7506726632447593765",
"episode_num": 0
},
{
"video_id": "7506726607525039369",
"episode_num": 0
},
{
"video_id": "7506726659584822565",
"episode_num": 0
},
{
"video_id": "7506726454617525541",
"episode_num": 0
},
{
"video_id": "7506726418294869285",
"episode_num": 0
},
{
"video_id": "7506726571374333210",
"episode_num": 0
},
{
"video_id": "7506726517443956019",
"episode_num": 0
},
{
"video_id": "7506726555305905418",
"episode_num": 0
},
{
"video_id": "7506726472007109914",
"episode_num": 0
},
{
"video_id": "7506726648675323173",
"episode_num": 0
},
{
"video_id": "7506726475693919515",
"episode_num": 0
},
{
"video_id": "7506726506924739850",
"episode_num": 0
},
{
"video_id": "7506726523471220019",
"episode_num": 0
},
{
"video_id": "7506726476633476379",
"episode_num": 0
},
{
"video_id": "7506726532715433226",
"episode_num": 0
},
{
"video_id": "7506726554542640394",
"episode_num": 0
},
{
"video_id": "7506726533835410698",
"episode_num": 0
},
{
"video_id": "7506726608326118683",
"episode_num": 0
}
],
"total_count": 57,
"last_update": "2025-10-22T09:55:59.359473",
"mix_name": "如意坊"
}

View File

@ -0,0 +1,55 @@
{
"episodes": [
{
"video_id": "7549025803313384746",
"episode_num": 0
},
{
"video_id": "7549025817850826003",
"episode_num": 0
},
{
"video_id": "7549025811861409043",
"episode_num": 0
},
{
"video_id": "7549025819251756342",
"episode_num": 0
},
{
"video_id": "7549025818131893547",
"episode_num": 0
},
{
"video_id": "7549025828185705767",
"episode_num": 0
},
{
"video_id": "7549025818874252563",
"episode_num": 0
},
{
"video_id": "7549025854819405097",
"episode_num": 0
},
{
"video_id": "7549025851971521828",
"episode_num": 0
},
{
"video_id": "7549025854999891239",
"episode_num": 0
},
{
"video_id": "7549025873840590134",
"episode_num": 0
},
{
"video_id": "7549025884955577643",
"episode_num": 0
}
],
"total_count": 12,
"last_update": "2025-10-22T09:55:37.752763",
"mix_name": "新平妖传"
}

View File

@ -0,0 +1,83 @@
{
"episodes": [
{
"video_id": "7517472781332696332",
"episode_num": 0
},
{
"video_id": "7517971761476979980",
"episode_num": 0
},
{
"video_id": "7518395078306483510",
"episode_num": 0
},
{
"video_id": "7518867709761686803",
"episode_num": 0
},
{
"video_id": "7519654070961949988",
"episode_num": 0
},
{
"video_id": "7520748725438811446",
"episode_num": 0
},
{
"video_id": "7521798836566641983",
"episode_num": 0
},
{
"video_id": "7529928581993041215",
"episode_num": 0
},
{
"video_id": "7530321831966805284",
"episode_num": 0
},
{
"video_id": "7531079565049941289",
"episode_num": 0
},
{
"video_id": "7532255656586399017",
"episode_num": 0
},
{
"video_id": "7533366600439762195",
"episode_num": 0
},
{
"video_id": "7534435304296516918",
"episode_num": 0
},
{
"video_id": "7535534271813979431",
"episode_num": 0
},
{
"video_id": "7536713189078175016",
"episode_num": 0
},
{
"video_id": "7538565796382002467",
"episode_num": 0
},
{
"video_id": "7538868180136037647",
"episode_num": 0
},
{
"video_id": "7540042632618167592",
"episode_num": 0
},
{
"video_id": "7543715550619241743",
"episode_num": 0
}
],
"total_count": 19,
"last_update": "2025-10-22T09:55:49.993319",
"mix_name": "苏祥云"
}

View File

@ -0,0 +1,23 @@
{
"episodes": [
{
"video_id": "7527336931760295168",
"episode_num": 0
},
{
"video_id": "7529100313467325711",
"episode_num": 0
},
{
"video_id": "7539690162612079872",
"episode_num": 0
},
{
"video_id": "7565426543275609378",
"episode_num": 0
}
],
"total_count": 4,
"last_update": "2025-10-27T10:05:06.655628",
"mix_name": "《小宝穿越|课本古诗文》"
}

View File

@ -0,0 +1,19 @@
{
"episodes": [
{
"video_id": "7476841679304051968",
"episode_num": 0
},
{
"video_id": "7519893460598541609",
"episode_num": 0
},
{
"video_id": "7548075831491415359",
"episode_num": 0
}
],
"total_count": 3,
"last_update": "2025-10-22T09:56:11.433105",
"mix_name": "僵尸师姐"
}

View File

@ -0,0 +1,63 @@
{
"episodes": [
{
"video_id": "7529542793484815642",
"episode_num": 0
},
{
"video_id": "7529837310423190825",
"episode_num": 0
},
{
"video_id": "7530392006963498279",
"episode_num": 0
},
{
"video_id": "7531919686112529716",
"episode_num": 0
},
{
"video_id": "7533566057529740559",
"episode_num": 0
},
{
"video_id": "7536843946534669631",
"episode_num": 0
},
{
"video_id": "7538371048060898600",
"episode_num": 0
},
{
"video_id": "7540606531264433418",
"episode_num": 0
},
{
"video_id": "7542079999621319986",
"episode_num": 0
},
{
"video_id": "7543609660469824822",
"episode_num": 0
},
{
"video_id": "7546200368829484339",
"episode_num": 0
},
{
"video_id": "7548447317729234239",
"episode_num": 0
},
{
"video_id": "7568747381357808923",
"episode_num": 0
},
{
"video_id": "7568800392985791784",
"episode_num": 0
}
],
"total_count": 14,
"last_update": "2025-11-06T17:48:06.014161",
"mix_name": "青云修仙传"
}

View File

@ -0,0 +1,195 @@
{
"episodes": [
{
"video_id": "7533908493456182538",
"episode_num": 0
},
{
"video_id": "7534294543475150118",
"episode_num": 0
},
{
"video_id": "7534686345395014950",
"episode_num": 0
},
{
"video_id": "7535429627486031144",
"episode_num": 0
},
{
"video_id": "7535787648519097634",
"episode_num": 0
},
{
"video_id": "7536148527018167604",
"episode_num": 0
},
{
"video_id": "7536756145902374159",
"episode_num": 0
},
{
"video_id": "7536916803734359296",
"episode_num": 0
},
{
"video_id": "7537291155520621858",
"episode_num": 0
},
{
"video_id": "7537711561570290998",
"episode_num": 0
},
{
"video_id": "7538026362255281447",
"episode_num": 0
},
{
"video_id": "7538291293873556778",
"episode_num": 0
},
{
"video_id": "7538394363068452115",
"episode_num": 0
},
{
"video_id": "7538755908755590463",
"episode_num": 0
},
{
"video_id": "7539159291803045159",
"episode_num": 0
},
{
"video_id": "7539494201172512063",
"episode_num": 0
},
{
"video_id": "7539497676841241898",
"episode_num": 0
},
{
"video_id": "7539878435678653750",
"episode_num": 0
},
{
"video_id": "7541743859181636910",
"episode_num": 0
},
{
"video_id": "7542114403907308810",
"episode_num": 0
},
{
"video_id": "7542826819817377070",
"episode_num": 0
},
{
"video_id": "7542829529639734574",
"episode_num": 0
},
{
"video_id": "7543242228785876275",
"episode_num": 0
},
{
"video_id": "7543611905496059174",
"episode_num": 0
},
{
"video_id": "7543976834791165210",
"episode_num": 0
},
{
"video_id": "7544364656991784219",
"episode_num": 0
},
{
"video_id": "7545110659919383859",
"episode_num": 0
},
{
"video_id": "7545458257129655567",
"episode_num": 0
},
{
"video_id": "7545864375643409704",
"episode_num": 0
},
{
"video_id": "7546225454894599464",
"episode_num": 0
},
{
"video_id": "7546575160715889966",
"episode_num": 0
},
{
"video_id": "7546934519836806409",
"episode_num": 0
},
{
"video_id": "7547336647730335027",
"episode_num": 0
},
{
"video_id": "7547711167158897958",
"episode_num": 0
},
{
"video_id": "7548073308894317875",
"episode_num": 0
},
{
"video_id": "7548371214998244658",
"episode_num": 0
},
{
"video_id": "7548819838773349670",
"episode_num": 0
},
{
"video_id": "7549193055358733606",
"episode_num": 0
},
{
"video_id": "7549574668735270190",
"episode_num": 0
},
{
"video_id": "7549952043016883502",
"episode_num": 0
},
{
"video_id": "7550323254922202378",
"episode_num": 0
},
{
"video_id": "7550694134588263720",
"episode_num": 0
},
{
"video_id": "7551058050275167540",
"episode_num": 0
},
{
"video_id": "7551425021642607906",
"episode_num": 0
},
{
"video_id": "7551790578816552226",
"episode_num": 0
},
{
"video_id": "7555439509588938024",
"episode_num": 0
},
{
"video_id": "7555845189366074662",
"episode_num": 0
}
],
"total_count": 47,
"last_update": "2025-10-22T09:55:27.641577",
"mix_name": "《风水之王》"
}

View File

@ -0,0 +1,279 @@
{
"episodes": [
{
"video_id": "7535701272712777002",
"episode_num": 0
},
{
"video_id": "7535701290232401194",
"episode_num": 0
},
{
"video_id": "7535701286658821430",
"episode_num": 0
},
{
"video_id": "7535701275858570532",
"episode_num": 0
},
{
"video_id": "7535701262310985003",
"episode_num": 0
},
{
"video_id": "7535701270364048682",
"episode_num": 0
},
{
"video_id": "7535701309303901481",
"episode_num": 0
},
{
"video_id": "7535701337573461289",
"episode_num": 0
},
{
"video_id": "7535701337984519460",
"episode_num": 0
},
{
"video_id": "7535701328048311571",
"episode_num": 0
},
{
"video_id": "7535701353595800874",
"episode_num": 0
},
{
"video_id": "7535701350357699903",
"episode_num": 0
},
{
"video_id": "7535701363007769898",
"episode_num": 0
},
{
"video_id": "7535701370368691492",
"episode_num": 0
},
{
"video_id": "7535701350932335911",
"episode_num": 0
},
{
"video_id": "7535701373900377385",
"episode_num": 0
},
{
"video_id": "7535701392267283775",
"episode_num": 0
},
{
"video_id": "7535701391872888106",
"episode_num": 0
},
{
"video_id": "7535701400672619795",
"episode_num": 0
},
{
"video_id": "7535701415142935827",
"episode_num": 0
},
{
"video_id": "7535701423242120484",
"episode_num": 0
},
{
"video_id": "7535701444511403305",
"episode_num": 0
},
{
"video_id": "7535701443542535465",
"episode_num": 0
},
{
"video_id": "7535701456305786153",
"episode_num": 0
},
{
"video_id": "7535701442590428455",
"episode_num": 0
},
{
"video_id": "7535701468439973162",
"episode_num": 0
},
{
"video_id": "7535701494906031399",
"episode_num": 0
},
{
"video_id": "7535701504049548580",
"episode_num": 0
},
{
"video_id": "7535701470788750635",
"episode_num": 0
},
{
"video_id": "7535701485867322687",
"episode_num": 0
},
{
"video_id": "7535701502833200403",
"episode_num": 0
},
{
"video_id": "7535701525734231359",
"episode_num": 0
},
{
"video_id": "7535701525520239914",
"episode_num": 0
},
{
"video_id": "7535701540334587190",
"episode_num": 0
},
{
"video_id": "7535701524362693930",
"episode_num": 0
},
{
"video_id": "7535701557594066217",
"episode_num": 0
},
{
"video_id": "7535701561180163367",
"episode_num": 0
},
{
"video_id": "7535701557178879268",
"episode_num": 0
},
{
"video_id": "7535701577760263463",
"episode_num": 0
},
{
"video_id": "7535701575201738026",
"episode_num": 0
},
{
"video_id": "7535701602775108905",
"episode_num": 0
},
{
"video_id": "7535701587893833014",
"episode_num": 0
},
{
"video_id": "7535701608986873127",
"episode_num": 0
},
{
"video_id": "7535701629786508580",
"episode_num": 0
},
{
"video_id": "7535701644424531239",
"episode_num": 0
},
{
"video_id": "7535701627827752211",
"episode_num": 0
},
{
"video_id": "7535701660933492009",
"episode_num": 0
},
{
"video_id": "7535701645422873897",
"episode_num": 0
},
{
"video_id": "7535701646941179179",
"episode_num": 0
},
{
"video_id": "7535701666885045540",
"episode_num": 0
},
{
"video_id": "7535701674799779135",
"episode_num": 0
},
{
"video_id": "7535701687755869476",
"episode_num": 0
},
{
"video_id": "7535701681867214099",
"episode_num": 0
},
{
"video_id": "7535701718235942180",
"episode_num": 0
},
{
"video_id": "7535701716738559295",
"episode_num": 0
},
{
"video_id": "7535701714779884841",
"episode_num": 0
},
{
"video_id": "7535701730080656679",
"episode_num": 0
},
{
"video_id": "7535701718118567211",
"episode_num": 0
},
{
"video_id": "7535701734463720743",
"episode_num": 0
},
{
"video_id": "7535701751924542756",
"episode_num": 0
},
{
"video_id": "7535701727887084842",
"episode_num": 0
},
{
"video_id": "7535701766235622710",
"episode_num": 0
},
{
"video_id": "7535701764960521508",
"episode_num": 0
},
{
"video_id": "7535701790038199606",
"episode_num": 0
},
{
"video_id": "7535701815388540223",
"episode_num": 0
},
{
"video_id": "7535701795494989098",
"episode_num": 0
},
{
"video_id": "7535701809969532214",
"episode_num": 0
},
{
"video_id": "7543884485494885666",
"episode_num": 0
}
],
"total_count": 68,
"last_update": "2025-10-22T09:56:42.812592",
"mix_name": "奶团太后宫心计"
}

View File

@ -0,0 +1,55 @@
{
"episodes": [
{
"video_id": "7538319264500010274",
"episode_num": 0
},
{
"video_id": "7538319253661895976",
"episode_num": 0
},
{
"video_id": "7538319258040667426",
"episode_num": 0
},
{
"video_id": "7538319250730142991",
"episode_num": 0
},
{
"video_id": "7538319251308956928",
"episode_num": 0
},
{
"video_id": "7538319255717088546",
"episode_num": 0
},
{
"video_id": "7538319260087635200",
"episode_num": 0
},
{
"video_id": "7538319313824943360",
"episode_num": 0
},
{
"video_id": "7538319304874282280",
"episode_num": 0
},
{
"video_id": "7538319315666357519",
"episode_num": 0
},
{
"video_id": "7538319302512921890",
"episode_num": 0
},
{
"video_id": "7538319312289926400",
"episode_num": 0
}
],
"total_count": 12,
"last_update": "2025-10-22T09:56:12.150347",
"mix_name": "燕赤伏妖"
}

View File

@ -0,0 +1,87 @@
{
"episodes": [
{
"video_id": "7540121867537894694",
"episode_num": 0
},
{
"video_id": "7540121873649093926",
"episode_num": 0
},
{
"video_id": "7540121890577272115",
"episode_num": 0
},
{
"video_id": "7540121898751986990",
"episode_num": 0
},
{
"video_id": "7540121893479664934",
"episode_num": 0
},
{
"video_id": "7540121902363233545",
"episode_num": 0
},
{
"video_id": "7540121896306707721",
"episode_num": 0
},
{
"video_id": "7540121918884547878",
"episode_num": 0
},
{
"video_id": "7540121908944162099",
"episode_num": 0
},
{
"video_id": "7540121914937822510",
"episode_num": 0
},
{
"video_id": "7540121923020246310",
"episode_num": 0
},
{
"video_id": "7540121952229330186",
"episode_num": 0
},
{
"video_id": "7540121960240434470",
"episode_num": 0
},
{
"video_id": "7540121958432689458",
"episode_num": 0
},
{
"video_id": "7540121974115241267",
"episode_num": 0
},
{
"video_id": "7540121980037582126",
"episode_num": 0
},
{
"video_id": "7540121986127727922",
"episode_num": 0
},
{
"video_id": "7540121993606171931",
"episode_num": 0
},
{
"video_id": "7540121997993397550",
"episode_num": 0
},
{
"video_id": "7540121998798671150",
"episode_num": 0
}
],
"total_count": 20,
"last_update": "2025-10-22T09:55:40.062057",
"mix_name": "废柴仙尊在凡间逆袭"
}

View File

@ -0,0 +1,63 @@
{
"episodes": [
{
"video_id": "7520060104645823780",
"episode_num": 0
},
{
"video_id": "7526440964852911360",
"episode_num": 0
},
{
"video_id": "7531224944424897807",
"episode_num": 0
},
{
"video_id": "7536966604467342634",
"episode_num": 0
},
{
"video_id": "7540495853090245898",
"episode_num": 0
},
{
"video_id": "7546582568850754825",
"episode_num": 0
},
{
"video_id": "7550653403244875044",
"episode_num": 0
},
{
"video_id": "7553221689224711466",
"episode_num": 0
},
{
"video_id": "7555476643674869028",
"episode_num": 0
},
{
"video_id": "7557563705563581723",
"episode_num": 0
},
{
"video_id": "7561372042944105770",
"episode_num": 0
},
{
"video_id": "7563638353325821203",
"episode_num": 0
},
{
"video_id": "7564982296051338534",
"episode_num": 0
},
{
"video_id": "7565346285362548019",
"episode_num": 0
}
],
"total_count": 14,
"last_update": "2025-10-27T11:04:23.469116",
"mix_name": "暗黑神话《葫芦兄弟》大电影"
}

View File

@ -0,0 +1,335 @@
{
"episodes": [
{
"video_id": "7542848851212848399",
"episode_num": 0
},
{
"video_id": "7542848809219509539",
"episode_num": 0
},
{
"video_id": "7542848845315607848",
"episode_num": 0
},
{
"video_id": "7542848836981640482",
"episode_num": 0
},
{
"video_id": "7542848861065301248",
"episode_num": 0
},
{
"video_id": "7542848884834323746",
"episode_num": 0
},
{
"video_id": "7542848870422711587",
"episode_num": 0
},
{
"video_id": "7542848880208022824",
"episode_num": 0
},
{
"video_id": "7542848850625662223",
"episode_num": 0
},
{
"video_id": "7542848915637357858",
"episode_num": 0
},
{
"video_id": "7542848899275443508",
"episode_num": 0
},
{
"video_id": "7542848861832793344",
"episode_num": 0
},
{
"video_id": "7542848917688405248",
"episode_num": 0
},
{
"video_id": "7542848921295539490",
"episode_num": 0
},
{
"video_id": "7542848955193822498",
"episode_num": 0
},
{
"video_id": "7542848980061785359",
"episode_num": 0
},
{
"video_id": "7542848967848004898",
"episode_num": 0
},
{
"video_id": "7542848965998415104",
"episode_num": 0
},
{
"video_id": "7542848979311070504",
"episode_num": 0
},
{
"video_id": "7542848967923584308",
"episode_num": 0
},
{
"video_id": "7542848984814062848",
"episode_num": 0
},
{
"video_id": "7542848924772470050",
"episode_num": 0
},
{
"video_id": "7542849015503768847",
"episode_num": 0
},
{
"video_id": "7542849044486343970",
"episode_num": 0
},
{
"video_id": "7542849026937408820",
"episode_num": 0
},
{
"video_id": "7542849016988536064",
"episode_num": 0
},
{
"video_id": "7542849050052152576",
"episode_num": 0
},
{
"video_id": "7542849050303827234",
"episode_num": 0
},
{
"video_id": "7542849070256098560",
"episode_num": 0
},
{
"video_id": "7542849073359883554",
"episode_num": 0
},
{
"video_id": "7542849080590978339",
"episode_num": 0
},
{
"video_id": "7542849054955277620",
"episode_num": 0
},
{
"video_id": "7542849095237422376",
"episode_num": 0
},
{
"video_id": "7542849079081012495",
"episode_num": 0
},
{
"video_id": "7542849104309751092",
"episode_num": 0
},
{
"video_id": "7542849117307915554",
"episode_num": 0
},
{
"video_id": "7542849134680739106",
"episode_num": 0
},
{
"video_id": "7542849125507779892",
"episode_num": 0
},
{
"video_id": "7542849118029286691",
"episode_num": 0
},
{
"video_id": "7542849137717349684",
"episode_num": 0
},
{
"video_id": "7542849160806927650",
"episode_num": 0
},
{
"video_id": "7542849105559604520",
"episode_num": 0
},
{
"video_id": "7542849158164565263",
"episode_num": 0
},
{
"video_id": "7542849186220297487",
"episode_num": 0
},
{
"video_id": "7542849155958394146",
"episode_num": 0
},
{
"video_id": "7542849176644717839",
"episode_num": 0
},
{
"video_id": "7542849167325039924",
"episode_num": 0
},
{
"video_id": "7542849189500226850",
"episode_num": 0
},
{
"video_id": "7542849227328621839",
"episode_num": 0
},
{
"video_id": "7542849207896395008",
"episode_num": 0
},
{
"video_id": "7542849211025444111",
"episode_num": 0
},
{
"video_id": "7542849162627386639",
"episode_num": 0
},
{
"video_id": "7542849207703555328",
"episode_num": 0
},
{
"video_id": "7542849259792502031",
"episode_num": 0
},
{
"video_id": "7542849258521644323",
"episode_num": 0
},
{
"video_id": "7542849225709735183",
"episode_num": 0
},
{
"video_id": "7542849302184365364",
"episode_num": 0
},
{
"video_id": "7542849280399232271",
"episode_num": 0
},
{
"video_id": "7542849289127464232",
"episode_num": 0
},
{
"video_id": "7542849295699987747",
"episode_num": 0
},
{
"video_id": "7542849251013954850",
"episode_num": 0
},
{
"video_id": "7542849316457565475",
"episode_num": 0
},
{
"video_id": "7542849309453077794",
"episode_num": 0
},
{
"video_id": "7542849330223287604",
"episode_num": 0
},
{
"video_id": "7542849366608973071",
"episode_num": 0
},
{
"video_id": "7542849356844649743",
"episode_num": 0
},
{
"video_id": "7542849376066997544",
"episode_num": 0
},
{
"video_id": "7542849360560737536",
"episode_num": 0
},
{
"video_id": "7542849368232086819",
"episode_num": 0
},
{
"video_id": "7542849392085110050",
"episode_num": 0
},
{
"video_id": "7542849341845720335",
"episode_num": 0
},
{
"video_id": "7542849423311637795",
"episode_num": 0
},
{
"video_id": "7542849409881558287",
"episode_num": 0
},
{
"video_id": "7542849389480529167",
"episode_num": 0
},
{
"video_id": "7542849421172657442",
"episode_num": 0
},
{
"video_id": "7542849419784359168",
"episode_num": 0
},
{
"video_id": "7542849444547415348",
"episode_num": 0
},
{
"video_id": "7542849467016383778",
"episode_num": 0
},
{
"video_id": "7542849454877986048",
"episode_num": 0
},
{
"video_id": "7542849456861973760",
"episode_num": 0
},
{
"video_id": "7542849403191594255",
"episode_num": 0
},
{
"video_id": "7543842316474207529",
"episode_num": 0
}
],
"total_count": 82,
"last_update": "2025-10-22T09:56:26.875722",
"mix_name": "200斤王妃天天想和离Q版"
}

View File

@ -0,0 +1,59 @@
{
"episodes": [
{
"video_id": "7546071652224765234",
"episode_num": 0
},
{
"video_id": "7546071610189548827",
"episode_num": 0
},
{
"video_id": "7546071665881500954",
"episode_num": 0
},
{
"video_id": "7546071681165495561",
"episode_num": 0
},
{
"video_id": "7546071674475515145",
"episode_num": 0
},
{
"video_id": "7546071695342243123",
"episode_num": 0
},
{
"video_id": "7546071699012193587",
"episode_num": 0
},
{
"video_id": "7546071697456254234",
"episode_num": 0
},
{
"video_id": "7546071708185283878",
"episode_num": 0
},
{
"video_id": "7546071712543165723",
"episode_num": 0
},
{
"video_id": "7546071720415857947",
"episode_num": 0
},
{
"video_id": "7546071728376565043",
"episode_num": 0
},
{
"video_id": "7546071716959685939",
"episode_num": 0
}
],
"total_count": 13,
"last_update": "2025-10-22T09:55:35.570013",
"mix_name": "末日来袭"
}

View File

@ -0,0 +1,35 @@
{
"episodes": [
{
"video_id": "7545715527935610150",
"episode_num": 0
},
{
"video_id": "7546851498567716123",
"episode_num": 0
},
{
"video_id": "7548440120316218662",
"episode_num": 0
},
{
"video_id": "7549782003667782954",
"episode_num": 0
},
{
"video_id": "7551038580261391616",
"episode_num": 0
},
{
"video_id": "7552606420504464650",
"episode_num": 0
},
{
"video_id": "7555059788590206234",
"episode_num": 0
}
],
"total_count": 7,
"last_update": "2025-10-22T09:56:07.724877",
"mix_name": "《南城诡事》"
}

View File

@ -0,0 +1,283 @@
{
"episodes": [
{
"video_id": "7553983620604579118",
"episode_num": 0
},
{
"video_id": "7553983636282953011",
"episode_num": 0
},
{
"video_id": "7553983647192288563",
"episode_num": 0
},
{
"video_id": "7553983646374448411",
"episode_num": 0
},
{
"video_id": "7553983671213133107",
"episode_num": 0
},
{
"video_id": "7553983656532937994",
"episode_num": 0
},
{
"video_id": "7553983683934326067",
"episode_num": 0
},
{
"video_id": "7553983686446697737",
"episode_num": 0
},
{
"video_id": "7553983694885752110",
"episode_num": 0
},
{
"video_id": "7553983711918755122",
"episode_num": 0
},
{
"video_id": "7553983719787285770",
"episode_num": 0
},
{
"video_id": "7553983712300403978",
"episode_num": 0
},
{
"video_id": "7553983719380487450",
"episode_num": 0
},
{
"video_id": "7553983734438038830",
"episode_num": 0
},
{
"video_id": "7553983745687178546",
"episode_num": 0
},
{
"video_id": "7553983751466831150",
"episode_num": 0
},
{
"video_id": "7553983762921524530",
"episode_num": 0
},
{
"video_id": "7553983765584923914",
"episode_num": 0
},
{
"video_id": "7553983782336957723",
"episode_num": 0
},
{
"video_id": "7553983772199275785",
"episode_num": 0
},
{
"video_id": "7553983806231891209",
"episode_num": 0
},
{
"video_id": "7553983819997547803",
"episode_num": 0
},
{
"video_id": "7553983816046595366",
"episode_num": 0
},
{
"video_id": "7553983829485112627",
"episode_num": 0
},
{
"video_id": "7553983842286161178",
"episode_num": 0
},
{
"video_id": "7553983841656950042",
"episode_num": 0
},
{
"video_id": "7553983832865737993",
"episode_num": 0
},
{
"video_id": "7553983844874013978",
"episode_num": 0
},
{
"video_id": "7553983845054352678",
"episode_num": 0
},
{
"video_id": "7553983863744187699",
"episode_num": 0
},
{
"video_id": "7553983873361677618",
"episode_num": 0
},
{
"video_id": "7553983883587423514",
"episode_num": 0
},
{
"video_id": "7553983889304194330",
"episode_num": 0
},
{
"video_id": "7553983897294359818",
"episode_num": 0
},
{
"video_id": "7553983891145575707",
"episode_num": 0
},
{
"video_id": "7553983921776512266",
"episode_num": 0
},
{
"video_id": "7553983915900374299",
"episode_num": 0
},
{
"video_id": "7553983931104693542",
"episode_num": 0
},
{
"video_id": "7553983944425737523",
"episode_num": 0
},
{
"video_id": "7553983937924680998",
"episode_num": 0
},
{
"video_id": "7553983956136381705",
"episode_num": 0
},
{
"video_id": "7553983954274077961",
"episode_num": 0
},
{
"video_id": "7553983968228494602",
"episode_num": 0
},
{
"video_id": "7553983980819713326",
"episode_num": 0
},
{
"video_id": "7553983971994995995",
"episode_num": 0
},
{
"video_id": "7553983994157632814",
"episode_num": 0
},
{
"video_id": "7553983997076966706",
"episode_num": 0
},
{
"video_id": "7553984007311019315",
"episode_num": 0
},
{
"video_id": "7553984019042422026",
"episode_num": 0
},
{
"video_id": "7553984009651408137",
"episode_num": 0
},
{
"video_id": "7553984031956798770",
"episode_num": 0
},
{
"video_id": "7553984033814859054",
"episode_num": 0
},
{
"video_id": "7553984062147431690",
"episode_num": 0
},
{
"video_id": "7553984067348352266",
"episode_num": 0
},
{
"video_id": "7553984082489658675",
"episode_num": 0
},
{
"video_id": "7553984074881256714",
"episode_num": 0
},
{
"video_id": "7553984094493773094",
"episode_num": 0
},
{
"video_id": "7553984092258241830",
"episode_num": 0
},
{
"video_id": "7553984101271850267",
"episode_num": 0
},
{
"video_id": "7553984113179462950",
"episode_num": 0
},
{
"video_id": "7553984119844228379",
"episode_num": 0
},
{
"video_id": "7553984134859722035",
"episode_num": 0
},
{
"video_id": "7553984126999711022",
"episode_num": 0
},
{
"video_id": "7553984154476514587",
"episode_num": 0
},
{
"video_id": "7553984147111415049",
"episode_num": 0
},
{
"video_id": "7553984154166168870",
"episode_num": 0
},
{
"video_id": "7553984160797363502",
"episode_num": 0
},
{
"video_id": "7553984182108572955",
"episode_num": 0
},
{
"video_id": "7553984168468811046",
"episode_num": 0
}
],
"total_count": 69,
"last_update": "2025-10-22T09:56:34.803954",
"mix_name": "我在荒年当女帝"
}

View File

@ -0,0 +1,59 @@
{
"episodes": [
{
"video_id": "7554009575922355482",
"episode_num": 0
},
{
"video_id": "7554009571266809139",
"episode_num": 0
},
{
"video_id": "7554009591776840970",
"episode_num": 0
},
{
"video_id": "7554009593425218866",
"episode_num": 0
},
{
"video_id": "7554009609409662217",
"episode_num": 0
},
{
"video_id": "7554312903608913195",
"episode_num": 0
},
{
"video_id": "7554312915164187940",
"episode_num": 0
},
{
"video_id": "7554312925851176234",
"episode_num": 0
},
{
"video_id": "7554312927336041791",
"episode_num": 0
},
{
"video_id": "7554312932851617078",
"episode_num": 0
},
{
"video_id": "7555847841084706111",
"episode_num": 0
},
{
"video_id": "7555847841931955510",
"episode_num": 0
},
{
"video_id": "7559429054969957684",
"episode_num": 0
}
],
"total_count": 13,
"last_update": "2025-10-22T09:56:17.655969",
"mix_name": "传武"
}

View File

@ -0,0 +1,87 @@
{
"episodes": [
{
"video_id": "7554750058999188776",
"episode_num": 0
},
{
"video_id": "7554750089743355151",
"episode_num": 0
},
{
"video_id": "7554750101374143784",
"episode_num": 0
},
{
"video_id": "7554750092658429199",
"episode_num": 0
},
{
"video_id": "7554750091874045224",
"episode_num": 0
},
{
"video_id": "7554750074610339107",
"episode_num": 0
},
{
"video_id": "7554750093157600512",
"episode_num": 0
},
{
"video_id": "7554750110882729251",
"episode_num": 0
},
{
"video_id": "7554751305978268963",
"episode_num": 0
},
{
"video_id": "7554750135679356195",
"episode_num": 0
},
{
"video_id": "7554750126988872995",
"episode_num": 0
},
{
"video_id": "7554750136304422144",
"episode_num": 0
},
{
"video_id": "7554750151248694568",
"episode_num": 0
},
{
"video_id": "7554750138007276835",
"episode_num": 0
},
{
"video_id": "7554750160660679970",
"episode_num": 0
},
{
"video_id": "7554750166654356770",
"episode_num": 0
},
{
"video_id": "7554750176326454562",
"episode_num": 0
},
{
"video_id": "7554750194152197416",
"episode_num": 0
},
{
"video_id": "7554750204159773967",
"episode_num": 0
},
{
"video_id": "7554750213030776064",
"episode_num": 0
}
],
"total_count": 20,
"last_update": "2025-10-22T09:55:34.882918",
"mix_name": "我的治愈系游戏"
}

View File

@ -0,0 +1,23 @@
{
"episodes": [
{
"video_id": "7555012459468295464",
"episode_num": 0
},
{
"video_id": "7555402801766092086",
"episode_num": 0
},
{
"video_id": "7555406069112130856",
"episode_num": 0
},
{
"video_id": "7559152702782049576",
"episode_num": 0
}
],
"total_count": 4,
"last_update": "2025-10-22T09:55:32.571577",
"mix_name": "我在山海世界当神仙奶奶"
}

View File

@ -0,0 +1,215 @@
{
"episodes": [
{
"video_id": "7555848626619043087",
"episode_num": 0
},
{
"video_id": "7555848620394745103",
"episode_num": 0
},
{
"video_id": "7555848626723884288",
"episode_num": 0
},
{
"video_id": "7555848639554374947",
"episode_num": 0
},
{
"video_id": "7555848651201908020",
"episode_num": 0
},
{
"video_id": "7555848646504303872",
"episode_num": 0
},
{
"video_id": "7555848634936479016",
"episode_num": 0
},
{
"video_id": "7555848658214833443",
"episode_num": 0
},
{
"video_id": "7555848701277670656",
"episode_num": 0
},
{
"video_id": "7555848676313206050",
"episode_num": 0
},
{
"video_id": "7555848687017151759",
"episode_num": 0
},
{
"video_id": "7555848690846551330",
"episode_num": 0
},
{
"video_id": "7555848704461196544",
"episode_num": 0
},
{
"video_id": "7555848729824152832",
"episode_num": 0
},
{
"video_id": "7555848716045847842",
"episode_num": 0
},
{
"video_id": "7555848730759531828",
"episode_num": 0
},
{
"video_id": "7555848731841662242",
"episode_num": 0
},
{
"video_id": "7555848757745618228",
"episode_num": 0
},
{
"video_id": "7555848764313947426",
"episode_num": 0
},
{
"video_id": "7555848763940572456",
"episode_num": 0
},
{
"video_id": "7555848769703644468",
"episode_num": 0
},
{
"video_id": "7555848788842171663",
"episode_num": 0
},
{
"video_id": "7555848805493558562",
"episode_num": 0
},
{
"video_id": "7555848796584938787",
"episode_num": 0
},
{
"video_id": "7555848822841232655",
"episode_num": 0
},
{
"video_id": "7555848807460752675",
"episode_num": 0
},
{
"video_id": "7555848826909625634",
"episode_num": 0
},
{
"video_id": "7555848831548525859",
"episode_num": 0
},
{
"video_id": "7555848844282449204",
"episode_num": 0
},
{
"video_id": "7555848849298820367",
"episode_num": 0
},
{
"video_id": "7555848861130951970",
"episode_num": 0
},
{
"video_id": "7555848854726348072",
"episode_num": 0
},
{
"video_id": "7555848849340910882",
"episode_num": 0
},
{
"video_id": "7555848876754849064",
"episode_num": 0
},
{
"video_id": "7555848903296371968",
"episode_num": 0
},
{
"video_id": "7555848872417971471",
"episode_num": 0
},
{
"video_id": "7555848898049363234",
"episode_num": 0
},
{
"video_id": "7555848895910235426",
"episode_num": 0
},
{
"video_id": "7555848919880699136",
"episode_num": 0
},
{
"video_id": "7555848946078207267",
"episode_num": 0
},
{
"video_id": "7555848941703613711",
"episode_num": 0
},
{
"video_id": "7555848946078305571",
"episode_num": 0
},
{
"video_id": "7555848977900375336",
"episode_num": 0
},
{
"video_id": "7555848968379419939",
"episode_num": 0
},
{
"video_id": "7555848963484683572",
"episode_num": 0
},
{
"video_id": "7559890467404434724",
"episode_num": 0
},
{
"video_id": "7555848989376089359",
"episode_num": 0
},
{
"video_id": "7555848987807386880",
"episode_num": 0
},
{
"video_id": "7555848993603898664",
"episode_num": 0
},
{
"video_id": "7555849016483777827",
"episode_num": 0
},
{
"video_id": "7555849024037801216",
"episode_num": 0
},
{
"video_id": "7555849027057667368",
"episode_num": 0
}
],
"total_count": 52,
"last_update": "2025-10-22T09:55:47.709471",
"mix_name": "婉心计"
}

View File

@ -0,0 +1,251 @@
{
"episodes": [
{
"video_id": "7556193043586600201",
"episode_num": 0
},
{
"video_id": "7556193049286675762",
"episode_num": 0
},
{
"video_id": "7556193069050105139",
"episode_num": 0
},
{
"video_id": "7556193070857899302",
"episode_num": 0
},
{
"video_id": "7556193075085741350",
"episode_num": 0
},
{
"video_id": "7556193078072134962",
"episode_num": 0
},
{
"video_id": "7556193079070379310",
"episode_num": 0
},
{
"video_id": "7556193078973975818",
"episode_num": 0
},
{
"video_id": "7556193108963118382",
"episode_num": 0
},
{
"video_id": "7556193112775822638",
"episode_num": 0
},
{
"video_id": "7556193121483115802",
"episode_num": 0
},
{
"video_id": "7556193118438067494",
"episode_num": 0
},
{
"video_id": "7556193118383623462",
"episode_num": 0
},
{
"video_id": "7556193123634908462",
"episode_num": 0
},
{
"video_id": "7556193146061868338",
"episode_num": 0
},
{
"video_id": "7556193153837960458",
"episode_num": 0
},
{
"video_id": "7556193142836448521",
"episode_num": 0
},
{
"video_id": "7556193162352512282",
"episode_num": 0
},
{
"video_id": "7556193174033616138",
"episode_num": 0
},
{
"video_id": "7556193192723402034",
"episode_num": 0
},
{
"video_id": "7556193187744845107",
"episode_num": 0
},
{
"video_id": "7556193208854646025",
"episode_num": 0
},
{
"video_id": "7556193223312510254",
"episode_num": 0
},
{
"video_id": "7556193225766112550",
"episode_num": 0
},
{
"video_id": "7556193223954287922",
"episode_num": 0
},
{
"video_id": "7556193234033069338",
"episode_num": 0
},
{
"video_id": "7556193250365771054",
"episode_num": 0
},
{
"video_id": "7556193252056059182",
"episode_num": 0
},
{
"video_id": "7556193259912006963",
"episode_num": 0
},
{
"video_id": "7556193260188847369",
"episode_num": 0
},
{
"video_id": "7556193250890075418",
"episode_num": 0
},
{
"video_id": "7556193288026361114",
"episode_num": 0
},
{
"video_id": "7556193288236190985",
"episode_num": 0
},
{
"video_id": "7556193301905345802",
"episode_num": 0
},
{
"video_id": "7556193302542961970",
"episode_num": 0
},
{
"video_id": "7556193321299823881",
"episode_num": 0
},
{
"video_id": "7556193328434269450",
"episode_num": 0
},
{
"video_id": "7556193321668922633",
"episode_num": 0
},
{
"video_id": "7556193341751266569",
"episode_num": 0
},
{
"video_id": "7556193355420405019",
"episode_num": 0
},
{
"video_id": "7556193355697458482",
"episode_num": 0
},
{
"video_id": "7556193360436841737",
"episode_num": 0
},
{
"video_id": "7556193361883974921",
"episode_num": 0
},
{
"video_id": "7556193358012534025",
"episode_num": 0
},
{
"video_id": "7556193378002668846",
"episode_num": 0
},
{
"video_id": "7556193386659728691",
"episode_num": 0
},
{
"video_id": "7556193404892253478",
"episode_num": 0
},
{
"video_id": "7556193406922312970",
"episode_num": 0
},
{
"video_id": "7556193422680329498",
"episode_num": 0
},
{
"video_id": "7556193423468842266",
"episode_num": 0
},
{
"video_id": "7556193432524410158",
"episode_num": 0
},
{
"video_id": "7556193450471804187",
"episode_num": 0
},
{
"video_id": "7556193451281222939",
"episode_num": 0
},
{
"video_id": "7556193461007928586",
"episode_num": 0
},
{
"video_id": "7556193466187861294",
"episode_num": 0
},
{
"video_id": "7556193487763328265",
"episode_num": 0
},
{
"video_id": "7556193490787421467",
"episode_num": 0
},
{
"video_id": "7556193478774918410",
"episode_num": 0
},
{
"video_id": "7556193479311887625",
"episode_num": 0
},
{
"video_id": "7556193503307484425",
"episode_num": 0
},
{
"video_id": "7556193515902995721",
"episode_num": 0
}
],
"total_count": 61,
"last_update": "2025-10-22T09:56:50.400526",
"mix_name": "我靠唱歌打脸全团"
}

View File

@ -0,0 +1,123 @@
{
"episodes": [
{
"video_id": "7497105467341147428",
"episode_num": 0
},
{
"video_id": "7501492304370601250",
"episode_num": 0
},
{
"video_id": "7502772081621273894",
"episode_num": 0
},
{
"video_id": "7503839169832062259",
"episode_num": 0
},
{
"video_id": "7504986016153062656",
"episode_num": 0
},
{
"video_id": "7506112308838403343",
"episode_num": 0
},
{
"video_id": "7506782177625394432",
"episode_num": 0
},
{
"video_id": "7507204856379378959",
"episode_num": 0
},
{
"video_id": "7507541015005744424",
"episode_num": 0
},
{
"video_id": "7507946206644210959",
"episode_num": 0
},
{
"video_id": "7508945450654141731",
"episode_num": 0
},
{
"video_id": "7514129646461078799",
"episode_num": 0
},
{
"video_id": "7519344535044066560",
"episode_num": 0
},
{
"video_id": "7521994647779970338",
"episode_num": 0
},
{
"video_id": "7524264312450698536",
"episode_num": 0
},
{
"video_id": "7527207390493723938",
"episode_num": 0
},
{
"video_id": "7531374405576248591",
"episode_num": 0
},
{
"video_id": "7532778856979451136",
"episode_num": 0
},
{
"video_id": "7535583078481136911",
"episode_num": 0
},
{
"video_id": "7536884524291263759",
"episode_num": 0
},
{
"video_id": "7538866617654365480",
"episode_num": 0
},
{
"video_id": "7539751142214438144",
"episode_num": 0
},
{
"video_id": "7549904246364376335",
"episode_num": 0
},
{
"video_id": "7554992404740214016",
"episode_num": 0
},
{
"video_id": "7555715283915312384",
"episode_num": 0
},
{
"video_id": "7558050978226982179",
"episode_num": 0
},
{
"video_id": "7560551213957500195",
"episode_num": 0
},
{
"video_id": "7562056353343966464",
"episode_num": 0
},
{
"video_id": "7567981488823318927",
"episode_num": 0
}
],
"total_count": 29,
"last_update": "2025-11-06T17:15:32.747557",
"mix_name": "绝境逆袭"
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,562 @@
from typing import Any, Optional
import mimetypes
from io import StringIO
import os
import tos
import urllib3
from urllib3.exceptions import InsecureRequestWarning
from config import API_CONFIG
# 火山对象存储
class TOSClient:
def __init__(
self,
access_key_id: str,
access_key_secret: str,
endpoint: str,
region: str,
bucket_name: str,
self_domain: str,
disable_ssl_warnings: bool = True
):
"""
初始化OSS客户端
Args:
access_key_id: ak
access_key_secret: sk
endpoint: OSS访问端点 (: https://oss-cn-hangzhou.aliyuncs.com)
bucket_name: 存储桶名称
self_domain: 自定义域名
disable_ssl_warnings: 是否禁用SSL警告
"""
# 禁用SSL警告如果需要
if disable_ssl_warnings:
urllib3.disable_warnings(InsecureRequestWarning)
sts_token: str = "token_test"
self.bucket_name = bucket_name
self.self_domain = self_domain
self.endpoint = endpoint
self.client = tos.TosClientV2(
ak=access_key_id,
sk=access_key_secret,
endpoint=self_domain,
region=region,
is_custom_domain=True,
# bucket_name,
# security_token=sts_token,
connection_time=30, socket_timeout=60, max_retry_count=3
)
def get_base_url(self, object_key: str) -> str:
"""获取基础URL不带签名参数"""
# endpoint = self.endpoint.replace('https://', '').replace('http://', '')
return f"https://{self.self_domain}/{object_key}"
def generate_url(self, object_key: str, expires: int = 3600) -> str:
"""生成带签名的临时访问URL"""
# 生成签名URL
pre_signed_url_output = self.client.pre_signed_url(
tos.HttpMethodType.Http_Method_Get,
bucket=self.bucket_name,
key=object_key,
expires=expires)
return pre_signed_url_output.signed_url
def upload_string(
self,
content_str: str,
object_key: str,
headers: Optional[dict] = None,
return_url: bool = True,
) -> str:
"""
上传本地文件到OSS
Args:
local_file_path: 本地文件路径
object_key: OSS对象键(路径)如果为None则使用本地文件名
headers: 自定义HTTP头
Returns:
str: 文件在OSS的公开URL
Raises:
Exception: 如果上传失败
"""
try:
# if headers is None:
# headers = {}
# if content_type and 'Content-Type' not in headers:
# headers['Content-Type'] = content_type
content = StringIO(content_str)
result = self.client.put_object(
bucket=self.bucket_name,
key=object_key,
content_type='text/plain',
content=content,
)
# HTTP状态码
print('upload_string http status code:{}'.format(result.status_code))
# 请求ID。请求ID是本次请求的唯一标识建议在日志中添加此参数
# print('request_id: {}'.format(result.request_id))
# hash_crc64_ecma 表示该对象的64位CRC值, 可用于验证上传对象的完整性
# print('crc64: {}'.format(result.hash_crc64_ecma))
if result.status_code != 200:
raise Exception(f"上传失败HTTP状态码: {result.status_code}")
return self.get_base_url(object_key) if return_url else object_key # 修改返回逻辑
except Exception as e:
raise Exception(f"上传文件到OSS失败: {str(e)}")
def upload_file(
self,
local_file_path: str,
object_key: Optional[str] = None,
headers: Optional[dict] = None,
return_url: bool = True,
expires: int = 3600 # 新增参数默认1小时
) -> str:
"""
上传本地文件到OSS
Args:
local_file_path: 本地文件路径
object_key: OSS对象键(路径)如果为None则使用本地文件名
headers: 自定义HTTP头
Returns:
str: 文件在OSS的公开URL
Raises:
Exception: 如果上传失败
"""
if not os.path.exists(local_file_path):
raise FileNotFoundError(f"本地文件不存在: {local_file_path}")
# 如果没有指定object_key则使用文件名
if object_key is None:
object_key = os.path.basename(local_file_path)
# 自动设置Content-Type
content_type, _ = mimetypes.guess_type(local_file_path)
try:
# file_name为本地文件的完整路径。
result = self.client.put_object_from_file(
bucket=self.bucket_name,
key=object_key,
content_type=content_type or '',
file_path=local_file_path,
)
if result.status_code != 200:
raise Exception(f"上传失败HTTP状态码: {result.status_code}")
return self.get_base_url(object_key) if return_url else object_key # 修改返回逻辑
except Exception as e:
raise Exception(f"上传文件到OSS失败: {str(e)}")
def upload_bytes(
self,
data: bytes,
object_key: str,
content_type: Optional[str] = None,
headers: Optional[dict] = None,
return_url: bool = True,
expires: int = 3600 # 新增参数
) -> str:
"""
上传字节数据到OSS
Args:
data: 要上传的字节数据
object_key: OSS对象键(路径)
content_type: 内容类型 (: image/jpeg)
headers: 自定义HTTP头
Returns:
str: 文件在OSS的公开URL
Raises:
Exception: 如果上传失败
"""
try:
result = self.client.put_object(
bucket=self.bucket_name,
key=object_key,
content_type=content_type or 'application/octet-stream',
content=data,
)
if result.status_code != 200:
raise Exception(f"上传失败HTTP状态码: {result.status_code}")
return self.get_base_url(object_key) if return_url else object_key # 修改返回逻辑
except Exception as e:
raise Exception(f"上传字节数据到OSS失败: {str(e)}")
def upload_from_url(
self,
url: str,
object_key: str,
headers: Optional[dict] = None,
timeout: int = 30,
return_url: bool = True,
expires: int = 3600 # 新增参数
) -> str:
"""
从网络URL下载文件并上传到OSS
Args:
url: 网络文件URL
object_key: OSS对象键(路径)
headers: 自定义HTTP头
timeout: 下载超时时间()
return_url: 是否返回完整URL
Returns:
str: 文件在OSS的公开URL或object_key
Raises:
Exception: 如果下载或上传失败
"""
import requests
from io import BytesIO
if not url.startswith(('http://', 'https://')):
raise ValueError("URL必须以http://或https://开头")
try:
# 下载文件
response = requests.get(url, stream=True, timeout=timeout)
response.raise_for_status()
# 获取内容类型
content_type = response.headers.get('Content-Type', '')
if not content_type:
content_type = mimetypes.guess_type(url)[0] or 'application/octet-stream'
# 上传到OSS
return self.upload_bytes(
data=response.content,
object_key=object_key,
content_type=content_type,
headers=headers,
return_url=return_url,
expires=expires # 传递参数
)
except requests.exceptions.RequestException as e:
raise Exception(f"下载网络文件失败: {str(e)}")
except Exception as e:
raise Exception(f"上传网络文件到OSS失败: {str(e)}")
def _format_object_key(self, object_key: str) -> str:
"""
格式化OSS对象键(路径)
"""
# 如果object_key包含self_domain截取self_domain后面的字符作为新的object_key
if self.self_domain and self.self_domain in object_key:
# 找到self_domain在object_key中的位置截取后面的部分
domain_index = object_key.find(self.self_domain)
if domain_index != -1:
# 截取self_domain后面的部分去掉开头的斜杠
object_key = object_key[domain_index + len(self.self_domain):].lstrip('/')
return object_key
# 删除文件
def delete_file(self, object_key: str) -> bool:
"""
删除OSS上的文件
Args:
object_key: OSS对象键(路径)
Returns:
bool: 删除是否成功
"""
try:
self.client.delete_object(
bucket=self.bucket_name,
key=self._format_object_key(object_key),
)
return True
except Exception as e:
print(f"删除文件失败: {str(e)}")
return False
def download_file(self, object_key: str) -> bytes:
"""
从TOS下载文件并返回文件数据
Args:
object_key: OSS对象键(路径)
Returns:
bytes: 文件的字节数据
Raises:
Exception: 如果下载失败
"""
try:
object_key = self._format_object_key(object_key)
object_stream = self.client.get_object(
bucket=self.bucket_name,
key=object_key,
)
content = object_stream.read() or b''
if not content:
raise Exception(f"文件内容为空: {object_key}")
return content
except tos.exceptions.TosClientError as e:
# 操作失败,捕获客户端异常,一般情况为非法请求参数或网络异常
print('TOS下载 fail with client error, message:{}, cause: {}'.format(e.message, e.cause))
raise Exception(f"下载异常: {object_key} {e.message}")
except tos.exceptions.TosServerError as e:
# 操作失败,捕获服务端异常,可从返回信息中获取详细错误信息
print('TOS下载 fail with server error, code: {}'.format(e.code))
# request id 可定位具体问题,强烈建议日志中保存
print('TOS下载 error with request id: {}'.format(e.request_id))
print('TOS下载 error with message: {}'.format(e.message))
print('TOS下载 error with http code: {}'.format(e.status_code))
print('TOS下载 error with ec: {}'.format(e.ec))
print('TOS下载 error with request url: {}'.format(e.request_url))
raise Exception(f"下载异常: {object_key} {e.message}")
except Exception as e:
raise Exception(f"下载文件失败: {str(e)}")
class TOSChunkUploader:
"""TOS分片上传类"""
def __init__(self, tos_client: TOSClient):
"""
初始化分片上传器
Args:
tos_client: TOS客户端实例
"""
self.client = tos_client.client
self.bucket_name = tos_client.bucket_name
self.self_domain = tos_client.self_domain
def init_multipart_upload(self, object_key: str, content_type: Optional[str] = None) -> Optional[str]:
"""
初始化分片上传
Args:
object_key: 对象键
content_type: 内容类型
Returns:
str: 上传ID
Raises:
Exception: 如果初始化失败
"""
try:
# 设置默认内容类型
if not content_type:
content_type = mimetypes.guess_type(object_key)[0] or 'application/octet-stream'
# 初始化分片上传
result = self.client.create_multipart_upload(
bucket=self.bucket_name,
key=object_key,
content_type=content_type
)
return result.upload_id
except tos.exceptions.TosClientError as e:
raise Exception(f"初始化分片上传失败(客户端错误): {e.message}")
except tos.exceptions.TosServerError as e:
raise Exception(f"初始化分片上传失败(服务端错误): {e.message}")
except Exception as e:
raise Exception(f"初始化分片上传失败: {str(e)}")
def upload_part(self, object_key: str, upload_id: str, part_number: int, data: bytes) -> dict:
"""
上传分片
Args:
object_key: 对象键
upload_id: 上传ID
part_number: 分片号(从1开始)
data: 分片数据
Returns:
dict: 包含完整分片信息的字典
Raises:
Exception: 如果上传失败
"""
try:
from io import BytesIO
import hashlib
# 计算分片大小
part_size = len(data)
# 计算CRC64如果需要的话这里先设为None
hash_crc64_ecma = None
# 上传分片
result = self.client.upload_part(
bucket=self.bucket_name,
key=object_key,
upload_id=upload_id,
part_number=part_number,
content=BytesIO(data)
)
return {
'part_number': part_number,
'etag': result.etag,
'part_size': part_size,
'hash_crc64_ecma': hash_crc64_ecma,
'is_completed': True
}
except tos.exceptions.TosClientError as e:
raise Exception(f"上传分片失败(客户端错误): {e.message}")
except tos.exceptions.TosServerError as e:
raise Exception(f"上传分片失败(服务端错误): {e.message}")
except Exception as e:
raise Exception(f"上传分片失败: {str(e)}")
def complete_multipart_upload(self, object_key: str, upload_id: str, parts: list) -> str:
"""
完成分片上传
Args:
object_key: 对象键
upload_id: 上传ID
parts: 分片信息列表每个元素包含part_number和etag
Returns:
str: 文件的完整URL
Raises:
Exception: 如果完成上传失败
"""
try:
# 按分片号排序
sorted_parts = sorted(parts, key=lambda x: x['part_number'])
# 构建分片列表并计算偏移量
part_list = []
current_offset = 0
for part in sorted_parts:
part_list.append(tos.models2.PartInfo(
part_number=part['part_number'],
etag=part['etag'],
part_size=part.get('part_size'),
offset=current_offset,
hash_crc64_ecma=part.get('hash_crc64_ecma'),
is_completed=part.get('is_completed', True)
))
# 更新偏移量
if part.get('part_size'):
current_offset += part['part_size']
# 完成分片上传
result = self.client.complete_multipart_upload(
bucket=self.bucket_name,
key=object_key,
upload_id=upload_id,
parts=part_list
)
# 返回完整URL
return f"https://{self.self_domain}/{object_key}"
except tos.exceptions.TosClientError as e:
raise Exception(f"完成分片上传失败(客户端错误): {e.message}")
except tos.exceptions.TosServerError as e:
raise Exception(f"完成分片上传失败(服务端错误): {e.message}")
except Exception as e:
raise Exception(f"完成分片上传失败: {str(e)}")
def abort_multipart_upload(self, object_key: str, upload_id: str) -> bool:
"""
取消分片上传
Args:
object_key: 对象键
upload_id: 上传ID
Returns:
bool: 是否取消成功
"""
try:
self.client.abort_multipart_upload(
bucket=self.bucket_name,
key=object_key,
upload_id=upload_id
)
return True
except tos.exceptions.TosClientError as e:
print(f"取消分片上传失败(客户端错误): {e.message}")
return False
except tos.exceptions.TosServerError as e:
print(f"取消分片上传失败(服务端错误): {e.message}")
return False
except Exception as e:
print(f"取消分片上传失败: {str(e)}")
return False
def list_parts(self, object_key: str, upload_id: str) -> list:
"""
列出已上传的分片
Args:
object_key: 对象键
upload_id: 上传ID
Returns:
list: 已上传的分片列表
"""
try:
result = self.client.list_parts(
bucket=self.bucket_name,
key=object_key,
upload_id=upload_id
)
parts = []
for part in result.parts:
parts.append({
'part_number': part.part_number,
'etag': part.etag,
'size': part.size,
'last_modified': part.last_modified
})
return parts
except Exception as e:
print(f"列出分片失败: {str(e)}")
return []
# 创建OSS客户端
from config import TOS_CONFIG
oss_client = TOSClient(
access_key_id=TOS_CONFIG['access_key_id'],
access_key_secret=TOS_CONFIG['access_key_secret'],
endpoint=TOS_CONFIG['endpoint'],
region=TOS_CONFIG['region'],
bucket_name=TOS_CONFIG['bucket_name'],
self_domain=TOS_CONFIG['self_domain'],
disable_ssl_warnings=TOS_CONFIG['disable_ssl_warnings']
)
# 创建分片上传器
chunk_uploader = TOSChunkUploader(oss_client)

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +0,0 @@
import os
import importlib
# 数据库配置
MONGO_URI = "mongodb://localhost:27017"
MONGO_DB_NAME = "Rankings"
# 应用配置
APP_ENV = os.getenv('APP_ENV', 'development')
DEBUG = APP_ENV == 'development'
# 日志配置
LOG_LEVEL = 'INFO'
LOG_DIR = 'logs'
print(f"Successfully loaded configuration for environment: {APP_ENV}")

960
docs/API接口文档.md Normal file
View File

@ -0,0 +1,960 @@
# 抖音播放量数据API接口文档
## 概述
本API服务提供抖音播放量数据和文章内容的查询、搜索、统计等功能专为小程序优化设计。
**基础信息**
- 服务地址:`http://localhost:5001`
- 数据源MongoDB数据库
- 数据更新每晚24:00自动更新
- 响应格式JSON
## 通用响应格式
所有API接口都遵循以下响应格式
```json
{
"success": true,
"data": {},
"message": "操作成功",
"update_time": "2025-10-17 15:30:00"
}
```
**字段说明**
- `success`: 请求是否成功
- `data`: 返回的数据内容
- `message`: 操作结果描述
- `update_time`: 数据更新时间
## 数据模型
### 合集数据项
```json
{
"_id": "674f1234567890abcdef",
"batch_time": "2025-10-17 15:30:00",
"mix_name": "合集名称",
"video_url": "https://www.douyin.com/video/xxx",
"playcount": "1.2亿",
"play_vv": 120000000,
"request_id": "request_xxx",
"rank": 1,
"cover_image_url": "https://p3.douyinpic.com/xxx"
}
```
### 文章数据项
```json
{
"_id": "68f3ad112ba085e5d676537e",
"title": "文章标题",
"author_id": "test_user_1",
"cover_image": "封面图片URL",
"status": "draft",
"summary": "文章摘要",
"created_at": "2025-10-18 15:06:57",
"likes": [],
"likes_count": 0
}
```
### 文章详情数据项
```json
{
"_id": "68f3ad112ba085e5d676537e",
"title": "文章标题",
"content": "文章完整内容",
"author_id": "test_user_1",
"cover_image": "封面图片URL",
"status": "draft",
"summary": "文章摘要",
"created_at": "2025-10-18 15:06:57",
"likes": [],
"likes_count": 0
}
```
### 分页信息
```json
{
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"pages": 5,
"has_next": true,
"has_prev": false
}
}
```
## API接口列表
### 1. 首页信息
**接口地址**
```
GET /
```
**功能描述**
获取API服务的基本信息和所有可用接口列表
**请求参数**
**响应示例**
```json
{
"success": true,
"data": {
"name": "抖音播放量数据API服务",
"version": "2.0",
"description": "主程序服务 - 整合小程序API功能",
"endpoints": {
"/api/rank/videos": "获取视频列表 (支持分页和排序)",
"/api/rank/top": "获取热门视频榜单",
"/api/rank/search": "搜索视频",
"/api/rank/detail": "获取视频详情",
"/api/rank/stats": "获取统计信息",
"/api/rank/health": "健康检查",
"/api/rank/rankings": "获取榜单列表",
"/api/rank/rankings/dates": "获取可用榜单日期",
"/api/rank/rankings/types": "获取榜单类型",
"/api/rank/rankings/latest": "获取最新榜单",
"/api/rank/rankings/stats": "获取榜单统计",
"/api/article/list": "获取文章列表 (支持分页和排序)",
"/api/article/search": "搜索文章",
"/api/article/detail": "获取文章详情",
"/api/article/stats": "获取文章统计信息",
"/api/article/health": "文章服务健康检查"
},
"features": [
"分页支持",
"多种排序方式",
"搜索功能",
"详情查看",
"统计分析",
"榜单查询",
"动态排序",
"小程序优化",
"文章管理",
"内容搜索"
]
}
}
```
### 2. 获取视频列表
**接口地址**
```
GET /api/rank/videos
```
**功能描述**
获取分页的视频合集列表,支持多种排序方式
**请求参数**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| page | int | 否 | 1 | 页码 |
| limit | int | 否 | 20 | 每页数量 |
| sort | string | 否 | playcount | 排序方式playcount(播放量) / growth(增长量) |
| start_date | string | 否 | 昨天 | 增长计算开始日期(格式: YYYY-MM-DD)仅在sort=growth时有效 |
| end_date | string | 否 | 今天 | 增长计算结束日期(格式: YYYY-MM-DD)仅在sort=growth时有效 |
**数据源说明**
- `sort=playcount`从Rankings_list集合动态查询当日播放量数据
- `sort=growth`从Ranking_storage集合读取预计算的增长排名数据由定时任务生成
**使用示例**
```
# 按播放量排序
GET /api/rank/videos?page=1&limit=20&sort=playcount
# 按增长量排序(默认昨天到今天的增长)
GET /api/rank/videos?page=1&limit=20&sort=growth
# 按自定义日期范围的增长排序
GET /api/rank/videos?page=1&limit=20&sort=growth&start_date=2025-10-16&end_date=2025-10-17
```
**响应示例**
```json
{
"success": true,
"data": [
{
"_id": "674f1234567890abcdef",
"batch_time": "2025-10-17 15:30:00",
"mix_name": "热门合集1",
"video_url": "https://www.douyin.com/video/xxx",
"playcount": "1.2亿",
"play_vv": 120000000,
"request_id": "request_xxx",
"rank": 1,
"cover_image_url": "https://p3.douyinpic.com/xxx",
"growth": 5000000,
"growth_rate": 4.35
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"pages": 5,
"has_next": true,
"has_prev": false
},
"sort_by": "growth",
"data_source": "ranking_storage",
"update_time": "2025-10-17 15:30:00"
}
```
### 3. 获取热门榜单
**接口地址**
```
GET /api/rank/top
```
**功能描述**
获取热门视频榜单(按播放量排序)
**请求参数**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| limit | int | 否 | 10 | 返回数量 |
**使用示例**
```
GET /api/rank/top?limit=10
```
**响应示例**
```json
{
"success": true,
"data": [
{
"_id": "674f1234567890abcdef",
"batch_time": "2025-10-17 15:30:00",
"mix_name": "热门合集1",
"video_url": "https://www.douyin.com/video/xxx",
"playcount": "1.2亿",
"play_vv": 120000000,
"request_id": "request_xxx",
"rank": 1,
"cover_image_url": "https://p3.douyinpic.com/xxx"
}
],
"total": 10,
"update_time": "2025-10-17 15:30:00"
}
```
### 4. 搜索视频
**接口地址**
```
GET /api/rank/search
```
**功能描述**
根据关键词搜索视频合集
**请求参数**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| q | string | 是 | - | 搜索关键词 |
| page | int | 否 | 1 | 页码 |
| limit | int | 否 | 10 | 每页数量 |
**使用示例**
```
GET /api/rank/search?q=关键词&page=1&limit=10
```
**响应示例**
```json
{
"success": true,
"data": [
{
"_id": "674f1234567890abcdef",
"batch_time": "2025-10-17 15:30:00",
"mix_name": "包含关键词的合集",
"video_url": "https://www.douyin.com/video/xxx",
"playcount": "1.2亿",
"play_vv": 120000000,
"request_id": "request_xxx",
"rank": 1,
"cover_image_url": "https://p3.douyinpic.com/xxx"
}
],
"keyword": "关键词",
"pagination": {
"page": 1,
"limit": 10,
"total": 15,
"pages": 2,
"has_next": true,
"has_prev": false
},
"update_time": "2025-10-17 15:30:00"
}
```
### 5. 获取视频详情
**接口地址**
```
GET /api/rank/detail
```
**功能描述**
获取指定合集的详细信息
**请求参数**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| id | string | 是 | - | 合集ID支持ObjectId、合集名称、request_id |
**使用示例**
```
GET /api/rank/detail?id=674f1234567890abcdef
```
**响应示例**
```json
{
"success": true,
"data": {
"_id": "674f1234567890abcdef",
"batch_time": "2025-10-17 15:30:00",
"mix_name": "合集名称",
"video_url": "https://www.douyin.com/video/xxx",
"playcount": "1.2亿",
"play_vv": 120000000,
"request_id": "request_xxx",
"rank": 1,
"cover_image_url": "https://p3.douyinpic.com/xxx"
},
"update_time": "2025-10-17 15:30:00"
}
```
### 6. 获取统计信息
**接口地址**
```
GET /api/rank/stats
```
**功能描述**
获取系统统计信息
**请求参数**
**响应示例**
```json
{
"success": true,
"data": {
"total_mixes": 1000,
"total_playcount": 5000000000,
"avg_playcount": 5000000,
"max_playcount": 200000000,
"min_playcount": 1000,
"categories": [
{
"name": "超热门",
"min": 100000000,
"count": 5
},
{
"name": "热门",
"min": 50000000,
"max": 99999999,
"count": 20
},
{
"name": "中等",
"min": 10000000,
"max": 49999999,
"count": 150
},
{
"name": "一般",
"min": 0,
"max": 9999999,
"count": 825
}
],
"latest_update": "2025-10-17 15:30:00"
},
"update_time": "2025-10-17 15:30:00"
}
```
### 7. 健康检查
**接口地址**
```
GET /api/rank/health
```
**功能描述**
检查服务状态和数据库连接
**请求参数**
**响应示例**
```json
{
"success": true,
"message": "服务正常",
"data": {
"database": "连接正常",
"total_records": 1000,
"timestamp": "2025-10-17 15:30:00"
}
}
```
### 8. 获取榜单列表
**接口地址**
```
GET /api/rank/rankings
```
**功能描述**
获取榜单列表,支持按日期和类型查询,支持动态排序
**请求参数**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| date | string | 否 | 最新日期 | 日期格式YYYY-MM-DD |
| type | string | 否 | - | 榜单类型playcount(播放量榜) / growth(增长榜) / newcomer(新晋榜) |
| sort_by | string | 否 | default | 排序方式default / play_vv_change / play_vv_change_rate / play_vv |
| sort_order | string | 否 | desc | 排序顺序asc / desc |
| page | int | 否 | 1 | 页码 |
| limit | int | 否 | 50 | 每页数量 |
**使用示例**
```
# 获取最新日期的所有榜单
GET /api/rank/rankings
# 获取指定日期的播放量榜
GET /api/rank/rankings?date=2025-01-17&type=playcount
# 按播放量变化排序
GET /api/rank/rankings?sort_by=play_vv_change&sort_order=desc
# 分页获取增长榜
GET /api/rank/rankings?type=growth&page=1&limit=20
```
**响应示例**
```json
{
"success": true,
"message": "获取榜单成功",
"data": {
"rankings": [
{
"date": "2025-01-17",
"ranking_type": "playcount",
"ranking_name": "播放量榜",
"description": "按播放量排序的榜单",
"data": [
{
"_id": "674f1234567890abcdef",
"mix_name": "热门合集1",
"play_vv": 120000000,
"rank": 1,
"timeline_data": {
"play_vv_change": 5000000,
"play_vv_change_rate": 4.35
}
}
],
"total_count": 100,
"current_page_count": 20,
"generated_at": "2025-01-17 15:30:00",
"version": "1.0",
"sort_info": {
"sort_by": "default",
"sort_order": "desc"
}
}
],
"total": 1,
"page": 1,
"limit": 50,
"sort_by": "default",
"sort_order": "desc"
}
}
```
### 9. 获取可用榜单日期
**接口地址**
```
GET /api/rank/rankings/dates
```
**功能描述**
获取所有可用的榜单日期列表
**请求参数**
**响应示例**
```json
{
"success": true,
"message": "获取日期列表成功",
"data": {
"dates": [
"2025-01-17",
"2025-01-16",
"2025-01-15"
],
"total": 3
}
}
```
### 10. 获取榜单类型
**接口地址**
```
GET /api/rank/rankings/types
```
**功能描述**
获取支持的榜单类型及其说明
**请求参数**
**响应示例**
```json
{
"success": true,
"message": "获取榜单类型成功",
"data": {
"types": [
{
"type": "playcount",
"description": "播放量榜 - 按播放量排序"
},
{
"type": "growth",
"description": "增长榜 - 播放量增长最快"
},
{
"type": "newcomer",
"description": "新晋榜 - 新上榜内容"
}
],
"total": 3
}
}
```
### 11. 获取最新榜单
**接口地址**
```
GET /api/rank/rankings/latest
```
**功能描述**
获取最新日期的所有类型榜单每个榜单只返回前20条数据
**请求参数**
**响应示例**
```json
{
"success": true,
"message": "获取最新榜单成功",
"data": {
"date": "2025-01-17",
"rankings": [
{
"ranking_type": "playcount",
"ranking_name": "播放量榜",
"description": "按播放量排序的榜单",
"data": [
{
"_id": "674f1234567890abcdef",
"mix_name": "热门合集1",
"play_vv": 120000000,
"rank": 1
}
],
"total_count": 100,
"preview_count": 20
}
],
"total_types": 3
}
}
```
### 12. 获取榜单统计
**接口地址**
```
GET /api/rank/rankings/stats
```
**功能描述**
获取榜单系统的统计信息
**请求参数**
**响应示例**
```json
{
"success": true,
"message": "获取榜单统计成功",
"data": {
"total_rankings": 150,
"total_dates": 30,
"total_types": 3,
"latest_date": "2025-01-17",
"earliest_date": "2024-12-18",
"date_range": "2024-12-18 至 2025-01-17"
}
}
```
## 错误处理
### 通用错误格式
```json
{
"success": false,
"message": "错误描述",
"update_time": "2025-10-17 15:30:00"
}
```
### 常见错误
- `数据库连接失败`MongoDB连接异常
- `未找到合集信息`:查询的合集不存在
- `未找到文章信息`:查询的文章不存在
- `请提供搜索关键词`:搜索接口缺少关键词参数
- `获取数据失败`:数据查询异常
## 文章管理接口
### 1. 获取文章列表
**接口地址**
```
GET /api/article/list
```
**功能描述**
获取文章列表,支持分页和排序
**请求参数**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| page | int | 否 | 1 | 页码 |
| limit | int | 否 | 10 | 每页数量 |
| sort | string | 否 | created_at | 排序字段 |
| order | string | 否 | desc | 排序方向 (asc/desc) |
**响应示例**
```json
{
"success": true,
"data": [
{
"_id": "68f3ad112ba085e5d676537e",
"title": "对于ai影视化的思考",
"author_id": "test_user_1",
"cover_image": "",
"status": "draft",
"summary": "AI影视化的发展趋势和思考",
"created_at": "2025-10-18 15:06:57",
"likes": [],
"likes_count": 0
}
],
"pagination": {
"current_page": 1,
"total_pages": 1,
"total_items": 1,
"has_next": false,
"has_prev": false
},
"message": "获取文章列表成功",
"update_time": "2025-10-18 15:06:57"
}
```
### 2. 搜索文章
**接口地址**
```
GET /api/article/search
```
**功能描述**
根据关键词搜索文章
**请求参数**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| q | string | 是 | - | 搜索关键词 |
| page | int | 否 | 1 | 页码 |
| limit | int | 否 | 10 | 每页数量 |
**响应示例**
```json
{
"success": true,
"data": [
{
"_id": "68f3ad112ba085e5d676537e",
"title": "对于ai影视化的思考",
"author_id": "test_user_1",
"cover_image": "",
"status": "draft",
"summary": "AI影视化的发展趋势和思考",
"created_at": "2025-10-18 15:06:57",
"likes": [],
"likes_count": 0
}
],
"pagination": {
"current_page": 1,
"total_pages": 1,
"total_items": 1,
"has_next": false,
"has_prev": false
},
"message": "搜索文章成功",
"update_time": "2025-10-18 15:06:57"
}
```
### 3. 获取文章详情
**接口地址**
```
GET /api/article/detail
```
**功能描述**
根据文章ID获取文章详细信息
**请求参数**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| id | string | 是 | - | 文章ID |
**响应示例**
```json
{
"success": true,
"data": {
"_id": "68f3ad112ba085e5d676537e",
"title": "对于ai影视化的思考",
"content": "# AI影视化的思考\n\n在2025年AI技术在影视行业的应用...",
"author_id": "test_user_1",
"cover_image": "",
"status": "draft",
"summary": "AI影视化的发展趋势和思考",
"created_at": "2025-10-18 15:06:57",
"likes": [],
"likes_count": 0
},
"message": "获取文章详情成功",
"update_time": "2025-10-18 15:06:57"
}
```
### 4. 获取文章统计信息
**接口地址**
```
GET /api/article/stats
```
**功能描述**
获取文章相关的统计信息
**请求参数**
**响应示例**
```json
{
"success": true,
"data": {
"total_articles": 1,
"latest_update": "2025-10-18 15:06:57",
"status_count": {
"draft": 1,
"published": 0,
"archived": 0
}
},
"message": "获取文章统计成功",
"update_time": "2025-10-18 15:06:57"
}
```
### 5. 文章服务健康检查
**接口地址**
```
GET /api/article/health
```
**功能描述**
检查文章服务和数据库连接状态
**请求参数**
**响应示例**
```json
{
"success": true,
"data": {
"status": "服务正常",
"database": "连接正常",
"article_count": 1
},
"message": "文章服务健康检查通过",
"update_time": "2025-10-18 15:06:57"
}
```
## 小程序使用建议
### 1. 分页加载
推荐使用分页加载,避免一次性加载过多数据:
```javascript
// 小程序端示例
wx.request({
url: 'http://localhost:5000/api/rank/videos',
data: {
page: 1,
limit: 20,
sort: 'playcount'
},
success: (res) => {
if (res.data.success) {
this.setData({
videos: res.data.data,
hasNext: res.data.pagination.has_next
})
}
}
})
```
### 2. 搜索优化
- 使用防抖处理搜索请求
- 显示搜索进度和结果数量
- 提供搜索建议
### 3. 图片加载
- 使用 `cover_image_url` 作为封面图片
- 添加图片加载失败处理
### 4. 数据更新
- 注意 `update_time` 字段,判断数据新鲜度
- 合理使用缓存策略
- 定期检查服务健康状态
### 5. 文章管理
推荐的文章列表加载方式:
```javascript
// 小程序端示例 - 文章列表
wx.request({
url: 'http://localhost:5001/api/article/list',
data: {
page: 1,
limit: 10,
sort: 'created_at',
order: 'desc'
},
success: (res) => {
if (res.data.success) {
this.setData({
articles: res.data.data,
hasNext: res.data.pagination.has_next
})
}
}
})
```
文章搜索示例:
```javascript
// 小程序端示例 - 文章搜索
wx.request({
url: 'http://localhost:5001/api/article/search',
data: {
q: 'AI',
page: 1,
limit: 5
},
success: (res) => {
if (res.data.success) {
this.setData({
searchResults: res.data.data
})
}
}
})
```
## 部署说明
### 启动服务
```bash
cd /Users/binghuixiong/剧变AI/rank_backend
python3 app.py
```
### 服务信息
- 端口5001
- 数据库MongoDB (localhost:27017)
- 数据库名kemeng_media
- 数据更新每晚24:00自动执行
### 注意事项
- 确保MongoDB服务已启动
- 确保网络连接正常
- 小程序端需要配置合法域名(生产环境)
---
**文档版本**v4.1
**最后更新**2025-01-18
**维护者**:系统自动生成
**更新内容**优化增长排名接口数据源策略现在专门使用Ranking_storage预计算数据提升查询性能和数据一致性

View File

@ -0,0 +1 @@
VITE_API_BASE_URL=http://localhost:8443/api

1
frontend/.env.production Normal file
View File

@ -0,0 +1 @@
VITE_API_BASE_URL=http://159.75.150.210:8443/api

30
frontend/.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?
*.tsbuildinfo

38
frontend/README.md Normal file
View File

@ -0,0 +1,38 @@
# .
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[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/)
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```

20
frontend/index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<title>Vite App</title>
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

8
frontend/jsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

4328
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
frontend/package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "rank",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.12.2",
"bootstrap": "^5.3.0-alpha1",
"bootstrap-icons": "^1.13.1",
"vue": "^3.5.22",
"vue-router": "^4.6.3"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.1",
"vite": "^7.1.7",
"vite-plugin-vue-devtools": "^8.0.2"
}
}

Some files were not shown because too many files have changed in this diff Show More