cf逻辑复用

This commit is contained in:
2026-06-09 10:13:28 +08:00
parent caa3e4342a
commit ada64a68ad
2 changed files with 37 additions and 11 deletions
+36 -11
View File
@@ -119,9 +119,12 @@ class CloudflareSessionManager:
"""带已有会话导航并消化 Cloudflare 挑战
与新建路径保持一致:导航后等待挑战自动通过,而非一遇非 200 就判死。
CF 对携带过期 cf_clearance 的请求常直接返回挑战页(首响应可能非 200),
因此即便首响应非 200,也先给页面一次自动通过 Turnstile 的机会,
仅当挑战超时/被硬阻断、或通过后仍无有效内容时才判定会话失效。
Returns:
通过验证的页面 HTML;若判定会话已失效(非 200 或挑战/阻断),返回 None
通过验证的页面 HTML;若判定会话已失效,返回 None
"""
async with self._browser_pool.page_session(storage_state=session.storage_state) as (_, page):
response = await page.goto(
@@ -130,16 +133,28 @@ class CloudflareSessionManager:
timeout=int(self._settings.request_timeout_seconds * 1000),
)
status = response.status if response is not None else None
if status != 200:
# 携带已有会话仍被判非 200,通常意味着 cf_clearance 已失效,交由上层重建
logger.debug("复用会话导航返回非 200:url=%s status=%s", target_url, status)
return None
try:
await self._wait_for_clearance(page)
# 复用使用短超时:快速验证能否通过,不行就放弃复用交由上层重建,
# 避免在无法自动通过的挑战上空等整个 cloudflare_wait_timeout_seconds。
await self._wait_for_clearance(
page, timeout_seconds=self._settings.cloudflare_reuse_wait_timeout_seconds
)
except (CloudflareChallengeError, UpstreamBlockedError):
logger.debug("复用会话仍处于挑战/阻断状态,判定会话失效:url=%s", target_url)
logger.info(
"复用会话未通过挑战,判定失效:url=%s status=%s had_cf_clearance=%s",
target_url, status, bool(session.cf_clearance),
)
return None
return await page.content()
html = await page.content()
# 挑战已通过但页面仍是挑战壳(极少数 wait 提前返回的情况):判失效,触发重建
if self._looks_like_challenge((await page.title()).lower(), html.lower()):
logger.info(
"复用会话通过等待后仍无有效内容,判定失效:url=%s status=%s had_cf_clearance=%s",
target_url, status, bool(session.cf_clearance),
)
return None
return html
async def invalidate(self, target_url: str) -> None:
"""主动让会话失效
@@ -203,7 +218,7 @@ class CloudflareSessionManager:
html=html_content,
)
async def _wait_for_clearance(self, page: object) -> None:
async def _wait_for_clearance(self, page: object, timeout_seconds: float | None = None) -> None:
"""检测并等待 Cloudflare 挑战完成
循环检测页面标题和内容:
@@ -211,11 +226,21 @@ class CloudflareSessionManager:
- 如果检测到挑战页面(如 "Just a moment"),继续等待
- 如果既非阻断也非挑战,认为已通过
Args:
page: Playwright 页面对象
timeout_seconds: 等待超时(秒);为 None 时使用新建会话的较长超时。
复用会话时传入较短超时,避免在无法通过的挑战上长时间空等。
Raises:
UpstreamBlockedError: 检测到被 Cloudflare 阻断
CloudflareChallengeError: 等待挑战超时
"""
timeout_at = asyncio.get_running_loop().time() + self._settings.cloudflare_wait_timeout_seconds
wait_timeout = (
self._settings.cloudflare_wait_timeout_seconds
if timeout_seconds is None
else timeout_seconds
)
timeout_at = asyncio.get_running_loop().time() + wait_timeout
while True:
title = (await page.title()).lower()
content = (await page.content()).lower()
@@ -238,7 +263,7 @@ class CloudflareSessionManager:
logger.debug("自动点击 Turnstile 失败或未找到: %s", e)
if asyncio.get_running_loop().time() >= timeout_at:
logger.warning("等待挑战超时(秒):%s", self._settings.cloudflare_wait_timeout_seconds)
logger.warning("等待挑战超时(秒):%s", wait_timeout)
raise CloudflareChallengeError()
await page.wait_for_timeout(1500)