Skip to content

指纹规避技术

本文档提供了使用 Pydoll 的 CDP 集成、JavaScript 覆盖和请求拦截来规避指纹的 实用、可操作的技术。这里描述的所有内容都经过了测试和验证。

模块导航

有关 Pydoll 的实际用法,请参阅 类人交互行为验证码绕过

理论 → 实践

在这里,您将应用所有学到的关于网络和浏览器指纹的知识。每种技术都包含 可用的代码示例,可随时与 Pydoll 集成。

基于 CDP 的指纹规避

Chrome 开发者工具协议 (CDP) 提供了在深层次修改浏览器行为的强大方法,远超 JavaScript 注入所能实现的。这使得基于 CDP 的自动化(如 Pydoll)比 Selenium 或 Puppeteer 隐蔽得多

User-Agent 不匹配问题

自动化中最 常见 的指纹不一致之一是以下两者之间的不匹配:

  1. HTTP User-Agent 标头(随每个请求发送)
  2. navigator.userAgent 属性(JavaScript 可访问)

问题所在:

# 错误方法:通过命令行参数设置 User-Agent
options = ChromiumOptions()
options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64)...')

# 结果:
# HTTP 标头:Mozilla/5.0 (Windows NT 10.0; Win64; x64)... (正确)
# navigator.userAgent: Chrome/120.0.0.0 (原始值 - 错误!)
# → 检测到不匹配!

为什么会这样:

  • --user-agent 标志只修改 HTTP 标头
  • navigator.userAgent 是在页面加载 之前 从 Chromium 内部值设置的
  • JavaScript 无法直接看到 HTTP 标头,但服务器可以比较这两个值

检测技术(服务器端):

def detect_user_agent_mismatch(request):
    """
    服务器端检测 User-Agent 不一致性。
    """
    # 获取 HTTP 标头
    http_user_agent = request.headers.get('User-Agent')

    # 执行 JavaScript 以获取 navigator.userAgent
    #(通过质询/验证码页面完成)
    navigator_user_agent = get_client_navigator_ua()

    if http_user_agent != navigator_user_agent:
        return 'AUTOMATION_DETECTED'  # 明显不匹配

    return 'OK'

解决方案:CDP 模拟域

设置 User-Agent 的正确方法是通过 CDP 的 Emulation.setUserAgentOverride 方法,该方法会同时修改 HTTP 标头和 navigator 属性。在 Pydoll 中,您可以直接执行 CDP 命令:

import asyncio
from pydoll.browser.chromium import Chrome
from pydoll.commands import PageCommands


async def set_user_agent_correctly(tab, user_agent: str, platform: str = 'Win32'):
    """
    使用 CDP Emulation 域正确设置 User-Agent。
    这确保了 HTTP 标头和 navigator 属性之间的一致性。

    注意:Pydoll 尚未直接公开 Emulation 命令,因此我们暂时使用
    execute_script 来覆盖 navigator 属性。
    """
    # 通过 JavaScript 覆盖 navigator.userAgent
    override_script = f```
        Object.defineProperty(Navigator.prototype, 'userAgent', {{
            get: () => '{user_agent}'
        }});
        Object.defineProperty(Navigator.prototype, 'platform', {{
            get: () => '{platform}'
        }});
    ```

    await tab.execute_script(override_script)


async def main():
    async with Chrome() as browser:
        # 通过命令行参数设置 User-Agent (影响 HTTP 标头)
        options = browser.options
        custom_ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
        options.add_argument(f'--user-agent={custom_ua}')

        tab = await browser.start()

        # 同时通过 JavaScript 覆盖 navigator.userAgent 以保持一致性
        await set_user_agent_correctly(tab, custom_ua)

        # 导航 (User-Agent 现已一致)
        await tab.go_to('https://example.com')

        # 验证一致性
        result = await tab.execute_script('return navigator.userAgent')
        nav_ua = result['result']['result']['value']
        print(f"navigator.userAgent: {nav_ua}")
        # 两者现在匹配了!

asyncio.run(main())

客户端提示一致性

设置自定义 User-Agent 时,您 必须 同时设置一致的 userAgentMetadata (客户端提示),否则现代 Chromium 将发送 不一致Sec-CH-UA 标头!

