Files
BiliNote/backend/app/utils/url_parser.py
T
pumpkinperson996andClaude Fable 5 b85d7bc1ff fix: 规范化含 BV 号的任意 B 站链接(稍后再看/收藏夹/追踪参数)
从"稍后再看"(/list/watchlater/?bvid=BV...)、收藏夹播放页等场景
复制的链接会被判为无效链接,尽管其中包含完整 BV 号;校验通过的
链接也会带着追踪参数原样传给 yt-dlp。

在请求入口新增 normalize_video_url():提取 BV 号重建标准
/video/BVxxx 链接,保留分 P 参数(?p=N),丢弃其余查询参数。
b23.tv 短链与其他平台行为不变,无 BV 号链接仍按原逻辑拒绝。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 15:03:07 -05:00

107 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import re
from typing import Optional
import requests
def extract_video_id(url: str, platform: str) -> Optional[str]:
"""
从视频链接中提取视频 ID
:param url: 视频链接
:param platform: 平台名(bilibili / youtube / douyin
:return: 提取到的视频 ID 或 None
"""
if platform == "bilibili":
# 如果是短链接,则解析真实链接
if "b23.tv" in url:
resolved_url = resolve_bilibili_short_url(url)
if resolved_url:
url = resolved_url
# 匹配 BV号(如 BV1vc411b7Wa
match = re.search(r"BV([0-9A-Za-z]+)", url)
return f"BV{match.group(1)}" if match else None
elif platform == "youtube":
# 匹配 v=xxxxx、youtu.be/xxxxx 或 shorts/xxxxxID 长度通常为 11
match = re.search(r"(?:v=|youtu\.be/|shorts/)([0-9A-Za-z_-]{11})", url)
return match.group(1) if match else None
elif platform == "douyin":
# 匹配 douyin.com/video/1234567890123456789
match = re.search(r"/video/(\d+)", url)
return match.group(1) if match else None
return None
def normalize_video_url(url: str) -> str:
"""
将任意包含 BV 号的 B 站链接规范化为标准视频链接。
支持稍后再看(/list/watchlater/?bvid=BV...)、收藏夹播放页(/list/mlXXX?bvid=BV...)、
带追踪参数的分享链接等。保留分 P 参数,丢弃其余查询参数。
b23.tv 短链与无 BV 号的链接原样返回(后者交由校验器拒绝)。
"""
if "b23.tv" in url:
return url
match = re.search(r"BV([0-9A-Za-z]+)", url)
if not match:
return url
normalized = f"https://www.bilibili.com/video/BV{match.group(1)}"
p = extract_bilibili_p_number(url)
if p:
normalized += f"?p={p}"
return normalized
def resolve_bilibili_short_url(short_url: str) -> Optional[str]:
"""
解析哔哩哔哩短链接以获取真实视频链接
:param short_url: Bilibili短链接(如"https://b23.tv/xxxxxx"
:return: 真实的视频链接或None
"""
try:
response = requests.head(short_url, allow_redirects=True)
return response.url
except requests.RequestException as e:
print(f"Error resolving short URL: {e}")
return None
def extract_bilibili_p_number(url: str) -> Optional[int]:
"""
从 B 站分 P 视频 URL 中提取 p 参数(分 P 序号)。
支持格式:
- https://www.bilibili.com/video/BVxxx/?p=36
- https://www.bilibili.com/video/BVxxx?p=5
- https://b23.tv/xxxxx?p=10
- https://www.bilibili.com/video/BVxxx/pN (尾缀形式)
:param url: B 站视频链接
:return: 分 P 序号(从 1 开始),非分 P 视频返回 None
"""
if "b23.tv" in url:
url = resolve_bilibili_short_url(url) or url
# 匹配 ?p=NNN 或 &p=NNN
match = re.search(r'[?&]p=(\d+)', url)
if match:
p = int(match.group(1))
if p >= 1:
return p
# 匹配 /pN 尾缀形式(较少见)
match = re.search(r'/p(\d+)(?:/?$|\?|&)', url)
if match:
p_val = int(match.group(1))
if p_val >= 1:
return p_val
return None