签到重试机制+超话选择持久化+healthcheck优化+JSON解码防护

This commit is contained in:
2026-04-09 08:40:29 +08:00
parent 9e69d34f81
commit 7fed107c3c
3 changed files with 98 additions and 46 deletions

View File

@@ -466,55 +466,103 @@ async def _async_do_signin(account_id: str, cron_expr: str = ""):
async def _fetch_topics(cookies: dict) -> list:
"""获取关注的超话列表。Cookie 失效时返回空列表。"""
"""
获取关注的超话列表。
带重试机制微博凌晨可能触发风控302 到通行证验证页),重试 2 次。
返回空列表表示获取失败Cookie 失效或风控)。
"""
import httpx
topics = []
try:
async with httpx.AsyncClient(timeout=15, follow_redirects=True) as client:
resp = await client.get("https://weibo.com/", headers=WEIBO_HEADERS, cookies=cookies)
# 检测是否被重定向到访客页Cookie 失效)
if "passport.weibo.com" in str(resp.url) or "visitor" in str(resp.url).lower():
logger.warning("Cookie 已失效,被重定向到登录/访客页")
return []
max_retries = 3
for attempt in range(max_retries):
topics = []
try:
# 不自动跟随重定向,手动检测 302
async with httpx.AsyncClient(timeout=15, follow_redirects=False) as client:
resp = await client.get("https://weibo.com/", headers=WEIBO_HEADERS, cookies=cookies)
xsrf = client.cookies.get("XSRF-TOKEN", "")
headers = {**WEIBO_HEADERS, "X-Requested-With": "XMLHttpRequest"}
if xsrf:
headers["X-XSRF-TOKEN"] = xsrf
# 302 重定向到登录页 = Cookie 失效或风控
if resp.status_code in (301, 302):
location = resp.headers.get("location", "")
if "login.sina.com.cn" in location or "passport" in location:
if attempt < max_retries - 1:
wait = (attempt + 1) * 3
logger.warning(f"被重定向到登录页(尝试 {attempt+1}/{max_retries}){wait}秒后重试...")
await asyncio.sleep(wait)
continue
else:
logger.warning(f"多次重试仍被重定向Cookie 可能已失效")
return []
# 其他 302如 weibo.com → www.weibo.com跟随一次
resp = await client.get(location, headers=WEIBO_HEADERS, cookies=cookies)
xsrf = ""
for c in resp.cookies.jar:
if c.name == "XSRF-TOKEN":
xsrf = c.value
break
headers = {**WEIBO_HEADERS, "X-Requested-With": "XMLHttpRequest"}
if xsrf:
headers["X-XSRF-TOKEN"] = xsrf
page = 1
while page <= 10:
resp = await client.get(
"https://weibo.com/ajax/profile/topicContent",
params={"tabid": "231093_-_chaohua", "page": str(page)},
headers=headers, cookies=cookies,
)
# 超话接口也可能被 302
if resp.status_code in (301, 302):
if attempt < max_retries - 1:
wait = (attempt + 1) * 3
logger.warning(f"超话接口被重定向(尝试 {attempt+1}/{max_retries}){wait}秒后重试...")
await asyncio.sleep(wait)
break # break inner loop, retry outer
else:
logger.warning("超话接口多次重定向,放弃")
return []
try:
data = resp.json()
except Exception:
logger.warning(f"超话列表响应非 JSON: {resp.text[:200]}")
break
if data.get("ok") != 1:
break
tlist = data.get("data", {}).get("list", [])
if not tlist:
break
for item in tlist:
title = item.get("topic_name", "") or item.get("title", "")
cid = ""
for field in ("oid", "scheme"):
m = re.search(r"100808[0-9a-fA-F]+", item.get(field, ""))
if m:
cid = m.group(0)
break
if title and cid:
topics.append({"title": title, "containerid": cid})
if page >= data.get("data", {}).get("max_page", 1):
break
page += 1
else:
# while 正常结束(没有 break说明成功
if topics:
return topics
# 如果有 topics 说明成功了
if topics:
return topics
except Exception as e:
logger.error(f"获取超话列表失败(尝试 {attempt+1}/{max_retries}): {e}")
if attempt < max_retries - 1:
await asyncio.sleep(3)
page = 1
while page <= 10:
resp = await client.get(
"https://weibo.com/ajax/profile/topicContent",
params={"tabid": "231093_-_chaohua", "page": str(page)},
headers=headers, cookies=cookies,
)
try:
data = resp.json()
except Exception:
logger.warning(f"超话列表响应非 JSON: {resp.text[:200]}")
break
if data.get("ok") != 1:
break
tlist = data.get("data", {}).get("list", [])
if not tlist:
break
for item in tlist:
title = item.get("topic_name", "") or item.get("title", "")
cid = ""
for field in ("oid", "scheme"):
m = re.search(r"100808[0-9a-fA-F]+", item.get(field, ""))
if m:
cid = m.group(0)
break
if title and cid:
topics.append({"title": title, "containerid": cid})
if page >= data.get("data", {}).get("max_page", 1):
break
page += 1
except Exception as e:
logger.error(f"获取超话列表失败: {e}")
return topics