前端爬虫检测

149 阅读2分钟

1. 基于 MDN 和 Can I Use 设计环境检测项

无头浏览器检测

  • 检测 window.chromewindow.__nightmare 等特定无头浏览器属性。
  • 检测 navigator.webdriver 属性是否存在(无头浏览器通常会暴露此属性)。
  • 检测 navigator.pluginsnavigator.languages 是否为空(无头浏览器通常不支持插件或多语言)。
  • 检测 screen.widthscreen.height 是否符合正常浏览器的分辨率。

常规 DOM 检测

  • 检测 document.createElementwindow.addEventListener
  • 检测 document.documentElement.style 是否可修改。
  • 检测 window.localStoragewindow.sessionStorage 是否可用。

JSDOM 检测

  • 检测 window.performance 是否存在(JSDOM 可能不支持完整的 Performance API)。
  • 检测 document.all 的行为是否符合浏览器标准(JSDOM 可能不完全模拟)。
  • 检测 window.alertwindow.prompt 是否被重写(JSDOM 可能不支持这些方法)。

其他检测

  • 检测 WebGL 支持情况(通过 canvas.getContext('webgl'))。
  • 检测 AudioContext 是否可用。
  • 检测 navigator.hardwareConcurrency 是否符合正常范围。

2. 对检测项分类并通过 Babel 转换为 AST 节点

无头浏览器检测 AST 示例

const isHeadless = () => {
  return navigator.webdriver !== undefined;
};

通过 Babel 转换为 AST 节点:

{
  "type": "FunctionDeclaration",
  "id": { "type": "Identifier", "name": "isHeadless" },
  "body": {
    "type": "BlockStatement",
    "body": [
      {
        "type": "ReturnStatement",
        "argument": {
          "type": "BinaryExpression",
          "operator": "!==",
          "left": {
            "type": "MemberExpression",
            "object": { "type": "Identifier", "name": "navigator" },
            "property": { "type": "Identifier", "name": "webdriver" }
          },
          "right": { "type": "Identifier", "name": "undefined" }
        }
      }
    ]
  }
}

常规 DOM 检测 AST 示例

const isDOMSupported = () => {
  return typeof document.createElement === 'function';
};

转换为 AST 节点后存储。

3. 随机提取每类检测项的 AST 节点

在每次生成代码时,从每类检测项中随机提取 N 个 AST 节点。例如:

  • 从无头浏览器检测类中随机提取 2 个 AST 节点。
  • 从常规 DOM 检测类中随机提取 3 个 AST 节点。
  • 从 JSDOM 检测类中随机提取 1 个 AST 节点。

4. 组装 AST 节点并填充适配代码

将随机提取的 AST 节点组装成一个完整的函数,并填充额外的适配代码。例如:

const environmentCheck = () => {
  const results = [];
  results.push(isHeadless());
  results.push(isDOMSupported());
  results.push(isWebGLSupported());
  return results.every(result => result === true);
};

将组装后的 AST 节点转换为最终的 JavaScript 代码。

5. 编译成 JSVM 字节码并存储到 Golang 代码中

使用 JSVM(JavaScript 虚拟机)将生成的 JavaScript 代码编译为字节码。例如:

  • 使用 QuickJSHermes 将 JavaScript 代码编译为字节码。
  • 将字节码存储到 Golang 代码中,作为二进制数据嵌入。

在 Golang 中,通过 Wasm 加载 JS 虚拟机并执行字节码:

import (
  "embed"
  "github.com/robertkrimen/otto"
)
​
//go:embed bytecode.js
var bytecode embed.FS
​
func main() {
  vm := otto.New()
  script, _ := bytecode.ReadFile("bytecode.js")
  vm.Run(script)
}

6. 使用 BrowserStack 验证混淆后的环境检测代码

将生成的环境检测代码部署到 BrowserStack 上,验证其在各种浏览器和设备上的可用性和兼容性。例如:

  • 在 Chrome、Firefox、Safari、Edge 等主流浏览器上测试。
  • 在移动设备(如 iOS 和 Android)上测试。
  • 在无头浏览器(如 Puppeteer 和 Selenium)上测试,确保检测代码能够正确识别。

通过 BrowserStack 的自动化测试工具,生成测试报告并修复发现的兼容性问题