news 2026/8/5 14:54:45

B站视频ID从AV到BV的进化史:我是怎么在API开发里被坑哭又救回来的

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
B站视频ID从AV到BV的进化史:我是怎么在API开发里被坑哭又救回来的

说实话,刚接触B站API开发的时候,我整个人是懵圈的。

那时候我还年轻,觉得写代码嘛,不就是调个接口,拿个数据,再渲染个页面,完事?太简单了。直到有一天,我想写个脚本去爬取某个UP主的历史视频列表,结果发现以前那种直接拼凑 av123456 这种URL的方式,突然就不灵了。页面跳转过去,要么报错,要么直接404。

我去查文档,发现B站搞了个大动作,把原来那种纯数字的AV号(AID),换成了一串看起来像乱码的BV号(BVID)。比如以前是 av170001,现在变成了 BV1GJ411x7h1

这不仅仅是换个名字那么简单。这背后涉及到底层数据库索引的变化、安全性的提升,还有开发者社区的一片哀嚎。今天,我就想跟大家掏心窝子聊聊,我是怎么在这个“双轨制”的坑里摔了一跤,又是怎么通过重构代码,把这个问题彻底解决的。如果你也在做B站相关的开发,或者对这种ID转换机制感兴趣,这篇文章你应该能看进去。

先说说为什么B站要搞这么一出。

早期的AV号,其实就是数据库里的自增ID。比如第一个视频是av1,第二个是av2。这种ID有个巨大的安全隐患:任何人只要遍历数字,就能把B站所有的视频都爬一遍。这就好比你的家门钥匙是1号、2号、3号这样排下去的,小偷只要拿着钥匙试一遍,你家就没了。

所以,B站引入了BV号。BV号基于Base58编码,还加上了校验位和混淆算法。你看那个 BV1GJ411x7h1,它看起来随机,但实际上它里面藏着原始的AV号。这种设计既保证了安全性,防止了恶意爬取,又保留了数据的可追溯性。

但是,对于开发者来说,这就成了噩梦。

因为B站的历史接口很多还是用AV号作为OID(Object ID)的,比如评论接口、弹幕接口。而新的视频详情接口,可能优先要求BV号。这就导致了一个尴尬的局面:你手里拿着BV号,想调评论接口,得先把它转成AV号;你手里拿着AV号,想调新的视频信息接口,又得把它转成BV号。

如果每次调用都去请求B站的服务器帮你转换,那网络延迟就受不了了。而且,频繁请求转换接口,容易被B站的风控判定为异常行为,直接封IP。

所以,最聪明的做法,就是在本地实现这个转换算法。

这里就要提到一个非常优秀的开源项目:bilibili-api。这个项目由MoyuScript维护,它在GitHub和Gitee上都有镜像。它最核心的贡献之一,就是提供了一个本地化的、高性能的AV/BV互转方案。

我研究了一下它的源码,发现这个转换算法其实挺巧妙的。它不是简单的查表,而是通过一系列位运算和字符置换来实现的。

核心逻辑在 bilibili_api/utils/aid_bvid_transformer.py 这个文件里。咱们来看看它是怎么干的。

首先,它定义了一些常量。比如 XOR_CODE 是一个异或掩码,MASK_CODE 是位掩码,BASE 是58,因为Base58编码嘛。还有一个 BV_LEN 是12,因为BV号固定是12位。

转换的核心函数有两个:bvid2aidaid2bvid

咱们先看 bvid2aid,也就是把BV号转回AV号。

`python

def bvid2aid(bvid: str) -> int:

"""BV号转AV号"""

# 首先,BV号的字符顺序是经过置换的,不能直接解码

# 需要把特定的位置交换回来

bvid = list(bvid)

# 这里交换了第3和第9位,第4和第7位

# 注意Python列表索引从0开始,所以这里是索引3,9和4,7

bvid[3], bvid[9] = bvid[9], bvid[3]

bvid[4], bvid[7] = bvid[7], bvid[4]

# 去掉前缀 "BV1",因为前缀是固定的,不参与数值计算

bvid = bvid[3:]

tmp = 0

# Base58解码

for i in bvid:

idx = data.index(i.encode()) # data是Base58的字符集

tmp = tmp * BASE + idx

# 最后通过异或和掩码还原出原始的AV号

return (tmp & MASK_CODE) ^ XOR_CODE

`

这段代码看起来有点硬核,但逻辑很清晰。它先做“逆向置换”,把打乱的字符顺序还原,然后当作一个Base58的大整数进行解码,最后通过异或运算还原出原始的整数ID。

反过来,aid2bvid 则是逆过程。

`python

def aid2bvid(aid: int) -> str:

"""AV号转BV号"""

# 初始化一个字节数组,填充默认值

bytes = [b"B", b"V", b"1", b"0", b"0", b"0", b"0", b"0", b"0", b"0", b"0", b"0"]

bv_idx = BV_LEN - 1

# 先进行异或和掩码处理,得到编码前的数值

tmp = (MAX_AID | aid) ^ XOR_CODE

# Base58编码

while int(tmp) != 0:

bytes[bv_idx] = data[int(tmp % BASE)]

tmp //= BASE

bv_idx -= 1

# 还原字符置换

bytes[3], bytes[9] = bytes[9], bytes[3]

bytes[4], bytes[7] = bytes[7], bytes[4]

# 拼接成字符串

return "".join([i.decode() for i in bytes])

`