不一致示例: - User-Agent: "Chrome/120.0.0.0" - Sec-CH-UA: "Chrome/119" (错误的版本!) - → 被检测!

指纹修改技术

虽然 Pydoll 没有直接公开所有 CDP Emulation 命令,但您可以使用 JavaScript 覆盖和浏览器选项达到类似的效果:

1. 时区覆盖 (通过 JavaScript)

async def set_timezone(tab, timezone_id: str):
    """
    通过 JavaScript 覆盖时区。
    示例:'America/New_York', 'Europe/London', 'Asia/Tokyo'

    注意:这会覆盖 JavaScript API,但不会影响系统级
    时区。请使用 --tz 命令行参数进行完整模拟。
    """
    script = f```
        // 覆盖 Intl.DateTimeFormat
        const originalDateTimeFormat = Intl.DateTimeFormat;
        Intl.DateTimeFormat = function(...args) {{
            const options = args[1] || {{}};
            options.timeZone = '{timezone_id}';
            return new originalDateTimeFormat(args[0], options);
        }};

        // 覆盖 Date.prototype.getTimezoneOffset
        const timezoneOffsets = {{
            'America/New_York': 300,
            'Europe/London': 0,
            'Asia/Tokyo': -540,
            'America/Los_Angeles': 480,
        }};
        Date.prototype.getTimezoneOffset = function() {{
            return timezoneOffsets['{timezone_id}'] || 0;
        }};
    ```
    await tab.execute_script(script)


# 用法
await set_timezone(tab, 'America/Los_Angeles')

# 验证
result = await tab.execute_script('return Intl.DateTimeFormat().resolvedOptions().timeZone')
tz = result['result']['result']['value']
print(f"Timezone: {tz}")  # America/Los_Angeles

2. 区域设置覆盖 (通过浏览器选项)

# 通过命令行参数设置区域设置
from pydoll.browser.chromium import Chrome
from pydoll.browser.options import ChromiumOptions

options = ChromiumOptions()
options.add_argument('--lang=pt-BR')
options.set_accept_languages('pt-BR,pt;q=0.9,en;q=0.8')

async with Chrome(options=options) as browser:
    tab = await browser.start()

    # 验证
    result = await tab.execute_script('return navigator.language')
    locale = result['result']['result']['value']
    print(f"Locale: {locale}")  # pt-BR

3. 地理位置覆盖 (通过 JavaScript)

async def set_geolocation(tab, latitude: float, longitude: float, accuracy: int = 1):
    """
    通过 JavaScript 覆盖地理位置。
    """
    script = f```
        navigator.geolocation.getCurrentPosition = function(success) {{
            const position = {{
                coords: {{
                    latitude: {latitude},
                    longitude: {longitude},
                    accuracy: {accuracy},
                    altitude: null,
                    altitudeAccuracy: null,
                    heading: null,
                    speed: null
                }},
                timestamp: Date.now()
            }};
            success(position);
        }};
    ```
    await tab.execute_script(script)


# 示例:纽约市
await set_geolocation(tab, 40.7128, -74.0060)

4. 设备指标 (通过浏览器选项)

# 通过命令行参数模拟移动设备
options = ChromiumOptions()
options.add_argument('--window-size=393,852')
options.add_argument('--device-scale-factor=3')
options.add_argument('--user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1')

async with Chrome(options=options) as browser:
    tab = await browser.start()

    # 覆盖额外的移动属性
    mobile_script = ```
        Object.defineProperty(Navigator.prototype, 'maxTouchPoints', {
            get: () => 5
        });

        // 覆盖屏幕属性
        Object.defineProperty(window.screen, 'width', { get: () => 393 });
        Object.defineProperty(window.screen, 'height', { get: () => 852 });
        Object.defineProperty(window.screen, 'availWidth', { get: () => 393 });
        Object.defineProperty(window.screen, 'availHeight', { get: () => 852 });
    ```
    await tab.execute_script(mobile_script)

5. 触摸事件 (通过 JavaScript)

async def enable_touch_events(tab, max_touch_points: int = 5):
    """
    覆盖触摸相关属性。
    """
    script = f```
        Object.defineProperty(Navigator.prototype, 'maxTouchPoints', {{
            get: () => {max_touch_points}
        }});

        // 添加触摸事件支持
        if (!window.TouchEvent) {{
            window.TouchEvent = class TouchEvent extends UIEvent {{}};
        }}
    ```
    await tab.execute_script(script)


