fix: 避免无 cookie 时重复预热会话

This commit is contained in:
2026-08-16 21:29:51 +08:00
parent 6c38089234
commit b01c9659d1
2 changed files with 26 additions and 4 deletions
+7 -4
View File
@@ -266,7 +266,7 @@ class SiteSession:
return reason.startswith("upstream status") or reason.startswith("httpx") or "Error:" in reason
async def _ensure_warm(self, profile: _Profile) -> None:
"""确保通道持有新鲜的 Akamai cookie;过期或缺失时访问首页预热"""
"""确保通道有一次新鲜的首页预热;过期时重新访问首页"""
if self._is_warm(profile):
return
@@ -275,7 +275,10 @@ class SiteSession:
return
try:
response = await profile.client.get(self._settings.home_url)
profile.warmed_at = time.monotonic()
# Akamai 不保证每次都下发 cookie;首页探测成功本身就是可复用的
# 预热结果,cookie 只用于观测和失败升级时的回灌。
if response.status_code < 400:
profile.warmed_at = time.monotonic()
logger.info(
"会话预热完成:profile=%s status=%s cookies=%s",
profile.name,
@@ -285,14 +288,14 @@ class SiteSession:
except httpx.HTTPError as exc:
# 预热失败不阻断本次抓取:直连目标页仍可能成功,只是慢
logger.warning("会话预热失败:profile=%s err=%s", profile.name, exc)
profile.warmed_at = time.monotonic()
profile.warmed_at = 0.0
def _is_warm(self, profile: _Profile) -> bool:
if not profile.warmed_at:
return False
if time.monotonic() - profile.warmed_at > self._settings.session_ttl_seconds:
return False
return bool(profile.cookie_names & set(site.AKAMAI_COOKIE_NAMES))
return True
async def _invalidate(self, profile: _Profile) -> None:
"""清空通道 cookie 并强制下次重新预热"""
+19
View File
@@ -89,6 +89,25 @@ async def test_warmup_happens_before_first_fetch_and_is_reused():
assert seen.count(TARGET) == 2
async def test_successful_warmup_is_reused_when_upstream_sets_no_cookie():
"""首页可能返回 200 但不下发 Akamai cookie,仍不得重复预热。"""
seen: list[str] = []
def handler(request: httpx.Request) -> httpx.Response:
seen.append(str(request.url))
return httpx.Response(200, text=GOOD_PAGE)
session = await build_session(handler)
try:
await session.fetch_html(TARGET, mobile=False)
await session.fetch_html(TARGET, mobile=False)
finally:
await session.close()
assert seen.count("https://www.rakuten.co.jp/") == 1
assert seen.count(TARGET) == 2
async def test_missing_state_marker_is_treated_as_blocked():
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(200, text="<html>no state here</html>")