feat(11-01): create stealth fingerprint masking module
- Add StealthConfig Pydantic model with all playwright-stealth options - Add apply_stealth_sync/apply_stealth_async functions for browser context - Add get_default_stealth_config convenience function - Add 9 unit tests covering config defaults, stealth application, and disabled mode - Update conftest.py with stealth_config and mock_browser_context fixtures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
"""Stealth module for browser fingerprint masking."""
|
||||
|
||||
from .stealth_config import (
|
||||
StealthConfig,
|
||||
apply_stealth_sync,
|
||||
apply_stealth_async,
|
||||
get_default_stealth_config,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"StealthConfig",
|
||||
"apply_stealth_sync",
|
||||
"apply_stealth_async",
|
||||
"get_default_stealth_config",
|
||||
]
|
||||
@@ -0,0 +1,169 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user