Metadata-Version: 2.4
Name: bili-course
Version: 0.1.1
Summary: Bilibili course subtitle crawler & local knowledge base builder (Bilibili -> Course -> Subtitle -> Knowledge Base -> AI Agent)
Author-email: Crribbe <yihouxiao@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/Crribbe/bili-course
Project-URL: Repository, https://github.com/Crribbe/bili-course
Project-URL: Issues, https://github.com/Crribbe/bili-course/issues
Keywords: bilibili,subtitle,course,knowledge-base,llm,agent,obsidian
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Education
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: Chinese (Simplified)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Education
Classifier: Topic :: Multimedia :: Video
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Requires-Dist: pydantic-settings>=2.3
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13.7
Requires-Dist: fastapi>=0.111
Requires-Dist: uvicorn>=0.30
Provides-Extra: browser
Requires-Dist: playwright>=1.44; extra == "browser"
Provides-Extra: dev
Requires-Dist: pytest>=8.2; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Dynamic: license-file

# bili-course

**Bilibili 课程字幕爬取 & 本地知识库构建系统**

```
Bilibili URL → Course → Videos → Subtitles → Local Knowledge Base → AI Agent
```

把 Bilibili 上的计算机课程(MySQL、CS224N、深度学习、Python……)一键变成
**本地 Markdown 知识库**,直接交给 Claude Code / Codex / 任意 LLM Agent 学习。