# 验证
result = await tab.execute_script('return navigator.maxTouchPoints')
touch_points = result['result']['result']['value']
print(f"Max Touch Points: {touch_points}")  # 5

用于标头修改的请求拦截

Pydoll 通过 Fetch 域为请求拦截提供本机支持。这允许您修改标头、阻止请求或提供自定义响应:

import asyncio
from pydoll.browser.chromium import Chrome
from pydoll.protocol.fetch.events import FetchEvent


async def setup_request_interception(tab):
    """
    使用 Pydoll 的本机方法拦截所有请求并修改标头。
    """
    # 启用 Fetch 域以进行请求拦截
    await tab.enable_fetch_events()

    # 监听请求暂停事件
    async def handle_request(event):
        """处理被拦截的请求。"""
        request_id = event['params']['requestId']
        request = event['params']['request']

        # 获取当前标头
        headers = request.get('headers', {})

        # 修复常见的不一致
        if 'Accept-Encoding' in headers:
            # 确保支持 Brotli
            if 'br' not in headers['Accept-Encoding']:
                headers['Accept-Encoding'] = 'gzip, deflate, br, zstd'

        # 移除自动化标记
        headers.pop('X-Requested-With', None)

        # 将标头转换为 HeaderEntry 格式
        header_list = [{'name': k, 'value': v} for k, v in headers.items()]

        # 使用修改后的标头继续请求
        await tab.continue_request(
            request_id=request_id,
            headers=header_list
        )

    # 为请求暂停事件注册事件监听器
    await tab.on(FetchEvent.REQUEST_PAUSED, handle_request)


async def main():
    async with Chrome() as browser:
        tab = await browser.start()

        # 导航前设置拦截
        await setup_request_interception(tab)

        # 现在所有请求都将具有修改后的标头
        await tab.go_to('https://example.com')

asyncio.run(main())

完整的指纹规避示例

这是一个使用 Pydoll 的 API 结合所有技术的综合示例:

import asyncio
from pydoll.browser.chromium import Chrome
from pydoll.browser.options import ChromiumOptions


class FingerprintEvader:
    """
    使用浏览器选项和 JavaScript 进行全面指纹规避。
    """

    def __init__(self, profile: dict):
        """
        使用目标配置文件初始化 (操作系统、位置、设备等)
        """
        self.profile = profile
        self.options = ChromiumOptions()
        self._configure_browser_options()

    def _configure_browser_options(self):
        """根据配置文件配置浏览器启动选项。"""
        # 1. User-Agent
        self.options.add_argument(f'--user-agent={self.profile["userAgent"]}')

        # 2. 语言和区域设置
        self.options.add_argument(f'--lang={self.profile["locale"]}')
        self.options.set_accept_languages(self.profile["acceptLanguage"])

        # 3. 窗口大小 (屏幕尺寸)
        screen = self.profile['screen']
        self.options.add_argument(f'--window-size={screen["width"]},{screen["height"]}')

        # 4. 设备缩放因子 (用于高 DPI 显示器)
        if screen.get('deviceScaleFactor', 1.0) != 1.0:
            self.options.add_argument(f'--device-scale-factor={screen["deviceScaleFactor"]}')

    async def apply_to_tab(self, tab):
        """
        启动后对选项卡应用 JavaScript 覆盖。
        """
        script = f```
            // 覆盖 User-Agent (为保持一致性)
            Object.defineProperty(Navigator.prototype, 'userAgent', {{
                get: () => '{self.profile["userAgent"]}'
            }});

            // 覆盖 platform
            Object.defineProperty(Navigator.prototype, 'platform', {{
                get: () => '{self.profile["platform"]}'
            }});

            // 覆盖 hardwareConcurrency
            Object.defineProperty(Navigator.prototype, 'hardwareConcurrency', {{
                get: () => {self.profile.get('hardwareConcurrency', 8)}
            }});

            // 覆盖 deviceMemory
            Object.defineProperty(Navigator.prototype, 'deviceMemory', {{
                get: () => {self.profile.get('deviceMemory', 8)}
            }});

            // 覆盖 languages
            Object.defineProperty(Navigator.prototype, 'languages', {{
                get: () => {self.profile['languages']}
            }});

            // 覆盖 vendor
            Object.defineProperty(Navigator.prototype, 'vendor', {{
                get: () => '{self.profile.get('vendor', 'Google Inc.')}'
            }});

            // 覆盖 max touch points (用于移动设备)
            Object.defineProperty(Navigator.prototype, 'maxTouchPoints', {{
                get: () => {self.profile.get('maxTouchPoints', 0)}
            }});
        ```

        await tab.execute_script(script)

        # 如果提供了地理位置,则应用
        if 'geolocation' in self.profile:
            await self._override_geolocation(tab)

        # 如果提供了时区,则应用
        if 'timezone' in self.profile:
            await self._override_timezone(tab)

    async def _override_geolocation(self, tab):
        """覆盖地理位置 API。"""
        geo = self.profile['geolocation']
        script = f```
            navigator.geolocation.getCurrentPosition = function(success) {{
                const position = {{
                    coords: {{
                        latitude: {geo['latitude']},
                        longitude: {geo['longitude']},
                        accuracy: 1,
                        altitude: null,
                        altitudeAccuracy: null,
                        heading: null,
                        speed: null
                    }},
                    timestamp: Date.now()
                }};
                success(position);
            }};
        ```
        await tab.execute_script(script)

    async def _override_timezone(self, tab):
        """覆盖时区相关函数。"""
        timezone = self.profile['timezone']
        # 时区到分钟偏移量的映射
        offsets = {{
            'America/New_York': 300,
            'Europe/London': 0,
            'Asia/Tokyo': -540,
            'America/Los_Angeles': 480,
        }}
        offset = offsets.get(timezone, 0)

        script = f```
            // 覆盖 Intl.DateTimeFormat
            const originalDateTimeFormat = Intl.DateTimeFormat;
            Intl.DateTimeFormat = function(...args) {{
                const options = args[1] || {{}};
                options.timeZone = '{timezone}';
                return new originalDateTimeFormat(args[0], options);
            }};

            // 覆盖 Date.prototype.getTimezoneOffset
            Date.prototype.getTimezoneOffset = function() {{
                return {offset};
            }};
        ```
        await tab.execute_script(script)