你会发现,这两个函数是对称的。这种设计非常优雅,而且速度极快。因为在本地进行位运算,比发起HTTP请求快了几个数量级。

但是,光有转换函数还不够。在实际开发中,我们更需要的是一个“智能”的对象,它能自动处理这些细节,让我们开发者不用操心。

这就是 bilibili-apiVideo 类的魅力所在。

bilibili_api/video.py 中,Video 类的构造函数非常贴心。你可以只传BV号,也可以只传AV号,甚至都不传(如果后续再设置的话)。

`python

class Video:

def __init__(

self,

bvid: Union[None, str] = None,

aid: Union[None, int] = None,

credential: Union[None, Credential] = None,

):

# ID检查:bvid和aid必须提供其中之一

if bvid is not None:

self.set_bvid(bvid)

elif aid is not None:

self.set_aid(aid)

else:

raise ArgsException("请至少提供bvid和aid中的其中一个参数。")

def set_bvid(self, bvid: str) -> None:

"""设置bvid并自动计算对应的aid"""

if not re.search("^BV[a-zA-Z0-9]{10}$", bvid):

raise ArgsException(

"bvid提供错误,必须是以BV开头的纯字母和数字组成的12位字符串。"

)

self.__bvid = bvid

# 关键一步:自动转换

self.__aid = bvid2aid(bvid)

def set_aid(self, aid: int) -> None:

"""设置aid并自动计算对应的bvid"""

if aid <= 0:

raise ArgsException("aid不能小于或等于0。")

self.__aid = aid

# 关键一步:自动转换

self.__bvid = aid2bvid(aid)

`

这个设计简直太棒了。这意味着,无论你的数据源给的是AV还是BV,你都可以统一用 Video 对象来包裹它。当你需要调用评论接口时,直接调用 video_obj.get_aid() 就能拿到AV号;当你需要调用视频详情接口时,调用 video_obj.get_bvid() 就能拿到BV号。

这种“隐式转换”的方案,极大地降低了开发者的认知负担。你不需要在代码里到处写 if bvid.startswith("BV"): ... else: ... 这种判断逻辑,也不用担心忘记转换导致接口报错。

当然,凡事都有两面性。隐式转换虽然方便,但在调试的时候,如果你发现数据不对,可能一时半会儿反应不过来是转换出了问题,还是API返回的问题。所以,我建议大家在开发阶段,可以适当打印一下中间变量,确认转换结果是否符合预期。

比如,你可以写一个简单的验证函数:

`python

def validate_video_identifier(identifier: str) -> Tuple[bool, str, Optional[int]]:

"""验证视频标识符并返回类型和转换结果"""

if identifier.startswith("BV") and len(identifier) == 12:

# 验证BV号格式

if re.match(r"^BV[a-zA-Z0-9]{10}$", identifier):

try:

aid = bvid2aid(identifier)

return True, "bvid", aid

except:

return False, "invalid", None

elif identifier.startswith("av"):

# 处理AV号

try:

aid = int(identifier[2:])

if aid > 0:

return True, "aid", aid

except:

pass

return False, "unknown", None

`

这个函数可以帮你快速排查问题。比如,当你拿到一个奇怪的字符串,不确定它是AV还是BV,或者格式有误,这个函数能给你明确的反馈。

在实际项目中,我还发现了一个性能优化的点。如果你的应用需要处理大量的视频ID转换,比如批量解析一个UP主的所有视频,那么频繁的函数调用可能会成为瓶颈。虽然本地计算很快,但也不是零成本。

这时候,缓存就派上用场了。Python的 functools.lru_cache 装饰器是神器。

`python

from functools import lru_cache

@lru_cache(maxsize=1024)

def cached_bvid2aid(bvid: str) -> int:

"""带缓存的BV到AV转换"""

return bvid2aid(bvid)

@lru_cache(maxsize=1024)

def cached_aid2bvid(aid: int) -> str:

"""带缓存的AV到BV转换"""

return aid2bvid(aid)

`

加上缓存后,重复的转换请求直接从内存中读取结果,速度几乎是瞬间完成的。对于大多数场景,1024的缓存大小已经足够了。毕竟,一个UP主的视频数量通常不会超过这个数,而且热点视频会被反复访问,缓存命中率很高。

说到这里,我想再聊聊B站API的整体架构设计。

bilibili-api 项目采用了一种分层架构。应用层提供统一的 Video 类,转换层负责底层的算法实现,API适配层则负责根据目标接口的要求,自动选择正确的标识符格式。