[![CI](https://img.shields.io/badge/CI-passing-brightgreen)]()
[![PyPI](https://img.shields.io/badge/PyPI-bili--course-blue)](https://pypi.org/project/bili-course/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Python](https://img.shields.io/badge/Python-3.12%2B-blue)]()
[![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)]()

## 为什么需要它

在 Bilibili 学课程,手动流程是:打开视频 → 复制字幕 → 粘贴给 AI → 下一节。
一门 42 节的课,重复 42 次。

本工具一条命令完成全部:

```
bili-course import "课程URL"
  → 自动发现全部视频(合集/播放列表/番剧/多 P)
  → 并发抓取字幕(限速 3 并发,自动重试,断点续传)
  → 标准化为 SRT + Markdown
  → 自动镜像进你的 Obsidian Vault
```

## 快速开始

```bash
# 1. 安装(需要 Python 3.12+)
pip install bili-course

# 2. 初始化全局配置(自动探测 Obsidian vault)
bili-course init

# 3. 登录(打开浏览器窗口,扫码/密码登录一次即可;会话存入本地配置)
bili-course login

# 4. 验证环境
bili-course doctor

# 5. 用起来(任意目录)
bili-course import "https://www.bilibili.com/video/BV1xxxxx"
bili-course subtitle BV1Kr4y1i7ru        # 单个视频快速抓取
```

`bili-course login` 打开的浏览器是**独立的空白会话**:你自己在 Bilibili 官网
登录(密码/二维码都直接输给 B 站,工具看不到),之后工具只从这个窗口读取
会话 Cookie 存入 `~/.bili-course/.env`。你的日常浏览器、密码库都不会被触碰。
(也可以手动配置,见下文「Cookie 配置」)

### 输出

工作区(`~/.bili-course/data/courses/<slug>/`,每节一组文件):

```
01-数据库介绍/
├── metadata.json
├── subtitle.json           # 标准化字幕段 [{index, start, end, text}]
├── subtitle.srt            # 播放器可用的标准字幕
└── transcript.md           # 带时间戳的忠实转写(Agent 学习入口)
```

Obsidian 镜像(`<vault>/Captions/<slug>/`,扁平布局,一视频一文件):

```
├── course.md               # 课程地图,链接指向下面每个文件
├── 01-数据库介绍.md
├── 02-SQL基础.md
└── 100-streamlit入门.md    # 三位数页码保持排序
```

transcript.md 示例:

```markdown
# 第 03 节:分组查询

> Course: MySQL 数据库入门到精通
> BVID: BVxxxxxxxx   > CID: xxxxx   > Duration: 32:15
> Subtitle: AI Generated (中文)

## Transcript

[00:00:00]

大家好，今天我们来学习分组查询。

[00:00:08]

首先我们来看 GROUP BY。
```

## 为什么要登录(Cookie)

**2025 年起 Bilibili 将字幕列表改为登录态可见**:匿名请求大多返回空列表和
`need_login_subtitle=true`。所以要拿到字幕,需要一个你自己的账号会话。

两种方式(选一):

| 方式 | 命令 | 说明 |
|---|---|---|
| 浏览器登录(推荐) | `bili-course login` | 打开独立浏览器窗口手动登录,自动保存会话 |
| 手动粘贴 | 编辑 `~/.bili-course/.env` | F12 → Network → 任意 `api.bilibili.com` 请求 → 复制 `Cookie:` 头的**值**填入 `BILI_COOKIE` |

**安全说明,务必阅读:**

- Cookie 等价于你的账号登录态,**任何人拿到它都能以你的身份操作**。
- 本工具只在你自己登录的会话中读取,绝不读取你的浏览器密码、绝不提取
  其他浏览器保存的凭据、绝不绕过付费权限或访问控制。
- 日志系统会全局自动打码所有 Cookie 值;`.env` 文件在 Linux/macOS 上以
  0600 权限创建;`.gitignore` 已排除所有 `.env`。
- Cookie 会过期(通常几个月),失效时重新 `bili-course login` 即可,
  `bili-course doctor` 可以随时检查会话有效性。

## CLI 参考

| 命令 | 作用 |
|---|---|
| `bili-course import <URL>` | 导入课程/合集/播放列表/多 P 视频,抓取全部字幕 |
| `bili-course subtitle <BVID\|URL>` | 单视频快速抓取 |
| `bili-course list` | 列出所有课程 |
| `bili-course show <COURSE>` | 课程详情 + 每节状态 |
| `bili-course status <COURSE>` | 逐节导入状态 |
| `bili-course resume <COURSE>` | 续传(自动跳过已完成节) |
| `bili-course fetch <COURSE>` | 续传别名 |
| `bili-course retry <COURSE>` | 重试失败节 |
| `bili-course export <COURSE>` | 从数据库重建 course.json/course.md |
| `bili-course sync <COURSE>` | 重新镜像课程到 Obsidian vault |
| `bili-course login` | 浏览器登录,保存会话 Cookie |
| `bili-course init` | 创建全局配置 ~/.bili-course/.env |
| `bili-course doctor` | 环境诊断(安全输出,可直接贴进 issue) |
| `bili-course serve` | 启动 FastAPI 服务 |

`<COURSE>` 可以是数字 id(`list` 里显示的)或 slug。
加 `--debug` 可显示完整堆栈。Ctrl-C 中断后 `resume` 从断点继续,
已成功的节不会重复请求。

## 支持的 URL 格式

| 类型 | 示例 |
|---|---|
| 单视频 | `bilibili.com/video/BV1GJ411x7h7`、`...?p=2`、`bilibili.com/video/av170001` |
| 短链 | `b23.tv/xxxxx`(实测通过) |
| 合集 | `space.bilibili.com/<mid>/channel/collectiondetail?sid=<sid>`(实测通过) |
| 播放列表(收藏夹) | `space.bilibili.com/<mid>/favlist?fid=<fid>`(私密收藏夹需登录;待真实数据验证) |
| 番剧 | `bilibili.com/bangumi/play/ep<id>` / `ss<id>`(⚠ 实验性,见下) |
| 付费课程 | 识别但拒绝处理(不绕过付费墙) |

**番剧(实验性)**:2026-09 实测 `pgc/view/web/season` 接口对 season/ep 参数
返回空数据(接口疑似改版),当前番剧 URL 会明确报错而不是产出空课程。
欢迎带具体番剧链接提 issue 协助适配新接口。

## 配置(.env)

| 变量 | 默认 | 说明 |
|---|---|---|
| `BILI_COOKIE` / `BILI_SESSDATA` | 空 | 登录 Cookie(见上) |
| `OBSIDIAN_CAPTIONS_DIR` | 空 | 绝对路径 `<vault>/Captions`;空 = 禁用镜像 |
| `MAX_CONCURRENCY` | 3 | 并发数(请保持礼貌,勿调高) |
| `REQUEST_TIMEOUT` | 15 | 单请求超时(秒) |
| `MAX_RETRIES` | 3 | 请求级重试次数 |
| `REQUEST_INTERVAL` | 0.4 | 客户端限速:请求最小间隔(秒) |
| `PREFERRED_LANGUAGES` | `zh-CN,zh-Hans,zh,zh-Hant` | 字幕语言偏好(按序) |
| `PREFER_MANUAL_SUBTITLES` | true | 人工字幕优先于 AI 字幕 |
| `OUTPUT_DIR` / `CACHE_DIR` / `DB_PATH` | `~/.bili-course/data/...` | 存储位置(与 CWD 无关) |
| `ENABLE_BROWSER_FALLBACK` | false | Playwright 兜底(实验性) |
| `BILI_WBI_MIXIN_KEY` | 空 | WBI 密钥表轮换时的手动覆盖 |

配置查找顺序:环境变量 > `./.env`(开发模式)> `~/.bili-course/.env`(日常)。

## 字幕选择策略

一个视频可能有多条字幕,按**明确优先级**选择,而不是取 `tracks[0]`:

1. 语言偏好(`PREFERRED_LANGUAGES` 顺序;`ai-zh` 按 base 语言 `zh` 匹配)
2. **人工字幕 > AI 字幕**(AI 转录可能听错技术术语,同语言层人工优先)
3. 未锁定 > 锁定
4. 列表原始顺序

## FAQ

**Q: 为什么提示 "requires a logged-in session"?**
B 站从 2025 年起对匿名请求隐藏字幕列表。跑一次 `bili-course login`,或手动配置 Cookie。

**Q: 报 403 / "风控校验失败" 怎么办?**
程序会自动重试并升级 WBI 签名。若仍失败:降低并发(`MAX_CONCURRENCY=1`)、
增大 `REQUEST_INTERVAL`、确认 Cookie 有效(`bili-course doctor`)。

**Q: Cookie 多久过期?**
通常几个月。失效表现:所有视频都报登录相关错误。重新 `bili-course login` 即可。

**Q: 为什么数据不在我的 OneDrive/网盘里?**
默认数据目录 `~/.bili-course/data/` 刻意放在云同步目录之外——SQLite 被云同步
并发写入有损坏风险。真正的学习材料(课程 Markdown)会自动镜像进你的 Obsidian
vault(它通常在云同步里,但那是静态文件,同步安全)。

**Q: 多 P 视频是什么?**
一个 BV 号可以包含多个视频页(P1/P2/P3…),每一页有独立的 cid 和字幕。
本工具把它们展开为一门课程的多个小节。

**Q: 字幕里能不能让 AI 帮我改写/总结?**
本工具不会(忠实原则:原始转写必须可审计)。改写是下游 Agent 的事——
把 transcript.md 交给 Claude Code 即可。

**Q: 支持哪些平台?**
主要开发与测试在 Windows;macOS / Linux 理论支持(路径与权限已做跨平台
处理)。如有问题,欢迎带着 `bili-course doctor` 的输出提 issue。

## HTTP API(FastAPI)

```bash
bili-course serve                      # http://127.0.0.1:8000(/docs 有交互文档)
```

| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/courses/import` | `{"url": "..."}` → 202 + course_id(后台处理) |
| GET | `/courses` | 课程列表 |
| GET | `/courses/{id}` | 课程详情 |
| GET | `/courses/{id}/status` | 导入状态(计数 + 逐节) |
| GET | `/courses/{id}/videos` | 节列表 |
| GET | `/videos/{id}` | 视频 + 字幕记录 |
| GET | `/videos/{id}/subtitle` | 字幕记录 |
| POST | `/videos/{id}/retry` | 重试单个失败视频 |

架构约定:**API → Service → Repository**,API 层不含业务逻辑。

## 架构

```
src/bili_course/
├── cli.py                 # CLI 入口(typer + rich)
├── config.py              # 全部配置(pydantic-settings + 全局 .env)
├── logging.py             # 日志 + 全局 Cookie 打码
├── errors.py              # 类型化异常体系
├── service.py             # 组合根:唯一依赖装配点
├── bilibili/              # ★ Bilibili 专用层(API 变了只改这里)
│   ├── urls.py            #   URL 解析(纯函数,零网络)
│   ├── http.py            #   异步客户端:重试/退避/限速/缓存/自动 WBI
│   ├── wbi.py             #   WBI 签名(2026-09 实测验证)
│   ├── auth.py            #   Cookie 封装(永不落盘、掩码 repr)
│   ├── video.py           #   metadata(view/pagelist,多 P 感知)
│   ├── subtitle.py        #   字幕轨道发现 + 下载
│   ├── selector.py        #   轨道选择策略
│   └── course.py          #   课程发现(合集/收藏夹/番剧/多 P)
├── models/                # 领域模型(Pydantic)
├── storage/               # SQLite / repository / filesystem / cache
├── pipeline/              # 业务管线(normalizer/exporter/downloader/tasks/importer)
├── browser/               # Playwright 兜底(可选依赖,惰性导入)
└── api/                   # FastAPI(app factory)
```

设计原则:

- **Bilibili 是 Source,不是系统的骨架**——API 细节全部隔离在 `bilibili/`;
- **领域模型是通用词汇表**——未来接入别的视频源(YouTube 等)时,
  Course/Video/Subtitle/Task 不变,只需新增 adapter;
- **存储分两层**——SQLite 是元数据索引,文件系统是知识本体;
  即使删掉数据库,`data/courses/` 里的 Markdown 依然是完整知识库。

## AI Agent 集成(设计)

**当前**:Agent(Claude Code / Codex)直接读文件系统:

```
courses/<slug>/course.md                  # 课程地图 → 引导 Agent 阅读顺序
courses/<slug>/<lesson>/transcript.md     # 每节带时间戳的忠实转写
```

例如在课程目录里对 Claude Code 说:"读 course.md,学习第 03 节,出 5 道练习题"。

**未来(接口已预留,勿过度实现)**:

1. **MCP Server** — `ImportService`/`Repository` 已足够干净,直接包一层
   MCP adapter 即可暴露 `search_course / get_course / list_lessons /
   get_lesson / get_transcript / search_transcript`(Service 层零改动)。
2. **学习管线** — `bili-course learn <COURSE> <LESSON>`:读 transcript →
   知识点解释 → 学习笔记 → 练习题 → 进度写入数据库。
3. **RAG / 向量库** — transcript.md 是干净的分段文本,直接切块嵌入;
   时间戳保留在 Markdown 里,可回跳原视频。

**忠实原则**:transcript 阶段**绝不**做 LLM 改写/总结——原始转写必须可审计,
改写/笔记是下游 Agent 的事,不是摄取管线的责任。

## 开发与贡献

见 [CONTRIBUTING.md](CONTRIBUTING.md)。

```bash
pip install -e ".[dev]"
pytest                                # 单元测试,零网络依赖(mock transport)
ruff check src tests scripts          # lint
RUN_LIVE_TESTS=1 pytest tests/integration -v   # 可选:真实网络冒烟
python scripts/probe_subtitles.py --selftest-wbi   # 随时验证 WBI 表是否过期
```

单元测试覆盖:URL 解析、WBI 签名(已知向量)、多 P metadata、字幕轨道选择、
标准化、SRT/Markdown 生成、重试/限速、任务状态机、repository、cache、
API 路由、日志打码、Obsidian 镜像。fixtures 基于真实 API 响应的形状。

## 兼容性与稳定性

- **Bilibili 接口会变**——这是本项目最大的外部风险。对策:
  - 所有 API 逻辑隔离在 `bilibili/` 层,上游变更只改这一层;
  - `scripts/probe_subtitles.py` 是探测当前接口行为的调试工具;
  - WBI 密钥表已实测验证(2026-09-04),且可通过 `.env` 热覆盖;
  - `tests/integration/` 用 `RUN_LIVE_TESTS=1` 随时对真实接口做冒烟。
- 浏览器兜底(Playwright)标记为实验性:需要
  `pip install "bili-course[browser]" && playwright install chromium`。
- 已知现状(2026-09):字幕轨道列表对匿名请求基本不可见 → 需要 Cookie;
  双栈网络下 Bilibili 的 IPv6 路径会被黑洞/风控(实测表现为 -404"啥都木有"),
  本工具强制走 IPv4。

## 合规与免责声明

本项目的唯一目的:**获取你自己正常有权访问的视频字幕,用于个人学习与
知识整理**。不实现、不鼓励:绕过付费/权限限制、破解 DRM、窃取 Cookie
或密码、读取浏览器凭据、大规模攻击式抓取、规避平台安全措施。
我们内置限速、退避重试、缓存,以最小化对平台的负载。

本工具与 Bilibili 官方无任何隶属关系;请遵守 Bilibili 的服务条款,
使用者对自身行为负责。

## Roadmap

- [x] MVP:单视频 → 字幕 → SRT/Markdown
- [x] 课程发现(合集/播放列表/番剧/多 P)+ 任务系统(并发/重试/resume)
- [x] CLI + FastAPI + 单元测试 + Obsidian 镜像
- [x] 全局配置 + login/doctor 命令(v0.1)
- [ ] MCP Server adapter
- [ ] `bili-course learn` 学习管线
- [ ] RAG / 向量索引(基于 transcript.md)
