#1 - 2019-5-10 13:37
astrea (-Wish upon a Shooting Star-)
总结一下我在使用Bangumi API中的一些坑,希望能帮到以后使用API的人。如果你发现了什么我没注意到的坑欢迎补充,我会补充到主楼。


# 如何阅读本指南

每个条目以下面其中的一个词开头:
*要* 开头所表述的内容是要准守的。 几乎从来不会有合理的理由来违反这些条目。

*推荐* 开头所表述的内容你应该准守。 但是在有些情况下,可能有更好的或者更合理的做法。 当你不遵守这些条目的时候,请确保你有合理的 理由这么做。

*避免* 开头所表述的内容和“推荐”相反: 如果没有足够好的理由,你不应该这么做。

*考虑* 开头所表述的内容你可以遵守, 也可以不准守,根据你的实际情况来选择。

*注意* Bangumi API中需要注意的坑

本格式参考 [Effective Dart](http://dart.goodev.org/guides/language/effective-dart)

# 正文
:在Oauth认证的过程中,出错后重试你的请求至少一次。
理由:具体原因就是https://github.com/bangumi/api/b ... -raw/How-to-Auth.md 里提到的`POST https://bgm.tv/oauth/access_token`步骤,这一步有大概(体感)10%~20%的几率返回一个`httpStatus=500`和`null`导致认证失败,要解决也很简单,直接带着原来的所有数据重试一次即可。

: 在使用搜索API `/search/subject/{keywords}` 时,在每个请求后附加`chii_searchDateLine=[填入搜索时间]`cookie。
理由:避免过于频繁的API搜索是我们应该遵守的,但实际上在使用过程中会发生非常奇妙的事情:请尝试`GET https://api.bgm.tv/search/subject/,,,`
    在不附加`chii_searchDateLine=[填入搜索时间]` cookie的情况下会无条件返回“搜索过于频繁”的html。目前只有一部分关键词会返回html,但因为不清楚触发条件,最好的做法是在每个搜索请求中附加此cookie
    参考bug https://github.com/bangumi/api/issues/43


避免 :使用"批量更新收视进度"API(`/subject/{subject_id}/update/watched_eps`)更新非书籍的进度
理由:先看官方文档,此API的作用非常简单,更新进度到指定话数/卷数。
对书籍这是唯一一个批量更新进度的方法也没什么坑,不多说了。
动画和三次元就有坑了,此API有众多令人惊讶的行为:
    1. 对于动画话数不是从第一话开始的作品,比如[一拳超人第二季](https://bgm.tv/subject/193619)从第13话开始,但是如果尝试传入`watched_eps=13`则会把用户的第13话到第25话全部更新为看过。没错,这里的`watched_eps=${x}`准确的说指的是“从这部作品的真正的第一话(对于一拳超人这是第13话)开始往后数x话,并把他们全部标为看过”。
    2. “看到”对非正篇的章节无效,还是拿一拳超人第二季举例,它有2个sp,第1话和第12.5话,我们知道1的坑了,那么现在如果尝试把前三话更新为看过,传入`watched_eps=3`,会被更新进度的是13,14和15话,而非第1,12.5和第13话。换句话说批量更新只对正篇起效。
    3. `watched_eps=${x}`之后的话数进度会被清空,这一行为对所有动画/三次元作品均起效。比如[皿三昧](https://bgm.tv/subject/239646),假设用户目前只标记第5话为看过,然后程序尝试通过批量更新收视进度API标记前三话为看过,会导致第5话进度直接消失。

    总结一下,"批量更新收视进度"的真实作用为“从这部作品的真正的第一话(而非作品本身的话数)开始更新x话正篇为看过,同时把x话之后的进度全部清空”
   
    那么正确进行“看过”操作的做法是什么?我的建议是对`/ep/{id}/status/{status}`批量传入需要更新的章节id。

摘抄自Bangumi的代码
if (ep_status == 'WatchedTill') {
                var epLiIndex = epAs.index(epBtn);
                var ids = new Array();
                for (var i = 0; i <= epLiIndex; i++) {
                    ids[i] = epAs[i].id.split('_')[1];
                }
                params['ep_id'] = ids.toString();
                if (OtherEps[subject_id] != undefined) {
                    params['ep_id'] = OtherEps[subject_id] + ',' + params['ep_id'];
                }
            }


另外需要注意,/ep/{id}/status/{status} status传入的值只对query的id有效,post里的ep status只要不是抛弃就总会变成“看过”(@ekibun)。
点击“看到某话”不影响已抛弃章节的状态这一行为和网页版点击看到的结果相同(另外此请求似乎需要发送两次,否则api返回的数据不会更新)。

推荐 :在拿回API数据后对Bangumi的API和json的field进行合理的重命名。
  理由:比如`GET /user/{username}/collection`的返回值里有一个`ep_status`,这里返回了一个`int`,值的意义其实是"完成过的话数"而不是“看过的话数的状态”,过一段时间重读代码很容易忘记这个field的真实含义,如果在开发初期就在拿回数据后重命名为更准确的名字比如`completedEpisodesCount`可以避免很多困惑。类似的field还有很多,建议不要怕麻烦在初期就想好一个清晰的重命名,否则到了后期需要重命名时只会更麻烦。

考虑 :在更新作品tag时,手动过滤非法字符
理由:Bangumi的服务器会帮我们过滤掉一部分的非法字符而非所有,有时候非法的参数会导致返回一个html,请参见https://github.com/bangumi/api/issues/27

考虑 :把bangumi返回的code作为辅助状态参考信息而非主要状态信息,并永远以Bangumi的返回值里不存在`code`为前提假设进行开发。
理由: 请参考 https://github.com/bangumi/api/issues/13
Bangumi会尽量尝试永远返回`HTTPStatusCode=200`并在返回的json里添加一个顶层field`code`来展示真实状态。但实际api并不会永远返回这个`code`。
比如“用户收视进度” `/user/{username}/progress` 返回的就是一个`List`或者`null`,其中并不含`code`但不管是`List`还是`null`其实都代表响应成功。

注意 :`GET /collection/{subject_id}`的返回值里有一个field叫`tags`,返回的是一个由`String`组成的`List`。但是当这个条目用户没有输入任何标签时,tags返回的是一个包含一个空字符串的`List`(`[""]`),而非`null`或者`[]`

注意 : `POST /collection/{subject_id}/{action}`里,请求的field有一个叫做`privacy`决定这是否是一个隐私收藏,但返回值里这个field则被称作`private`

注意 : 搜索API `/search/subject/{keywords}`里有一个`max_results`可以指定返回条目的数目,这里的`max_results`就是字面意思`max_results`,有些条目不会被返回,所以`max_results=10`可能只返回8个条目。

注意(@trim21): calendar里面的name_cn是经过html escape过的,需要你在使用的时候unescape

注意 :Bangumi的API偶尔会导致串号,既认证成功后尝试用token更新用户状态时,会导致其他用户的状态被更新。
  目前没有发现避免的方法,不过这个问题并不频繁。串号之后访问`https://bgm.tv/oauth/token_status`会拿到串号用户的信息,可以在每次刷新token后验证此信息。


目前我能想到的就这些问题,欢迎补充。

顺便打个广告,我们(BangumiN, https://github.com/edwardez/BangumiN/tree/develop/app )正在开发基于flutter的Bangumi客户端,计划发布iOS/Android双端,除了官方API的功能之外还通过解析html实现了查看时间线,查看用户主页和所有收藏,搜索人物,刷超展开,查看作品所有评论,一键生成作品评论海报等功能,欢迎有使用flutter经验的朋友来贡献代码:P
#2 - 2019-5-10 13:58
#3 - 2019-5-10 15:54
/ep/{id}/status/{status} status传入的值只对query的id有效,post里的ep无论status是任何值总会变成“看过”

另外很好奇你们怎么管理两套登录状态的,毕竟API即使每次都refresh还是会掉。。。
#3-1 - 2019-5-11 03:37
astrea
第一个加上去了

原来会掉吗,侠坐茶说
#3-2 - 2019-5-11 05:19
ekibun
astrea 说: 第一个加上去了 原来会掉吗,侠坐茶说
我不清楚是不是我代码有问题,不用api有段时间了
现象大概是在refreshtoken能返回token的情况下(好像还能正确获取到进度信息),会偶然出现无法获取和设置收藏状态。
这时候重新授权问题消失。
检查了一下,获取到的token应该是没问题的,因为每次都会根据token的user_id更新用户名和头像:https://github.com/ekibun/Bangum ... ainPresenter.kt#L77
#3-3 - 2019-5-11 12:11
astrea
ekibun 说: 我不清楚是不是我代码有问题,不用api有段时间了
现象大概是在refreshtoken能返回token的情况下(好像还能正确获取到进度信息),会偶然出现无法获取和设置收藏状态。
这时候重新授权问题消失...
其实刷新授权我本身写的也有点小bug,修好了看看
#3-4 - 2019-5-13 01:44
astrea
> post里的ep无论status是任何值总会变成“看过”

我刚才突然发现如果status是抛弃那就不受影响..改了一下这句话
#4 - 2019-5-10 16:13
(プリキュアなりたい)
楼主辛苦了(
#5 - 2019-5-10 17:18
第二个学到了
#6 - 2019-5-10 17:25
我个人觉得这个行为就API说明并没有问题,毕竟说明是更新进度到指定话数/卷数(bgm38)
1. 对于动画话数不是从第一话开始的作品,比如[一拳超人第二季](https://bgm.tv/subject/193619)从第13话开始,但是如果尝试传入`watched_eps=13`则会把用户的第13话到第25话全部更新为看过。没错,这里的`watched_eps=${x}`准确的说指的是“从这部作品的真正的第一话(对于一拳超人这是第13话)开始往后数x话,并把他们全部标为看过”。
至于第一话还是第13话的问题,已经有其他讨论了: https://bgm.tv/group/topic/350641
#6-1 - 2019-5-11 00:24
bangumi大西王
这个api是有问题的,因为subject/${subject_id}中返回值有一个sort字段,这个字段对应着bgm网页上显示的集数

如果你按照sort去调用这个api,就会踩坑

这个api现在的行为实际上是网页右侧的修改完成度,而不是所描述的修改播放进度

在某集tooltip中调用看到本集的话,不会清空某集之后的进度的

如果这个api叫做修改条目完成度的话倒是一点问题都没有
#6-2 - 2019-5-11 02:55
astrea
其实你可以注意到这三点是有先后顺序的,第一点是有争议(到底该传入1还是13),到了第三点则是严重的问题导致此api直接不可用(会重置用户进度状态),所以综合来说我给出了强烈建议避免使用此api更新动画进度的建议。
回到第一点,要看你怎么定义有问题,对我来说容易被误解容易出错的说明就是有问题的,就像trim21所说在任何api里都是拿不到一拳超人第一话其实是1这个数据的,同时api又会返回一个sort(话数)=13,我相信不止我一个人会误解这一点,所以就在建议里说明了一下。
#7 - 2019-5-10 17:32
Nice.
#8 - 2019-5-11 00:17
(天生万物以养人,人无一物以报天)
我能理解你想用markdown,但是bgm不支持啊(bgm38)

建议:在Oauth认证过程中,如果是bgm服务器那边,可以直接把用户重定向回授权链接,这样在用户浏览器中的行为是他点了bgm的授权图标但是没反应,而不是出了什么错需要重新进行授权(bgm38)

bgm的社区化开发还在吗 @sai
#8-1 - 2019-5-11 00:30
ekibun
要不写个md转bbcode的插件(伸手
#8-2 - 2019-5-11 00:32
bangumi大西王
ekibun 说: 要不写个md转bbcode的插件(伸手
我动过这个念头,发现没有现成的可调用的库,没写
#8-3 - 2019-5-11 02:46
astrea
用markdown编辑器写的这个懒得改了,我相信愿意读这篇文章的开发者都有脑内渲染markdown的能力:P
如果要做发帖估计我会研究一下怎么markdown转bbcode,不过这块目前app完全没做,不知道第一版发出来之前有没有时间研究一下
#8-4 - 2019-5-11 02:58
bangumi大西王
astrea 说: 用markdown编辑器写的这个懒得改了,我相信愿意读这篇文章的开发者都有脑内渲染markdown的能力:P
如果要做发帖估计我会研究一下怎么markdown转bbcode,不过这块目前app完全没做...
https://www.trim21.cn/md2bbc
(((
#8-5 - 2019-5-11 02:59
bangumi大西王
ekibun 说: 要不写个md转bbcode的插件(伸手
写插件太麻烦了,写了个api
#8-6 - 2019-5-11 04:08
astrea
Trim21 说: https://www.trim21.cn/md2bbc
(((
看着还不错!是怎么实现的?
#8-7 - 2019-5-11 04:13
bangumi大西王
astrea 说: 看着还不错!是怎么实现的?
https://github.com/Trim21/person ... 2e047/app/md2bbc.py
#8-8 - 2019-5-11 11:23
ekibun
Trim21 说: 写插件太麻烦了,写了个api
效率极高,接下来是bbcode转md(再次伸手
#8-9 - 2019-5-11 11:24
bangumi大西王
ekibun 说: 效率极高,接下来是bbcode转md(再次伸手
多喝热水少做梦.jpg
#8-10 - 2019-5-11 12:16
bangumi大西王
ekibun 说: 效率极高,接下来是bbcode转md(再次伸手
其实为啥会有bbcode转markdown的需求,我想不到任何使用的场景(bgm38)

我之前想过用Markdown来发帖但是没有类似的转换工具,所以才写了一个。
但bbcode转markdown到底要什么情况下从能用的到,会bbcode又不会markdown的人又怎么会有markdown的使用需求。。。
#8-11 - 2019-5-11 12:20
ekibun
Trim21 说: 其实为啥会有bbcode转markdown的需求,我想不到任何使用的场景 我之前想过用Markdown来发帖但是没有类似的转换工具,所以才写了一个。 但bbcode转markdown到底要什么情况下从能用的到,会bbcode又不会markdown的人又怎么会有markdown的使用需求。。。
修改帖子拿到的是bbcode啊
混合编辑似乎也不错
#8-12 - 2019-5-11 12:22
bangumi大西王
ekibun 说: 修改帖子拿到的是bbcode啊
混合编辑似乎也不错
那我建议你保留原本的markdown再修改之后再转换一次
#8-13 - 2019-5-18 21:21
Rくん
我觉得大家都是目力markdown的,就像看到“b38”的时候,脑子里就已经出现那个影响了。
#8-14 - 2019-5-18 23:07
bangumi大西王
Rくん 说: 我觉得大家都是目力markdown的,就像看到“b38”的时候,脑子里就已经出现那个影响了。
我人眼渲染markdown全靠编辑器的语法高亮,如果大家都是同一个颜色我就抓瞎了
#9 - 2019-5-11 02:40
(天生万物以养人,人无一物以报天)
再补充一个,calendar里面的name_cn是经过html escape过的,需要你在使用的时候unescape
#9-1 - 2019-5-11 02:47
astrea
加上去了!
#10 - 2019-5-16 17:13
发现一个不知道算不算bug的feature:
仅自己可见的收藏条目,用/user/:user_id/collection貌似可以获取到……这个接口是不需要身份验证的
#10-1 - 2019-5-16 17:25
bangumi大西王
仅自己可见恐怕说的是吐槽。。。
#10-2 - 2019-5-16 20:59
dan
Trim21 说: 仅自己可见恐怕说的是吐槽。。。
应该不是,我退出登录之后看自己的“在看”是没有“仅自己可见”的条目的
#10-3 - 2019-5-17 01:09
bangumi大西王
Dan 说: 应该不是,我退出登录之后看自己的“在看”是没有“仅自己可见”的条目的
在你发帖的时候,我看到你在看的动画是18条,在看的电视剧1条。然后API返回数据里是19个元素,应该是对应的吧。
#10-4 - 2019-5-17 20:48
dan
Trim21 说: 在你发帖的时候,我看到你在看的动画是18条,在看的电视剧1条。然后API返回数据里是19个元素,应该是对应的吧。
虽然写的是18条,但是数起来是17(
#10-5 - 2019-5-17 20:53
bangumi大西王
Dan 说: 虽然写的是18条,但是数起来是17(
ylrc
不过我刚刚发现,个人主页这个预览也有可能会暴露(bgm38)

比如你个人主页的在看显示了一个“向山进发第二季”的条目,但是在看的全部列表里面没有
#10-6 - 2019-5-17 22:00
dan
Trim21 说: ylrc
不过我刚刚发现,个人主页这个预览也有可能会暴露

比如你个人主页的在看显示了一个“向山进发第二季”的条目,但是在看的全部列表里面没有
主页那个应该是第三季?
#10-7 - 2019-5-17 22:01
bangumi大西王
Dan 说: 主页那个应该是第三季?
我这里看到的是主页里第二季和第三季都有,第三季在第一行,第二季在第二行
#10-8 - 2019-5-18 02:48
astrea
试了下确实有这个问题...加上去了
我想了下这其实不算一个坑更像是一个bug,应该去github开个issue?
#11 - 2019-5-16 21:57
(✨️make bangumi great again✨️)
搜索的提醒用到了(bgm24)
#12 - 2019-5-18 20:53
貌似cookie那个问题不仅仅是搜索有,我自己在写的app,图片这块也是偶尔会加载失败。
#13 - 2020-3-2 20:50
个人在使用的时候出现了api响应过慢的问题,用的是retrofit+okhttp,想问下有好的解决办法吗
#14 - 2020-3-25 08:00
(天生万物以养人,人无一物以报天)
https://bangumi.github.io/api/#/ ... sername__collection

我认为的正常情况:

对于存在的用户,如果没有收藏,返回空array

实际情况:

https://mirror.api.bgm.rin.cat/user/23/collection
用户没有收藏,返回的是{code:404, error:"Not Found"}

彩蛋:

https://mirror.api.bgm.rin.cat/user/23/collection?cat=watching
返回了null
#14-1 - 2020-3-25 08:40
猫苇
因为cat是必须参数吧(bgm38)
#14-2 - 2020-3-25 08:42
bangumi大西王
猫苇 说: 因为cat是必须参数吧
对哦
主要问题是这个null
而且下面的说明里面还有“默认为在看”
#15 - 2021-1-12 08:24
(资深致郁番粉。)
mark
#16 - 2021-2-13 14:54
(水中月是天上月,眼前喵是心上喵)
马。万一以后有能用到的地方呢(bgm38)