# 用法示例
async def main():
    # 定义目标配置文件
    profile = {{
        'userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'platform': 'Win32',
        'acceptLanguage': 'en-US,en;q=0.9',
        'languages': ['en-US', 'en'],
        'timezone': 'America/New_York',
        'locale': 'en-US',
        'geolocation': {{
            'latitude': 40.7128,
            'longitude': -74.0060
        }},
        'screen': {{
            'width': 1920,
            'height': 1080,
            'deviceScaleFactor': 1.0
        }},
        'hardwareConcurrency': 8,
        'deviceMemory': 8,
        'vendor': 'Google Inc.',
        'maxTouchPoints': 0,  # 桌面
    }}

    # 使用配置文件创建规避器
    evader = FingerprintEvader(profile)

    # 使用配置的选项启动浏览器
    async with Chrome(options=evader.options) as browser:
        tab = await browser.start()

        # 应用 JavaScript 覆盖
        await evader.apply_to_tab(tab)

        # 使用一致的指纹进行导航
        await tab.go_to('https://example.com')

        # 验证指纹
        result = await tab.execute_script(```
            return {
                userAgent: navigator.userAgent,
                platform: navigator.platform,
                languages: navigator.languages,
                hardwareConcurrency: navigator.hardwareConcurrency,
                deviceMemory: navigator.deviceMemory,
                timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
                maxTouchPoints: navigator.maxTouchPoints,
            };
        ```)

        fingerprint = result['result']['result']['value']

        print("应用的指纹:")
        for key, value in fingerprint.items():
            print(f"  {key}: {value}")

asyncio.run(main())

指纹一致性是关键

指纹规避最重要的方面是 跨所有层的一致性

  1. HTTP 标头 (User-Agent, Accept-Language, Sec-CH-UA)
  2. Navigator 属性 (userAgent, platform, languages)
  3. 系统属性 (时区, 区域设置, 屏幕分辨率)
  4. 网络指纹 (TLS, HTTP/2 设置)

一个单一的不一致就可能暴露自动化!

CDP 模拟参考

行为规避策略

