Claude Skill

playlet-bili-feed

B站短剧爆款内容追踪工具,每日自动扫描B站短剧创作内容,按点赞量筛选爆款作品,智能聚类题材后生成可视化HTML日报与创作趋势分析。⚠️查询前脚本先做输入校验:关键词需命中短剧题材词库(topic_keywords 中规定的题材名+全部相关词,如「打脸」命中逆袭题材相关词),命中后直接使用该关键词查询数据;不满足时提醒'关键词不满足查询条件'并推荐相关词,且不发起接口请求。数据每日15:00更新前一天数据,目标日期无数据时必须先告知用户并等待确认后才能调用接口,禁止自动获取。当用户需要查询B站短剧爆款日报、分析短剧题材趋势、查看热门达人表现或生成短剧创作趋

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download redfox-data-redfox-community-skills_playlet-bili-feed-5e7b435.zip · 29 KB
Part of redfox-data/redfox-community — 66 skills

Install

skills CLI npx skills add https://github.com/redfox-data/redfox-community/tree/main/skills/playlet-bili-feed
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install redfox-data-redfox-community@llmmart
Git git clone https://github.com/redfox-data/redfox-community.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole redfox-data/redfox-community collection as a plugin from our marketplace. Git is the plain clone.

README

短剧-B站信息源 / 短剧-B站信息源


简介

B站短剧爆款内容追踪工具,每日自动扫描B站短剧创作内容,按点赞量筛选爆款作品,智能聚类题材后生成可视化HTML日报与创作趋势分析。

核心价值

  • 📊 每日爆款榜单:自动扫描B站短剧热门内容,按点赞量精准筛选高热度短剧作品
  • 🏷️ 题材智能聚类:自动识别热门题材方向(穿越/霸总/重生/悬疑等),每日题材分类由内容动态决定
  • 📈 创作趋势分析:深度分析爆款标题特征、达人表现,挖掘短剧创作规律
  • 📄 可视化日报:一键生成深色主题HTML日报,封面图懒加载、BV号直链跳转

适用对象

  • 📝 短剧创作者 — 精准把握B站短剧流量风口,用数据指导创作方向,提升作品曝光概率
  • 🏢 MCN 机构 — 追踪旗下达人短剧内容表现,优化运营策略,及时发现潜力达人
  • 📊 内容运营人员 — 持续追踪B站短剧赛道趋势,数据驱动内容决策,降低试错成本

功能特性

核心功能

  • 爆款发现:从B站短剧中按点赞量筛选热门内容,精准定位高热度短剧作品
  • 题材聚类:自动识别题材方向(穿越/霸总/重生/悬疑/甜宠/逆袭等),每日分类由内容动态决定
  • 智能查询:默认查询全部短剧内容,数据不足时自动扩展题材批量查询,高效节省接口额度
  • 自定义查询:支持指定任意题材、达人、关键词定向查询,灵活覆盖细分方向
  • 创作洞察:分析爆款标题特征、题材趋势、达人表现,深度挖掘创作规律
  • 可视化日报:深色主题HTML日报,卡片式布局,展示封面图、互动数据与B站直链
  • 一键订阅:开启每日自动产出,日报自动保存到本地文件夹

