修复: 恢复follow_redirects避免误判Cookie失效, 改为检查最终URL判断登录态
This commit is contained in:
@@ -282,7 +282,7 @@ def _push_signin_result(account_id: str, result: dict, elapsed: float):
|
|||||||
elif status == "completed" and result.get("signed", 0) == 0 and result.get("total", 0) == 0:
|
elif status == "completed" and result.get("signed", 0) == 0 and result.get("total", 0) == 0:
|
||||||
msg = result.get("message", "")
|
msg = result.get("message", "")
|
||||||
if "no topics" in msg:
|
if "no topics" in msg:
|
||||||
lines = [f"⚠️ {remark} Cookie 已失效,请重新扫码添加"]
|
lines = [f"⚠️ {remark} 获取超话失败,可能被风控或 Cookie 失效,请检查"]
|
||||||
elif "no selected" in msg:
|
elif "no selected" in msg:
|
||||||
lines = [f"⚠️ {remark} 签到跳过: 没有选中的超话"]
|
lines = [f"⚠️ {remark} 签到跳过: 没有选中的超话"]
|
||||||
else:
|
else:
|
||||||
@@ -388,14 +388,15 @@ async def _async_do_signin(account_id: str, cron_expr: str = ""):
|
|||||||
try:
|
try:
|
||||||
topics = await _fetch_topics(cookies)
|
topics = await _fetch_topics(cookies)
|
||||||
if not topics:
|
if not topics:
|
||||||
|
# 获取超话失败,可能是风控或 Cookie 失效
|
||||||
|
# 不立即标记 invalid_cookie,等用户手动验证确认
|
||||||
async with SessionFactory() as session:
|
async with SessionFactory() as session:
|
||||||
result = await session.execute(select(Account).where(Account.id == acc_id))
|
result = await session.execute(select(Account).where(Account.id == acc_id))
|
||||||
acc = result.scalar_one_or_none()
|
acc = result.scalar_one_or_none()
|
||||||
if acc:
|
if acc:
|
||||||
acc.status = "invalid_cookie"
|
|
||||||
acc.last_checked_at = datetime.now()
|
acc.last_checked_at = datetime.now()
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return {"status": "completed", "signed": 0, "message": "no topics - cookie可能已失效"}
|
return {"status": "completed", "signed": 0, "message": "no topics - 获取超话失败"}
|
||||||
|
|
||||||
# 如果用户选择了特定超话,只签选中的
|
# 如果用户选择了特定超话,只签选中的
|
||||||
if acc_selected_topics and isinstance(acc_selected_topics, list):
|
if acc_selected_topics and isinstance(acc_selected_topics, list):
|
||||||
@@ -477,7 +478,7 @@ async def _async_do_signin(account_id: str, cron_expr: str = ""):
|
|||||||
async def _fetch_topics(cookies: dict) -> list:
|
async def _fetch_topics(cookies: dict) -> list:
|
||||||
"""
|
"""
|
||||||
获取关注的超话列表。
|
获取关注的超话列表。
|
||||||
带重试机制:微博凌晨可能触发风控(302 到通行证验证页),重试 2 次。
|
带重试机制:微博可能触发风控,重试 3 次。
|
||||||
返回空列表表示获取失败(Cookie 失效或风控)。
|
返回空列表表示获取失败(Cookie 失效或风控)。
|
||||||
"""
|
"""
|
||||||
import httpx
|
import httpx
|
||||||
@@ -486,32 +487,34 @@ async def _fetch_topics(cookies: dict) -> list:
|
|||||||
for attempt in range(max_retries):
|
for attempt in range(max_retries):
|
||||||
topics = []
|
topics = []
|
||||||
try:
|
try:
|
||||||
# 不自动跟随重定向,手动检测 302
|
async with httpx.AsyncClient(timeout=20, follow_redirects=True) as client:
|
||||||
async with httpx.AsyncClient(timeout=15, follow_redirects=False) as client:
|
|
||||||
resp = await client.get("https://weibo.com/", headers=WEIBO_HEADERS, cookies=cookies)
|
resp = await client.get("https://weibo.com/", headers=WEIBO_HEADERS, cookies=cookies)
|
||||||
|
final_url = str(resp.url)
|
||||||
|
|
||||||
# 302 重定向到登录页 = Cookie 失效或风控
|
# 最终落地在登录页 = Cookie 失效或风控
|
||||||
if resp.status_code in (301, 302):
|
if "login.sina.com.cn" in final_url or "passport.weibo.com" in final_url:
|
||||||
location = resp.headers.get("location", "")
|
if attempt < max_retries - 1:
|
||||||
if "login.sina.com.cn" in location or "passport" in location:
|
wait = (attempt + 1) * 3
|
||||||
if attempt < max_retries - 1:
|
logger.warning(f"被重定向到登录页(尝试 {attempt+1}/{max_retries}),{wait}秒后重试... url={final_url[:120]}")
|
||||||
wait = (attempt + 1) * 3
|
await asyncio.sleep(wait)
|
||||||
logger.warning(f"被重定向到登录页(尝试 {attempt+1}/{max_retries}),{wait}秒后重试...")
|
continue
|
||||||
await asyncio.sleep(wait)
|
else:
|
||||||
continue
|
logger.warning(f"多次重试仍被重定向到登录页,Cookie 已失效: {final_url[:120]}")
|
||||||
else:
|
return []
|
||||||
logger.warning(f"多次重试仍被重定向,Cookie 可能已失效")
|
|
||||||
return []
|
|
||||||
|
|
||||||
# 其他 302(如 weibo.com → www.weibo.com),跟随一次
|
# 检查响应内容是否是通行证验证页(HTML 而非正常页面)
|
||||||
resp = await client.get(location, headers=WEIBO_HEADERS, cookies=cookies)
|
content_type = resp.headers.get("content-type", "")
|
||||||
|
if "text/html" in content_type and "通行证" in resp.text[:500]:
|
||||||
xsrf = ""
|
if attempt < max_retries - 1:
|
||||||
for c in resp.cookies.jar:
|
wait = (attempt + 1) * 3
|
||||||
if c.name == "XSRF-TOKEN":
|
logger.warning(f"触发通行证验证(尝试 {attempt+1}/{max_retries}),{wait}秒后重试...")
|
||||||
xsrf = c.value
|
await asyncio.sleep(wait)
|
||||||
break
|
continue
|
||||||
|
else:
|
||||||
|
logger.warning("多次重试仍触发通行证验证")
|
||||||
|
return []
|
||||||
|
|
||||||
|
xsrf = client.cookies.get("XSRF-TOKEN", "")
|
||||||
headers = {**WEIBO_HEADERS, "X-Requested-With": "XMLHttpRequest"}
|
headers = {**WEIBO_HEADERS, "X-Requested-With": "XMLHttpRequest"}
|
||||||
if xsrf:
|
if xsrf:
|
||||||
headers["X-XSRF-TOKEN"] = xsrf
|
headers["X-XSRF-TOKEN"] = xsrf
|
||||||
@@ -524,22 +527,25 @@ async def _fetch_topics(cookies: dict) -> list:
|
|||||||
headers=headers, cookies=cookies,
|
headers=headers, cookies=cookies,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 超话接口也可能被 302
|
# 超话接口被重定向到登录页
|
||||||
if resp.status_code in (301, 302):
|
final_url = str(resp.url)
|
||||||
|
if "login.sina.com.cn" in final_url or "passport" in final_url:
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
wait = (attempt + 1) * 3
|
logger.warning(f"超话接口被重定向(尝试 {attempt+1}/{max_retries}),重试...")
|
||||||
logger.warning(f"超话接口被重定向(尝试 {attempt+1}/{max_retries}),{wait}秒后重试...")
|
await asyncio.sleep((attempt + 1) * 3)
|
||||||
await asyncio.sleep(wait)
|
break
|
||||||
break # break inner loop, retry outer
|
|
||||||
else:
|
else:
|
||||||
logger.warning("超话接口多次重定向,放弃")
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning(f"超话列表响应非 JSON: {resp.text[:200]}")
|
logger.warning(f"超话列表响应非 JSON(尝试 {attempt+1}): {resp.text[:200]}")
|
||||||
break
|
if attempt < max_retries - 1:
|
||||||
|
await asyncio.sleep((attempt + 1) * 3)
|
||||||
|
break
|
||||||
|
return []
|
||||||
|
|
||||||
if data.get("ok") != 1:
|
if data.get("ok") != 1:
|
||||||
break
|
break
|
||||||
tlist = data.get("data", {}).get("list", [])
|
tlist = data.get("data", {}).get("list", [])
|
||||||
@@ -559,11 +565,9 @@ async def _fetch_topics(cookies: dict) -> list:
|
|||||||
break
|
break
|
||||||
page += 1
|
page += 1
|
||||||
else:
|
else:
|
||||||
# while 正常结束(没有 break),说明成功
|
|
||||||
if topics:
|
if topics:
|
||||||
return topics
|
return topics
|
||||||
|
|
||||||
# 如果有 topics 说明成功了
|
|
||||||
if topics:
|
if topics:
|
||||||
return topics
|
return topics
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user