2026 年 Playwright 隐身模式:真正重要的 7 个补丁

发布: (2026年4月3日 GMT+8 12:52)
6 分钟阅读
原文: Dev.to

Source: Dev.to

原始的 playwright‑stealth npm 包已经一年多没有更新。
如果你直接使用它,许多最关键的检测仍未被修补。

下面是 2026 年仍然重要的 7 个浏览器指纹修补,并附有可通过 page.addInitScript() 注入的可用代码。

1. navigator.webdriverundefined

await page.addInitScript(() => {
  Object.defineProperty(navigator, 'webdriver', {
    get: () => undefined,   // note: undefined, not false
    configurable: true
  });
});

2. navigator.pluginsnavigator.mimeTypes

await page.addInitScript(() => {
  const plugins = [
    {
      name: 'PDF Viewer',
      description: 'Portable Document Format',
      filename: 'internal-pdf-viewer'
    },
    {
      name: 'Chrome PDF Viewer',
      description: '',
      filename: 'internal-pdf-viewer'
    },
    {
      name: 'Chromium PDF Viewer',
      description: '',
      filename: 'internal-pdf-viewer'
    },
    {
      name: 'Microsoft Edge PDF Viewer',
      description: '',
      filename: 'internal-pdf-viewer'
    },
    {
      name: 'WebKit built-in PDF',
      description: '',
      filename: 'internal-pdf-viewer'
    }
  ];

  Object.defineProperty(navigator, 'plugins', {
    get: () =>
      Object.assign(plugins, {
        item: i => plugins[i],
        namedItem: n => plugins.find(p => p.name === n),
        refresh: () => {}
      }),
    configurable: true
  });

  Object.defineProperty(navigator, 'mimeTypes', {
    get: () => ({
      length: 2,
      item: i => null,
      namedItem: n => null
    }),
    configurable: true
  });
});

