签到重试机制+超话选择持久化+healthcheck优化+JSON解码防护
This commit is contained in:
@@ -466,19 +466,43 @@ 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:
|
||||||
"""获取关注的超话列表。Cookie 失效时返回空列表。"""
|
"""
|
||||||
|
获取关注的超话列表。
|
||||||
|
带重试机制:微博凌晨可能触发风控(302 到通行证验证页),重试 2 次。
|
||||||
|
返回空列表表示获取失败(Cookie 失效或风控)。
|
||||||
|
"""
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
|
max_retries = 3
|
||||||
|
for attempt in range(max_retries):
|
||||||
topics = []
|
topics = []
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(timeout=15, follow_redirects=True) as client:
|
# 不自动跟随重定向,手动检测 302
|
||||||
|
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)
|
||||||
# 检测是否被重定向到访客页(Cookie 失效)
|
|
||||||
if "passport.weibo.com" in str(resp.url) or "visitor" in str(resp.url).lower():
|
# 302 重定向到登录页 = Cookie 失效或风控
|
||||||
logger.warning("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 []
|
return []
|
||||||
|
|
||||||
xsrf = client.cookies.get("XSRF-TOKEN", "")
|
# 其他 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"}
|
headers = {**WEIBO_HEADERS, "X-Requested-With": "XMLHttpRequest"}
|
||||||
if xsrf:
|
if xsrf:
|
||||||
headers["X-XSRF-TOKEN"] = xsrf
|
headers["X-XSRF-TOKEN"] = xsrf
|
||||||
@@ -490,6 +514,18 @@ async def _fetch_topics(cookies: dict) -> list:
|
|||||||
params={"tabid": "231093_-_chaohua", "page": str(page)},
|
params={"tabid": "231093_-_chaohua", "page": str(page)},
|
||||||
headers=headers, cookies=cookies,
|
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:
|
try:
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -513,8 +549,20 @@ async def _fetch_topics(cookies: dict) -> list:
|
|||||||
if page >= data.get("data", {}).get("max_page", 1):
|
if page >= data.get("data", {}).get("max_page", 1):
|
||||||
break
|
break
|
||||||
page += 1
|
page += 1
|
||||||
|
else:
|
||||||
|
# while 正常结束(没有 break),说明成功
|
||||||
|
if topics:
|
||||||
|
return topics
|
||||||
|
|
||||||
|
# 如果有 topics 说明成功了
|
||||||
|
if topics:
|
||||||
|
return topics
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"获取超话列表失败: {e}")
|
logger.error(f"获取超话列表失败(尝试 {attempt+1}/{max_retries}): {e}")
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
await asyncio.sleep(3)
|
||||||
|
|
||||||
return topics
|
return topics
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,6 @@ USER appuser
|
|||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
|
||||||
CMD curl -f http://localhost:5000/ || exit 1
|
CMD curl -f http://localhost:5000/health || exit 1
|
||||||
|
|
||||||
CMD ["python", "app.py"]
|
CMD ["python", "app.py"]
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ def index():
|
|||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/health')
|
||||||
|
def health():
|
||||||
|
return 'ok', 200
|
||||||
|
|
||||||
@app.route('/register', methods=['GET', 'POST'])
|
@app.route('/register', methods=['GET', 'POST'])
|
||||||
def register():
|
def register():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
|||||||
Reference in New Issue
Block a user