这种设计思想,其实值得所有API封装库借鉴。很多第三方库,要么只支持BV,要么只支持AV,要么要求开发者手动转换,体验都很差。而 bilibili-api 通过这种“透明化”的处理,让开发者感觉不到底层的变化,这才是好库该有的样子。

当然,技术总是在发展的。B站的标识符系统未来会不会再变?

很有可能。随着数据量的爆炸式增长,Base58编码可能也会遇到瓶颈。未来可能会出现更复杂的分布式ID生成方案,比如雪花算法的变种,或者基于区块链的不可篡改ID。

但无论如何,核心思想不会变:既要保证唯一性和安全性,又要兼顾兼容性和性能。

对于咱们开发者来说,面对这种变化,最好的策略就是“拥抱变化,但保持抽象”。不要在你的业务逻辑里硬编码任何ID格式。就像我上面说的,封装一个 Video 对象,或者类似的对象,把所有的ID操作都封装在里面。这样,即使B站明天把BV号改成CV号,你只需要修改封装层的一个函数,业务代码一行都不用改。

最后,我想分享一点个人感悟。

做API开发,尤其是这种大厂平台的API,往往充满了坑。B站的反爬策略、接口的频繁变更、标识符的诡异转换,每一个都可能让你debug到深夜。但正是这些挑战,逼着我们写出更健壮、更优雅、更抽象的代码。

当你看着自己的代码,能够优雅地处理各种边缘情况,能够无缝兼容新旧系统,那种成就感,是写“Hello World”无法比拟的。

所以,如果你正在做B站相关的开发,不妨试试 bilibili-api 这个库。它不仅能帮你解决AV/BV转换的问题,还能帮你处理登录状态、弹幕解析、评论获取等一系列复杂操作。它的文档虽然不算特别详细,但源码非常清晰,值得细细研读。

毕竟,站在巨人的肩膀上,我们才能看得更远。

希望这篇文章能帮到正在坑里挣扎的你。如果有什么疑问,或者有更好的优化建议,欢迎在评论区留言

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/5 14:55:04

计算门窗型材惯性矩小技巧

计算门窗型材惯性矩小技巧 门窗设计师们在进行门窗抗风压性能较核时,经常碰到计算型材截面惯性矩的事,这对于学过材料力学的人来说并不是一件难事,但是对于一般技术人员来说可就不那么容易了。 即使你熟练掌握计算方法,繁琐的微积分计算过程也让你劳心费时。 AutoCAD有一…

作者头像 李华
网站建设 2026/8/5 14:55:00

QuickBMS:游戏资源解包的终极工具箱 - 支持200+格式的跨平台神器

QuickBMS&#xff1a;游戏资源解包的终极工具箱 - 支持200格式的跨平台神器 【免费下载链接】QuickBMS QuickBMS by aluigi - Github Mirror 项目地址: https://gitcode.com/gh_mirrors/qui/QuickBMS 你是否曾经面对游戏中的.pak、.dat、.arc等神秘文件感到无从下手&am…

作者头像 李华
网站建设 2026/8/5 14:54:22

告别手工填表!这套SpringBoot小学生体测系统,让数据管理不再头秃

各位正在为毕业设计愁白头发的同学们,或者是那些想要快速搭建一个管理系统来应付项目需求的开发者们,大家好呀!今天咱们不聊那些高深莫测的大模型原理,也不谈什么架构设计的玄学,咱们来聊聊一个特别接地气、特别实用,而且绝对能帮你搞定毕业答辩或者实际项目需求的选题—…

作者头像 李华
网站建设 2026/8/5 14:53:46

手把手教你用C#搞定西门子S7-1500 Modbus通讯,避坑指南来了

在工业自动化这个圈子里摸爬滚打久了,大家都会发现,上位机软件跟PLC之间的“沟通”才是整个系统的灵魂。以前大家习惯用S7协议直接连西门子,虽然快,但一旦涉及跨品牌或者老旧设备,Modbus TCP就成了那个“万金油”式的解决方案。特别是现在西门子S7-1500系列这么普及,很多…

作者头像 李华
网站建设 2026/8/5 14:53:32

搞懂油气知识图谱:从数据清洗到深度学习模型落地的硬核干货

大家好,我是那个平时喜欢捣鼓数据、写代码,偶尔也帮师弟师妹们改改论文的老博主。今天咱们不聊那些虚头巴脑的理论,来聊点实实在在的硬核技术——基于深度学习的油气知识图谱平台搭建。说实话,最近好多做能源、地质或者计算机交叉学科的同学私信我,说现在的课题太难了。特…

作者头像 李华
网站建设 2026/8/5 14:53:24

别再盲目扫了!这款开源神器afrog,让漏洞检测快准狠(附保姆级教程)

各位安全圈的兄弟们,还有那些在一线摸爬滚打的运维老铁们,大家好。今天咱们不聊那些虚头巴脑的大道理,直接来点干货。相信大家在日常工作中,都遇到过这种崩溃时刻:手里拿着个老旧的扫描器,跑个全量扫描要半天,出来的报告一堆误报,看得人眼瞎;或者为了测几个特定的组件…

作者头像 李华