鉴于 Pydoll 基于 CDP 的架构,行为指纹识别需要仔细关注类似人类的交互模式。有关行为检测的理论背景,请参阅 行为指纹

当前状态:需要手动随机化

类人交互 中所述,Pydoll 当前需要手动实现 行为真实性:

  • 鼠标移动:必须使用贝塞尔曲线和随机化来实现
  • 打字:需要以可变间隔逐个字符输入
  • 滚动:需要手动 JavaScript 模拟动量
  • 事件序列:必须确保正确的顺序 (mousemove → mousedown → mouseup → click)

未来的改进

Pydoll 的未来版本将包括自动化的行为真实性:

# 未来 API (尚未实现)
await element.click(
    realistic=True,              # 自动贝塞尔曲线移动
    offset='random',             # 边界内的随机偏移
    thinking_time=(1.0, 3.0)     # 操作前的随机延迟
)

await input_field.type_text(
    "human-like text",
    realistic=True,              # 具有 bigram 计时的可变打字速度
    error_rate=0.05              # 5% 的几率出现拼写错误 + 退格
)

await tab.scroll_to(
    target_y=1000,
    realistic=True,              # 动量 + 惯性模拟
    speed='medium'               # 类似人类的滚动速度
)

立即实用实施

在内置自动化之前,请遵循以下实践:

1. 点击前的鼠标移动

# 错误:没有移动的瞬时点击
await element.click()  # 瞬移光标并点击中心

# 良好:先进行真实的移动
# (需要手动实现)
await move_mouse_realistically(element)
await asyncio.sleep(random.uniform(0.1, 0.3))
await element.click(x_offset=random.randint(-10, 10))

2. 可变的打字速度

# 错误:恒定间隔
await input.type_text("text", interval=0.1)  # 机器人的计时

# 良好:每个字符的可变间隔
for char in "text":
    await input.type_text(char, interval=0)
    await asyncio.sleep(random.uniform(0.08, 0.22))

3. 思考时间

# 错误:页面加载后立即操作
await tab.go_to('https://example.com')
await button.click()  # 太快了!

# 良好:用于阅读/扫描的自然延迟
await tab.go_to('https://example.com')
await asyncio.sleep(random.uniform(2.0, 5.0))  # 阅读页面
await random_mouse_movement()  # 用光标扫描
await button.click()  # 然后行动

4. 带动量的滚动

# 错误:瞬时滚动
await tab.execute_script("window.scrollTo(0, 1000)")

# 良好:带减速的逐渐滚动
scroll_events = simulate_human_scroll(target=1000)
for delta, delay in scroll_events:
    await tab.execute_script(f"window.scrollBy(0, {delta})")
    await asyncio.sleep(delay)

行为检测由机器学习驱动

现代反机器人系统使用在数十亿次交互上训练的机器学习。它们不使用简单的规则——它们检测 统计模式。专注于:

  1. 可变性:没有两个动作应该是完全相同的
  2. 上下文:动作必须遵循自然的顺序
  3. 计时:基于人类生物力学的真实间隔
  4. 一致性:不要混合机器人式和类人式模式

指纹规避的最佳实践

基于本指南中涵盖的所有技术,以下是 Web 自动化中成功规避指纹的基本最佳实践:

1. 从真实的浏览器配置文件开始

不要从头开始发明指纹。捕获真实的浏览器配置文件并使用它们:

# 从您自己的浏览器捕获真实的指纹
# 访问 https://browserleaks.com/ 并收集所有数据
REAL_PROFILES = {
    'windows_chrome': {
        'userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...',
        'platform': 'Win32',
        'hardwareConcurrency': 8,
        'deviceMemory': 8,
        'canvas_hash': 'captured_from_real_browser',
        # ... 所有其他属性
    }
}

2. 保持所有层的一致性

检查以下一致性点:

  • User-Agent 与 navigator.userAgent 匹配
  • Platform 与 User-Agent 操作系统匹配
  • 语言与时区/地理位置匹配
  • 屏幕分辨率对于所声称的设备是真实的
  • 硬件规格与所声称的平台匹配 (CPU 核心数, RAM)
  • Canvas/WebGL 指纹是稳定的 (不是随机的)
  • 时区与 Accept-Language 标头匹配
  • 客户端提示与 User-Agent 匹配

3. 使用浏览器偏好设置以实现隐蔽

