Skip to main content

bpi_rs/creativecenter/
statistics_data.rs

1//! 创作中心统计数据 API
2//!
3//! [参考文档](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/creativecenter/statistics&data.md)
4
5use serde::{ Deserialize, Serialize };
6
7use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
8
9/// UP主视频状态数据
10#[derive(Debug, Serialize, Clone, Deserialize)]
11pub struct UpStatData {
12    /// 新增投币数
13    #[serde(rename = "inc_coin")]
14    pub inc_coin: i64,
15
16    /// 新增充电数
17    #[serde(rename = "inc_elec")]
18    pub inc_elec: i64,
19
20    /// 新增收藏数
21    #[serde(rename = "inc_fav")]
22    pub inc_fav: i64,
23
24    /// 新增点赞数
25    #[serde(rename = "inc_like")]
26    pub inc_like: i64,
27
28    /// 新增分享数
29    #[serde(rename = "inc_share")]
30    pub inc_share: i64,
31
32    /// 新增播放数
33    #[serde(rename = "incr_click")]
34    pub incr_click: i64,
35
36    /// 新增弹幕数
37    #[serde(rename = "incr_dm")]
38    pub incr_dm: i64,
39
40    /// 新增粉丝数
41    #[serde(rename = "incr_fans")]
42    pub incr_fans: i64,
43
44    /// 新增评论数
45    #[serde(rename = "incr_reply")]
46    pub incr_reply: i64,
47
48    /// 总计播放数
49    #[serde(rename = "total_click")]
50    pub total_click: i64,
51
52    /// 总计投币数
53    #[serde(rename = "total_coin")]
54    pub total_coin: i64,
55
56    /// 总计弹幕数
57    #[serde(rename = "total_dm")]
58    pub total_dm: i64,
59
60    /// 总计充电数
61    #[serde(rename = "total_elec")]
62    pub total_elec: i64,
63
64    /// 总计粉丝数
65    #[serde(rename = "total_fans")]
66    pub total_fans: i64,
67
68    /// 总计收藏数
69    #[serde(rename = "total_fav")]
70    pub total_fav: i64,
71
72    /// 总计点赞数
73    #[serde(rename = "total_like")]
74    pub total_like: i64,
75
76    /// 总计评论数
77    #[serde(rename = "total_reply")]
78    pub total_reply: i64,
79
80    /// 总计分享数
81    #[serde(rename = "total_share")]
82    pub total_share: i64,
83}
84
85/// 单个视频对比数据
86#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct ArchiveCompareItem {
89    /// av号
90    pub aid: i64,
91    /// bv号
92    pub bvid: String,
93    /// 封面 url
94    pub cover: String,
95    /// 标题
96    pub title: String,
97    /// 发布时间(秒级时间戳)
98    pub pubtime: i64,
99    /// 视频长度(秒)
100    pub duration: i64,
101    pub stat: Stat,
102    #[serde(rename = "is_only_self")]
103    pub is_only_self: bool,
104    #[serde(rename = "hour_stat")]
105    pub hour_stat: HourStat,
106}
107
108#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct Stat {
111    #[serde(rename = "not_ready_field")]
112    pub not_ready_field: serde_json::Value,
113    /// 播放数
114    pub play: i64,
115    pub vt: i64,
116    // ===== 百分比类指标,B站返回一般是整数,100 表示 1% =====
117    /// 完播比
118    #[serde(rename = "full_play_ratio")]
119    pub full_play_ratio: i64,
120    /// 游客播放数占比
121    #[serde(rename = "play_viewer_rate")]
122    pub play_viewer_rate: i64,
123    #[serde(rename = "play_viewer_rate_med")]
124    pub play_viewer_rate_med: i64,
125    /// 粉丝观看率
126    #[serde(rename = "play_fan_rate")]
127    pub play_fan_rate: i64,
128    #[serde(rename = "play_fan_rate_med")]
129    pub play_fan_rate_med: i64,
130    #[serde(rename = "active_fans_rate")]
131    pub active_fans_rate: i64,
132    #[serde(rename = "active_fans_med")]
133    pub active_fans_med: i64,
134    /// 封标点击率
135    #[serde(rename = "tm_rate")]
136    pub tm_rate: i64,
137    /// 自己平均封标点击率
138    #[serde(rename = "tm_rate_med")]
139    pub tm_rate_med: i64,
140    /// 同类up粉丝封标点击率
141    #[serde(rename = "tm_fan_simi_rate_med")]
142    pub tm_fan_simi_rate_med: i64,
143    /// 同类up游客封标点击率
144    #[serde(rename = "tm_viewer_simi_rate_med")]
145    pub tm_viewer_simi_rate_med: i64,
146    /// 粉丝封标点击率
147    #[serde(rename = "tm_fan_rate")]
148    pub tm_fan_rate: i64,
149    /// 游客封标点击率
150    #[serde(rename = "tm_viewer_rate")]
151    pub tm_viewer_rate: i64,
152    /// 封标点击率超过n%同类稿件
153    #[serde(rename = "tm_pass_rate")]
154    pub tm_pass_rate: i64,
155    /// 粉丝封标点击率超过n%同类稿件
156    #[serde(rename = "tm_fan_pass_rate")]
157    pub tm_fan_pass_rate: i64,
158    /// 游客封标点击率超过n%同类稿件
159    #[serde(rename = "tm_viewer_pass_rate")]
160    pub tm_viewer_pass_rate: i64,
161    /// 3秒退出率
162    #[serde(rename = "crash_rate")]
163    pub crash_rate: i64,
164    #[serde(rename = "crash_rate_med")]
165    pub crash_rate_med: i64,
166    /// 同类up粉丝3秒退出率
167    #[serde(rename = "crash_fan_simi_rate_med")]
168    pub crash_fan_simi_rate_med: i64,
169    /// 同类up游客3秒退出率
170    #[serde(rename = "crash_viewer_simi_rate_med")]
171    pub crash_viewer_simi_rate_med: i64,
172    /// 粉丝3秒退出率
173    #[serde(rename = "crash_fan_rate")]
174    pub crash_fan_rate: i64,
175    /// 游客3秒退出率
176    #[serde(rename = "crash_viewer_rate")]
177    pub crash_viewer_rate: i64,
178    /// 互动率
179    #[serde(rename = "interact_rate")]
180    pub interact_rate: i64,
181    #[serde(rename = "interact_rate_med")]
182    pub interact_rate_med: i64,
183    /// 同类up粉丝互动率
184    #[serde(rename = "interact_fan_simi_rate_med")]
185    pub interact_fan_simi_rate_med: i64,
186    /// 同类up游客互动率
187    #[serde(rename = "interact_viewer_simi_rate_med")]
188    pub interact_viewer_simi_rate_med: i64,
189    /// 粉丝互动率
190    #[serde(rename = "interact_fan_rate")]
191    pub interact_fan_rate: i64,
192    /// 游客互动率
193    #[serde(rename = "interact_viewer_rate")]
194    pub interact_viewer_rate: i64,
195    /// 平均播放时间(目前总是0)
196    #[serde(rename = "avg_play_time")]
197    pub avg_play_time: i64,
198    #[serde(rename = "avg_play_time_int")]
199    pub avg_play_time_int: i64,
200    /// 涨粉
201    #[serde(rename = "total_new_attention_cnt")]
202    pub total_new_attention_cnt: i64,
203    /// 播转粉率
204    #[serde(rename = "play_trans_fan_rate")]
205    pub play_trans_fan_rate: i64,
206    /// 其他up平均播转粉率
207    #[serde(rename = "play_trans_fan_rate_med")]
208    pub play_trans_fan_rate_med: i64,
209    /// 点赞数
210    pub like: i64,
211    /// 评论数
212    pub comment: i64,
213    /// 弹幕数
214    pub dm: i64,
215    /// 收藏数
216    pub fav: i64,
217    /// 投币数
218    pub coin: i64,
219    /// 分享数
220    pub share: i64,
221    #[serde(rename = "unfollow")]
222    pub unfollow: i64,
223    #[serde(rename = "tm_star")]
224    pub tm_star: i64,
225    #[serde(rename = "tm_viewer_star")]
226    pub tm_viewer_star: i64,
227    #[serde(rename = "tm_fan_star")]
228    pub tm_fan_star: i64,
229    #[serde(rename = "crash_p50")]
230    pub crash_p50: i64,
231    #[serde(rename = "crash_viewer_p50")]
232    pub crash_viewer_p50: i64,
233    #[serde(rename = "crash_fan_p50")]
234    pub crash_fan_p50: i64,
235    #[serde(rename = "interact_p50")]
236    pub interact_p50: i64,
237    #[serde(rename = "interact_viewer_p50")]
238    pub interact_viewer_p50: i64,
239    #[serde(rename = "interact_fan_p50")]
240    pub interact_fan_p50: i64,
241    #[serde(rename = "play_trans_fan_p50")]
242    pub play_trans_fan_p50: i64,
243}
244
245#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
246#[serde(rename_all = "camelCase")]
247pub struct HourStat {
248    #[serde(rename = "not_ready_field")]
249    pub not_ready_field: serde_json::Value,
250    /// 播放数
251    pub play: i64,
252    pub vt: i64,
253    /// 点赞数
254    pub like: i64,
255    /// 评论数
256    pub comment: i64,
257    /// 弹幕数
258    pub dm: i64,
259    /// 收藏数
260    pub fav: i64,
261    /// 投币数
262    pub coin: i64,
263    /// 分享数
264    pub share: i64,
265    /// 封标点击率超过n%同类稿件
266    #[serde(rename = "tm_pass_rate")]
267    pub tm_pass_rate: i64,
268    /// 互动率
269    #[serde(rename = "interact_rate")]
270    pub interact_rate: i64,
271    #[serde(rename = "tm_star")]
272    pub tm_star: i64,
273}
274
275/// UP主视频数据比较
276#[derive(Debug, Serialize, Clone, Deserialize)]
277pub struct ArchiveCompareData {
278    pub list: Vec<ArchiveCompareItem>,
279}
280
281/// UP主专栏状态数据
282#[derive(Debug, Serialize, Clone, Deserialize)]
283pub struct UpArticleStatData {
284    /// 总计阅读数
285    pub view: i64,
286    /// 总计评论数
287    pub reply: i64,
288    /// 总计点赞数
289    pub like: i64,
290    /// 总计投币数
291    pub coin: i64,
292    /// 总计收藏数
293    pub fav: i64,
294    /// 总计分享数
295    pub share: i64,
296    /// 新增阅读数
297    #[serde(rename = "incr_view")]
298    pub incr_view: i64,
299    /// 新增评论数
300    #[serde(rename = "incr_reply")]
301    pub incr_reply: i64,
302    /// 新增点赞数
303    #[serde(rename = "incr_like")]
304    pub incr_like: i64,
305    /// 新增投币数
306    #[serde(rename = "incr_coin")]
307    pub incr_coin: i64,
308    /// 新增收藏数
309    #[serde(rename = "incr_fav")]
310    pub incr_fav: i64,
311    /// 新增分享数
312    #[serde(rename = "incr_share")]
313    pub incr_share: i64,
314}
315
316/// UP主视频数据增量趋势项
317#[derive(Debug, Serialize, Clone, Deserialize)]
318pub struct VideoTrendItem {
319    /// 对应时间戳(前一天8:00)
320    pub date_key: i64,
321    /// 增加数量,数据类型决定
322    pub total_inc: i64,
323}
324
325/// UP主专栏数据增量趋势项
326#[derive(Debug, Serialize, Clone, Deserialize)]
327pub struct ArticleTrendItem {
328    /// 对应时间戳(前一天8:00)
329    pub date_key: i64,
330    /// 增加数量,数据类型决定
331    pub total_inc: i64,
332}
333
334/// 播放来源情况(播放方式)
335#[derive(Debug, Serialize, Clone, Deserialize)]
336pub struct PageSource {
337    /// 通过动态
338    pub dynamic: i64,
339    /// 其他方式
340    pub other: i64,
341    /// 通过推荐列表
342    #[serde(rename = "related_video")]
343    pub related_video: i64,
344    /// 通过搜索
345    pub search: i64,
346    /// 空间列表播放
347    pub space: i64,
348    /// 天马来源(APP推荐信息流)
349    pub tenma: i64,
350}
351
352/// 播放平台占比
353#[derive(Debug, Serialize, Clone, Deserialize)]
354pub struct PlayProportion {
355    /// 安卓端
356    pub android: i64,
357    /// 移动端H5
358    pub h5: i64,
359    /// iOS端
360    pub ios: i64,
361    /// 站外
362    pub out: i64,
363    /// PC网页版
364    pub pc: i64,
365}
366
367/// 播放来源占比数据
368#[derive(Debug, Serialize, Clone, Deserialize)]
369pub struct PlaySourceData {
370    pub page_source: PageSource,
371    pub play_proportion: PlayProportion,
372}
373
374/// 播放地区提示信息
375#[derive(Debug, Serialize, Clone, Deserialize)]
376pub struct Period {
377    pub module_one: Option<String>,
378    pub module_two: Option<String>,
379    pub module_three: Option<String>,
380    pub module_four: Option<String>,
381}
382
383/// 播放地区情况(粉丝或路人)
384pub type ViewerAreaMap = std::collections::HashMap<String, i64>;
385
386#[derive(Debug, Serialize, Clone, Deserialize)]
387pub struct ViewerArea {
388    pub fan: ViewerAreaMap,
389    pub not_fan: ViewerAreaMap,
390}
391
392/// 播放数据情况(粉丝或路人)
393#[derive(Debug, Serialize, Clone, Deserialize)]
394pub struct ViewerBaseDetail {
395    pub male: i64,
396    pub female: i64,
397    #[serde(rename = "age_one")]
398    pub age_one: i64,
399    #[serde(rename = "age_two")]
400    pub age_two: i64,
401    #[serde(rename = "age_three")]
402    pub age_three: i64,
403    #[serde(rename = "age_four")]
404    pub age_four: i64,
405    #[serde(rename = "plat_pc")]
406    pub plat_pc: i64,
407    #[serde(rename = "plat_h5")]
408    pub plat_h5: i64,
409    #[serde(rename = "plat_out")]
410    pub plat_out: i64,
411    #[serde(rename = "plat_ios")]
412    pub plat_ios: i64,
413    #[serde(rename = "plat_android")]
414    pub plat_android: i64,
415    #[serde(rename = "plat_other_app")]
416    pub plat_other_app: i64,
417}
418
419#[derive(Debug, Serialize, Clone, Deserialize)]
420pub struct ViewerBase {
421    pub fan: ViewerBaseDetail,
422    pub not_fan: ViewerBaseDetail,
423}
424
425/// 播放分布情况
426#[derive(Debug, Serialize, Clone, Deserialize)]
427pub struct ViewerData {
428    pub period: Period,
429    pub viewer_area: ViewerArea,
430    pub viewer_base: ViewerBase,
431}
432
433impl BpiClient {
434    /// 获取 UP 主视频状态数据
435    ///
436    /// 获取 UP 主的视频统计数据,包括播放、点赞、投币、收藏等数据。
437    ///
438    /// # 文档
439    /// [获取 UP 主视频状态数据](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/creativecenter/statistics&data.md#获取-up-主视频状态数据)
440    pub async fn up_stat(&self) -> Result<BpiResponse<UpStatData>, BpiError> {
441        self
442            .get("https://member.bilibili.com/x/web/index/stat")
443            .send_bpi("获取UP主视频状态数据").await
444    }
445
446    /// 获取 UP 主视频数据比较
447    ///
448    /// 获取 UP 主视频的数据对比分析,包括播放量、互动率等指标。
449    ///
450    /// # 参数
451    /// | 名称 | 类型 | 说明 |
452    /// | ---- | ---- | ---- |
453    /// | `t` | `Option<i64>` | 时间戳,可选 |
454    /// | `size` | `Option<i64>` | 最近 N 条视频,可选,默认 5 |
455    ///
456    /// # 文档
457    /// [获取 UP 主视频数据比较](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/creativecenter/statistics&data.md#获取-up-主视频数据比较)
458    pub async fn up_archive_compare(
459        &self,
460        t: Option<i64>,
461        size: Option<i64>
462    ) -> Result<BpiResponse<ArchiveCompareData>, BpiError> {
463        let mut req = self.get("https://member.bilibili.com/x/web/data/archive_diagnose/compare");
464
465        if let Some(t) = t {
466            req = req.query(&[("t", t)]);
467        }
468        if let Some(size) = size {
469            req = req.query(&[("size", size)]);
470        }
471
472        req.send_bpi("获取UP主视频数据比较").await
473    }
474
475    /// 获取UP主专栏状态数据
476    ///
477    /// 获取 UP 主专栏的统计数据,包括阅读、评论、点赞等数据。
478    ///
479    /// # 文档
480    /// [获取UP主专栏状态数据](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/creativecenter/statistics&data.md#获取up主专栏状态数据)
481    pub async fn up_article_stat(&self) -> Result<BpiResponse<UpArticleStatData>, BpiError> {
482        self
483            .get("https://member.bilibili.com/x/web/data/article")
484            .send_bpi("获取UP主专栏状态数据").await
485    }
486
487    /// 获取UP主视频数据增量趋势
488    ///
489    /// 获取 UP 主视频数据的增量趋势,支持多种数据类型。
490    ///
491    /// # 参数
492    /// | 名称 | 类型 | 说明 |
493    /// | ---- | ---- | ---- |
494    /// | `type_code` | i64 | 数据类型:1播放 2弹幕 3评论 4分享 5投币 6收藏 7充电 8点赞 |
495    ///
496    /// # 文档
497    /// [获取UP主视频数据增量趋势](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/creativecenter/statistics&data.md#获取up主视频数据增量趋势)
498    pub async fn up_video_trend(
499        &self,
500        type_code: i64
501    ) -> Result<BpiResponse<Vec<VideoTrendItem>>, BpiError> {
502        self
503            .get("https://member.bilibili.com/x/web/data/pandect")
504            .query(&[("type", type_code)])
505            .send_bpi("获取UP主视频数据增量趋势").await
506    }
507
508    /// 获取UP主专栏数据增量趋势
509    ///
510    /// 获取 UP 主专栏数据的增量趋势,支持多种数据类型。
511    ///
512    /// # 参数
513    /// | 名称 | 类型 | 说明 |
514    /// | ---- | ---- | ---- |
515    /// | `type_code` | i64 | 数据类型:1阅读 2评论 3分享 4投币 5收藏 6点赞 |
516    ///
517    /// # 文档
518    /// [获取UP主专栏数据增量趋势](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/creativecenter/statistics&data.md#获取up主专栏数据增量趋势)
519    pub async fn up_article_trend(
520        &self,
521        type_code: i64
522    ) -> Result<BpiResponse<Vec<ArticleTrendItem>>, BpiError> {
523        self
524            .get("https://member.bilibili.com/x/web/data/article/thirty")
525            .query(&[("type", type_code)])
526            .send_bpi("获取UP主专栏数据增量趋势").await
527    }
528
529    /// 获取播放来源占比
530    ///
531    /// 获取视频播放来源的占比情况,包括动态、搜索、推荐等来源。
532    ///
533    /// # 文档
534    /// [获取播放来源占比](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/creativecenter/statistics&data.md#获取播放来源占比)
535    #[allow(dead_code)]
536    async fn up_play_source(&self) -> Result<BpiResponse<PlaySourceData>, BpiError> {
537        self
538            .get("https://member.bilibili.com/x/web/data/playsource")
539            .with_bilibili_headers()
540            .send_bpi("获取播放来源占比情况").await
541    }
542
543    /// 获取播放分布情况
544    ///
545    /// 获取视频播放的分布情况,包括粉丝与路人的观看数据。
546    ///
547    /// # 文档
548    /// [获取播放分布情况](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/creativecenter/statistics&data.md#获取播放分布情况)
549    pub async fn up_viewer_data(&self) -> Result<BpiResponse<ViewerData>, BpiError> {
550        self.get("https://member.bilibili.com/x/web/data/base").send_bpi("获取播放分布情况").await
551    }
552}
553
554#[cfg(test)]
555mod tests {
556    use super::*;
557    use tracing::info;
558
559    #[tokio::test]
560    async fn test_up_stat() -> Result<(), Box<BpiError>> {
561        let bpi = BpiClient::new();
562        let data = bpi.up_stat().await?.into_data()?;
563        info!("UP主视频状态数据: {:?}", data);
564        Ok(())
565    }
566
567    #[tokio::test]
568    async fn test_archive_compare() -> Result<(), Box<BpiError>> {
569        let bpi = BpiClient::new();
570        let data = bpi.up_archive_compare(None, Some(3)).await?.into_data()?;
571        info!("UP主视频数据比较: {:?}", data);
572        Ok(())
573    }
574
575    #[tokio::test]
576    async fn test_up_article_stat() -> Result<(), Box<BpiError>> {
577        let bpi = BpiClient::new();
578        let data = bpi.up_article_stat().await?.into_data()?;
579        info!("UP主专栏状态数据: {:?}", data);
580        Ok(())
581    }
582
583    #[tokio::test]
584    async fn test_video_trend() -> Result<(), Box<BpiError>> {
585        let bpi = BpiClient::new();
586        let data = bpi.up_video_trend(1).await?.into_data()?; // 1 = 播放
587        info!("UP主视频数据增量趋势: {:?}", data);
588        Ok(())
589    }
590
591    #[tokio::test]
592    async fn test_article_trend() -> Result<(), Box<BpiError>> {
593        let bpi = BpiClient::new();
594        let data = bpi.up_article_trend(1).await?.into_data()?; // 1 = 阅读
595        info!("UP主专栏数据增量趋势: {:?}", data);
596        Ok(())
597    }
598
599    #[tokio::test]
600    async fn test_viewer_data() -> Result<(), Box<BpiError>> {
601        let bpi = BpiClient::new();
602        let data = bpi.up_viewer_data().await?.into_data()?;
603        info!("播放分布情况: {:?}", data);
604        Ok(())
605    }
606}