在B站点一下分享按钮,复制出来的链接基本都是 b23.tv 开头的短链。短是够短,可光看这串字符,你根本不知道它背后指向哪个视频、哪篇专栏。不管是做数据采集、批量整理内容,还是单纯想在点开之前确认一下落地页,都得先把短链还原成长链。这件事用 Python 来做并不麻烦,核心思路一句话就能说清:短链本质上就是一次(有时是好几次)HTTP 重定向,顺着重定向链条一路追到终点,终点就是我们要的长链接。
最顺手的工具是 requests 库。它提供的 HEAD 请求只拿响应头、不下载页面正文,速度快、开销小,正适合干这种"只问路、不走全程"的活:
import requests
<img src="/uploads/20251015/52.png?t=1793003450" alt="" class="img-fluid" />
def get_permanent_link(url):
# HEAD 请求只取响应头,比 GET 更快、更省资源
# 这里关掉自动重定向,手动一跳一跳地跟,中间过程都在掌控之中
response = requests.head(url, allow_redirects=False, timeout=10)
# 响应头里出现 location,说明这一跳还没到头,递归继续追
if 'location' in response.headers:
return get_permanent_link(response.headers['location'])
# 不再跳转了,当前地址就是最终的长链接
return response.url
temporary_link = "https://b23.tv/example_short_link" # 替换成实际的B站短链
permanent_link = get_permanent_link(temporary_link)
print("永久链接是:", permanent_link)
代码不长,逻辑也直白:收到 location 就继续追,直到某一跳不再跳转为止。B站的短链通常跳一次就到目标页,但有些链接会连环跳转,递归写法就是为了兜住这种情况。跑完之后,你会拿到类似 https://www.bilibili.com/video/BV1xx411c7mD?spm_id_from=... 这样的结果。
拿到长链先别急着收工。仔细看会发现,尾巴上挂着一串问号开头的参数——spm_id_from、vd_source 之类,大多是分享统计用的追踪参数,对定位内容本身没什么用。想把它们去掉,一小段正则就够了:
import re
<img src="/uploads/20251015/4.png?t=1940912707" alt="" class="img-fluid" />
def get_pure_url(text):
# 只保留问号之前的部分,把追踪参数整串砍掉
pattern = r'^(https?://[^\?]+)'
match = re.search(pattern, text)
if match:
return match.group()
return None
pure_link = get_pure_url(permanent_link)
print("纯净链接是:", pure_link)

处理完,剩下的就是干净的视频地址。BV号本身嵌在路径里,砍参数动不了它,不用担心把关键信息弄丢。当然,这个正则属于"一刀切",哪天你需要保留某个特定参数(比如分P的 p=2),就得按实际需求改写匹配规则。
最后聊几个实际使用中容易踩的坑。一是网络和异常:requests 默认会一直等响应,正式使用时务必加上 timeout,再用 try/except 把连接失败、超时这些情况兜住,否则一个坏链就能让整个脚本卡死。二是请求频率:批量还原短链时,最好在循环里留个间隔,别对着服务器猛刷——这既是基本礼貌,也能避免 IP 被限制。三是 HEAD 请求并非万能,个别服务对它支持不好,遇到返回 405 之类的情况,换成带 stream=True 的 GET 请求再读响应头即可。还有一点得心里有数:B站的跳转策略和链接格式不是一成不变的,今天能跑的代码,过段时间可能就要微调,建议定期拿真实短链验证一下。
整体算下来,核心代码不过十几行。但把重定向追踪、参数清理、异常处理这几块都照顾到之后,它就不只是个能跑通的 demo,而是可以真正放进日常工具箱的小脚本了。
تسجيل الدخول الآن