利用 Pydoll 的浏览器偏好设置 (参见 浏览器偏好设置):

from pydoll.browser.options import ChromiumOptions

options = ChromiumOptions()
options.browser_preferences = {
    # 模拟使用历史
    'profile': {
        'created_by_version': '120.0.6099.130',
        'creation_time': str(time.time() - (90 * 24 * 60 * 60)),  # 90 天前
        'exit_type': 'Normal',
    },

    # 真实的内容设置
    'profile.default_content_setting_values': {
        'cookies': 1,
        'images': 1,
        'javascript': 1,
        'notifications': 2,  # 询问 (真实)
    },

    # WebRTC IP 处理 (防止泄露)
    'webrtc': {
        'ip_handling_policy': 'disable_non_proxied_udp',
    },
}

4. 明智地轮换指纹

不要 在同一站点上过于频繁地更改指纹:

# 错误:每个请求都使用新指纹
for url in urls:
    fingerprint = generate_random_fingerprint()  # 可疑!
    apply_fingerprint(tab, fingerprint)
    await tab.go_to(url)

# 良好:每个会话使用一致的指纹
fingerprint = select_fingerprint_for_target(target_site)
apply_fingerprint(tab, fingerprint)

for url in urls:
    await tab.go_to(url)  # 相同的指纹

5. 测试你的指纹

在部署之前,使用这些工具来验证您的指纹:

工具 URL 测试
BrowserLeaks https://browserleaks.com/ 全面:Canvas, WebGL, 字体, IP, WebRTC
AmIUnique https://amiunique.org/ 指纹唯一性分析
CreepJS https://abrahamjuliot.github.io/creepjs/ 高级欺骗检测
Fingerprint.com Demo https://fingerprint.com/demo/ 商业级检测
PixelScan https://pixelscan.net/ 机器人检测分析
IPLeak https://ipleak.net/ WebRTC, DNS, IP 泄露

验证脚本:

async def verify_fingerprint(tab):
    """
    在实际使用前验证指纹一致性。
    """
    tests = []

    # 测试 1:User-Agent 一致性
    nav_ua = await tab.execute_script('return navigator.userAgent')
    print(f"User-Agent: {nav_ua[:50]}...")

    # 测试 2:时区/语言一致性
    tz = await tab.execute_script('return Intl.DateTimeFormat().resolvedOptions().timeZone')
    lang = await tab.execute_script('return navigator.language')
    print(f"Timezone: {tz}, Language: {lang}")

    # 测试 3:WebDriver 检测
    webdriver = await tab.execute_script('return navigator.webdriver')
    if webdriver:
        print("navigator.webdriver is true! (DETECTED)")
        tests.append(False)
    else:
        print("navigator.webdriver is undefined (OK)")
        tests.append(True)

    # 测试 4:Canvas 一致性
    canvas1 = await get_canvas_fingerprint(tab)
    await asyncio.sleep(0.5)
    canvas2 = await get_canvas_fingerprint(tab)
    if canvas1 == canvas2:
        print("Canvas fingerprint is consistent (OK)")
        tests.append(True)
    else:
        print("Canvas fingerprint is inconsistent, noise detected (DETECTED)")
        tests.append(False)

    # 测试 5:插件
    plugins = await tab.execute_script('return navigator.plugins.length')
    print(f"Plugins: {plugins}")

    return all(tests)

6. 结合行为真实性

仅靠指纹规避是不够的。请结合:

7. 监控检测信号

实施日志记录以检测您的自动化何时被标记:

async def monitor_detection_signals(tab):
    """
    监控检测迹象。
    """
    signals = await tab.execute_script(```
        () => {
            return {
                // 检查已知的检测脚本
                fpjs: typeof window.Fingerprint !== 'undefined',
                datadome: typeof window.DD_RUM !== 'undefined',
                perimeter_x: typeof window._pxAppId !== 'undefined',
                cloudflare: document.querySelector('script[src*="challenges.cloudflare.com"]') !== null,

                // 检查质询页面
                is_captcha: document.title.includes('Captcha') || 
                           document.title.includes('Challenge') ||
                           document.body.innerText.includes('verification'),
            };
        }
    ```)

    if any(signals.values()):
        print("发现检测信号:")
        for key, value in signals.items():
            if value:
                print(f"  - {key}: detected")

8. 正确使用代理

