feat(monitor): 关掉链接校验 —— 认不出来的目标原样收下
以前新建任务粘目标时是白名单校验:链接形状不匹配平台的规则就拒收(「无法从链接中 解析出博主主页 ID」/「无法识别的目标」),而 v.douyin.com 那种分享短链是**明确** 拒绝的。平台一改链接形状,用户粘一个完全正确的链接也只能干瞪眼。 现在认不出来的一律原样存下(截到 128 字符,external_id 是 varchar(128),而带参数的 完整链接常常 130+)。只留两件不能省的事: * 空值仍然拒收 —— 空目标连库都写不进去; * **不许跨平台误抠** —— 小红书的正则不会从抖音链接里抠出一个 id。那是「看起来正常 但完全错误」的目标,比原样存更糟。这条有测试钉着。 代价(已知并接受):认不出的值会变成永远抓不到东西的目标。好在不静默 —— 跑完一轮 ingest 会发现「一条都没抓到」并报出来,而且会先排除登录问题。 顺带删掉 short_link_hosts:现在没人读它了,留着会让下一个人以为短链还是被拒的。
This commit is contained in:
+64
-27
@@ -70,13 +70,33 @@ class TestParseTargetInput:
|
||||
assert parsed["external_id"] == "6aa3d827000000002802c5c8"
|
||||
assert parsed["xsec_token"] == ""
|
||||
|
||||
def test_creator_url_rejected_in_note_mode(self):
|
||||
with pytest.raises(TargetParseError):
|
||||
parse_target_input(CREATOR_URL, "note")
|
||||
def test_a_creator_url_in_note_mode_falls_through_to_the_raw_value(self):
|
||||
"""粘错类型的链接**不再被拦**(2026-10-12 关掉了校验)。
|
||||
|
||||
def test_garbage_is_rejected(self):
|
||||
代价是明摆着的:以前它会告诉你「这是博主链接,但任务要的是笔记」,现在它照单
|
||||
全收,存进去的是一个抓不到东西的 id。那个代价落在采集那一轮 —— ingest 会发现
|
||||
「一条都没抓到」并报出来,而不是静默通过。
|
||||
"""
|
||||
parsed = parse_target_input(CREATOR_URL, "note")
|
||||
assert parsed["external_id"] == CREATOR_URL[:128]
|
||||
|
||||
def test_garbage_is_stored_as_is(self):
|
||||
parsed = parse_target_input("not a url at all !!", "creator")
|
||||
assert parsed["external_id"] == "not a url at all !!"
|
||||
|
||||
def test_empty_input_is_still_rejected(self):
|
||||
"""「关掉校验」放的是**认不出来的链接**,不是「什么都没粘」。空目标连库都写不进去。"""
|
||||
with pytest.raises(TargetParseError):
|
||||
parse_target_input("not a url at all !!", "creator")
|
||||
parse_target_input(" ", "creator")
|
||||
|
||||
def test_a_too_long_value_is_truncated_to_fit_the_column(self):
|
||||
"""`external_id` 是 varchar(128),而带参数的完整链接常常就 130+ 字符
|
||||
(抖音的 sec_uid 本身 66 位)。不截断的话这一条会直接写库失败。"""
|
||||
long_url = "https://www.douyin.com/root/search/python?aid=b733a3b0&" + "x" * 200
|
||||
|
||||
parsed = parse_target_input(long_url, "note", "dy")
|
||||
|
||||
assert len(parsed["external_id"]) == 128
|
||||
|
||||
# --- 抖音 -------------------------------------------------------------
|
||||
# 链接形态由平台决定,所以每一个都要显式带上 "dy"。
|
||||
@@ -116,21 +136,16 @@ class TestParseTargetInput:
|
||||
assert parsed["external_id"] == sec_uid
|
||||
|
||||
def test_douyin_bare_sec_uid_beyond_the_xhs_length_cap(self):
|
||||
"""裸 id 的长度上限必须按平台分开。
|
||||
"""长 sec_uid 两个平台都收得下 —— 身份校验已经关掉了。
|
||||
|
||||
小红书那条规则封顶 64 字符,而 sec_user_id 长过 64 是常态(实测样本 55,
|
||||
但字段本身是变长的)。共用一条规则的话,长一点的 sec_uid 会被直接拒掉 ——
|
||||
对用户来说就是「粘贴了一个完全正确的链接却报无法识别」。
|
||||
以前小红书那条规则封顶 64 字符,而 sec_user_id 长过 64 是常态,于是「粘贴了一个
|
||||
完全正确的链接却报无法识别」。现在两个平台都原样收下。
|
||||
"""
|
||||
sec_uid = "MS4wLjABAAAA" + "aB3dEf6hIj9lMn2pQr5tUv8xYz1" * 3
|
||||
assert len(sec_uid) > 64
|
||||
|
||||
parsed = parse_target_input(sec_uid, "creator", "dy")
|
||||
assert parsed["external_id"] == sec_uid
|
||||
|
||||
# 同一条 id 拿小红书规则来解析会被拒 —— 这正是两条规则必须分开的原因。
|
||||
with pytest.raises(TargetParseError):
|
||||
parse_target_input(sec_uid, "creator", "xhs")
|
||||
assert parse_target_input(sec_uid, "creator", "dy")["external_id"] == sec_uid
|
||||
assert parse_target_input(sec_uid, "creator", "xhs")["external_id"] == sec_uid
|
||||
|
||||
def test_douyin_bare_video_id(self):
|
||||
parsed = parse_target_input("7525082444551310602", "note", "dy")
|
||||
@@ -138,21 +153,27 @@ class TestParseTargetInput:
|
||||
# 抖音不需要 xsec_token —— 和小红书不同,裸链接就能用。
|
||||
assert parsed["xsec_token"] == ""
|
||||
|
||||
def test_douyin_short_link_is_rejected_with_a_reason(self):
|
||||
"""短链要联网跳一次才知道指向谁。明确拒绝好过存一个永远抓不到东西的目标。"""
|
||||
with pytest.raises(TargetParseError) as excinfo:
|
||||
parse_target_input("https://v.douyin.com/drIPtQ_WPWY/", "note", "dy")
|
||||
assert "短链" in str(excinfo.value)
|
||||
def test_douyin_short_link_is_accepted_but_will_not_fetch(self):
|
||||
"""分享短链现在放行,但存进去的是短码本身 —— 那个目标抓不到东西。
|
||||
|
||||
以前这里是明确拒绝的(「短链要联网跳一次才知道指向谁」)。关掉校验之后不拦了,
|
||||
代价转移到了采集那一轮。要真正支持短链得联网跟一次跳转解出 sec_uid,那是另一件事。
|
||||
"""
|
||||
parsed = parse_target_input("https://v.douyin.com/drIPtQ_WPWY/", "note", "dy")
|
||||
|
||||
assert parsed["external_id"] == "https://v.douyin.com/drIPtQ_WPWY/"
|
||||
|
||||
def test_a_douyin_link_is_not_parsed_with_xhs_rules(self):
|
||||
with pytest.raises(TargetParseError):
|
||||
parse_target_input(
|
||||
"https://www.douyin.com/video/7525082444551310602", "note", "xhs"
|
||||
)
|
||||
"""**这条仍然重要**:关掉的是「拦截」,不是「辨别」。
|
||||
|
||||
小红书的正则不该从抖音链接里抠出一个 id 来 —— 那会是一个**看起来正常但完全
|
||||
错误**的目标,比原样存下来糟得多。
|
||||
"""
|
||||
url = "https://www.douyin.com/video/7525082444551310602"
|
||||
assert parse_target_input(url, "note", "xhs")["external_id"] == url
|
||||
|
||||
def test_an_xhs_link_is_not_parsed_for_douyin(self):
|
||||
with pytest.raises(TargetParseError):
|
||||
parse_target_input(NOTE_URL, "note", "dy")
|
||||
assert parse_target_input(NOTE_URL, "note", "dy")["external_id"] == NOTE_URL
|
||||
|
||||
def test_a_platform_without_an_adapter_is_rejected(self):
|
||||
with pytest.raises(TargetParseError):
|
||||
@@ -230,11 +251,27 @@ class TestTaskCrud:
|
||||
assert listing.json()["tasks"][0]["target_count"] == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_target_returns_400(self, client):
|
||||
async def test_an_unrecognised_target_is_accepted_as_is(self, client):
|
||||
"""认不出来的目标不再被拒(2026-10-12 关掉了链接校验)。
|
||||
|
||||
存的是原值 —— 采集那一轮会抓不到东西并报出来,而不是静默通过。
|
||||
"""
|
||||
response = await client.post(
|
||||
"/api/monitor/tasks",
|
||||
json={"name": "bad", "mode": "creator", "targets": ["!!! nonsense !!!"]},
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
tasks = (await client.get("/api/monitor/tasks")).json()["tasks"]
|
||||
assert tasks[0]["targets"][0]["external_id"] == "!!! nonsense !!!"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_an_empty_target_is_still_rejected(self, client):
|
||||
"""空目标连库都写不进去,这个仍然要拦。"""
|
||||
response = await client.post(
|
||||
"/api/monitor/tasks",
|
||||
json={"name": "bad", "mode": "creator", "targets": [" "]},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user