3. window.chrome(包括 loadTimescsi

await page.addInitScript(() => {
  if (!window.chrome) window.chrome = {};

  window.chrome.app = {
    isInstalled: false,
    InstallState: {
      DISABLED: 'disabled',
      INSTALLED: 'installed',
      NOT_INSTALLED: 'not_installed'
    },
    RunningState: {
      CANNOT_RUN: 'cannot_run',
      READY_TO_RUN: 'ready_to_run',
      RUNNING: 'running'
    }
  };

  window.chrome.runtime = {
    OnInstalledReason: {
      CHROME_UPDATE: 'chrome_update',
      INSTALL: 'install',
      SHARED_MODULE_UPDATE: 'shared_module_update',
      UPDATE: 'update'
    },
    OnRestartRequiredReason: {
      APP_UPDATE: 'app_update',
      OS_UPDATE: 'os_update',
      PERIODIC: 'periodic'
    },
    PlatformArch: {
      ARM: 'arm',
      ARM64: 'arm64',
      MIPS: 'mips',
      MIPS64: 'mips64',
      X86_32: 'x86-32',
      X86_64: 'x86-64'
    },
    PlatformOs: {
      ANDROID: 'android',
      CROS: 'cros',
      LINUX: 'linux',
      MAC: 'mac',
      OPENBSD: 'openbsd',
      WIN: 'win'
    },
    RequestUpdateCheckStatus: {
      NO_UPDATE: 'no_update',
      THROTTLED: 'throttled',
      UPDATE_AVAILABLE: 'update_available'
    },
    id: undefined,
    connect: () => {},
    sendMessage: () => {}
  };

  // Required: loadTimes and csi (missing from most stealth libraries)
  window.chrome.loadTimes = function () {
    return {
      requestTime: Date.now() / 1000,
      startLoadTime: Date.now() / 1000,
      commitLoadTime: Date.now() / 1000,
      finishDocumentLoadTime: 0,
      finishLoadTime: 0,
      firstPaintTime: 0,
      firstPaintAfterLoadTime: 0,
      navigationType: 'Other',
      wasFetchedViaSpdy: false,
      wasNpnNegotiated: false,
      npnNegotiatedProtocol: 'unknown',
      wasAlternateProtocolAvailable: false,
      connectionInfo: 'http/1.1'
    };
  };

  window.chrome.csi = function () {
    return {
      startE: Date.now(),
      onloadT: Date.now(),
      pageT: 3000 + Math.random() * 1000,
      tran: 15
    };
  };
});

4. navigator.permissions(通知)

await page.addInitScript(() => {
  const originalQuery = navigator.permissions.query.bind(navigator.permissions);
  navigator.permissions.query = parameters =>
    parameters.name === 'notifications'
      ? Promise.resolve({ state: Notification.permission })
      : originalQuery(parameters);
});

5. WebGL 指纹(供应商 & 渲染器)

await page.addInitScript(() => {
  const getParameter = WebGLRenderingContext.prototype.getParameter;
  WebGLRenderingContext.prototype.getParameter = function (parameter) {
    // 37445 → UNMASKED_VENDOR_WEBGL
    // 37446 → UNMASKED_RENDERER_WEBGL
    if (parameter === 37445) return 'Intel Inc.';               // replace with your target GPU vendor
    if (parameter === 37446) return 'Intel Iris OpenGL Engine'; // replace with your target GPU renderer
    return getParameter.call(this, parameter);
  };
});

提示: 将 GPU 字符串替换为符合目标受众的值(例如,针对高级用户使用 NVIDIA,普通笔记本使用 Intel)。

6. navigator.languagenavigator.languages

await page.addInitScript(() => {
  Object.defineProperty(navigator, 'language', {
    get: () => 'en-US'               // adjust to your proxy location
  });
  Object.defineProperty(navigator, 'languages', {
    get: () => ['en-US', 'en']        // adjust as needed
  });
});

7. 基于 iframe 的检测(在嵌套框架中修补 webdriver

await page.addInitScript(() => {
  const origCreateElement = document.createElement.bind(document);
  document.createElement = function (...args) {
    const element = origCreateElement(...args);
    if (args[0].toLowerCase() === 'iframe') {
      const origContentWindow = Object.getOwnPropertyDescriptor(
        HTMLIFrameElement.prototype,
        'contentWindow'
      ).get;

      Object.defineProperty(element, 'contentWindow', {
        get: function () {
          const win = origContentWindow.call(this);
          if (win) {
            Object.defineProperty(win.navigator, 'webdriver', {
              get: () => undefined,
              configurable: true
            });
          }
          return win;
        },
        configurable: true
      });
    }
    return element;
  };
});

助手:一次性应用所有补丁

async function applyStealthPatches(page) {
  // Patch 1 – webdriver
  await page.addInitScript(() => {
    Object.defineProperty(navigator, 'webdriver', {
      get: () => undefined,
      configurable: true
    });
  });

  // Patch 2 – plugins & mimeTypes
  await page.addInitScript(/* code from section 2 */);

  // Patch 3 – window.chrome (including loadTimes & csi)
  await page.addInitScript(/* code from section 3 */);

  // Patch 4 – permissions (notifications)
  await page.addInitScript(/* code from section 4 */);

  // Patch 5 – WebGL vendor/renderer
  await page.addInitScript(/* code from section 5 */);

  // Patch 6 – language & languages
  await page.addInitScript(/* code from section 6 */);

  // Patch 7 – iframe detection
  await page.addInitScript(/* code from section 7 */);
}

您可以直接内联这些代码片段,或将它们保存在单独的辅助文件中以提升可读性。

示例用法

import { chromium } from 'playwright';

const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
  userAgent:
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  viewport: { width: 1920, height: 1080 }
});

const page = await context.newPage();
await applyStealthPatches(page);

// Now navigate to the target site
await page.goto('https://example.com');

这些补丁覆盖了 2026 年观察到的最常见失败模式(WebGL、权限、window.chrome、HTTP/2 JA3 泄漏、iframe 检测等)。根据您模拟的代理或设备的配置,调整相应的数值(GPU 字符串、语言、用户代理等)。

TL;DR

即使进行完美的浏览器补丁,网络层的 TLS 指纹仍然会暴露 Node.js/Playwright(例如,对抗 Kasada)。

选项

  • Playwright + residential proxies + CAPTCHA services – 约为 $3‑$8 每 1 K 请求
  • Pre‑built cloud actors 为您处理整个堆栈

即用解决方案

  • Apify Scrapers Bundle$29
    包括预先配置了隐身设置的演员,针对最常见的抓取目标(LinkedIn、Amazon、Google Maps、Twitter),为您节省调试周期。

  • n8n AI Automation Pack$39
    提供 5 个可投入生产的工作流

Need help?

Which anti‑bot system are you trying to work around? Drop a comment with the domain — I’ll tell you whether patches are enough or if you need a different approach.

相关工具

  • facebook-public-scraper
  • threads-profile-scraper
0 浏览
Back to Blog

相关文章

阅读更多 »