This commit is contained in:
2026-06-09 16:31:14 +08:00
parent 2d75d21edc
commit 731552f93d
+28 -13
View File
@@ -25,6 +25,7 @@ class BrowserPool:
def __init__(self, settings: Settings): def __init__(self, settings: Settings):
self._settings = settings self._settings = settings
self._playwright: Any | None = None self._playwright: Any | None = None
self._playwright_cm: Any | None = None # stealth 钩子包装的 playwright 上下文管理器
self._browser: Any | None = None self._browser: Any | None = None
self._semaphore = asyncio.Semaphore(settings.max_site_concurrency) self._semaphore = asyncio.Semaphore(settings.max_site_concurrency)
self._restart_lock = asyncio.Lock() self._restart_lock = asyncio.Lock()
@@ -66,15 +67,29 @@ class BrowserPool:
headless = self._settings.browser_headless_effective headless = self._settings.browser_headless_effective
logger.info( logger.info(
"启动浏览器:enabled=%s headless=%s keep_open_on_error=%s channel=%s proxy=%s env=%s", "启动浏览器:enabled=%s headless=%s keep_open_on_error=%s channel=%s proxy=%s stealth=%s env=%s",
self._settings.browser_enabled, self._settings.browser_enabled,
headless, headless,
self._settings.browser_keep_open_on_error_effective, self._settings.browser_keep_open_on_error_effective,
self._settings.browser_channel, self._settings.browser_channel,
bool(self._settings.proxy_server), bool(self._settings.proxy_server),
self._settings.browser_stealth_enabled,
self._settings.app_env, self._settings.app_env,
) )
self._playwright = await async_playwright().start() if self._settings.browser_stealth_enabled:
# 用 playwright-stealth 钩子包装 playwright:后续 new_context/new_page
# 会自动应用 stealth(含 HTTP sec-ch-ua 客户端提示),并令 navigator.languages
# 与日语环境保持一致,避免指纹自相矛盾。
from playwright_stealth import Stealth
stealth = Stealth(
navigator_languages_override=("ja-JP", "ja", "en-US", "en"),
)
self._playwright_cm = stealth.use_async(async_playwright())
self._playwright = await self._playwright_cm.__aenter__()
else:
self._playwright_cm = None
self._playwright = await async_playwright().start()
chromium = self._playwright.chromium chromium = self._playwright.chromium
launch_args = [ launch_args = [
"--disable-blink-features=AutomationControlled", "--disable-blink-features=AutomationControlled",
@@ -138,13 +153,20 @@ class BrowserPool:
await self._browser.close() await self._browser.close()
except Exception: except Exception:
logger.exception("关闭浏览器实例失败") logger.exception("关闭浏览器实例失败")
if self._playwright is not None: if self._playwright_cm is not None:
# stealth 钩子包装:通过上下文管理器退出来停止 playwright
try:
await self._playwright_cm.__aexit__(None, None, None)
except Exception:
logger.exception("停止 Playwright(stealth 包装)运行时失败")
elif self._playwright is not None:
try: try:
await self._playwright.stop() await self._playwright.stop()
except Exception: except Exception:
logger.exception("停止 Playwright 运行时失败") logger.exception("停止 Playwright 运行时失败")
self._browser = None self._browser = None
self._playwright = None self._playwright = None
self._playwright_cm = None
self._started = False self._started = False
async def _restart_browser(self, reason: str) -> None: async def _restart_browser(self, reason: str) -> None:
@@ -251,9 +273,11 @@ class BrowserPool:
logger.exception("关闭旧的保留调试上下文失败") logger.exception("关闭旧的保留调试上下文失败")
async def _build_context_obj(self, storage_state: dict[str, Any] | None) -> Any: async def _build_context_obj(self, storage_state: dict[str, Any] | None) -> Any:
"""创建浏览器上下文(含断链重试与 stealth),仅负责创建本身。 """创建浏览器上下文(含断链重试),仅负责创建本身。
计入回收阈值统计;不负责并发信号量与关闭,由调用方管理生命周期。 计入回收阈值统计;不负责并发信号量与关闭,由调用方管理生命周期。
启用 stealth 时,playwright 已被 use_async 钩子包装,new_context 会自动
应用 stealth(含 HTTP sec-ch-ua),此处无需再手动 apply。
""" """
context = None context = None
for attempt in range(2): for attempt in range(2):
@@ -275,15 +299,6 @@ class BrowserPool:
# 上下文创建成功,计入回收阈值统计 # 上下文创建成功,计入回收阈值统计
self._contexts_since_start += 1 self._contexts_since_start += 1
if self._settings.browser_stealth_enabled:
try:
from playwright_stealth import Stealth
await Stealth().apply_stealth_async(context)
except Exception as e:
logger.warning("应用 playwright-stealth 失败,隐身模式未生效, err=%s", e)
return context return context
@staticmethod @staticmethod