feat(ingest): parse video refs and fetch detail by bvid
This commit is contained in:
1 parent
e903c49921
commit
d2adb65e08
6 files changed
+410
No files matched your search
@@ -176,6 +176,12 @@ VIEW_PAYLOAD: dict[str, Any] = {
|
||||
"pubdate": 1793370433,
|
||||
"ctime": 1793370433,
|
||||
"duration": 9504,
|
||||
"pic": "http://i1.hdslb.com/bfs/archive/e5d3d4f25777b08c1dfae8bc9dc3d56b21d548f1.jpg",
|
||||
"videos": 2,
|
||||
"pages": [
|
||||
{"cid": 43184885058, "page": 1, "from": "vupload", "part": "第一部分"},
|
||||
{"cid": 43184885059, "page": 2, "from": "vupload", "part": "第二部分"},
|
||||
],
|
||||
"owner": {"mid": 434442072, "name": "_-抱抱-_", "face": ""},
|
||||
"stat": {
|
||||
"aid": 117530286889638,
|
||||
|
||||
@@ -340,6 +340,100 @@ def test_view_unavailable_and_malformed():
|
||||
raise AssertionError("缺少 stat 字段应报协议错误")
|
||||
|
||||
|
||||
def test_view_detail_by_aid_and_bvid():
|
||||
transport = QueueTransport([http_json(VIEW_PAYLOAD)])
|
||||
client, _ = make_client(transport)
|
||||
detail = client.view_detail(aid=117530286889638)
|
||||
assert detail.aid == 117530286889638
|
||||
assert detail.bvid == "BV1xvxi6LEZD"
|
||||
assert detail.up_mid == 434442072
|
||||
assert detail.up_name == "_-抱抱-_"
|
||||
assert detail.pubdate == 1793370433
|
||||
assert detail.duration == 9504
|
||||
assert detail.pages == 2
|
||||
assert detail.cover and detail.cover.startswith("http")
|
||||
assert detail.metrics()["view"] == 819
|
||||
assert "aid=117530286889638" in transport.calls[0]["url"]
|
||||
|
||||
transport = QueueTransport([http_json(VIEW_PAYLOAD)])
|
||||
client, _ = make_client(transport)
|
||||
detail = client.view_detail(bvid="BV1xvxi6LEZD")
|
||||
assert detail.aid == 117530286889638
|
||||
assert "bvid=BV1xvxi6LEZD" in transport.calls[0]["url"]
|
||||
|
||||
|
||||
def test_view_detail_fallbacks_and_required_arg():
|
||||
payload = view_for(1)
|
||||
for key in ("owner", "duration", "pages", "videos", "pic"):
|
||||
payload["data"].pop(key, None)
|
||||
transport = QueueTransport([http_json(payload)])
|
||||
client, _ = make_client(transport)
|
||||
detail = client.view_detail(aid=1)
|
||||
assert detail.up_mid is None and detail.up_name is None
|
||||
assert detail.duration == 0
|
||||
assert detail.pages is None
|
||||
assert detail.cover is None
|
||||
|
||||
client, _ = make_client(QueueTransport([]))
|
||||
for kwargs in ({}, {"aid": 1, "bvid": "BV1xvxi6LEZD"}):
|
||||
try:
|
||||
client.view_detail(**kwargs)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("必须且只能提供 aid 或 bvid")
|
||||
|
||||
|
||||
def test_view_detail_unavailable():
|
||||
transport = QueueTransport([
|
||||
http_json(VIEW_UNAVAILABLE), http_json(VIEW_UNAVAILABLE),
|
||||
])
|
||||
client, _ = make_client(transport)
|
||||
try:
|
||||
client.view_detail(bvid="BV1xvxi6LEZD")
|
||||
except BilibiliAPIError as exc:
|
||||
assert exc.code == 62012
|
||||
else:
|
||||
raise AssertionError("不可访问视频应抛业务错误")
|
||||
|
||||
|
||||
def test_resolve_short_link():
|
||||
redirect = HTTPResponse(
|
||||
302,
|
||||
{"location": "https://www.bilibili.com/video/BV1x8x166EzY"},
|
||||
b"",
|
||||
)
|
||||
transport = QueueTransport([redirect])
|
||||
client, _ = make_client(transport)
|
||||
final = client.resolve_short_link("https://b23.tv/abc")
|
||||
assert final == "https://www.bilibili.com/video/BV1x8x166EzY"
|
||||
assert transport.calls[0]["method"] == "GET"
|
||||
assert "b23.tv" in transport.calls[0]["url"]
|
||||
|
||||
# 多跳重定向,大小写不同的 Location 头
|
||||
transport = QueueTransport([
|
||||
HTTPResponse(301, {"Location": "https://b23.tv/next"}, b""),
|
||||
HTTPResponse(302, {"location": "https://www.bilibili.com/video/BV1x8x166EzY"}, b""),
|
||||
])
|
||||
client, _ = make_client(transport)
|
||||
assert client.resolve_short_link("https://b23.tv/start").endswith("BV1x8x166EzY")
|
||||
|
||||
# 非重定向直接返回最终 URL
|
||||
transport = QueueTransport([HTTPResponse(200, {}, b"<html>")])
|
||||
client, _ = make_client(transport)
|
||||
assert client.resolve_short_link("https://b23.tv/x") == "https://b23.tv/x"
|
||||
|
||||
# HTTP 错误应抛出
|
||||
transport = QueueTransport([HTTPResponse(404, {}, b"not found")])
|
||||
client, _ = make_client(transport)
|
||||
try:
|
||||
client.resolve_short_link("https://b23.tv/x")
|
||||
except BilibiliHTTPError as exc:
|
||||
assert exc.status == 404
|
||||
else:
|
||||
raise AssertionError("短链 HTTP 404 应抛出")
|
||||
|
||||
|
||||
def test_relation_stat():
|
||||
transport = QueueTransport([http_json(RELATION_PAYLOAD)])
|
||||
client, _ = make_client(transport)
|
||||
@@ -436,6 +530,10 @@ def main():
|
||||
test_timeout_and_method_forwarded()
|
||||
test_view_seven_metrics()
|
||||
test_view_unavailable_and_malformed()
|
||||
test_view_detail_by_aid_and_bvid()
|
||||
test_view_detail_fallbacks_and_required_arg()
|
||||
test_view_detail_unavailable()
|
||||
test_resolve_short_link()
|
||||
test_relation_stat()
|
||||
test_check_login()
|
||||
test_check_login_without_cookie_makes_no_request()
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""视频引用解析纯函数测试: uv run python tests/test_video_ref.py"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
|
||||
from reaction_board.video_ref import VideoRef, extract_video_refs # noqa: E402
|
||||
|
||||
BV = "BV1x8x166EzY"
|
||||
|
||||
|
||||
def test_bv_url_and_bare_bvid():
|
||||
assert extract_video_refs(f"https://www.bilibili.com/video/{BV}") == [
|
||||
VideoRef("bvid", BV)
|
||||
]
|
||||
assert extract_video_refs(BV) == [VideoRef("bvid", BV)]
|
||||
assert extract_video_refs(
|
||||
f"https://m.bilibili.com/video/{BV}?p=2&spm_id_from=333.999"
|
||||
) == [VideoRef("bvid", BV)]
|
||||
assert extract_video_refs(f"https://www.bilibili.com/video/{BV}/?share=1") == [
|
||||
VideoRef("bvid", BV)
|
||||
]
|
||||
|
||||
|
||||
def test_arbitrary_text_containing_bvid():
|
||||
assert extract_video_refs(f"看看这个 {BV} 很有意思") == [VideoRef("bvid", BV)]
|
||||
assert extract_video_refs(f"前缀{BV}后缀") == [VideoRef("bvid", BV)]
|
||||
|
||||
|
||||
def test_av_id_forms():
|
||||
assert extract_video_refs("av12345") == [VideoRef("aid", 12345)]
|
||||
assert extract_video_refs("AV12345") == [VideoRef("aid", 12345)]
|
||||
assert extract_video_refs("https://www.bilibili.com/video/av12345") == [
|
||||
VideoRef("aid", 12345)
|
||||
]
|
||||
|
||||
|
||||
def test_short_link():
|
||||
assert extract_video_refs("https://b23.tv/abcDEF12") == [
|
||||
VideoRef("short", "abcDEF12")
|
||||
]
|
||||
assert extract_video_refs("b23.tv/xyz") == [VideoRef("short", "xyz")]
|
||||
|
||||
|
||||
def test_multiple_and_order_and_dedup():
|
||||
text = f"{BV} 还有 av100 再看 {BV} 以及 https://b23.tv/short1"
|
||||
assert extract_video_refs(text) == [
|
||||
VideoRef("bvid", BV),
|
||||
VideoRef("aid", 100),
|
||||
VideoRef("short", "short1"),
|
||||
]
|
||||
|
||||
|
||||
def test_limit():
|
||||
text = " ".join(["av1", "av2", "av3"])
|
||||
assert extract_video_refs(text, limit=2) == [
|
||||
VideoRef("aid", 1),
|
||||
VideoRef("aid", 2),
|
||||
]
|
||||
assert len(extract_video_refs(text, limit=None)) == 3
|
||||
|
||||
|
||||
def test_invalid_and_boundaries():
|
||||
assert extract_video_refs("") == []
|
||||
assert extract_video_refs(None) == []
|
||||
assert extract_video_refs(12345) == []
|
||||
assert extract_video_refs("hello world") == []
|
||||
# BV 号长度不足
|
||||
assert extract_video_refs("BV1x8") == []
|
||||
# 更长的字母数字串不应被截断成假 BV 号
|
||||
assert extract_video_refs(f"{BV}EXTRA") == []
|
||||
# "have123" 不应命中 av
|
||||
assert extract_video_refs("have123") == []
|
||||
# av 后紧跟字母不应命中
|
||||
assert extract_video_refs("av12x") == []
|
||||
|
||||
|
||||
def main():
|
||||
test_bv_url_and_bare_bvid()
|
||||
test_arbitrary_text_containing_bvid()
|
||||
test_av_id_forms()
|
||||
test_short_link()
|
||||
test_multiple_and_order_and_dedup()
|
||||
test_limit()
|
||||
test_invalid_and_boundaries()
|
||||
print("OK: 视频引用(BV/av/URL/短链)解析测试全部通过")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user