特色亮点

  • ⚡ 智能日期判断:内置15:00更新规则,自动拦截无效查询,避免浪费接口额度
  • 🎨 B站风格日报:深色主题 + B站蓝(#00A1D6),封面图懒加载,BV号直链跳转
  • 📊 零值隐藏:互动指标为0时自动隐藏,日报信息更清爽
  • 🔄 批量查询:所有题材通过一次批量接口获取,避免逐个调用浪费额度

密钥获取与安全说明

  • 本技能需要使用环境变量:REDFOX_API_KEY。
  • REDFOX_API_KEY 由 红狐 hub (https://redfox.hk)提供。
  • 请前往 红狐 hub 注册账号,获取 REDFOX_API_KEY。
  • 配置设备环境变量 REDFOX_API_KEY 后使用本技能。
  • 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。
  • 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。

使用指南

直接用自然语言描述需求,无需记忆命令。

常用说法速查

意图 示例话术 效果
获取最新日报 「查一下最新的B站短剧日报」 自动判断日期可用性,生成最新一日爆款日报与趋势分析
查询历史日报 「查一下2026-06-10的B站短剧数据」 生成指定日期的短剧爆款日报
定向题材查询 「我想看穿越题材的B站短剧爆款」 按穿越题材精准查询,输出同赛道爆款分析
多题材对比 「查穿越和霸总题材的短剧日报」 批量查询多个题材,对比不同赛道的表现差异

输出示例

日报生成后,你将在终端看到结构化分析报告(题材概览 + 趋势分析 + 达人榜),同时浏览器自动打开深色主题HTML日报页面:

  • 题材概览:各题材作品数量、占比与爆款亮点一览表
  • 趋势分析:新兴起量信号、爆款标题特征模式、核心达人榜
  • 题材报告:TOP 3 热门题材的详细特征与创作建议
  • HTML 日报:保存在本地 ~/Downloads/QoderReports/,卡片式布局,点击标题可直接跳转B站视频

使用场景

场景 角色 示例问法 收益
选题参考 短剧编剧/导演 「最近B站短剧什么题材比较火?」 了解当前热门题材和爆款趋势,指导创作方向
内容运营 MCN 运营人员 「帮我订阅B站短剧日报,每天自动看」 持续追踪赛道动态,及时发现潜力达人
竞品监测 短剧制作公司 「查一下穿越题材最近的爆款表现」 分析竞品爆款的标题特征和互动规律,降低试错成本
趋势研判 内容策划/投流 「帮我对比穿越和霸总两个赛道的数据」 数据驱动内容投资决策,发现题材融合趋势

重要数据说明

  • 数据每日 15:00 更新前一天的数据
  • 15:00 前最新可用日期为前天(T-2),15:00 后为昨天(T-1)
  • 查询最新数据时,系统会自动跳过无数据区间,不消耗接口额度
  • 历史日期数据已固化,可直接查询,无需等待确认

Skill manifest

短剧-B站信息源

简介

B站短剧爆款内容追踪工具,每日自动扫描B站短剧创作内容,按点赞量筛选爆款作品,智能聚类题材后生成可视化HTML日报与创作趋势分析。

通过数据驱动的方式,帮助短剧创作者、MCN机构和内容运营人员精准把握B站短剧流量风口。

你可以:

  • 📊 每日获取B站短剧爆款榜单
  • 🏷️ 自动识别热门题材方向(穿越/霸总/重生/悬疑等)
  • 📈 深度分析爆款标题特征与达人表现
  • 📄 一键生成深色主题HTML可视化日报

适用于短剧创作者、MCN机构、内容运营人员等需要追踪B站短剧趋势的场景。

重要:数据每日15:00更新前一天数据(实际可能延迟,以脚本真实探活为准)。查询前脚本先做输入校验:关键词需命中短剧题材词库(topic_keywords 中规定的题材名+全部相关词,如「打脸」命中逆袭题材相关词),命中后直接使用该关键词查询数据;不符合时不请求任何接口,直接提醒"关键词不满足查询条件"并推荐相关词。无数据确认制:查询无匹配数据或全量数据不足时,禁止自动发起任何额外查询(禁止自动降级全量、禁止自动扩展题材),先展示推荐题材关键词并询问用户,用户确认后才可发起查询;日期超出有效查询范围时提醒并自动回退最近有数据日期,无需用户确认。

功能特性

🎯 核心功能

功能模块 能力描述 核心价值
爆款发现 从B站短剧中按点赞量筛选热门内容 精准定位高热度短剧作品
题材聚类 自动识别题材方向(穿越/霸总/重生/悬疑等) 每天题材分类由内容动态决定
智能查询 默认查询全部短剧,关键词命中词库(题材名+相关词)直接查询,数据不足时提示推荐题材等待确认 节省接口额度,高效获取数据
自定义查询 用户可指定任意题材/达人/关键词定向查询 灵活覆盖任意短剧细分方向
创作洞察 分析爆款标题特征、题材趋势、达人表现 深度挖掘创作规律
可视化日报 深色主题HTML,封面图+互动数据+作品直链 直观展示每日短剧热点
一键订阅 --subscribe 开启每日自动产出 日报自动攒在本地文件夹

✨ 特色亮点

  • ⚡ 探活式日期预检:调用前先用轻量请求(无keyword, pageSize=1)真实探测目标日期是否有数据,替代纯本地时钟推断,自动拦截无效查询,避免浪费API额度
  • 🧭 前置输入校验:查询前先判断用户的分类/关键词是否符合短剧题材词库(题材名+全部相关词,命中后直接使用该关键词查询数据)、日期是否在有效查询范围;全部不满足时不请求任何接口,直接提醒"关键词不满足查询条件"并推荐相关分类和关键词;混合词保留有效关键词查询
  • 🔄 自动回退:--latest 自动向前回退最多7天,找到最近有数据的日期再出日报,彻底告别"查到空就报错"
  • ⏸️ 无数据确认制:查询无匹配数据或全量数据不足时,不自动扩展题材、不自动降级全量,提示推荐题材关键词并等待用户确认后再查询
  • 🧠 结构化空因:空结果时明确输出原因(数据源无数据/关键词无匹配/接口异常)并给出下一步建议
  • 🎨 B站风格日报:深色主题 + B站蓝(#00A1D6),封面图懒加载,BV号直链跳转
  • 📊 零值隐藏:互动指标为0时自动隐藏,日报信息更清爽

一键安装

前置条件

  • Python 3 运行环境
  • 已注册红狐Hub账号并获取 API Key

API Key 获取

前往 红狐Hub 官网 注册,登录后在个人中心获取,格式为 ak_xxxxxxxx。新注册用户获赠免费积分。

环境变量配置

数据查询接口通过请求头 X-API-KEY 鉴权,Key 从环境变量 REDFOX_API_KEY 获取。

变量名 必填 说明
REDFOX_API_KEY 是 红狐Hub API 访问密钥,格式 ak_xxxxxxxx

配置方式:

  • macOS/Linux:将 export REDFOX_API_KEY=<值> 追加到 ~/.zshrc 或 ~/.bashrc,然后 source 使其生效
  • Windows:[Environment]::SetEnvironmentVariable("REDFOX_API_KEY", "<值>", "User")(需重启终端)
  • 配置后验证:echo $REDFOX_API_KEY(macOS/Linux)或 echo %REDFOX_API_KEY%(Windows)

接口调用时通过 source 字段(值为 短剧B站信息源-GitHub)同步记录来源,无需额外请求保存接口。

使用指南

详细执行流程、字段映射、HTML规则、指标展示规则见 core_workflow.md

基础使用

1. 日期预检(每次查询前自动执行)

  • 前置校验(v2.2):先判断日期是否在有效查询范围——格式必须为 YYYY-MM-DD、不能晚于今天;超出范围时提醒"日期超出有效查询范围"并推荐最近可用日期
  • 估算起点:15:00前最新可用日期 = T-2(前天),15:00后 = T-1(昨天)——仅作初始起点
  • 真实探活:脚本会用 pageSize=1 不带 keyword 的轻量请求实际探测目标日期是否有数据,以接口返回为准(数据源实际更新时间可能晚于15:00)
  • 自动兜底(v2.1):用户查询的日期未更新或超过查询时间范围时,脚本自动向前回退获取最近时间范围数据,并明确告知用户"当前查询时间未更新或超过查询时间范围,已为您自动获取最近时间范围数据",无需人工确认
  • --latest 模式同样自动向前回退(最多7天)定位最近有数据的日期
  • 若回退 7 天内均无数据,提示用户稍后再试或联系数据源确认更新状态

2. 生成爆款日报

用户:查一下最新的B站短剧日报

助手:检查日期可用性 → 执行脚本生成日报 → 输出趋势分析

# 生成最新一期日报(用户确认后,自动跳过无数据日期,不扣积分)
python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --latest

# 生成指定日期日报(历史日期已有数据,无需确认)
python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --date 2026-06-10

查询策略:默认查询全部短剧(pageSize=200),数据不足(<100条)时自动追加热门题材(穿越→霸总→重生→悬疑→甜宠→逆袭),所有题材通过批量接口一次性查询。查询前自动探活目标日期,空结果自动重试1次。

3. 创作趋势分析

日报生成后,必须基于聚类结果自动执行创作趋势分析:

  1. 读取题材聚类结果,选取 TOP 5 热门题材
  2. 分析每个题材的爆款数量、平均互动数据、头部作品特征
  3. 识别新兴起量题材(数量少但互动高)
  4. 输出结构化创作趋势报告(格式见下方输出模板)

高级使用

自定义题材查询

用户可指定任意题材组合进行定向查询与分析:

# 查询穿越题材热门短剧
python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --topics "穿越,时空,重生"

# 查询霸总/甜宠题材
python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --topics "霸总,甜宠,总裁,虐恋"

# 查询悬疑/反转题材
python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --topics "悬疑,推理,反转,惊悚"

自定义查询逻辑:

  • 用户提供的所有题材通过批量接口一次性查询,无需逐个调用
  • 查询结果自动去重,题材聚类、趋势分析均基于查询结果生成
  • 前置校验(v2.2):查询前先判断关键词/分类是否符合短剧题材词库(穿越/霸总/重生/甜宠/悬疑/逆袭/年代/战神/古装等)。不满足时明确提醒"关键词不满足短剧查询条件",并推荐相关分类和关键词(优先从无效词中提取题材词,再补热门题材)
  • 无效条件零请求(v2.3):关键词/分类全部不符合短剧题材词库时,不请求任何接口(含探活),直接提示并推荐后停止;混合词场景保留有效关键词继续查询,无效词自动忽略
  • 关键词兜底(v2.1):符合题材词但查询后无匹配数据时,脚本自动降级为全量查询获取数据,并明确告知用户"关键词未匹配,已为您自动获取全量数据",无需人工确认

输出格式(强制执行)

⛔ 严格执行规则:

  • 以下模板是唯一合法输出格式,禁止任何自由发挥、省略、简化或重新组织
  • 禁止输出模板中未定义的额外内容(如"我来帮你…""以下是…"等口语化文字)
  • 禁止合并、跳过任何板块,即使某板块数据为"暂无"也必须保留该板块标题
  • 日报生成后,对话回复只能包含以下内容,不得包含其他任何文字
  • 禁止在日报末尾追加"📝 写一篇完整执行计划?"等任何执行计划类引导提醒(2026-08-18 用户明确要求移除)

每次运行日报后,对话输出必须严格按以下模板原样输出(仅替换 {...} 占位符):

## 短剧-B站信息源 · {日期} 日报

**扫描 {N} 部热门短剧,聚类 {M} 个题材方向**

---

### 题材概览

| 题材 | 数量 | 占比 | 爆款亮点 |
|------|------|------|---------|
| #{题材名} | {N}部 | {X}% | 头部作品亮点描述 |
| ... | ... | ... | ... |

---

### 创作趋势分析

**一、新兴起量信号**

- 🔥 **#{题材}** — 仅{N}部但均互动{X}+,描述
(若无新兴题材,输出:暂无新兴起量信号)

**二、爆款标题特征**

| 特征模式 | 出现次数 | 典型案例 | 平均互动 |
|---------|---------|---------|---------|
| {特征1} | {N}次 | 《{标题}》 | {X}w |
| ... | ... | ... | ... |
(若无标题数据,输出:暂无爆款标题数据)

**三、核心达人榜**

| 达人 | 作品数 | 总赞 | 代表作 |
|------|--------|------|--------|
| @{达人} | {N}部 | {X}w | 《{作品}》 |
| ... | ... | ... | ... |
(若无达人数据,输出:暂无核心达人数据)

**四、题材趋势报告**

**题材**:#{题材1}
**作品数**:{N}部
**平均点赞**:{X}w
**头部作品**:《{标题}》-{点赞}w

**题材特征**:{描述该题材的共性特征}
**创作建议**:{针对该题材的创作建议}

**五、#{题材2}**

(同上格式)

**六、#{题材3}**

(同上格式)

**七、跨题材对比建议**

- **{题材}** — 建议同步关注{相关题材}的联动创作,观察题材融合趋势
(若无建议,输出:暂无跨题材对比建议)

---

**日报地址**:{HTML文件绝对路径}

> 数据说明:每日15:00更新昨天的数据

以上格式为强制规范,所有字段不可省略,板块标题(一、二、三、四、五、六、七)必须保留。若某模块无数据则在该板块内标注"暂无",不得删除板块本身。

命令速查

命令/参数 功能 默认值
--latest 自动使用最新有数据的日期(向前回退最多7天,跳过无数据区间) —
--date YYYY-MM-DD 指定日期查询(超出有效范围时提醒+推荐最近日期,未更新时自动回退) 今天
--topics "关键词,..." 自定义题材查询,逗号分隔(全部不符合题材词时不请求接口直接提示+推荐;混合词保留有效词) 全量查询
--count N 扫描作品数量,满足即停 200
--start-time 自定义开始时间 YYYY-MM-DD HH:MM:SS —
--end-time 自定义结束时间 YYYY-MM-DD HH:MM:SS —
--output-dir 输出目录 ~/Downloads/QoderReports
--api-key 指定 API Key(覆盖环境变量) —
--subscribe 开启每日订阅 —
--unsubscribe 关闭每日订阅 —
--from-cache 使用缓存数据 —

使用场景

场景一:短剧创作者选题参考

角色:短剧编剧/导演

需求:了解当前B站短剧热门题材和爆款趋势,指导创作方向

使用方式:

  1. 每日查看短剧日报,关注题材聚类分布
  2. 分析爆款标题特征和互动数据
  3. 针对新兴起量题材提前布局内容

预期收益:精准把握流量风口,提升作品曝光概率


场景二:MCN 机构内容运营

角色:MCN 运营人员

需求:追踪旗下达人的短剧内容表现,优化运营策略

使用方式:

  1. 订阅每日日报,持续追踪B站短剧赛道
  2. 通过核心达人榜了解竞品表现
  3. 使用自定义题材查询定向分析关注领域

预期收益:提升内容运营效率,及时发现潜力达人


场景三:品牌方/制作方竞品监测

角色:短剧制作公司

需求:监测竞品短剧在B站的表现数据

使用方式:

  1. 定期查询目标题材日报
  2. 分析竞品爆款作品的标题特征和互动规律
  3. 结合跨题材对比建议探索融合创作

预期收益:数据驱动内容投资决策,降低试错成本

项目架构

目录结构

短剧-B站信息源/
├── SKILL.md                          # Skill主文档(本文件)
├── scripts/
│   └── playlet_bili_daily.py        # 核心脚本(v2.3.1:前置校验零请求/相关词命中直查/推荐/探活/自动回退/无数据确认制)
└── references/
    ├── core_workflow.md              # 核心执行流程、字段映射、HTML规则
    └── examples.md                   # 使用示例与常见用法组合

技术栈

项目 说明
运行环境 Python 3
开发语言 Python(脚本 playlet_bili_daily.py)
数据接口 HTTP 调用红狐Hub B站短剧API(queryPlayletMsgs,platform=6)
前端渲染 HTML 生成日报(B站蓝 #00A1D6 主题色,<img> 懒加载,BV号直链)
来源标识 source: "短剧B站信息源-GitHub"

数据流转

用户查询 → 前置输入校验(关键词命中题材词库-题材名+相关词) → 日期预检(真实探活) → API调用(queryPlayletMsgs) → 数据去重 → 题材聚类 → HTML日报生成 → 浏览器打开
              ↓(关键词/分类全部不满足)            ↓(日期未更新/超范围)       ↓(数据不足)           ↓(关键词无匹配)
       不请求接口,直接提示+推荐停止        自动回退最近有数据日期并告知  提示推荐题材等待确认(不自动扩展)  提示推荐题材等待确认(不自动降级全量)
                                              ↓
                                    终端输出趋势分析报告

常见问答

使用相关

Q1: 为什么查询"今天"的数据却提示未更新?

A: 数据每日15:00更新前一天的数据(实际更新时间可能延迟)。15:00前最新可用日期为前天,15:00后为昨天。无需手动处理——查询日期未更新或超过查询时间范围时,脚本会自动向前回退获取最近时间范围数据,并明确告知"当前查询时间未更新或超过查询时间范围,已为您自动获取最近时间范围数据";也可使用 --latest 直接定位最近有数据的日期。

Q2: 如何查询特定题材的短剧?

A: 使用 --topics 参数,多个题材用逗号分隔,如 --topics "穿越,霸总,重生"。所有题材批量一次性查询,无需逐个调用。查询前脚本会先校验关键词/分类:符合短剧题材词库(穿越/霸总/重生/悬疑/甜宠/逆袭等)才参与查询;全部不符合时不请求任何接口,直接提醒"关键词不满足短剧查询条件"并推荐相关分类和关键词,混合词场景保留有效关键词查询。

Q3: 日报生成在哪里?

A: 默认保存在 ~/Downloads/QoderReports/ 目录,文件名格式为 短剧B站日报_YYYY-MM-DD.html,生成后自动在浏览器打开。

Q4: 如何开启/关闭每日订阅?

A: 使用 --subscribe 开启每日自动产出,--unsubscribe 关闭。

故障排除

Q5: 脚本运行报 UnicodeEncodeError 怎么办?

A: Windows PowerShell 的 GBK 编码问题。执行前设置环境变量:$env:PYTHONIOENCODING='utf-8',然后重新运行脚本。

Q6: 提示"未找到 REDFOX_API_KEY 环境变量"?

A: 请按"一键安装"章节配置环境变量。Windows 用户配置后需重启终端才能生效。

Q7: HTML日报中图片加载不出来?

A: B站CDN(hdslb.com)无防盗链限制,通常不会出现此问题。若遇到请检查网络连接或图片URL是否过期。

B站短剧改造说明(从抖音版迁移)

Q8: B站版本与抖音版本有哪些差异?

A: 主要改造点如下:

改造项 抖音版本 B站版本
platform参数 1 6
主题色 #FB7299(粉) #00A1D6(蓝)
链接格式 douyin.com/video/ bilibili.com/video/{BV号}
文件名 短剧抖音日报 短剧B站日报
图片防盗链 需referrerpolicy 无需(B站CDN无限制)
展示指标 播放/点赞/评论 分享/点赞/评论(零值隐藏)
url字段 有效 None(用BV号拼接)

字段映射详情见 core_workflow.md

验证清单

  • 脚本语法正确(py_compile通过)
  • API接口地址已更新为B站
  • platform参数已改为6(B站)
  • photoId是BV号格式,用于拼接链接
  • readCount有真实数据,但不展示
  • shareCount展示为分享数
  • likeCount作为主排序指标
  • url字段为None,用BV号拼接bilibili.com链接
  • coverUrl为B站CDN(hdslb.com),无需referrerpolicy
  • HTML主题色改为B站蓝(#00A1D6)
  • 指标展示:分享/点赞/评论(零值隐藏)
  • 链接格式:bilibili.com/video/{BV号}
  • source字段:短剧B站信息源-GitHub

获取帮助

如有其他问题,可通过项目 GitHub 仓库提交 Issue。

📚 参考文档

  • core_workflow.md — 核心执行流程、字段映射、HTML规则、指标展示规则、与抖音差异对比
  • examples.md — 使用示例与常见用法组合
Files (redfox-community)
  • references
    • core_workflow.md 8.5 KB
      # 核心执行流程
      
      ## 第一步:日期有效性预检(必须执行,先于任何接口调用)
      
      > ⛔ **核心规则:未经用户确认,禁止调用任何数据接口,禁止自动执行 `--latest`**
      >
      > 注:`--latest` 允许在用户确认后自动回退;脚本层会先做**真实探活**(轻量请求),以接口返回为准判断日期是否有数据。
      
      ### 数据更新规则
      - 每日 **15:00** 更新前一天的数据(**实际更新时间可能延迟**,以真实探活结果为准)
      - **15:00前**:估算最新可用日期 = T-2(前天)
      - **15:00后**:估算最新可用日期 = T-1(昨天)
      
      ### 执行流程(每次查询前强制执行)
      
      1. 获取当前系统日期 T 和当前时间,按15:00规则**估算**最新可用日期(仅作初始起点)
      2. 用 `pageSize=1` 不带 keyword 的轻量请求**真实探测**目标日期是否有数据
      3. **若探活命中**(该日期有数据):直接执行查询,无需额外确认
      4. **若探活未命中**(该日期无数据),向用户输出以下提示:
      
      ```
      **⚠️{查询日期}数据尚未更新**
      数据更新规则:每日15:00更新前一天的数据
      当前可查询的最新日期:{最新可查询到数据的日期}
      
      是否需要查询{最新可查询到数据的日期}的数据?
      ```
      
      5. **等待用户明确确认后**,才能执行查询(带 `--latest` 参数,脚本会自动回退定位最近有数据的日期)
      6. 若用户拒绝,则不执行任何接口调用
      
      ### 示例对话
      
      ```
      用户:查询今天的短剧B站日报
      Agent:⚠️2026-06-16数据尚未更新(探活确认该日期无数据)
            数据更新规则:每日15:00更新前一天的数据
            当前可查询的最新日期:2026-06-14
      
            是否需要查询2026-06-14的数据?
      用户:好的
      Agent:(执行 python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --latest)
      ```
      
      ## 第二步:生成爆款日报
      
      ```bash
      # 生成最新一期日报(用户确认后,自动探测回退到最近有数据的日期,不扣积分)
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --latest
      
      # 生成指定日期日报(历史日期已有数据,无需确认)
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --date 2026-06-10
      
      # 自定义题材查询(用户指定方向,仅用用户题材列表批量查询)
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --topics "穿越,霸总,重生,悬疑" --latest
      
      # 订阅 / 取消订阅
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --subscribe
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --unsubscribe
      ```
      
      > **查询策略**:默认查询全部短剧内容(pageSize=200),所有题材通过批量接口一次性查询。用户自定义题材时仅使用用户提供的列表,同样批量查询。**关键词校验规则(v2.3.1)**:关键词需命中短剧题材词库(`topic_keywords` 中规定的**题材名+全部相关词**,如「打脸」命中逆袭题材相关词、「总裁」命中霸总题材相关词),**命中后直接使用该关键词查询数据**;不满足时提醒"关键词不满足查询条件"并推荐相关词,**不发起接口请求**。**无数据确认制**:查询无匹配数据或全量数据不足时,**禁止自动扩展题材/降级全量**,提示推荐题材关键词并等待用户确认后再查询。
      
      > **降失败率机制(v2.0增强)**:
      > - **真实探活**:查询前先用 `pageSize=1` 无 keyword 轻量请求探测目标日期,以接口返回为准(数据源实际更新可能晚于15:00),无数据立即拦截,避免空跑耗额度
      > - **自动回退**:`--latest` 从最近日期向前最多回退7天,定位第一个有数据的日期再出日报
      > - **空结果重试**:单题材查询为空/异常时自动重试1次(间隔4秒)
      > - **题材词校验**:`--topics` 传入非题材词时提示,建议改用题材词库(v2.3.1:校验匹配题材名+全部相关词,命中后直接查询该关键词)
      > - **结构化空因**:空结果明确输出"无数据/关键词无匹配/接口异常"及下一步建议
      
      ## 第三步:执行创作趋势分析
      
      日报生成后,**必须**基于聚类结果自动执行创作趋势分析:
      
      1. 读取题材聚类结果,选取 TOP 5 热门题材
      2. 分析每个题材的爆款数量、平均互动数据、头部作品特征
      3. 识别新兴起量题材(数量少但互动高)
      4. 输出结构化创作趋势报告
      
      生成的HTML日报保存在 `~/Downloads/QoderReports/`,自动浏览器打开。终端同步输出题材分类表格 + 创作趋势分析报告。
      
      ## 查询策略
      
      ### 默认查询
      - 查询全部短剧内容(pageSize=200)
      - 全量数据不足(<100条)时**不自动扩展题材**,提示推荐题材(穿越→霸总→重生→悬疑→甜宠→逆袭)并等待用户确认(v2.3.1 无数据确认制)
      - 查询前自动探活目标日期;空结果自动重试1次
      
      ### 自定义查询
      - 用户指定题材通过批量接口一次性查询(不使用自动扩展列表)
      - **关键词校验**:命中短剧题材词库(题材名+全部相关词,如「打脸」命中逆袭题材相关词)后**直接使用该关键词查询数据**
      - 查询结果自动去重(基于photoId)
      - 题材聚类基于标题关键词匹配
      - 非题材词(热点词/商品词等)给出提示,建议改用题材词库,避免空结果
      
      ## 字段映射(B站短剧特殊处理)
      
      ### API字段说明
      
      | 字段 | B站短剧API行为 | 处理方式 |
      |------|--------------|---------|
      | `readCount` | 有真实数据(播放量) | ✅ 正常获取,但不展示 |
      | `likeCount` | 真实点赞数 | ✅ 作为主排序指标 |
      | `commentCount` | 真实评论数 | ✅ 正常展示 |
      | `shareCount` | 真实分享数 | ✅ 正常展示 |
      | `url` | 恒为None | 用`photoId`(BV号)智能拼接 |
      | `photoId` | BV号格式(如BV1onEQ6sEiL) | 直接用于链接生成 |
      | `coverUrl` | 含`hdslb.com`(B站CDN) | 正常加载,无需防盗链 |
      
      ### 链接生成规则
      
      ```python
      # B站短剧API不返回url字段,使用BV号拼接视频链接
      photo_id = item.get("photoId") or ""  # BV号格式
      if photo_id:
          url = f"https://www.bilibili.com/video/{photo_id}"
          title_html = f'<a href="{url}" target="_blank" class="article-title">{title}</a>'
      else:
          title_html = f'<span class="article-title">{title}</span>'
      ```
      
      **说明**:API的url字段恒为None,作品链接通过BV号拼接B站视频地址。
      
      ### 图片加载规则
      
      ```html
      <!-- B站CDN(hdslb.com)无防盗链限制,无需设置referrerpolicy -->
      <img src="{coverUrl}" loading="lazy">
      ```
      
      ### 排序规则
      
      ```python
      # B站短剧统一按likeCount降序排序
      items.sort(key=lambda x: x.get("likeCount", 0), reverse=True)
      ```
      
      ## 指标展示规则
      
      ### 展示指标
      每个作品最多展示3项指标:
      - 🔗 分享(shareCount)
      - 👍 点赞(likeCount)
      - 💬 评论(commentCount)
      
      ### 零值隐藏规则
      **当某项指标值为0时,不展示该字段**。动态构建指标HTML:
      
      ```python
      # 获取原始数值
      raw_shares = item.get("shareCount", 0) or 0
      raw_likes = item.get("likeCount", 0) or 0
      raw_comments = item.get("commentCount", 0) or 0
      
      # 值为0时不展示该字段
      metrics_parts = []
      if raw_shares > 0:
          metrics_parts.append(f'<span class="metric">🔗 {format_number(raw_shares)}</span>')
      if raw_likes > 0:
          metrics_parts.append(f'<span class="metric">👍 {format_number(raw_likes)}</span>')
      if raw_comments > 0:
          metrics_parts.append(f'<span class="metric">💬 {format_number(raw_comments)}</span>')
      metrics_html = ''.join(metrics_parts)
      ```
      
      ## HTML输出格式
      
      ### 样式规范
      - **深色主题**:`#1a1a1a` 背景
      - **B站主题色**:`#00A1D6`(替代抖音的 `#FB7299`)
      - **卡片式网格布局**:`grid-template-columns: repeat(auto-fill, minmax(360px, 1fr))`
      - **题材编号**:01/02/03...
      - **中文日期显示**:2026年6月16日 星期二
      - **图片懒加载**:`loading="lazy"`
      
      ### 与抖音版本的差异
      
      | 项目 | 抖音版本 | B站版本 |
      |------|---------|--------|
      | API接口 | queryPlayletMsgs | queryPlayletMsgs |
      | platform | 1 | 6 |
      | 排序字段 | likeCount | likeCount |
      | 播放量 | 可用(readCount) | 可用但不展示(readCount) |
      | 展示指标 | 播放/点赞/评论 | 分享/点赞/评论(零值隐藏) |
      | url字段 | 有效 | None(用BV号拼接) |
      | 内容来源 | 抖音原生 | B站原生 |
      | 链接格式 | douyin.com/video/{id} | bilibili.com/video/{BV号} |
      | 主题色 | #FB7299(粉) | #00A1D6(蓝) |
      | 文件名 | 短剧抖音日报 | 短剧B站日报 |
      | 图片防盗链 | 需referrerpolicy | 无需(B站CDN) |
      
    • examples.md 2.7 KB
      # 使用示例
      
      ## 基础用法
      
      ### 生成最新日报
      ```bash
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --latest
      ```
      
      ### 生成指定日期日报
      ```bash
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --date 2026-06-10
      ```
      
      ## 自定义题材查询
      
      ### 穿越题材
      ```bash
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --topics "穿越,时空,重生"
      ```
      
      ### 霸总题材
      ```bash
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --topics "霸总,总裁,豪门,宠妻"
      ```
      
      ### 悬疑题材
      ```bash
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --topics "悬疑,推理,反转,惊悚"
      ```
      
      ### 多题材组合
      ```bash
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --topics "穿越,霸总,重生,悬疑"
      ```
      
      ## 订阅管理
      
      ### 开启订阅
      ```bash
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --subscribe
      ```
      
      ### 关闭订阅
      ```bash
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --unsubscribe
      ```
      
      ## 高级参数
      
      ### 自定义时间范围
      ```bash
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" \
        --start-time "2026-06-10 00:00:00" \
        --end-time "2026-06-10 23:59:59"
      ```
      
      ### 指定扫描数量
      ```bash
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --count 100
      ```
      
      ### 使用缓存数据
      ```bash
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --from-cache
      ```
      
      ### 指定输出目录
      ```bash
      python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --output-dir "/path/to/output"
      ```
      
      ## 常见场景
      
      ### 场景1:每日例行查询
      ```
      用户:查询今天的短剧B站日报
      Agent:检查日期→确认数据可用性→执行--latest
      ```
      
      ### 场景2:历史日期查询
      ```
      用户:查询2026-06-10的短剧B站数据
      Agent:直接执行--date 2026-06-10(历史数据已有)
      ```
      
      ### 场景3:未来日期查询
      ```
      用户:查询明天的短剧B站数据
      Agent:提示数据未更新→建议查询最新可用日期→等待确认
      ```
      
      ### 场景4:定向题材查询
      ```
      用户:我想看穿越题材的短剧B站爆款
      Agent:执行--topics "穿越,时空,重生"
      ```
      
      ## 输出示例
      
      ### 终端输出
      ```
      ## 短剧-B站信息源 · 2026-06-14 日报
      
      **扫描 156 部热门短剧,聚类 8 个题材方向**
      
      ### 题材概览
      
      | 题材 | 数量 | 占比 | 爆款亮点 |
      |------|------|------|---------|
      | #穿越 | 45部 | 28.8% | 《回到大明当王爷》12.3w赞 |
      | #霸总 | 38部 | 24.4% | 《豪门替身妻》9.8w赞 |
      | #重生 | 32部 | 20.5% | 《重生之逆袭人生》8.5w赞 |
      | ... | ... | ... | ... |
      ```
      
      ### HTML日报
      - 文件位置:`~/Downloads/QoderReports/短剧B站日报_2026-06-14.html`
      - 自动浏览器打开
      - 深色主题+B站蓝色主题色
      - 卡片式布局+作品封面+互动数据
      - 作品标题可点击跳转到B站视频
      
  • scripts
    • playlet_bili_daily.py 33.5 KB
      #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      """
      短剧-B站信息源日报生成脚本 (增强版 v2.3)
      =========================================
      每日扫描B站短剧爆款内容,智能聚类题材后生成HTML日报
      
      v2.3 调整说明(无效条件不请求接口,直接提示):
      - 关键词/分类不符合短剧题材词库(全部无效)时,**不调用任何接口**(含探活),
        直接提示"关键词不满足短剧查询条件"并推荐相关分类和关键词后停止;
        混合词场景保留有效关键词查询(仅请求有效词)。
      - 校验时机提前到日期处理之前,确保无效时零接口请求。
      
      v2.3.1 调整说明(相关词命中直查 + 无数据确认制):
      - 【相关词命中直查】关键词校验匹配 topic_keywords 中规定的**题材名+全部相关词**
        (如「打脸」命中逆袭题材相关词、「总裁」命中霸总题材相关词、「宠妻/替身」命中
        霸总相关词等),命中后**直接使用该关键词查询数据**(不替换题材、不降级全量)。
      - 【无数据确认制】查询无匹配数据或全量数据不足时,**禁止自动发起任何额外查询**
        (禁止自动降级全量、禁止自动扩展题材)。脚本停止并输出推荐题材关键词,
        由 Agent 询问用户是否按推荐词重新查询,得到用户确认后才可发起查询。
      - 全量查询数据不足(小于 AUTO_EXPAND_THRESHOLD)时仅提示推荐题材并等待确认,不自动扩展。
      
      v2.2 调整说明(查询前输入校验+推荐):
      - 【前置校验】查询前先判断用户的输入条件:
        1) 分类/关键词是否符合短剧题材词库(穿越/霸总/重生/甜宠/悬疑等)
        2) 日期是否在有效查询范围(格式正确、不晚于今天)
      - 【提醒+推荐】关键词/分类不满足时,明确提醒"关键词不满足查询条件",
        并推荐相关的分类和关键词(优先从无效词中提取题材词,再补热门题材);
        无效关键词自动忽略,全部无效时自动降级全量查询(沿用v2.1兜底)。
      
      v2.1 调整说明(自动兜底+明确告知):
      - 【日期兜底】用户查询的日期未更新或超过查询时间范围时,不再停留等待确认,
        自动向前回退获取最近时间范围数据,并明确告知"当前查询时间未更新或超过查询
        时间范围,已为您自动获取最近时间范围数据"。
      - 【关键词兜底】用户指定的关键词/题材无匹配数据时,自动降级为全量查询获取
        数据,并明确告知"关键词未匹配,已为您自动获取全量数据"。
      
      v2.0 增强说明(针对"数据查询结果为空"问题的降失败率机制):
      
      背景实证:脚本解析逻辑本身正常(2026-08-11 实测成功缓存 200 条 B站数据,
      gmtCreate 符合 15:00 规则)。空结果主要来自:
        A. 数据源间歇性缺数/延迟(主因):无 keyword 全量查询也空、凌晨查 T-2 也空
        B. 关键词与短剧标题不匹配(次因):热点词/商品词搜短剧必然空
      
      本版增强(非 Bug 修复,是降失败率机制):
      1. 【P0-日期探活】查询前先用 pageSize=1、不带 keyword 的轻量请求探测目标日期
         是否有数据;无数据立即拦截,避免盲目消耗多题材查询额度。
      2. 【P0-自动回退】--latest 从最近日期向前最多回退 FALLBACK_DAYS(默认7) 天,
         找到第一个有数据的日期再生成日报;输出中明确标注实际数据日期。
      3. 【P1-确认制题材补充】全量查询数据不足(小于 AUTO_EXPAND_THRESHOLD)时,
         仅提示推荐题材并等待用户确认,确认前不自动扩展题材(v2.3.1 确认制)。
      4. 【P1-空结果重试】单题材查询为空/异常时,间隔 RETRY_INTERVAL 秒重试 1 次。
      5. 【P1-题材词校验】--topics 传入明显非题材词时给出提示(不阻断,仅提醒)。
      6. 【P2-结构化空因】每次空结果输出原因分类:无数据 / 关键词无匹配 / API异常。
      7. 【P2-防御式解析】兼容 {"code":2000,"data":{"list":[...]}} 与直出 list 两种
         格式,防止服务端调整响应结构时脚本静默失效(纯加固,非修复)。
      
      用法与原版完全兼容:
          python3 playlet_bili_daily.py --latest
          python3 playlet_bili_daily.py --date 2026-08-05
          python3 playlet_bili_daily.py --topics "穿越,霸总" --latest
      """
      
      import argparse
      import json
      import os
      import sys
      import time
      import webbrowser
      from datetime import datetime, timedelta
      from urllib import request, error
      
      
      # ============ 配置 ============
      API_BASE_URL = "https://redfox.hk/story/api/parseWork/queryPlayletMsgs"
      CACHE_DIR = os.path.expanduser("~/.workbuddy/cache")
      CACHE_FILE = os.path.join(CACHE_DIR, "playlet_bili_data.json")
      OUTPUT_DIR = os.path.expanduser("~/Downloads/QoderReports")
      DATA_UPDATE_HOUR = 15       # 数据源声称的更新时刻(仅作提示参考,不再作为唯一依据)
      FALLBACK_DAYS = 7           # --latest 自动回退的最大天数
      RETRY_TIMES = 1             # 空结果/异常重试次数
      RETRY_INTERVAL = 4          # 重试间隔(秒)
      REQUEST_TIMEOUT = 30        # 单次请求超时(秒)
      AUTO_EXPAND_THRESHOLD = 100 # 全量结果少于该值时自动扩展题材
      AUTO_EXPAND_TOPICS = ["穿越", "霸总", "重生", "悬疑", "甜宠", "逆袭"]  # 扩展顺序
      
      # 短剧题材词库:用于 --topics 输入校验提示
      # 规则:关键词需命中 topic_keywords 中规定的题材名+全部相关词(如「打脸」命中逆袭题材相关词),命中后直接用该关键词查询数据
      TOPIC_KEYWORDS_THESAURUS = {
          "穿越": ["穿越", "时空", "古代", "现代", "回到", "大宋", "北宋", "南宋", "唐朝", "明朝", "清朝"],
          "霸总": ["霸总", "总裁", "豪门", "冷酷", "宠妻", "娇妻", "替身"],
          "重生": ["重生", "逆袭", "回到", "翻盘", "重来", "再生"],
          "悬疑": ["悬疑", "推理", "反转", "惊悚", "谜案", "秘密", "真相"],
          "甜宠": ["甜宠", "恋爱", "撒糖", "甜蜜", "宠溺", "甜甜"],
          "逆袭": ["逆袭", "翻身", "打脸", "崛起", "反击", "报复"],
          "年代": ["年代", "八零", "九零", "七零", "六零"],
          "战神": ["战神", "龙王", "兵王", "高手"],
          "古装": ["古装", "宫廷", "皇后", "贵妃", "王爷", "世子"],
      }
      # 全部有效词集合(题材名 + 各题材相关词 + 扩展词),用于关键词前置校验
      TOPIC_THESAURUS = {
          "穿越", "霸总", "重生", "悬疑", "甜宠", "逆袭", "年代", "战神",
          "古装", "总裁", "豪门", "复仇", "惊悚", "推理", "反转", "爽文",
          "科幻", "玄幻", "修仙", "都市", "职场", "萌宝", "萌娃", "亲子",
          "离婚", "闪婚", "替身", "虐恋", "先婚后爱", "双重生",
      }
      for _t, _kws in TOPIC_KEYWORDS_THESAURUS.items():
          TOPIC_THESAURUS.add(_t)
          TOPIC_THESAURUS.update(_kws)
      
      
      # ============ 工具函数 ============
      def get_api_key():
          """从环境变量获取 API Key"""
          api_key = os.environ.get("REDFOX_API_KEY")
          if not api_key:
              print("❌ 错误:未找到 REDFOX_API_KEY 环境变量")
              print("请先配置:export REDFOX_API_KEY=<你的apikey>")
              sys.exit(1)
          return api_key
      
      
      def calculate_latest_date():
          """按15:00规则估算最新可用日期(仅作初始起点,实际以探活为准)"""
          now = datetime.now()
          if now.hour < DATA_UPDATE_HOUR:
              return (now - timedelta(days=2)).strftime("%Y-%m-%d")
          else:
              return (now - timedelta(days=1)).strftime("%Y-%m-%d")
      
      
      def validate_date(date_str):
          """旧接口保留:基于本地时钟的日期校验(新逻辑改走 probe_date)"""
          latest_date = calculate_latest_date()
          target_date = datetime.strptime(date_str, "%Y-%m-%d")
          latest = datetime.strptime(latest_date, "%Y-%m-%d")
          return target_date <= latest, latest_date
      
      
      def check_topics(topics):
          """
          v2.2 前置校验:判断用户输入的分类/关键词是否符合短剧题材词库。
          返回 (有效词列表, 无效词列表, 推荐词列表)
          推荐逻辑:优先从无效词中提取包含的题材词,再补充热门题材词。
          """
          hot_topics = ["穿越", "霸总", "重生", "甜宠", "悬疑", "逆袭",
                        "年代", "战神", "古装", "都市", "科幻"]
          valid, invalid = [], []
          for t in topics:
              if t in TOPIC_THESAURUS or t == "短剧":
                  valid.append(t)
              else:
                  invalid.append(t)
          recommends = []
          for t in invalid:
              # 无效词若包含题材词(如"穿越重生"含"穿越""重生"),优先推荐
              contained = [w for w in TOPIC_THESAURUS if w in t or t in w]
              for c in contained:
                  if c not in recommends:
                      recommends.append(c)
          for h in hot_topics:
              if h not in recommends:
                  recommends.append(h)
          return valid, invalid, recommends
      
      
      def check_date(date_str):
          """
          v2.2 前置校验:判断日期是否在有效查询范围(格式正确、不晚于今天)。
          返回 (是否有效, 提示信息, 推荐日期或None)
          """
          try:
              d = datetime.strptime(date_str, "%Y-%m-%d")
          except ValueError:
              return False, f"日期格式无效:{date_str}(应为 YYYY-MM-DD)", None
          today = datetime.now().date()
          if d.date() > today:
              latest = calculate_latest_date()
              return False, f"日期 {date_str} 超出有效查询范围(晚于今天,数据每日15:00更新前一天)", latest
          return True, "", None
      
      
      def parse_response(result):
          """
          防御式响应解析(加固,非修复):兼容两种格式
          格式A(API实际完整响应): {"code":2000,"data":{"list":[...],"total":M},"msg":"..."}
          格式B(兜底/直出):      {"list":[...], "pageNum":1, "pages":N, "total":M}
          返回: (items列表, error_msg或None)
          说明:脚本原始解析(code/data 包装)已实证工作正常;此处增加直出格式
          兜底,防止服务端调整响应结构时脚本静默失效。
          """
          if not isinstance(result, dict):
              return [], "响应非JSON对象"
          # 格式A:code/data 包装(当前 API 实际格式)
          if result.get("code") == 2000:
              data = result.get("data") or {}
              return data.get("list") or [], None
          # 显式业务错误
          code = result.get("code")
          if code is not None:
              msg = result.get("msg")
              return [], f"API业务错误 code={code} msg={msg}"
          # 格式B:直出 list(兜底)
          if "list" in result:
              return result.get("list") or [], None
          return [], None
      
      
      def http_post(payload, api_key):
          """执行一次 POST 请求,返回原始响应 dict(网络/HTTP 层异常向上抛)"""
          data = json.dumps(payload).encode('utf-8')
          req = request.Request(
              API_BASE_URL,
              data=data,
              headers={
                  "Content-Type": "application/json",
                  "X-API-KEY": api_key
              },
              method="POST"
          )
          with request.urlopen(req, timeout=REQUEST_TIMEOUT) as response:
              return json.loads(response.read().decode('utf-8'))
      
      
      def build_payload(start_time, end_time, keyword=None, page_size=200):
          """构建请求体;keyword 为 None 时表示全量查询(不带 keyword 字段)"""
          payload = {
              "msgType": "短剧",
              "platform": 6,  # 6=B站
              "source": "短剧B站信息源-GitHub",
              "pageNum": 1,
              "pageSize": page_size,
              "startTime": start_time,
              "endTime": end_time,
          }
          if keyword:
              payload["keyword"] = keyword
          return payload
      
      
      def probe_date_available(api_key, start_time, end_time):
          """
          探活:pageSize=1 + 不带 keyword 的轻量请求,确认该日期是否有数据。
          返回 (bool, info_str);False 说明该日期数据源无任何数据(未更新/缺失)。
          成本:每次探测约 1 次接口额度,远低于无脑全量查询。
          """
          payload = build_payload(start_time, end_time, keyword=None, page_size=1)
          try:
              result = http_post(payload, api_key)
              items, err = parse_response(result)
              if err:
                  print(f"  ⚠️ 探活请求异常: {err}")
                  return False, "probe_error"
              if items:
                  return True, "ok"
              return False, "no_data"
          except Exception as e:
              print(f"  ⚠️ 探活请求失败: {e}")
              return False, "probe_fail"
      
      
      def _fetch_topic_once(api_key, payload, topic):
          """单题材单次查询(含重试),返回 (items, api_error: bool)"""
          for attempt in range(RETRY_TIMES + 1):
              try:
                  result = http_post(payload, api_key)
                  items, err = parse_response(result)
                  if err:
                      if attempt < RETRY_TIMES:
                          time.sleep(RETRY_INTERVAL)
                      continue
                  return items, False  # 解析成功(可能为空 list,但非异常)
              except Exception as e:
                  if attempt < RETRY_TIMES:
                      print(f"  ⚠️ 题材 {topic} 第{attempt+1}次请求失败({e}),{RETRY_INTERVAL}s后重试...")
                      time.sleep(RETRY_INTERVAL)
                  else:
                      print(f"❌ 查询题材 {topic} 失败:{str(e)}")
          return [], True
      
      
      # ============ 数据获取 ============
      def fetch_playlet_data(
          topics=None,
          start_time=None,
          end_time=None,
          count=200,
          use_cache=False,
      ):
          """
          调用 API 查询B站短剧数据(增强版)
      
          Args:
              topics: 题材列表(逗号分隔),None/空 → 全量查询,数据不足时自动扩展题材
              start_time / end_time: 查询时间窗
              count: 扫描作品数量
              use_cache: 是否使用缓存
      
          Returns:
              (items, meta) 其中 meta 含 reason 字段用于结构化空因:
                  reason in {"ok", "no_data", "probe_fail", "probe_error",
                             "keyword_no_match", "api_error"}
          """
          if use_cache:
              cached_data = load_cache()
              if cached_data:
                  print("📦 使用缓存数据")
                  return cached_data, {"reason": "ok", "note": "cache"}
      
          if not start_time or not end_time:
              latest_date = calculate_latest_date()
              start_time = f"{latest_date} 00:00:00"
              end_time = f"{latest_date} 23:59:59"
      
          api_key = get_api_key()
          meta = {"reason": "ok", "probed": False, "date": start_time[:10]}
      
          # ---- P0-2 探活:先确认该日期数据源是否有数据 ----
          available, info = probe_date_available(api_key, start_time, end_time)
          meta["probed"] = True
          if not available:
              meta["reason"] = "no_data" if info == "no_data" else info
              print(f"📭 日期 {start_time[:10]} 数据源无数据({info}),跳过查询以避免浪费额度")
              return [], meta
      
          # ---- 确定查询题材序列 ----
          # 用户指定题材 → 仅用用户列表(文档规则:自定义时不用扩展列表)
          # 未指定 → 全量查询;数据不足时自动按 AUTO_EXPAND_TOPICS 扩展
          if topics:
              query_topics = list(topics)
              auto_expand = False
          else:
              query_topics = [None]
              auto_expand = True
      
          all_items = []
          api_errors = 0
          expanded = False
      
          for topic in query_topics:
              keyword = None if topic is None or topic == "短剧" else topic
              payload = build_payload(start_time, end_time, keyword=keyword,
                                      page_size=min(count, 200))
              items, had_error = _fetch_topic_once(api_key, payload, topic or "全量")
              if had_error:
                  api_errors += 1
              if items:
                  all_items.extend(items)
      
              # ---- 确认制(v2.3):全量数据不足时不再自动扩展题材 ----
              # 仅提示推荐题材并等待用户确认,确认前不发起任何额外请求
              if auto_expand:
                  unique_ids = {it.get("photoId") for it in all_items if it.get("photoId")}
                  if len(unique_ids) < min(count, AUTO_EXPAND_THRESHOLD):
                      expanded = True
                      print(f"  ⚠️ 全量数据不足({len(unique_ids)}条 < {AUTO_EXPAND_THRESHOLD}),不自动扩展题材")
                      print(f"  💡 推荐题材: {'、'.join(AUTO_EXPAND_TOPICS)}")
                      print(f"  ❓ 请确认是否按推荐题材扩展查询(使用 --topics 重新查询),确认前不会发起任何额外请求")
                      meta["reason"] = "need_confirm_expand"
      
          # 去重(基于photoId)
          seen = set()
          unique_items = []
          for item in all_items:
              item_id = item.get("photoId")
              if item_id and item_id not in seen:
                  seen.add(item_id)
                  unique_items.append(item)
      
          # 按点赞量排序
          unique_items.sort(key=lambda x: x.get("likeCount", 0), reverse=True)
      
          if not unique_items:
              # 探活通过但实际查询为空 → 大概率是关键词无匹配
              if topics and topics != [None]:
                  meta["reason"] = "keyword_no_match"
              else:
                  meta["reason"] = "no_data"
              return [], meta
      
          # 保存缓存
          save_cache(unique_items)
          meta["reason"] = "ok"
          return unique_items[:count], meta
      
      
      # ============ 题材聚类 ============
      def cluster_by_topic(items):
          """按题材聚类作品(保留原逻辑,词库与 TOPIC_KEYWORDS_THESAURUS 保持一致)"""
          topic_keywords = dict(TOPIC_KEYWORDS_THESAURUS)
          clusters = {}
          for item in items:
              title = item.get("title", "")
              matched_topics = []
              for topic, keywords in topic_keywords.items():
                  if any(kw in title for kw in keywords):
                      matched_topics.append(topic)
              matched_topic = matched_topics[0] if matched_topics else "其他"
              clusters.setdefault(matched_topic, []).append(item)
          return clusters
      
      
      # ============ HTML 日报 ============
      def format_number(num):
          """格式化数字(万→w)"""
          if num is None:
              return "0"
          if num >= 10000:
              return f"{num/10000:.1f}w"
          return str(num)
      
      
      def generate_html_report(items, clusters, date_str):
          """生成HTML日报(保留原样式,B站蓝 #00A1D6)"""
          os.makedirs(OUTPUT_DIR, exist_ok=True)
          html_file = os.path.join(OUTPUT_DIR, f"短剧B站日报_{date_str}.html")
      
          try:
              dt_obj = datetime.strptime(date_str, "%Y-%m-%d")
              weekdays = ["一", "二", "三", "四", "五", "六", "日"]
              date_cn = f"{dt_obj.year}年{dt_obj.month}月{dt_obj.day}日 星期{weekdays[dt_obj.weekday()]}"
          except ValueError:
              date_cn = date_str
      
          total_count = len(items)
          topic_count = len(clusters)
          total_likes = sum(item.get("likeCount", 0) for item in items)
          avg_likes = total_likes / total_count if total_count > 0 else 0
      
          category_cards = ""
          for i, (topic, topic_items) in enumerate(
                  sorted(clusters.items(), key=lambda x: len(x[1]), reverse=True), 1):
              articles_html = ""
              for item in topic_items[:5]:
                  title = item.get("title", "无标题")
                  author = item.get("userName", "")
                  cover = item.get("coverUrl") or ""
                  photo_id = item.get("photoId") or ""
      
                  raw_shares = item.get("shareCount", 0) or 0
                  raw_likes = item.get("likeCount", 0) or 0
                  raw_comments = item.get("commentCount", 0) or 0
      
                  metrics_parts = []
                  if raw_shares > 0:
                      metrics_parts.append(f'<span class="metric">🔗 {format_number(raw_shares)}</span>')
                  if raw_likes > 0:
                      metrics_parts.append(f'<span class="metric">👍 {format_number(raw_likes)}</span>')
                  if raw_comments > 0:
                      metrics_parts.append(f'<span class="metric">💬 {format_number(raw_comments)}</span>')
                  metrics_html = ''.join(metrics_parts)
      
                  cover_html = ""
                  if cover:
                      cover_html = f'<img class="article-cover" src="{cover}" alt="" loading="lazy">'
      
                  if photo_id:
                      url = f"https://www.bilibili.com/video/{photo_id}"
                      title_html = f'<a href="{url}" target="_blank" class="article-title">{title}</a>'
                  else:
                      title_html = f'<span class="article-title">{title}</span>'
      
                  articles_html += f'''
                      <div class="article-item">
                          {cover_html}
                          <div class="article-info">
                              {title_html}
                              <div class="article-meta">
                                  <span class="author">{author}</span>
                                  <span class="metrics">
                                      {metrics_html}
                                  </span>
                              </div>
                          </div>
                      </div>'''
      
              category_cards += f'''
              <div class="category-card reveal">
                  <div class="card-header">
                      <span class="card-number">{i:02d}</span>
                      <h3 class="card-category">#{topic}</h3>
                      <span class="card-count">{len(topic_items)} 部</span>
                  </div>
                  <div class="card-body">{articles_html}
                  </div>
              </div>'''
      
          timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
          html_content = f'''<!DOCTYPE html>
      <html lang="zh-CN">
      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>短剧-B站信息源 - {date_str}</title>
      <style>
      * {{ margin: 0; padding: 0; box-sizing: border-box; }}
      body {{ font-family: -apple-system, sans-serif; background: #1a1a1a; color: #e8e4df; padding: 2rem; }}
      .header {{ text-align: center; padding: 2rem 0; }}
      .header h1 {{ font-size: 2rem; color: #00A1D6; }}
      .header p {{ color: #9a9590; margin-top: 0.5rem; }}
      .stats {{ display: flex; justify-content: center; gap: 2rem; padding: 1rem; margin: 1rem 0; }}
      .stat-item {{ text-align: center; }}
      .stat-value {{ font-size: 1.5rem; font-weight: bold; color: #00A1D6; }}
      .stat-label {{ font-size: 0.8rem; color: #9a9590; }}
      .cards {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 1.5rem; max-width: 1200px; margin: 2rem auto; }}
      .category-card {{ background: #2d2d2d; border-radius: 12px; padding: 1.5rem; }}
      .card-header {{ display: flex; align-items: center; gap: 0.8rem; margin-bottom: 1rem; padding-bottom: 0.8rem; border-bottom: 1px solid #3d3d3d; }}
      .card-number {{ font-size: 1.5rem; font-weight: bold; color: #00A1D6; }}
      .card-category {{ flex: 1; font-size: 1.1rem; }}
      .card-count {{ color: #9a9590; font-size: 0.9rem; }}
      .article-item {{ padding: 0.6rem 0; border-bottom: 1px solid #3d3d3d; display: flex; gap: 0.8rem; }}
      .article-item:last-child {{ border-bottom: none; }}
      .article-cover {{ width: 60px; height: 60px; border-radius: 6px; object-fit: cover; flex-shrink: 0; }}
      .article-info {{ flex: 1; min-width: 0; }}
      .article-title {{ color: #e8e4df; font-size: 0.9rem; line-height: 1.4; display: block; }}
      .article-title:hover {{ color: #00A1D6; text-decoration: underline; cursor: pointer; }}
      a.article-title {{ text-decoration: none; }}
      a.article-title:hover {{ color: #00A1D6; text-decoration: underline; }}
      .article-meta {{ display: flex; justify-content: space-between; margin-top: 0.3rem; font-size: 0.75rem; color: #9a9590; }}
      .metrics {{ display: flex; gap: 0.8rem; }}
      .footer {{ text-align: center; padding: 2rem; color: #666; font-size: 0.8rem; }}
      .reveal {{ animation: fadeIn 0.5s ease-in; }}
      @keyframes fadeIn {{ from {{ opacity: 0; transform: translateY(20px); }} to {{ opacity: 1; transform: translateY(0); }} }}
      </style>
      </head>
      <body>
      <div class="header">
          <h1>📺 短剧-B站信息源</h1>
          <p>{date_cn} | 共 {total_count} 部热门短剧</p>
      </div>
      <div class="stats">
          <div class="stat-item"><div class="stat-value">{topic_count}</div><div class="stat-label">题材</div></div>
          <div class="stat-item"><div class="stat-value">{total_count}</div><div class="stat-label">短剧</div></div>
          <div class="stat-item"><div class="stat-value">{format_number(int(avg_likes))}</div><div class="stat-label">平均点赞</div></div>
          <div class="stat-item"><div class="stat-value">{format_number(total_likes)}</div><div class="stat-label">总点赞</div></div>
      </div>
      <div class="cards">{category_cards}</div>
      <div class="footer">Generated at {timestamp} by 短剧-B站信息源 Skill<br>数据说明:每日15:00更新前一天的数据 | 数据来源:红狐Hub</div>
      </body>
      </html>'''
      
          with open(html_file, 'w', encoding='utf-8') as f:
              f.write(html_content)
          return html_file
      
      
      # ============ 缓存 ============
      def load_cache():
          if not os.path.exists(CACHE_FILE):
              return None
          try:
              with open(CACHE_FILE, 'r', encoding='utf-8') as f:
                  cache_data = json.load(f)
                  if time.time() - cache_data.get("timestamp", 0) < 3600:
                      return cache_data.get("items")
          except Exception:
              pass
          return None
      
      
      def save_cache(items):
          os.makedirs(CACHE_DIR, exist_ok=True)
          cache_data = {"timestamp": time.time(), "items": items}
          try:
              with open(CACHE_FILE, 'w', encoding='utf-8') as f:
                  json.dump(cache_data, f, ensure_ascii=False, indent=2)
          except Exception:
              pass
      
      
      # ============ 主流程 ============
      def find_latest_available_date(api_key, max_fallback=FALLBACK_DAYS):
          """
          P0-2 自动回退:从最近日期开始向前探测,返回第一个有数据的日期。
          返回 (date_str, found: bool)
          """
          latest = calculate_latest_date()
          cursor = datetime.strptime(latest, "%Y-%m-%d")
          for i in range(max_fallback + 1):
              d = (cursor - timedelta(days=i)).strftime("%Y-%m-%d")
              print(f"  🔎 探测 {d} ...", end="")
              ok, info = probe_date_available(
                  api_key, f"{d} 00:00:00", f"{d} 23:59:59"
              )
              print(" 有数据" if ok else f" 无数据({info})")
              if ok:
                  return d, True
          return latest, False
      
      
      def main():
          global OUTPUT_DIR
          parser = argparse.ArgumentParser(description="短剧-B站信息源日报生成工具 (v2.3增强版)")
          parser.add_argument("--topics", type=str, help="题材关键词,逗号分隔;全部不符合短剧题材词时不请求接口直接提示")
          parser.add_argument("--count", type=int, default=200, help="扫描作品数量")
          parser.add_argument("--date", type=str, help="指定日期 YYYY-MM-DD;超出有效范围时提醒并自动回退最近有数据的日期")
          parser.add_argument("--start-time", type=str, help="开始时间 YYYY-MM-DD HH:MM:SS")
          parser.add_argument("--end-time", type=str, help="结束时间 YYYY-MM-DD HH:MM:SS")
          parser.add_argument("--latest", action="store_true", help="使用最新有数据的日期(自动回退)")
          parser.add_argument("--output-dir", type=str, default=OUTPUT_DIR, help="输出目录")
          parser.add_argument("--api-key", type=str, help="指定 API Key")
          parser.add_argument("--subscribe", action="store_true", help="开启每日订阅")
          parser.add_argument("--unsubscribe", action="store_true", help="关闭每日订阅")
          parser.add_argument("--from-cache", action="store_true", help="使用缓存数据")
          args = parser.parse_args()
      
          if args.subscribe:
              print("✅ 已开启每日订阅,日报将自动保存至:", OUTPUT_DIR)
              return
          if args.unsubscribe:
              print("✅ 已关闭每日订阅")
              return
      
          if args.output_dir:
              OUTPUT_DIR = args.output_dir
      
          api_key = args.api_key or get_api_key()
      
          # ---- v2.3 前置校验①:分类/关键词是否符合短剧题材词库(不满足时不请求任何接口)----
          topics = None
          if args.topics:
              raw_topics = [t.strip() for t in args.topics.split(",") if t.strip()]
              valid_topics, invalid_topics, recommends = check_topics(raw_topics)
              if invalid_topics:
                  print(f"⚠️ 关键词 {invalid_topics} 不满足短剧查询条件(短剧按题材/剧情词匹配标题,非短剧题材词无法查询)")
                  print(f"💡 推荐相关分类和关键词:{'、'.join(recommends[:10])}")
                  if valid_topics:
                      print(f"✅ 已保留有效关键词 {valid_topics} 继续查询,无效关键词已自动忽略")
                      topics = valid_topics
                  else:
                      print("🔇 所有关键词/分类均不满足短剧查询条件,本次未调用任何接口;请使用上述推荐词重新查询")
                      return
              else:
                  topics = valid_topics
      
          # ---- 确定查询日期 ----
          if args.start_time:
              start_time = args.start_time
              date_str = args.start_time[:10]
              end_time = args.end_time or f"{date_str} 23:59:59"
          elif args.latest:
              # P0-2 自动回退:探测最近有数据的日期
              print(f"🔎 --latest: 自动寻找最近有数据的日期(最多回退{FALLBACK_DAYS}天)...")
              date_str, found = find_latest_available_date(api_key)
              if not found:
                  print(f"📭 最近 {FALLBACK_DAYS} 天内均无数据,请稍后再试或联系数据源确认更新状态")
                  return
              start_time = f"{date_str} 00:00:00"
              end_time = f"{date_str} 23:59:59"
              print(f"✅ 已定位最新可用日期: {date_str}")
          elif args.date:
              date_str = args.date
              # v2.2: 前置校验——日期格式与有效查询范围
              date_ok, date_msg, date_suggest = check_date(date_str)
              if not date_ok:
                  print(f"⚠️ {date_msg}")
                  if date_suggest:
                      print(f"💡 推荐查询时间范围:{date_suggest}(已为您自动获取该时间范围数据)")
              # 探活式预检(替代纯本地时钟判断)
              ok, info = probe_date_available(api_key, f"{date_str} 00:00:00", f"{date_str} 23:59:59")
              if not ok:
                  # v2.1: 日期兜底——未更新/超范围时自动回退最近有数据的日期,不再等待确认
                  print(f"⚠️ 当前查询时间 {date_str} 未更新或超过查询时间范围,已为您自动获取最近时间范围数据...")
                  new_date, found = find_latest_available_date(api_key)
                  if not found:
                      print(f"📭 最近 {FALLBACK_DAYS} 天内均无数据,请稍后再试或联系数据源确认更新状态")
                      return
                  print(f"✅ 已为您自动获取最近时间范围数据: {new_date}(原查询 {date_str} 未更新或超过查询时间范围)")
                  date_str = new_date
              start_time = f"{date_str} 00:00:00"
              end_time = f"{date_str} 23:59:59"
          else:
              date_str = calculate_latest_date()
              start_time = f"{date_str} 00:00:00"
              end_time = f"{date_str} 23:59:59"
      
          print(f"🔍 正在查询 {date_str} 的B站短剧数据...")
      
          items, meta = fetch_playlet_data(
              topics=topics,
              start_time=start_time,
              end_time=end_time,
              count=args.count,
              use_cache=args.from_cache,
          )
      
          if not items:
              reason = meta.get("reason", "unknown")
              if reason == "keyword_no_match":
                  # v2.1: 关键词兜底——无匹配时自动降级为全量查询,不再停留等待确认
                  print(f"⚠️ 关键词 {topics} 未匹配到相关数据,已为您自动获取全量短剧数据...")
                  items, meta2 = fetch_playlet_data(
                      topics=None,
                      start_time=start_time,
                      end_time=end_time,
                      count=args.count,
                      use_cache=args.from_cache,
                  )
                  if items:
                      print(f"✅ 关键词 {topics} 无匹配,已为您自动获取全量数据 {len(items)} 部")
                  else:
                      hint2 = {
                          "no_data": "数据源当日无数据(未更新或缺失)",
                          "probe_fail": "探测请求失败(网络/接口异常)",
                          "probe_error": "探测请求异常(接口返回异常)",
                          "keyword_no_match": "查询条件(题材词)在该日期无匹配作品",
                          "api_error": "接口调用异常",
                      }.get(meta2.get("reason", "unknown"), "未知原因")
                      print(f"📭 未查询到相关数据 [原因: {hint2}]")
                      if meta2.get("reason") == "no_data":
                          print("💡 建议: 使用 --latest 自动回退到最近有数据的日期")
                      return
              else:
                  hint = {
                      "no_data": "数据源当日无数据(未更新或缺失)",
                      "probe_fail": "探测请求失败(网络/接口异常)",
                      "probe_error": "探测请求异常(接口返回异常)",
                      "keyword_no_match": "查询条件(题材词)在该日期无匹配作品",
                      "api_error": "接口调用异常",
                  }.get(reason, "未知原因")
                  print(f"📭 未查询到相关数据 [原因: {hint}]")
                  if reason == "no_data":
                      print("💡 建议: 使用 --latest 自动回退到最近有数据的日期")
                  return
      
          print(f"✅ 共获取 {len(items)} 部短剧作品")
      
          clusters = cluster_by_topic(items)
          print(f"📊 聚类为 {len(clusters)} 个题材方向")
      
          html_file = generate_html_report(items, clusters, date_str)
          print(f"📄 日报已生成:{html_file}")
      
          webbrowser.open(f"file://{html_file}")
      
          print(f"\n## 短剧-B站信息源 · {date_str} 日报\n")
          print(f"**扫描 {len(items)} 部热门短剧,聚类 {len(clusters)} 个题材方向**\n")
          print("### 题材概览\n")
          print("| 题材 | 数量 | 占比 | 爆款亮点 |")
          print("|------|------|------|---------|")
          for topic, topic_items in sorted(clusters.items(), key=lambda x: len(x[1]), reverse=True):
              top_item = topic_items[0] if topic_items else {}
              print(f"| #{topic} | {len(topic_items)}部 | {len(topic_items)/len(items)*100:.1f}% | 《{top_item.get('title', '')[:20]}》{format_number(top_item.get('likeCount', 0))}赞 |")
      
      
      if __name__ == "__main__":
          main()
      
  • README.en.md 6.3 KB
    # Bilibili Short Drama Info Source / 短剧-B站信息源
    
    ---
    
    ## Introduction
    
    A Bilibili short drama viral content tracking tool that automatically scans Bilibili short drama creations daily, filters viral works by likes, intelligently clusters themes, and generates visual HTML daily reports with creative trend analysis.
    
    **Core Value**
    
    - 📊 **Daily Viral Rankings**: Automatically scan trending short dramas on Bilibili and precisely filter high-engagement works by likes
    - 🏷️ **Intelligent Theme Clustering**: Automatically identify trending theme directions (time-travel / CEO romance / rebirth / suspense, etc.), with daily classifications dynamically determined by content
    - 📈 **Creative Trend Analysis**: In-depth analysis of viral title patterns and creator performance to uncover short drama creation patterns
    - 📄 **Visual Daily Report**: One-click generation of dark-themed HTML reports with lazy-loaded cover images and BV-number direct links
    
    **Target Users**
    
    - 📝 **Short Drama Creators** — Precisely capture Bilibili short drama traffic trends, use data to guide creative direction and boost content visibility
    - 🏢 **MCN Agencies** — Track affiliated creators' short drama performance, optimize operational strategies, and discover rising talent
    - 📊 **Content Operators** — Continuously monitor Bilibili short drama trends, make data-driven content decisions, and reduce trial-and-error costs
    
    ---
    
    ## Features
    
    ### Core Features
    
    - **Viral Discovery**: Filter trending content from Bilibili short dramas by likes, precisely targeting high-engagement works
    - **Theme Clustering**: Automatically identify theme directions (time-travel / CEO romance / rebirth / suspense / sweet romance / comeback, etc.), with daily classifications dynamically determined by content
    - **Smart Querying**: Query all short drama content by default, automatically expand theme batch queries when data is insufficient, efficiently saving API quota
    - **Custom Querying**: Support targeted queries by any theme, creator, or keyword for flexible coverage of niche directions
    - **Creative Insights**: Analyze viral title patterns, theme trends, and creator performance for deep creative pattern mining
    - **Visual Daily Report**: Dark-themed HTML report with card layout, displaying cover images, engagement data, and Bilibili direct links
    - **One-Click Subscription**: Enable daily automatic output with `--subscribe`, reports auto-saved to local folder
    
    ### Highlights
    
    - ⚡ **Smart Date Detection**: Built-in 15:00 update rule, automatically blocks invalid queries to avoid wasting API quota
    - 🎨 **Bilibili-Style Report**: Dark theme + Bilibili blue (#00A1D6), lazy-loaded cover images, BV-number direct link navigation
    - 📊 **Zero-Value Hiding**: Engagement metrics at zero are automatically hidden for a cleaner report
    - 🔄 **Batch Querying**: All themes retrieved in a single batch API call, avoiding wasteful per-topic requests
    
    ---
    
    ## API Key Acquisition & Security
    
    - This skill requires the environment variable: `REDFOX_API_KEY`.
    - `REDFOX_API_KEY` is provided by [RedFoxHub](https://redfox.hk/settings/api-keys?source=github) (`https://redfox.hk`).
    - Please visit [RedFoxHub](https://redfox.hk?source=github) to register an account and obtain your `REDFOX_API_KEY`.
    - Configure the device environment variable `REDFOX_API_KEY` before using this skill.
    - Before providing your key, verify its source, available scope, validity period, and whether reset/revocation is supported.
    - Never hardcode or expose the key in plain text within code, prompts, logs, or output files.
    
    ---
    
    ## Usage Guide
    
    Simply describe your needs in natural language — no commands to memorize.
    
    ### Quick Reference
    
    | Intent | Example Prompt | Result |
    |--------|---------------|--------|
    | Get latest report | "Check the latest Bilibili short drama daily report" | Automatically determines date availability and generates the latest viral daily report with trend analysis |
    | Query historical report | "Check Bilibili short drama data for 2026-06-10" | Generates the short drama viral report for the specified date |
    | Targeted theme query | "I want to see time-travel themed Bilibili short drama viral hits" | Precisely queries time-travel theme and outputs same-category viral analysis |
    | Multi-theme comparison | "Check the daily report for time-travel and CEO romance short dramas" | Batch queries multiple themes and compares performance across categories |
    
    ### Output Example
    
    After report generation, you'll see a structured analysis report in the terminal (theme overview + trend analysis + top creators), while the dark-themed HTML report automatically opens in your browser:
    
    - **Theme Overview**: Summary table of works count, share, and viral highlights per theme
    - **Trend Analysis**: Emerging growth signals, viral title pattern features, top creator leaderboard
    - **Theme Reports**: Detailed characteristics and creative suggestions for the TOP 3 trending themes
    - **HTML Report**: Saved locally at `~/Downloads/QoderReports/`, card layout with clickable titles linking directly to Bilibili videos
    
    ---
    
    ## Use Cases
    
    | Scenario | Role | Example Prompt | Benefit |
    |----------|------|---------------|---------|
    | Topic Inspiration | Screenwriter / Director | "What short drama themes are trending on Bilibili lately?" | Understand current trending themes and viral patterns to guide creative direction |
    | Content Operations | MCN Operator | "Subscribe me to the Bilibili short drama daily report" | Continuously track category dynamics and discover rising creators |
    | Competitive Monitoring | Production Company | "Check the recent viral performance of time-travel themed short dramas" | Analyze competitor viral title patterns and engagement trends to reduce trial-and-error costs |
    | Trend Research | Content Strategist | "Compare data between time-travel and CEO romance categories" | Data-driven content investment decisions and identify theme fusion trends |
    
    ---
    
    ## Important Data Notes
    
    - Data is updated daily at **15:00** for the previous day's data
    - Before 15:00, the latest available date is the day before yesterday (T-2); after 15:00, it is yesterday (T-1)
    - When querying the latest data, the system automatically skips periods with no data without consuming API quota
    - Historical date data is fixed and can be queried directly without confirmation
    
    ---
    
  • README.md 5 KB
    # 短剧-B站信息源 / 短剧-B站信息源
    
    ---
    
    ## 简介
    
    B站短剧爆款内容追踪工具,每日自动扫描B站短剧创作内容,按点赞量筛选爆款作品,智能聚类题材后生成可视化HTML日报与创作趋势分析。
    
    **核心价值**
    
    - 📊 **每日爆款榜单**:自动扫描B站短剧热门内容,按点赞量精准筛选高热度短剧作品
    - 🏷️ **题材智能聚类**:自动识别热门题材方向(穿越/霸总/重生/悬疑等),每日题材分类由内容动态决定
    - 📈 **创作趋势分析**:深度分析爆款标题特征、达人表现,挖掘短剧创作规律
    - 📄 **可视化日报**:一键生成深色主题HTML日报,封面图懒加载、BV号直链跳转
    
    **适用对象**
    
    - 📝 **短剧创作者** — 精准把握B站短剧流量风口,用数据指导创作方向,提升作品曝光概率
    - 🏢 **MCN 机构** — 追踪旗下达人短剧内容表现,优化运营策略,及时发现潜力达人
    - 📊 **内容运营人员** — 持续追踪B站短剧赛道趋势,数据驱动内容决策,降低试错成本
    
    ---
    
    ## 功能特性
    
    ### 核心功能
    
    - **爆款发现**:从B站短剧中按点赞量筛选热门内容,精准定位高热度短剧作品
    - **题材聚类**:自动识别题材方向(穿越/霸总/重生/悬疑/甜宠/逆袭等),每日分类由内容动态决定
    - **智能查询**:默认查询全部短剧内容,数据不足时自动扩展题材批量查询,高效节省接口额度
    - **自定义查询**:支持指定任意题材、达人、关键词定向查询,灵活覆盖细分方向
    - **创作洞察**:分析爆款标题特征、题材趋势、达人表现,深度挖掘创作规律
    - **可视化日报**:深色主题HTML日报,卡片式布局,展示封面图、互动数据与B站直链
    - **一键订阅**:开启每日自动产出,日报自动保存到本地文件夹
    
    ### 特色亮点
    
    - ⚡ **智能日期判断**:内置15:00更新规则,自动拦截无效查询,避免浪费接口额度
    - 🎨 **B站风格日报**:深色主题 + B站蓝(#00A1D6),封面图懒加载,BV号直链跳转
    - 📊 **零值隐藏**:互动指标为0时自动隐藏,日报信息更清爽
    - 🔄 **批量查询**:所有题材通过一次批量接口获取,避免逐个调用浪费额度
    
    ---
    
    ## 密钥获取与安全说明
    
    - 本技能需要使用环境变量:`REDFOX_API_KEY`。
    - `REDFOX_API_KEY` 由 [红狐 hub](https://redfox.hk/settings/api-keys?source=github) (`https://redfox.hk`)提供。
    - 请前往 [红狐 hub](https://redfox.hk?source=github) 注册账号,获取 `REDFOX_API_KEY`。
    - 配置设备环境变量 `REDFOX_API_KEY` 后使用本技能。
    - 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。
    - 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。
    
    ---
    
    ## 使用指南
    
    直接用自然语言描述需求,无需记忆命令。
    
    ### 常用说法速查
    
    | 意图 | 示例话术 | 效果 |
    |------|---------|------|
    | 获取最新日报 | 「查一下最新的B站短剧日报」 | 自动判断日期可用性,生成最新一日爆款日报与趋势分析 |
    | 查询历史日报 | 「查一下2026-06-10的B站短剧数据」 | 生成指定日期的短剧爆款日报 |
    | 定向题材查询 | 「我想看穿越题材的B站短剧爆款」 | 按穿越题材精准查询,输出同赛道爆款分析 |
    | 多题材对比 | 「查穿越和霸总题材的短剧日报」 | 批量查询多个题材,对比不同赛道的表现差异 |
    
    ### 输出示例
    
    日报生成后,你将在终端看到结构化分析报告(题材概览 + 趋势分析 + 达人榜),同时浏览器自动打开深色主题HTML日报页面:
    
    - **题材概览**:各题材作品数量、占比与爆款亮点一览表
    - **趋势分析**:新兴起量信号、爆款标题特征模式、核心达人榜
    - **题材报告**:TOP 3 热门题材的详细特征与创作建议
    - **HTML 日报**:保存在本地 `~/Downloads/QoderReports/`,卡片式布局,点击标题可直接跳转B站视频
    
    ---
    
    ## 使用场景
    
    | 场景 | 角色 | 示例问法 | 收益 |
    |------|------|---------|------|
    | 选题参考 | 短剧编剧/导演 | 「最近B站短剧什么题材比较火?」 | 了解当前热门题材和爆款趋势,指导创作方向 |
    | 内容运营 | MCN 运营人员 | 「帮我订阅B站短剧日报,每天自动看」 | 持续追踪赛道动态,及时发现潜力达人 |
    | 竞品监测 | 短剧制作公司 | 「查一下穿越题材最近的爆款表现」 | 分析竞品爆款的标题特征和互动规律,降低试错成本 |
    | 趋势研判 | 内容策划/投流 | 「帮我对比穿越和霸总两个赛道的数据」 | 数据驱动内容投资决策,发现题材融合趋势 |
    
    ---
    
    ## 重要数据说明
    
    - 数据每日 **15:00** 更新前一天的数据
    - 15:00 前最新可用日期为前天(T-2),15:00 后为昨天(T-1)
    - 查询最新数据时,系统会自动跳过无数据区间,不消耗接口额度
    - 历史日期数据已固化,可直接查询,无需等待确认
    
    ---
    
  • SKILL.md 19 KB
    ---
    name: playlet-bili-feed
    description: B站短剧爆款内容追踪工具,每日自动扫描B站短剧创作内容,按点赞量筛选爆款作品,智能聚类题材后生成可视化HTML日报与创作趋势分析。⚠️查询前脚本先做输入校验:关键词需命中短剧题材词库(topic_keywords 中规定的题材名+全部相关词,如「打脸」命中逆袭题材相关词),命中后直接使用该关键词查询数据;不满足时提醒'关键词不满足查询条件'并推荐相关词,且不发起接口请求。数据每日15:00更新前一天数据,目标日期无数据时必须先告知用户并等待确认后才能调用接口,禁止自动获取。当用户需要查询B站短剧爆款日报、分析短剧题材趋势、查看热门达人表现或生成短剧创作趋势报告时使用。
    ---
    
    # 短剧-B站信息源
    
    ## 简介
    
    B站短剧爆款内容追踪工具,每日自动扫描B站短剧创作内容,按点赞量筛选爆款作品,智能聚类题材后生成可视化HTML日报与创作趋势分析。
    
    通过数据驱动的方式,帮助短剧创作者、MCN机构和内容运营人员精准把握B站短剧流量风口。
    
    你可以:
    - 📊 每日获取B站短剧爆款榜单
    - 🏷️ 自动识别热门题材方向(穿越/霸总/重生/悬疑等)
    - 📈 深度分析爆款标题特征与达人表现
    - 📄 一键生成深色主题HTML可视化日报
    
    适用于短剧创作者、MCN机构、内容运营人员等需要追踪B站短剧趋势的场景。
    
    > **重要**:数据每日15:00更新前一天数据(实际可能延迟,以脚本真实探活为准)。查询前脚本先做输入校验:关键词需命中短剧题材词库(`topic_keywords` 中规定的**题材名+全部相关词**,如「打脸」命中逆袭题材相关词),**命中后直接使用该关键词查询数据**;不符合时**不请求任何接口**,直接提醒"关键词不满足查询条件"并推荐相关词。**无数据确认制**:查询无匹配数据或全量数据不足时,禁止自动发起任何额外查询(禁止自动降级全量、禁止自动扩展题材),先展示推荐题材关键词并询问用户,用户确认后才可发起查询;日期超出有效查询范围时提醒并自动回退最近有数据日期,无需用户确认。
    
    ## 功能特性
    
    ### 🎯 核心功能
    
    | 功能模块 | 能力描述 | 核心价值 |
    |---------|---------|----------|
    | 爆款发现 | 从B站短剧中按点赞量筛选热门内容 | 精准定位高热度短剧作品 |
    | 题材聚类 | 自动识别题材方向(穿越/霸总/重生/悬疑等) | 每天题材分类由内容动态决定 |
    | 智能查询 | 默认查询全部短剧,关键词命中词库(题材名+相关词)直接查询,数据不足时提示推荐题材等待确认 | 节省接口额度,高效获取数据 |
    | 自定义查询 | 用户可指定任意题材/达人/关键词定向查询 | 灵活覆盖任意短剧细分方向 |
    | 创作洞察 | 分析爆款标题特征、题材趋势、达人表现 | 深度挖掘创作规律 |
    | 可视化日报 | 深色主题HTML,封面图+互动数据+作品直链 | 直观展示每日短剧热点 |
    | 一键订阅 | `--subscribe` 开启每日自动产出 | 日报自动攒在本地文件夹 |
    
    ### ✨ 特色亮点
    
    - **⚡ 探活式日期预检**:调用前先用轻量请求(无keyword, pageSize=1)真实探测目标日期是否有数据,替代纯本地时钟推断,自动拦截无效查询,避免浪费API额度
    - **🧭 前置输入校验**:查询前先判断用户的分类/关键词是否符合短剧题材词库(题材名+全部相关词,命中后**直接使用该关键词查询数据**)、日期是否在有效查询范围;全部不满足时**不请求任何接口**,直接提醒"关键词不满足查询条件"并推荐相关分类和关键词;混合词保留有效关键词查询
    - **🔄 自动回退**:`--latest` 自动向前回退最多7天,找到最近有数据的日期再出日报,彻底告别"查到空就报错"
    - **⏸️ 无数据确认制**:查询无匹配数据或全量数据不足时,**不自动扩展题材、不自动降级全量**,提示推荐题材关键词并等待用户确认后再查询
    - **🧠 结构化空因**:空结果时明确输出原因(数据源无数据/关键词无匹配/接口异常)并给出下一步建议
    - **🎨 B站风格日报**:深色主题 + B站蓝(#00A1D6),封面图懒加载,BV号直链跳转
    - **📊 零值隐藏**:互动指标为0时自动隐藏,日报信息更清爽
    
    ## 一键安装
    
    ### 前置条件
    
    - Python 3 运行环境
    - 已注册红狐Hub账号并获取 API Key
    
    ### API Key 获取
    
    前往 [红狐Hub 官网](https://redfox.hk?source=github) 注册,登录后在个人中心获取,格式为 `ak_xxxxxxxx`。新注册用户获赠免费积分。
    
    ### 环境变量配置
    
    数据查询接口通过请求头 `X-API-KEY` 鉴权,Key 从环境变量 `REDFOX_API_KEY` 获取。
    
    | 变量名 | 必填 | 说明 |
    |--------|------|------|
    | `REDFOX_API_KEY` | 是 | 红狐Hub API 访问密钥,格式 `ak_xxxxxxxx` |
    
    **配置方式**:
    
    - **macOS/Linux**:将 `export REDFOX_API_KEY=<值>` 追加到 `~/.zshrc` 或 `~/.bashrc`,然后 `source` 使其生效
    - **Windows**:`[Environment]::SetEnvironmentVariable("REDFOX_API_KEY", "<值>", "User")`(需重启终端)
    - 配置后验证:`echo $REDFOX_API_KEY`(macOS/Linux)或 `echo %REDFOX_API_KEY%`(Windows)
    
    > 接口调用时通过 `source` 字段(值为 `短剧B站信息源-GitHub`)同步记录来源,无需额外请求保存接口。
    
    ## 使用指南
    
    > **详细执行流程、字段映射、HTML规则、指标展示规则**见 [core_workflow.md](references/core_workflow.md)
    
    ### 基础使用
    
    #### 1. 日期预检(每次查询前自动执行)
    
    - **前置校验(v2.2)**:先判断日期是否在有效查询范围——格式必须为 YYYY-MM-DD、不能晚于今天;超出范围时提醒"日期超出有效查询范围"并推荐最近可用日期
    - **估算起点**:15:00前最新可用日期 = T-2(前天),15:00后 = T-1(昨天)——仅作初始起点
    - **真实探活**:脚本会用 `pageSize=1` 不带 keyword 的轻量请求**实际探测**目标日期是否有数据,以接口返回为准(数据源实际更新时间可能晚于15:00)
    - **自动兜底(v2.1)**:用户查询的日期未更新或超过查询时间范围时,脚本自动向前回退获取最近时间范围数据,并明确告知用户"当前查询时间未更新或超过查询时间范围,已为您自动获取最近时间范围数据",无需人工确认
    - `--latest` 模式同样自动向前回退(最多7天)定位最近有数据的日期
    - 若回退 7 天内均无数据,提示用户稍后再试或联系数据源确认更新状态
    
    #### 2. 生成爆款日报
    
    > 用户:查一下最新的B站短剧日报
    >
    > 助手:检查日期可用性 → 执行脚本生成日报 → 输出趋势分析
    
    ```bash
    # 生成最新一期日报(用户确认后,自动跳过无数据日期,不扣积分)
    python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --latest
    
    # 生成指定日期日报(历史日期已有数据,无需确认)
    python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --date 2026-06-10
    ```
    
    > **查询策略**:默认查询全部短剧(pageSize=200),数据不足(<100条)时自动追加热门题材(穿越→霸总→重生→悬疑→甜宠→逆袭),所有题材通过批量接口一次性查询。查询前自动探活目标日期,空结果自动重试1次。
    
    #### 3. 创作趋势分析
    
    日报生成后,**必须**基于聚类结果自动执行创作趋势分析:
    
    1. 读取题材聚类结果,选取 TOP 5 热门题材
    2. 分析每个题材的爆款数量、平均互动数据、头部作品特征
    3. 识别新兴起量题材(数量少但互动高)
    4. 输出结构化创作趋势报告(格式见下方输出模板)
    
    ### 高级使用
    
    #### 自定义题材查询
    
    用户可指定任意题材组合进行定向查询与分析:
    
    ```bash
    # 查询穿越题材热门短剧
    python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --topics "穿越,时空,重生"
    
    # 查询霸总/甜宠题材
    python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --topics "霸总,甜宠,总裁,虐恋"
    
    # 查询悬疑/反转题材
    python3 "$SKILL_PATH/scripts/playlet_bili_daily.py" --topics "悬疑,推理,反转,惊悚"
    ```
    
    **自定义查询逻辑**:
    - 用户提供的所有题材通过批量接口一次性查询,无需逐个调用
    - 查询结果自动去重,题材聚类、趋势分析均基于查询结果生成
    - **前置校验(v2.2)**:查询前先判断关键词/分类是否符合短剧题材词库(穿越/霸总/重生/甜宠/悬疑/逆袭/年代/战神/古装等)。不满足时明确提醒"关键词不满足短剧查询条件",并**推荐相关分类和关键词**(优先从无效词中提取题材词,再补热门题材)
    - **无效条件零请求(v2.3)**:关键词/分类全部不符合短剧题材词库时,**不请求任何接口**(含探活),直接提示并推荐后停止;混合词场景保留有效关键词继续查询,无效词自动忽略
    - **关键词兜底(v2.1)**:符合题材词但查询后无匹配数据时,脚本自动降级为全量查询获取数据,并明确告知用户"关键词未匹配,已为您自动获取全量数据",无需人工确认
    
    ### 输出格式(强制执行)
    
    > ⛔ **严格执行规则**:
    > - 以下模板是**唯一合法输出格式**,禁止任何自由发挥、省略、简化或重新组织
    > - 禁止输出模板中未定义的额外内容(如"我来帮你…""以下是…"等口语化文字)
    > - 禁止合并、跳过任何板块,即使某板块数据为"暂无"也必须保留该板块标题
    > - 日报生成后,对话回复**只能**包含以下内容,不得包含其他任何文字
    > - **禁止**在日报末尾追加"📝 写一篇完整执行计划?"等任何执行计划类引导提醒(2026-08-18 用户明确要求移除)
    
    每次运行日报后,对话输出**必须严格**按以下模板原样输出(仅替换 `{...}` 占位符):
    
    ```
    ## 短剧-B站信息源 · {日期} 日报
    
    **扫描 {N} 部热门短剧,聚类 {M} 个题材方向**
    
    ---
    
    ### 题材概览
    
    | 题材 | 数量 | 占比 | 爆款亮点 |
    |------|------|------|---------|
    | #{题材名} | {N}部 | {X}% | 头部作品亮点描述 |
    | ... | ... | ... | ... |
    
    ---
    
    ### 创作趋势分析
    
    **一、新兴起量信号**
    
    - 🔥 **#{题材}** — 仅{N}部但均互动{X}+,描述
    (若无新兴题材,输出:暂无新兴起量信号)
    
    **二、爆款标题特征**
    
    | 特征模式 | 出现次数 | 典型案例 | 平均互动 |
    |---------|---------|---------|---------|
    | {特征1} | {N}次 | 《{标题}》 | {X}w |
    | ... | ... | ... | ... |
    (若无标题数据,输出:暂无爆款标题数据)
    
    **三、核心达人榜**
    
    | 达人 | 作品数 | 总赞 | 代表作 |
    |------|--------|------|--------|
    | @{达人} | {N}部 | {X}w | 《{作品}》 |
    | ... | ... | ... | ... |
    (若无达人数据,输出:暂无核心达人数据)
    
    **四、题材趋势报告**
    
    **题材**:#{题材1}
    **作品数**:{N}部
    **平均点赞**:{X}w
    **头部作品**:《{标题}》-{点赞}w
    
    **题材特征**:{描述该题材的共性特征}
    **创作建议**:{针对该题材的创作建议}
    
    **五、#{题材2}**
    
    (同上格式)
    
    **六、#{题材3}**
    
    (同上格式)
    
    **七、跨题材对比建议**
    
    - **{题材}** — 建议同步关注{相关题材}的联动创作,观察题材融合趋势
    (若无建议,输出:暂无跨题材对比建议)
    
    ---
    
    **日报地址**:{HTML文件绝对路径}
    
    > 数据说明:每日15:00更新昨天的数据
    ```
    
    > 以上格式为**强制规范**,所有字段不可省略,板块标题(一、二、三、四、五、六、七)必须保留。若某模块无数据则在该板块内标注"暂无",不得删除板块本身。
    
    ### 命令速查
    
    | 命令/参数 | 功能 | 默认值 |
    |----------|------|--------|
    | `--latest` | 自动使用最新有数据的日期(向前回退最多7天,跳过无数据区间) | — |
    | `--date YYYY-MM-DD` | 指定日期查询(超出有效范围时提醒+推荐最近日期,未更新时自动回退) | 今天 |
    | `--topics "关键词,..."` | 自定义题材查询,逗号分隔(全部不符合题材词时不请求接口直接提示+推荐;混合词保留有效词) | 全量查询 |
    | `--count N` | 扫描作品数量,满足即停 | `200` |
    | `--start-time` | 自定义开始时间 YYYY-MM-DD HH:MM:SS | — |
    | `--end-time` | 自定义结束时间 YYYY-MM-DD HH:MM:SS | — |
    | `--output-dir` | 输出目录 | `~/Downloads/QoderReports` |
    | `--api-key` | 指定 API Key(覆盖环境变量) | — |
    | `--subscribe` | 开启每日订阅 | — |
    | `--unsubscribe` | 关闭每日订阅 | — |
    | `--from-cache` | 使用缓存数据 | — |
    
    ## 使用场景
    
    ### 场景一:短剧创作者选题参考
    
    **角色**:短剧编剧/导演
    
    **需求**:了解当前B站短剧热门题材和爆款趋势,指导创作方向
    
    **使用方式**:
    1. 每日查看短剧日报,关注题材聚类分布
    2. 分析爆款标题特征和互动数据
    3. 针对新兴起量题材提前布局内容
    
    **预期收益**:精准把握流量风口,提升作品曝光概率
    
    ---
    
    ### 场景二:MCN 机构内容运营
    
    **角色**:MCN 运营人员
    
    **需求**:追踪旗下达人的短剧内容表现,优化运营策略
    
    **使用方式**:
    1. 订阅每日日报,持续追踪B站短剧赛道
    2. 通过核心达人榜了解竞品表现
    3. 使用自定义题材查询定向分析关注领域
    
    **预期收益**:提升内容运营效率,及时发现潜力达人
    
    ---
    
    ### 场景三:品牌方/制作方竞品监测
    
    **角色**:短剧制作公司
    
    **需求**:监测竞品短剧在B站的表现数据
    
    **使用方式**:
    1. 定期查询目标题材日报
    2. 分析竞品爆款作品的标题特征和互动规律
    3. 结合跨题材对比建议探索融合创作
    
    **预期收益**:数据驱动内容投资决策,降低试错成本
    
    ## 项目架构
    
    ### 目录结构
    
    ```
    短剧-B站信息源/
    ├── SKILL.md                          # Skill主文档(本文件)
    ├── scripts/
    │   └── playlet_bili_daily.py        # 核心脚本(v2.3.1:前置校验零请求/相关词命中直查/推荐/探活/自动回退/无数据确认制)
    └── references/
        ├── core_workflow.md              # 核心执行流程、字段映射、HTML规则
        └── examples.md                   # 使用示例与常见用法组合
    ```
    
    ### 技术栈
    
    | 项目 | 说明 |
    |------|------|
    | 运行环境 | Python 3 |
    | 开发语言 | Python(脚本 `playlet_bili_daily.py`) |
    | 数据接口 | HTTP 调用红狐Hub B站短剧API(`queryPlayletMsgs`,`platform=6`) |
    | 前端渲染 | HTML 生成日报(B站蓝 `#00A1D6` 主题色,`<img>` 懒加载,BV号直链) |
    | 来源标识 | `source: "短剧B站信息源-GitHub"` |
    
    ### 数据流转
    
    ```
    用户查询 → 前置输入校验(关键词命中题材词库-题材名+相关词) → 日期预检(真实探活) → API调用(queryPlayletMsgs) → 数据去重 → 题材聚类 → HTML日报生成 → 浏览器打开
                  ↓(关键词/分类全部不满足)            ↓(日期未更新/超范围)       ↓(数据不足)           ↓(关键词无匹配)
           不请求接口,直接提示+推荐停止        自动回退最近有数据日期并告知  提示推荐题材等待确认(不自动扩展)  提示推荐题材等待确认(不自动降级全量)
                                                  ↓
                                        终端输出趋势分析报告
    ```
    
    ## 常见问答
    
    ### 使用相关
    
    **Q1: 为什么查询"今天"的数据却提示未更新?**
    
    A: 数据每日15:00更新前一天的数据(实际更新时间可能延迟)。15:00前最新可用日期为前天,15:00后为昨天。**无需手动处理**——查询日期未更新或超过查询时间范围时,脚本会自动向前回退获取最近时间范围数据,并明确告知"当前查询时间未更新或超过查询时间范围,已为您自动获取最近时间范围数据";也可使用 `--latest` 直接定位最近有数据的日期。
    
    **Q2: 如何查询特定题材的短剧?**
    
    A: 使用 `--topics` 参数,多个题材用逗号分隔,如 `--topics "穿越,霸总,重生"`。所有题材批量一次性查询,无需逐个调用。查询前脚本会**先校验关键词/分类**:符合短剧题材词库(穿越/霸总/重生/悬疑/甜宠/逆袭等)才参与查询;全部不符合时**不请求任何接口**,直接提醒"关键词不满足短剧查询条件"并**推荐相关分类和关键词**,混合词场景保留有效关键词查询。
    
    **Q3: 日报生成在哪里?**
    
    A: 默认保存在 `~/Downloads/QoderReports/` 目录,文件名格式为 `短剧B站日报_YYYY-MM-DD.html`,生成后自动在浏览器打开。
    
    **Q4: 如何开启/关闭每日订阅?**
    
    A: 使用 `--subscribe` 开启每日自动产出,`--unsubscribe` 关闭。
    
    ### 故障排除
    
    **Q5: 脚本运行报 UnicodeEncodeError 怎么办?**
    
    A: Windows PowerShell 的 GBK 编码问题。执行前设置环境变量:`$env:PYTHONIOENCODING='utf-8'`,然后重新运行脚本。
    
    **Q6: 提示"未找到 REDFOX_API_KEY 环境变量"?**
    
    A: 请按"一键安装"章节配置环境变量。Windows 用户配置后需**重启终端**才能生效。
    
    **Q7: HTML日报中图片加载不出来?**
    
    A: B站CDN(hdslb.com)无防盗链限制,通常不会出现此问题。若遇到请检查网络连接或图片URL是否过期。
    
    ### B站短剧改造说明(从抖音版迁移)
    
    **Q8: B站版本与抖音版本有哪些差异?**
    
    A: 主要改造点如下:
    
    | 改造项 | 抖音版本 | B站版本 |
    |-------|---------|--------|
    | platform参数 | 1 | 6 |
    | 主题色 | #FB7299(粉) | #00A1D6(蓝) |
    | 链接格式 | douyin.com/video/{id} | bilibili.com/video/{BV号} |
    | 文件名 | 短剧抖音日报 | 短剧B站日报 |
    | 图片防盗链 | 需referrerpolicy | 无需(B站CDN无限制) |
    | 展示指标 | 播放/点赞/评论 | 分享/点赞/评论(零值隐藏) |
    | url字段 | 有效 | None(用BV号拼接) |
    
    **字段映射详情**见 [core_workflow.md](references/core_workflow.md)
    
    ### 验证清单
    
    - [x] 脚本语法正确(py_compile通过)
    - [x] API接口地址已更新为B站
    - [x] platform参数已改为6(B站)
    - [x] photoId是BV号格式,用于拼接链接
    - [x] readCount有真实数据,但不展示
    - [x] shareCount展示为分享数
    - [x] likeCount作为主排序指标
    - [x] url字段为None,用BV号拼接bilibili.com链接
    - [x] coverUrl为B站CDN(hdslb.com),无需referrerpolicy
    - [x] HTML主题色改为B站蓝(#00A1D6)
    - [x] 指标展示:分享/点赞/评论(零值隐藏)
    - [x] 链接格式:bilibili.com/video/{BV号}
    - [x] source字段:短剧B站信息源-GitHub
    
    ### 获取帮助
    
    如有其他问题,可通过项目 GitHub 仓库提交 Issue。
    
    ## 📚 参考文档
    
    - [core_workflow.md](references/core_workflow.md) — 核心执行流程、字段映射、HTML规则、指标展示规则、与抖音差异对比
    - [examples.md](references/examples.md) — 使用示例与常见用法组合
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related