bpi-py 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. bpi_py-0.3.0/.gitignore +8 -0
  2. bpi_py-0.3.0/LICENSE +21 -0
  3. bpi_py-0.3.0/PKG-INFO +290 -0
  4. bpi_py-0.3.0/README.md +277 -0
  5. bpi_py-0.3.0/migration/SOURCE-LICENSE.txt +21 -0
  6. bpi_py-0.3.0/pyproject.toml +51 -0
  7. bpi_py-0.3.0/src/bpi/__init__.py +29 -0
  8. bpi_py-0.3.0/src/bpi/_core/__init__.py +1 -0
  9. bpi_py-0.3.0/src/bpi/_core/params.py +21 -0
  10. bpi_py-0.3.0/src/bpi/_core/response.py +51 -0
  11. bpi_py-0.3.0/src/bpi/_generated/__init__.py +1 -0
  12. bpi_py-0.3.0/src/bpi/_generated/audio_client.py +217 -0
  13. bpi_py-0.3.0/src/bpi/_generated/audio_models.py +320 -0
  14. bpi_py-0.3.0/src/bpi/_generated/bangumi_client.py +68 -0
  15. bpi_py-0.3.0/src/bpi/_generated/bangumi_models.py +602 -0
  16. bpi_py-0.3.0/src/bpi/_generated/cheese_client.py +49 -0
  17. bpi_py-0.3.0/src/bpi/_generated/cheese_models.py +198 -0
  18. bpi_py-0.3.0/src/bpi/_generated/login_client.py +41 -0
  19. bpi_py-0.3.0/src/bpi/_generated/login_models.py +49 -0
  20. bpi_py-0.3.0/src/bpi/_generated/user_client.py +257 -0
  21. bpi_py-0.3.0/src/bpi/_generated/user_models.py +455 -0
  22. bpi_py-0.3.0/src/bpi/_generated/video_client.py +250 -0
  23. bpi_py-0.3.0/src/bpi/_generated/video_models.py +577 -0
  24. bpi_py-0.3.0/src/bpi/activity/__init__.py +4 -0
  25. bpi_py-0.3.0/src/bpi/activity/client.py +45 -0
  26. bpi_py-0.3.0/src/bpi/activity/models.py +42 -0
  27. bpi_py-0.3.0/src/bpi/activity/params.py +29 -0
  28. bpi_py-0.3.0/src/bpi/article/__init__.py +29 -0
  29. bpi_py-0.3.0/src/bpi/article/client.py +93 -0
  30. bpi_py-0.3.0/src/bpi/article/models.py +391 -0
  31. bpi_py-0.3.0/src/bpi/article/params.py +54 -0
  32. bpi_py-0.3.0/src/bpi/audio/__init__.py +58 -0
  33. bpi_py-0.3.0/src/bpi/audio/actions.py +111 -0
  34. bpi_py-0.3.0/src/bpi/audio/client.py +9 -0
  35. bpi_py-0.3.0/src/bpi/bangumi/__init__.py +18 -0
  36. bpi_py-0.3.0/src/bpi/bangumi/actions.py +34 -0
  37. bpi_py-0.3.0/src/bpi/bangumi/client.py +54 -0
  38. bpi_py-0.3.0/src/bpi/bangumi/models.py +55 -0
  39. bpi_py-0.3.0/src/bpi/cheese/__init__.py +3 -0
  40. bpi_py-0.3.0/src/bpi/cheese/client.py +51 -0
  41. bpi_py-0.3.0/src/bpi/cheese/models.py +120 -0
  42. bpi_py-0.3.0/src/bpi/client.py +410 -0
  43. bpi_py-0.3.0/src/bpi/clientinfo/__init__.py +4 -0
  44. bpi_py-0.3.0/src/bpi/clientinfo/client.py +43 -0
  45. bpi_py-0.3.0/src/bpi/clientinfo/models.py +11 -0
  46. bpi_py-0.3.0/src/bpi/comment/__init__.py +14 -0
  47. bpi_py-0.3.0/src/bpi/comment/client.py +139 -0
  48. bpi_py-0.3.0/src/bpi/comment/models.py +332 -0
  49. bpi_py-0.3.0/src/bpi/comment/params.py +176 -0
  50. bpi_py-0.3.0/src/bpi/creativecenter/__init__.py +63 -0
  51. bpi_py-0.3.0/src/bpi/creativecenter/client.py +344 -0
  52. bpi_py-0.3.0/src/bpi/creativecenter/models.py +526 -0
  53. bpi_py-0.3.0/src/bpi/creativecenter/params.py +237 -0
  54. bpi_py-0.3.0/src/bpi/danmaku/__init__.py +19 -0
  55. bpi_py-0.3.0/src/bpi/danmaku/client.py +219 -0
  56. bpi_py-0.3.0/src/bpi/danmaku/models.py +124 -0
  57. bpi_py-0.3.0/src/bpi/danmaku/params.py +238 -0
  58. bpi_py-0.3.0/src/bpi/dynamic/__init__.py +21 -0
  59. bpi_py-0.3.0/src/bpi/dynamic/client.py +285 -0
  60. bpi_py-0.3.0/src/bpi/dynamic/models.py +373 -0
  61. bpi_py-0.3.0/src/bpi/dynamic/params.py +161 -0
  62. bpi_py-0.3.0/src/bpi/electric/__init__.py +29 -0
  63. bpi_py-0.3.0/src/bpi/electric/client.py +181 -0
  64. bpi_py-0.3.0/src/bpi/electric/models.py +305 -0
  65. bpi_py-0.3.0/src/bpi/electric/params.py +139 -0
  66. bpi_py-0.3.0/src/bpi/errors.py +92 -0
  67. bpi_py-0.3.0/src/bpi/fav/__init__.py +41 -0
  68. bpi_py-0.3.0/src/bpi/fav/client.py +212 -0
  69. bpi_py-0.3.0/src/bpi/fav/models.py +196 -0
  70. bpi_py-0.3.0/src/bpi/fav/params.py +155 -0
  71. bpi_py-0.3.0/src/bpi/historytoview/__init__.py +24 -0
  72. bpi_py-0.3.0/src/bpi/historytoview/client.py +97 -0
  73. bpi_py-0.3.0/src/bpi/historytoview/models.py +176 -0
  74. bpi_py-0.3.0/src/bpi/historytoview/params.py +102 -0
  75. bpi_py-0.3.0/src/bpi/live/__init__.py +45 -0
  76. bpi_py-0.3.0/src/bpi/live/client.py +653 -0
  77. bpi_py-0.3.0/src/bpi/live/models.py +676 -0
  78. bpi_py-0.3.0/src/bpi/live/params.py +84 -0
  79. bpi_py-0.3.0/src/bpi/login/__init__.py +29 -0
  80. bpi_py-0.3.0/src/bpi/login/client.py +174 -0
  81. bpi_py-0.3.0/src/bpi/login/models.py +107 -0
  82. bpi_py-0.3.0/src/bpi/login/params.py +75 -0
  83. bpi_py-0.3.0/src/bpi/manga/__init__.py +25 -0
  84. bpi_py-0.3.0/src/bpi/manga/client.py +177 -0
  85. bpi_py-0.3.0/src/bpi/manga/models.py +116 -0
  86. bpi_py-0.3.0/src/bpi/manga/params.py +69 -0
  87. bpi_py-0.3.0/src/bpi/message/__init__.py +13 -0
  88. bpi_py-0.3.0/src/bpi/message/client.py +119 -0
  89. bpi_py-0.3.0/src/bpi/message/models.py +113 -0
  90. bpi_py-0.3.0/src/bpi/message/params.py +71 -0
  91. bpi_py-0.3.0/src/bpi/misc/__init__.py +13 -0
  92. bpi_py-0.3.0/src/bpi/misc/client.py +78 -0
  93. bpi_py-0.3.0/src/bpi/misc/models.py +44 -0
  94. bpi_py-0.3.0/src/bpi/misc/params.py +31 -0
  95. bpi_py-0.3.0/src/bpi/misc/sign.py +28 -0
  96. bpi_py-0.3.0/src/bpi/note/__init__.py +25 -0
  97. bpi_py-0.3.0/src/bpi/note/client.py +118 -0
  98. bpi_py-0.3.0/src/bpi/note/models.py +142 -0
  99. bpi_py-0.3.0/src/bpi/note/params.py +88 -0
  100. bpi_py-0.3.0/src/bpi/opus/__init__.py +5 -0
  101. bpi_py-0.3.0/src/bpi/opus/client.py +33 -0
  102. bpi_py-0.3.0/src/bpi/opus/models.py +29 -0
  103. bpi_py-0.3.0/src/bpi/opus/params.py +42 -0
  104. bpi_py-0.3.0/src/bpi/py.typed +0 -0
  105. bpi_py-0.3.0/src/bpi/search/__init__.py +58 -0
  106. bpi_py-0.3.0/src/bpi/search/client.py +156 -0
  107. bpi_py-0.3.0/src/bpi/search/models.py +316 -0
  108. bpi_py-0.3.0/src/bpi/search/params.py +160 -0
  109. bpi_py-0.3.0/src/bpi/session.py +42 -0
  110. bpi_py-0.3.0/src/bpi/sign/__init__.py +1 -0
  111. bpi_py-0.3.0/src/bpi/sign/wbi.py +107 -0
  112. bpi_py-0.3.0/src/bpi/user/__init__.py +3 -0
  113. bpi_py-0.3.0/src/bpi/user/actions.py +185 -0
  114. bpi_py-0.3.0/src/bpi/user/client.py +9 -0
  115. bpi_py-0.3.0/src/bpi/user/models.py +31 -0
  116. bpi_py-0.3.0/src/bpi/video/__init__.py +41 -0
  117. bpi_py-0.3.0/src/bpi/video/actions.py +277 -0
  118. bpi_py-0.3.0/src/bpi/video/client.py +64 -0
  119. bpi_py-0.3.0/src/bpi/video/models.py +135 -0
  120. bpi_py-0.3.0/src/bpi/video_ranking/__init__.py +24 -0
  121. bpi_py-0.3.0/src/bpi/video_ranking/client.py +132 -0
  122. bpi_py-0.3.0/src/bpi/video_ranking/models.py +101 -0
  123. bpi_py-0.3.0/src/bpi/video_ranking/params.py +108 -0
  124. bpi_py-0.3.0/src/bpi/vip/__init__.py +23 -0
  125. bpi_py-0.3.0/src/bpi/vip/client.py +50 -0
  126. bpi_py-0.3.0/src/bpi/vip/models.py +108 -0
  127. bpi_py-0.3.0/src/bpi/vip/params.py +13 -0
  128. bpi_py-0.3.0/src/bpi/wallet/__init__.py +4 -0
  129. bpi_py-0.3.0/src/bpi/wallet/client.py +56 -0
  130. bpi_py-0.3.0/src/bpi/wallet/models.py +18 -0
  131. bpi_py-0.3.0/src/bpi/web_widget/__init__.py +11 -0
  132. bpi_py-0.3.0/src/bpi/web_widget/client.py +44 -0
  133. bpi_py-0.3.0/src/bpi/web_widget/models.py +86 -0
  134. bpi_py-0.3.0/src/bpi/web_widget/params.py +9 -0
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ dist/
7
+ *.egg-info/
8
+ flightdeck/
bpi_py-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 YUELI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
bpi_py-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,290 @@
1
+ Metadata-Version: 2.5
2
+ Name: bpi-py
3
+ Version: 0.3.0
4
+ Summary: Async Bilibili API SDK for Python
5
+ Project-URL: Repository, https://github.com/Yuelioi/bpi-py
6
+ Project-URL: Issues, https://github.com/Yuelioi/bpi-py/issues
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: httpx<1,>=0.28
11
+ Requires-Dist: pydantic<3,>=2.10
12
+ Description-Content-Type: text/markdown
13
+
14
+ # bpi-py
15
+
16
+ 一个面向 Python 的异步 Bilibili API SDK。
17
+
18
+ 如果你想在 Python 里获取视频信息、搜索、用户资料、排行榜、直播、动态、评论、收藏夹、音频、番剧,或者调用登录态与创作中心接口,`bpi-py` 提供了一套统一、类型化的调用方式。
19
+
20
+ 当前覆盖 **27 个领域、316 个公开方法**。
21
+
22
+ ```python
23
+ import asyncio
24
+
25
+ from bpi import AsyncBpiClient
26
+
27
+
28
+ async def main() -> None:
29
+ async with AsyncBpiClient() as client:
30
+ video = await client.video.view(bvid="BV1xx411c7mD")
31
+ print(video.title)
32
+ print(video.owner.name)
33
+
34
+
35
+ asyncio.run(main())
36
+ ```
37
+
38
+ ## 这个项目从哪里来?
39
+
40
+ `bpi-py` 是 [`bpi-rs`](https://github.com/Yuelioi/bpi-rs) **v0.3.0** 的 Python 重构版本。
41
+
42
+ Python 版本延续了 `bpi-rs` 已经整理过的接口、参数、响应模型、特殊协议与错误语义,并针对 Python 的异步生态重新组织为 `asyncio` + HTTPX + Pydantic 的使用方式。
43
+
44
+ ## 为什么用 bpi-py?
45
+
46
+ 从使用者角度,主要是少写很多和 Bilibili 协议本身有关的重复代码。
47
+
48
+ | 你需要做的事 | `bpi-py` 帮你处理 |
49
+ | --- | --- |
50
+ | 调很多不同类型的 B 站接口 | 视频、用户、搜索、直播、动态、评论、收藏等都挂在同一个客户端上 |
51
+ | 手工拼 URL 和查询参数 | 使用领域方法和类型化参数 |
52
+ | 自己解析 JSON | 常用响应直接返回 Pydantic 模型 |
53
+ | 自己处理 WBI | SDK 自动完成签名和密钥缓存 |
54
+ | 自己管理 Cookie / CSRF | 登录态显式传入,写接口自动从 Cookie 中取 `bili_jct` |
55
+ | 处理 XML、deflate、protobuf、multipart | SDK 已封装对应特殊响应和上传流程 |
56
+ | 判断登录、权限、风控错误 | 提供统一的异常类型和语义判断 |
57
+ | 在爬虫 / bot / 后端里并发请求 | 基于 `asyncio` + HTTPX,直接使用异步调用 |
58
+
59
+ 调用方式也比较统一:
60
+
61
+ ```python
62
+ client.video.view(...)
63
+ client.search.video(...)
64
+ client.user.card(...)
65
+ client.live.room_info(...)
66
+ client.dynamic.all(...)
67
+ client.comment.list(...)
68
+ client.creativecenter.season_list(...)
69
+ ```
70
+
71
+ 不需要记一整套扁平函数名。
72
+
73
+ ## 安装
74
+
75
+ 要求 Python 3.11+。
76
+
77
+ 从 PyPI 安装:
78
+
79
+ ```bash
80
+ pip install bpi-py
81
+ ```
82
+
83
+ 使用 `uv` 安装:
84
+
85
+ ```bash
86
+ uv add bpi-py
87
+ ```
88
+
89
+ 如果你在开发这个仓库本身,再使用 `uv sync` 安装开发环境。
90
+
91
+ 运行时主要依赖:
92
+
93
+ - HTTPX
94
+ - Pydantic v2
95
+
96
+ ## 示例
97
+
98
+ ### 1. 获取视频信息
99
+
100
+ 不需要登录。
101
+
102
+ ```python
103
+ import asyncio
104
+
105
+ from bpi import AsyncBpiClient
106
+
107
+
108
+ async def main() -> None:
109
+ async with AsyncBpiClient() as client:
110
+ video = await client.video.view(bvid="BV1xx411c7mD")
111
+
112
+ print(video.title)
113
+ print(video.owner.name)
114
+ print(video.stat.view)
115
+
116
+
117
+ asyncio.run(main())
118
+ ```
119
+
120
+ 也可以使用 `aid`:
121
+
122
+ ```python
123
+ video = await client.video.view(aid=2)
124
+ ```
125
+
126
+ ### 2. 搜索视频
127
+
128
+ WBI 签名由 SDK 自动完成。
129
+
130
+ ```python
131
+ async with AsyncBpiClient() as client:
132
+ result = await client.search.video(keyword="Python", page=1)
133
+
134
+ for video in result.result or []:
135
+ print(video.title)
136
+ ```
137
+
138
+ 搜索模块还提供文章、番剧、影视、用户、直播间等分类搜索。
139
+
140
+ ### 3. 获取视频排行榜
141
+
142
+ ```python
143
+ async with AsyncBpiClient() as client:
144
+ ranking = await client.video_ranking.ranking_list()
145
+
146
+ print(ranking.note)
147
+ print(len(ranking.list))
148
+ ```
149
+
150
+ ### 4. 获取直播间信息
151
+
152
+ ```python
153
+ async with AsyncBpiClient() as client:
154
+ room = await client.live.room_info(room_id=6)
155
+
156
+ print(room.title)
157
+ print(room.online)
158
+ ```
159
+
160
+ ### 5. 使用登录态
161
+
162
+ 需要登录的接口显式传入 Cookie:
163
+
164
+ ```python
165
+ from bpi import AsyncBpiClient
166
+
167
+
168
+ async with AsyncBpiClient(
169
+ cookie="SESSDATA=...; bili_jct=...; DedeUserID=..."
170
+ ) as client:
171
+ nav = await client.login.nav()
172
+ print(nav.is_login)
173
+ ```
174
+
175
+ SDK 不会偷偷读取浏览器 Cookie、本地账号文件或环境里的账号信息。
176
+
177
+ 需要 CSRF 的写接口会从当前 Cookie jar 中读取 `bili_jct`。
178
+
179
+ ### 6. 二维码登录
180
+
181
+ ```python
182
+ async with AsyncBpiClient() as client:
183
+ qr = await client.login.qr_generate()
184
+
185
+ print(qr.url)
186
+ print(qr.qrcode_key)
187
+
188
+ status = await client.login.qr_poll(qrcode_key=qr.qrcode_key)
189
+ print(status.code)
190
+ ```
191
+
192
+ SDK 负责请求和登录状态解析,二维码如何展示、多久轮询一次由你的应用决定。
193
+
194
+ ## 覆盖了哪些模块?
195
+
196
+ 目前公开方法已经全部映射,共 **316/316**:
197
+
198
+ | 类型 | 模块 |
199
+ | --- | --- |
200
+ | 视频与内容 | `video`、`video_ranking`、`bangumi`、`cheese`、`audio`、`article`、`note`、`opus`、`manga` |
201
+ | 用户与互动 | `user`、`comment`、`dynamic`、`message`、`fav`、`historytoview` |
202
+ | 直播 | `live` |
203
+ | 搜索 | `search` |
204
+ | 账号 | `login`、`vip`、`wallet`、`electric` |
205
+ | 创作者 | `creativecenter` |
206
+ | 其他 | `activity`、`clientinfo`、`danmaku`、`misc`、`web_widget` |
207
+
208
+ 完整 Rust → Python API 映射见 [`migration/python-api.json`](migration/python-api.json)。
209
+
210
+ ## 响应是类型化的
211
+
212
+ 常规 API 不会只给你一个裸 `dict`:
213
+
214
+ ```python
215
+ video = await client.video.view(bvid="BV1xx411c7mD")
216
+
217
+ print(video.title)
218
+ print(video.owner.name)
219
+ print(video.stat.view)
220
+ ```
221
+
222
+ 包内包含 `py.typed`,可以被 mypy、Pyright 等类型检查器识别。
223
+
224
+ 对于 Bilibili 本身结构不稳定、上游 Rust 版本也保留为动态 JSON 的字段,Python 版本同样保留动态边界,避免为了“全类型化”而错误假设协议结构。
225
+
226
+ ## 错误处理
227
+
228
+ SDK 提供统一异常:
229
+
230
+ ```python
231
+ from bpi import ApiError, AuthenticationError, HttpStatusError
232
+
233
+ try:
234
+ video = await client.video.view(aid=2)
235
+ except AuthenticationError:
236
+ print("需要登录")
237
+ except HttpStatusError as error:
238
+ print("HTTP:", error.status_code)
239
+ except ApiError as error:
240
+ print("API code:", error.code)
241
+ ```
242
+
243
+ 常见类型包括:
244
+
245
+ - `InvalidParameterError`
246
+ - `AuthenticationError`
247
+ - `ApiError`
248
+ - `HttpStatusError`
249
+ - `TransportError`
250
+ - `MissingDataError`
251
+ - `ResponseDecodeError`
252
+ - `UnsupportedResponseError`
253
+
254
+ `ApiError` / `HttpStatusError` 还提供登录、VIP、权限、风控等稳定语义判断。
255
+
256
+ ## 客户端生命周期
257
+
258
+ 推荐始终使用:
259
+
260
+ ```python
261
+ async with AsyncBpiClient() as client:
262
+ ...
263
+ ```
264
+
265
+ 也可以手动:
266
+
267
+ ```python
268
+ client = AsyncBpiClient()
269
+ try:
270
+ ...
271
+ finally:
272
+ await client.aclose()
273
+ ```
274
+
275
+ 如果传入已有的 `httpx.AsyncClient`,SDK 只借用它,不会替你关闭。
276
+
277
+ 当前只提供异步客户端。
278
+
279
+ ## 关于接口稳定性
280
+
281
+ Bilibili Web API 并不是官方稳定公开 API,上游字段、错误码或接口行为可能随时变化。
282
+
283
+ 本项目目前主要通过来自 `bpi-rs` v0.3.0 的脱敏契约响应、请求形状和离线测试进行验证,没有为了测试而自动读取真实账号,也不会执行线上写操作。
284
+
285
+ ## License
286
+
287
+ MIT License,见 [`LICENSE`](LICENSE)。
288
+
289
+ 来自 `bpi-rs` 的协议、类型和测试资产沿用其 MIT 许可与归属信息,见 [`migration/SOURCE-LICENSE.txt`](migration/SOURCE-LICENSE.txt)。
290
+
bpi_py-0.3.0/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # bpi-py
2
+
3
+ 一个面向 Python 的异步 Bilibili API SDK。
4
+
5
+ 如果你想在 Python 里获取视频信息、搜索、用户资料、排行榜、直播、动态、评论、收藏夹、音频、番剧,或者调用登录态与创作中心接口,`bpi-py` 提供了一套统一、类型化的调用方式。
6
+
7
+ 当前覆盖 **27 个领域、316 个公开方法**。
8
+
9
+ ```python
10
+ import asyncio
11
+
12
+ from bpi import AsyncBpiClient
13
+
14
+
15
+ async def main() -> None:
16
+ async with AsyncBpiClient() as client:
17
+ video = await client.video.view(bvid="BV1xx411c7mD")
18
+ print(video.title)
19
+ print(video.owner.name)
20
+
21
+
22
+ asyncio.run(main())
23
+ ```
24
+
25
+ ## 这个项目从哪里来?
26
+
27
+ `bpi-py` 是 [`bpi-rs`](https://github.com/Yuelioi/bpi-rs) **v0.3.0** 的 Python 重构版本。
28
+
29
+ Python 版本延续了 `bpi-rs` 已经整理过的接口、参数、响应模型、特殊协议与错误语义,并针对 Python 的异步生态重新组织为 `asyncio` + HTTPX + Pydantic 的使用方式。
30
+
31
+ ## 为什么用 bpi-py?
32
+
33
+ 从使用者角度,主要是少写很多和 Bilibili 协议本身有关的重复代码。
34
+
35
+ | 你需要做的事 | `bpi-py` 帮你处理 |
36
+ | --- | --- |
37
+ | 调很多不同类型的 B 站接口 | 视频、用户、搜索、直播、动态、评论、收藏等都挂在同一个客户端上 |
38
+ | 手工拼 URL 和查询参数 | 使用领域方法和类型化参数 |
39
+ | 自己解析 JSON | 常用响应直接返回 Pydantic 模型 |
40
+ | 自己处理 WBI | SDK 自动完成签名和密钥缓存 |
41
+ | 自己管理 Cookie / CSRF | 登录态显式传入,写接口自动从 Cookie 中取 `bili_jct` |
42
+ | 处理 XML、deflate、protobuf、multipart | SDK 已封装对应特殊响应和上传流程 |
43
+ | 判断登录、权限、风控错误 | 提供统一的异常类型和语义判断 |
44
+ | 在爬虫 / bot / 后端里并发请求 | 基于 `asyncio` + HTTPX,直接使用异步调用 |
45
+
46
+ 调用方式也比较统一:
47
+
48
+ ```python
49
+ client.video.view(...)
50
+ client.search.video(...)
51
+ client.user.card(...)
52
+ client.live.room_info(...)
53
+ client.dynamic.all(...)
54
+ client.comment.list(...)
55
+ client.creativecenter.season_list(...)
56
+ ```
57
+
58
+ 不需要记一整套扁平函数名。
59
+
60
+ ## 安装
61
+
62
+ 要求 Python 3.11+。
63
+
64
+ 从 PyPI 安装:
65
+
66
+ ```bash
67
+ pip install bpi-py
68
+ ```
69
+
70
+ 使用 `uv` 安装:
71
+
72
+ ```bash
73
+ uv add bpi-py
74
+ ```
75
+
76
+ 如果你在开发这个仓库本身,再使用 `uv sync` 安装开发环境。
77
+
78
+ 运行时主要依赖:
79
+
80
+ - HTTPX
81
+ - Pydantic v2
82
+
83
+ ## 示例
84
+
85
+ ### 1. 获取视频信息
86
+
87
+ 不需要登录。
88
+
89
+ ```python
90
+ import asyncio
91
+
92
+ from bpi import AsyncBpiClient
93
+
94
+
95
+ async def main() -> None:
96
+ async with AsyncBpiClient() as client:
97
+ video = await client.video.view(bvid="BV1xx411c7mD")
98
+
99
+ print(video.title)
100
+ print(video.owner.name)
101
+ print(video.stat.view)
102
+
103
+
104
+ asyncio.run(main())
105
+ ```
106
+
107
+ 也可以使用 `aid`:
108
+
109
+ ```python
110
+ video = await client.video.view(aid=2)
111
+ ```
112
+
113
+ ### 2. 搜索视频
114
+
115
+ WBI 签名由 SDK 自动完成。
116
+
117
+ ```python
118
+ async with AsyncBpiClient() as client:
119
+ result = await client.search.video(keyword="Python", page=1)
120
+
121
+ for video in result.result or []:
122
+ print(video.title)
123
+ ```
124
+
125
+ 搜索模块还提供文章、番剧、影视、用户、直播间等分类搜索。
126
+
127
+ ### 3. 获取视频排行榜
128
+
129
+ ```python
130
+ async with AsyncBpiClient() as client:
131
+ ranking = await client.video_ranking.ranking_list()
132
+
133
+ print(ranking.note)
134
+ print(len(ranking.list))
135
+ ```
136
+
137
+ ### 4. 获取直播间信息
138
+
139
+ ```python
140
+ async with AsyncBpiClient() as client:
141
+ room = await client.live.room_info(room_id=6)
142
+
143
+ print(room.title)
144
+ print(room.online)
145
+ ```
146
+
147
+ ### 5. 使用登录态
148
+
149
+ 需要登录的接口显式传入 Cookie:
150
+
151
+ ```python
152
+ from bpi import AsyncBpiClient
153
+
154
+
155
+ async with AsyncBpiClient(
156
+ cookie="SESSDATA=...; bili_jct=...; DedeUserID=..."
157
+ ) as client:
158
+ nav = await client.login.nav()
159
+ print(nav.is_login)
160
+ ```
161
+
162
+ SDK 不会偷偷读取浏览器 Cookie、本地账号文件或环境里的账号信息。
163
+
164
+ 需要 CSRF 的写接口会从当前 Cookie jar 中读取 `bili_jct`。
165
+
166
+ ### 6. 二维码登录
167
+
168
+ ```python
169
+ async with AsyncBpiClient() as client:
170
+ qr = await client.login.qr_generate()
171
+
172
+ print(qr.url)
173
+ print(qr.qrcode_key)
174
+
175
+ status = await client.login.qr_poll(qrcode_key=qr.qrcode_key)
176
+ print(status.code)
177
+ ```
178
+
179
+ SDK 负责请求和登录状态解析,二维码如何展示、多久轮询一次由你的应用决定。
180
+
181
+ ## 覆盖了哪些模块?
182
+
183
+ 目前公开方法已经全部映射,共 **316/316**:
184
+
185
+ | 类型 | 模块 |
186
+ | --- | --- |
187
+ | 视频与内容 | `video`、`video_ranking`、`bangumi`、`cheese`、`audio`、`article`、`note`、`opus`、`manga` |
188
+ | 用户与互动 | `user`、`comment`、`dynamic`、`message`、`fav`、`historytoview` |
189
+ | 直播 | `live` |
190
+ | 搜索 | `search` |
191
+ | 账号 | `login`、`vip`、`wallet`、`electric` |
192
+ | 创作者 | `creativecenter` |
193
+ | 其他 | `activity`、`clientinfo`、`danmaku`、`misc`、`web_widget` |
194
+
195
+ 完整 Rust → Python API 映射见 [`migration/python-api.json`](migration/python-api.json)。
196
+
197
+ ## 响应是类型化的
198
+
199
+ 常规 API 不会只给你一个裸 `dict`:
200
+
201
+ ```python
202
+ video = await client.video.view(bvid="BV1xx411c7mD")
203
+
204
+ print(video.title)
205
+ print(video.owner.name)
206
+ print(video.stat.view)
207
+ ```
208
+
209
+ 包内包含 `py.typed`,可以被 mypy、Pyright 等类型检查器识别。
210
+
211
+ 对于 Bilibili 本身结构不稳定、上游 Rust 版本也保留为动态 JSON 的字段,Python 版本同样保留动态边界,避免为了“全类型化”而错误假设协议结构。
212
+
213
+ ## 错误处理
214
+
215
+ SDK 提供统一异常:
216
+
217
+ ```python
218
+ from bpi import ApiError, AuthenticationError, HttpStatusError
219
+
220
+ try:
221
+ video = await client.video.view(aid=2)
222
+ except AuthenticationError:
223
+ print("需要登录")
224
+ except HttpStatusError as error:
225
+ print("HTTP:", error.status_code)
226
+ except ApiError as error:
227
+ print("API code:", error.code)
228
+ ```
229
+
230
+ 常见类型包括:
231
+
232
+ - `InvalidParameterError`
233
+ - `AuthenticationError`
234
+ - `ApiError`
235
+ - `HttpStatusError`
236
+ - `TransportError`
237
+ - `MissingDataError`
238
+ - `ResponseDecodeError`
239
+ - `UnsupportedResponseError`
240
+
241
+ `ApiError` / `HttpStatusError` 还提供登录、VIP、权限、风控等稳定语义判断。
242
+
243
+ ## 客户端生命周期
244
+
245
+ 推荐始终使用:
246
+
247
+ ```python
248
+ async with AsyncBpiClient() as client:
249
+ ...
250
+ ```
251
+
252
+ 也可以手动:
253
+
254
+ ```python
255
+ client = AsyncBpiClient()
256
+ try:
257
+ ...
258
+ finally:
259
+ await client.aclose()
260
+ ```
261
+
262
+ 如果传入已有的 `httpx.AsyncClient`,SDK 只借用它,不会替你关闭。
263
+
264
+ 当前只提供异步客户端。
265
+
266
+ ## 关于接口稳定性
267
+
268
+ Bilibili Web API 并不是官方稳定公开 API,上游字段、错误码或接口行为可能随时变化。
269
+
270
+ 本项目目前主要通过来自 `bpi-rs` v0.3.0 的脱敏契约响应、请求形状和离线测试进行验证,没有为了测试而自动读取真实账号,也不会执行线上写操作。
271
+
272
+ ## License
273
+
274
+ MIT License,见 [`LICENSE`](LICENSE)。
275
+
276
+ 来自 `bpi-rs` 的协议、类型和测试资产沿用其 MIT 许可与归属信息,见 [`migration/SOURCE-LICENSE.txt`](migration/SOURCE-LICENSE.txt)。
277
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 YUELI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,51 @@
1
+ [project]
2
+ name = "bpi-py"
3
+ version = "0.3.0"
4
+ description = "Async Bilibili API SDK for Python"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.11"
8
+ dependencies = ["httpx>=0.28,<1", "pydantic>=2.10,<3"]
9
+
10
+ [project.urls]
11
+ Repository = "https://github.com/Yuelioi/bpi-py"
12
+ Issues = "https://github.com/Yuelioi/bpi-py/issues"
13
+
14
+ [dependency-groups]
15
+ dev = ["pytest>=8,<10", "pytest-asyncio>=1,<2", "ruff>=0.11,<1", "mypy>=1.15,<3", "tree-sitter>=0.25,<0.26", "tree-sitter-rust>=0.24,<0.25"]
16
+
17
+ [build-system]
18
+ requires = ["hatchling>=1.27,<2"]
19
+ build-backend = "hatchling.build"
20
+
21
+ [tool.hatch.build.targets.wheel]
22
+ packages = ["src/bpi"]
23
+
24
+ [tool.hatch.build.targets.sdist]
25
+ include = [
26
+ "/src/bpi",
27
+ "/README.md",
28
+ "/LICENSE",
29
+ "/pyproject.toml",
30
+ "/migration/SOURCE-LICENSE.txt",
31
+ ]
32
+
33
+ [tool.pytest.ini_options]
34
+ testpaths = ["tests"]
35
+ pythonpath = ["."]
36
+ asyncio_mode = "auto"
37
+
38
+ [tool.ruff]
39
+ target-version = "py311"
40
+ line-length = 100
41
+
42
+ [tool.ruff.lint]
43
+ select = ["E", "F", "I", "UP", "B"]
44
+
45
+ [tool.ruff.lint.isort]
46
+ known-first-party = ["bpi", "tools"]
47
+
48
+ [tool.mypy]
49
+ python_version = "3.11"
50
+ strict = true
51
+ files = ["src/bpi", "tools/migration"]
@@ -0,0 +1,29 @@
1
+ from .client import AsyncBpiClient
2
+ from .errors import (
3
+ ApiError,
4
+ AuthenticationError,
5
+ BpiError,
6
+ ClientClosedError,
7
+ HttpStatusError,
8
+ InvalidParameterError,
9
+ MissingDataError,
10
+ ResponseDecodeError,
11
+ TransportError,
12
+ UnsupportedResponseError,
13
+ )
14
+ from .session import Account
15
+
16
+ __all__ = [
17
+ "Account",
18
+ "ApiError",
19
+ "AsyncBpiClient",
20
+ "AuthenticationError",
21
+ "BpiError",
22
+ "ClientClosedError",
23
+ "HttpStatusError",
24
+ "InvalidParameterError",
25
+ "MissingDataError",
26
+ "ResponseDecodeError",
27
+ "TransportError",
28
+ "UnsupportedResponseError",
29
+ ]
@@ -0,0 +1 @@
1
+ """Internal request and decoding implementation."""