"""Stealth configuration for browser fingerprint masking. Uses playwright-stealth library to hide Playwright automation signatures. """ from typing import Optional from playwright.sync_api import BrowserContext from playwright.async_api import BrowserContext as AsyncBrowserContext from pydantic import BaseModel, Field from playwright_stealth import Stealth class StealthConfig(BaseModel): """Configuration for browser stealth settings. All options map to playwright-stealth evasion scripts. Default values optimized for Zhihu anti-detection. """ stealth_enabled: bool = Field( default=True, description="Enable stealth mode (set False to disable)" ) # Navigator evasion options navigator_webdriver: bool = Field( default=True, description="Hide navigator.webdriver property (STEALTH-01)" ) navigator_user_agent: bool = Field( default=True, description="Replace HeadlessChrome in User-Agent (STEALTH-04)" ) navigator_plugins: bool = Field( default=True, description="Mock navigator.plugins array" ) navigator_languages: bool = Field( default=True, description="Set navigator.languages" ) navigator_platform: bool = Field( default=True, description="Set navigator.platform" ) navigator_vendor: bool = Field( default=True, description="Set navigator.vendor" ) navigator_permissions: bool = Field( default=True, description="Override navigator.permissions" ) # Chrome object evasion (STEALTH-03) chrome_app: bool = Field( default=True, description="Mock chrome.app object" ) chrome_csi: bool = Field( default=True, description="Mock chrome.csi function" ) chrome_load_times: bool = Field( default=True, description="Mock chrome.loadTimes function" ) # WebGL and rendering webgl_vendor: bool = Field( default=True, description="Override WebGL vendor/renderer strings" ) # Other evasion options hairline: bool = Field( default=True, description="Fix hairline detection" ) iframe_content_window: bool = Field( default=True, description="Fix iframe contentWindow" ) media_codecs: bool = Field( default=True, description="Mock media codecs" ) error_prototype: bool = Field( default=True, description="Fix error prototype chain" ) sec_ch_ua: bool = Field( default=True, description="Set sec-ch-ua headers" ) def create_stealth(self) -> Stealth: """Create playwright-stealth Stealth instance from config. Returns: Configured Stealth instance """ return Stealth( navigator_webdriver=self.navigator_webdriver, navigator_user_agent=self.navigator_user_agent, navigator_plugins=self.navigator_plugins, navigator_languages=self.navigator_languages, navigator_platform=self.navigator_platform, navigator_vendor=self.navigator_vendor, navigator_permissions=self.navigator_permissions, chrome_app=self.chrome_app, chrome_csi=self.chrome_csi, chrome_load_times=self.chrome_load_times, webgl_vendor=self.webgl_vendor, hairline=self.hairline, iframe_content_window=self.iframe_content_window, media_codecs=self.media_codecs, error_prototype=self.error_prototype, sec_ch_ua=self.sec_ch_ua, ) def apply_stealth_sync( context: BrowserContext, config: Optional[StealthConfig] = None ) -> None: """Apply stealth settings to browser context (synchronous). Args: context: Playwright BrowserContext to apply stealth to config: StealthConfig instance (uses defaults if not provided) """ if config is None: config = StealthConfig() if not config.stealth_enabled: return stealth = config.create_stealth() stealth.apply_stealth_sync(context) async def apply_stealth_async( context: AsyncBrowserContext, config: Optional[StealthConfig] = None ) -> None: """Apply stealth settings to browser context (asynchronous). Args: context: Playwright async BrowserContext to apply stealth to config: StealthConfig instance (uses defaults if not provided) """ if config is None: config = StealthConfig() if not config.stealth_enabled: return stealth = config.create_stealth() await stealth.apply_stealth_async(context) def get_default_stealth_config() -> StealthConfig: """Get default StealthConfig for Zhihu crawling. Returns: StealthConfig with all evasion options enabled """ return StealthConfig()