网络级指纹识别需要正确使用代理:

  • 匹配代理位置 与时区/语言
  • 使用住宅代理 应对高价值目标
  • 轮换代理 但保持每个代理的指纹一致性
  • 测试 WebRTC 泄露 (参见 代理配置)

要避免的常见错误

错误 1:随机化所有内容

# 错误:毫无意义的随机指纹
fingerprint = {
    'userAgent': 'Chrome 120 on Windows',
    'platform': 'Linux x86_64',  # 不匹配!
    'hardwareConcurrency': random.randint(1, 32),  # 太随机
    'deviceMemory': random.choice([0.5, 128]),  # 不切实际的值
}

失败原因:真实浏览器具有 一致、切合实际 的配置。随机值会产生不可能的组合。

错误 2:忽略客户端提示

# 错误:设置 User-Agent 时没有客户端提示
await tab.send_cdp_command('Emulation.setUserAgentOverride', {
    'userAgent': 'Chrome/120...',
    # 缺少 userAgentMetadata!
})
# 结果:Sec-CH-UA 标头将不一致

错误 3:Canvas 噪声注入

# 错误:向 canvas 添加随机噪声
def add_canvas_noise(ctx):
    # 随机化像素值
    imageData = ctx.getImageData(0, 0, 100, 100)
    for i in range(len(imageData.data)):
        imageData.data[i] += random.randint(-5, 5)  # 噪声注入
    ctx.putImageData(imageData, 0, 0)

失败原因:噪声使指纹 不一致,这本身是可检测的。网站可以多次请求指纹并检测变化。

错误 4:过时的 User-Agent

# 错误:使用旧的浏览器版本
userAgent = 'Mozilla/5.0 ... Chrome/90.0.0.0'  # 2 年前了!

失败原因:缺少现代功能的旧版本很容易被检测到。请使用最近 3-6 个月内的版本。

错误 5:无头模式检测

# 错误:使用无头模式但没有正确配置
options = ChromiumOptions()
options.headless = True  # 可通过窗口尺寸检测

修复:使用 --headless=new 并配合真实的窗口大小:

options = ChromiumOptions()
options.add_argument('--headless=new')
options.add_argument('--window-size=1920,1080')

结论

浏览器和网络指纹是自动化开发者与反机器人系统之间一场复杂的猫鼠游戏。成功需要理解 多个层面 的指纹:

网络层面: - TCP/IP 特性 (TTL, 窗口大小, 选项) - TLS 握手模式 (JA3, 密码套件, GREASE) - HTTP/2 设置和流优先级

浏览器层面: - HTTP 标头一致性 - JavaScript API 属性 (navigator, screen 等) - Canvas 和 WebGL 渲染 - 基于 CDP 的规避技术

行为层面: - 鼠标移动模式和物理学 (菲茨定律, 贝塞尔曲线) - 按键动力学和打字节奏 (bigrams, 停留/飞行时间) - 滚动动量和惯性 - 事件序列和计时分析

关键要点:

  1. 一致性至关重要 - 一个单一的不匹配就可能暴露自动化
  2. 使用真实配置文件 - 不要从头开始发明指纹
  3. CDP 功能强大 - 利用 Emulation 域进行深度修改
  4. 充分测试 - 部署前在指纹测试网站上进行测试
  5. 结合多层 - 网络 + 浏览器 + 行为规避
  6. 保持更新 - 检测技术不断发展;保持指纹最新

Pydoll 的优势:

  • 没有 navigator.webdriver (不像 Selenium/Puppeteer)
  • 直接 CDP 访问 以实现深度浏览器控制
  • 通过 Fetch 域实现 请求拦截
  • 浏览器偏好设置 以实现真实的历史/设置
  • 异步架构 以实现自然的计时模式

借助本指南中的技术,您可以创建 高度隐蔽 的浏览器自动化,在各个层面模仿真实的用户行为。

保持学习

指纹识别是一个活跃的研究领域。通过以下方式保持更新:

  • 关注安全会议 (USENIX, Black Hat, DEF CON)
  • 监控反机器人供应商 (Akamai, Cloudflare, DataDome)
  • 定期在检测网站上测试您的指纹
  • 阅读 Chromium 源代码以寻找新的指纹向量

进一步阅读

综合指南

外部资源

学术论文