"""搜索页抓取延迟探针:复现线上 ~5s 的慢请求,定位耗时阶段 测量: 1. 首次抓取的 预热耗时 / 目标页耗时 2. 同会话连续抓取的稳态耗时(预热复用是否生效) 3. 对比「无预热直连」的耗时(验证 Akamai 限速行为是否变化) """ from __future__ import annotations import asyncio import sys import time from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from app.shared.config import get_settings # noqa: E402 from app.scraping.services.browser_fallback import BrowserFallback # noqa: E402 from app.scraping.services.site_session import SiteSession # noqa: E402 from app.scraping.core import site # noqa: E402 URL = "https://search.rakuten.co.jp/search/mall/kitty/?p=2" async def main() -> int: settings = get_settings() print(f"proxy={bool(settings.proxy_server)} timeout={settings.request_timeout_seconds} " f"ttl={settings.session_ttl_seconds} attempts={settings.http_max_attempts}") session = SiteSession(settings, BrowserFallback(settings)) await session.start() profile = session._profiles["pc"] # 1) 首次抓取(含预热) t0 = time.monotonic() await session._ensure_warm(profile) t_warm = time.monotonic() - t0 print(f"[1] 预热: {t_warm:.2f}s cookies={sorted(profile.cookie_names & set(site.AKAMAI_COOKIE_NAMES))}") t0 = time.monotonic() page = await session.fetch_html(URL, mobile=False) t_first = time.monotonic() - t0 print(f"[1] 首次抓取: {t_first:.2f}s html={len(page)}") # 2) 稳态:同会话连抓 3 次 for i in range(2, 5): t0 = time.monotonic() page = await session.fetch_html(URL, mobile=False) print(f"[{i}] 稳态抓取: {time.monotonic() - t0:.2f}s html={len(page)}") await session.close() # 3) 对照:全新客户端、无预热、直接打目标页(Akamai 限速基线) import httpx from app.shared.proxy import httpx_client_options async with httpx.AsyncClient( headers=site.default_headers(mobile=False), timeout=settings.request_timeout_seconds, follow_redirects=True, http2=True, **httpx_client_options(settings), ) as cold: t0 = time.monotonic() resp = await cold.get(URL) print(f"[对照] 无预热直连: {time.monotonic() - t0:.2f}s status={resp.status_code} " f"html={len(resp.text)} cookies={sorted(c for c in resp.cookies.keys())}") return 0 if __name__ == "__main__": raise SystemExit(asyncio.run(main()))