Headless Browser 反检测:如何在自动化测试和爬虫中模拟真实用户环境和指纹,以规避检测?

各位观众老爷们,大家好!我是你们的老朋友,代码界的段子手,今天咱们来聊聊一个让爬虫工程师和自动化测试工程师头疼,但又不得不面对的问题:Headless Browser 的反检测。

想象一下,你辛辛苦苦写了一个爬虫,准备大干一场,结果一启动就被网站无情地屏蔽了,是不是感觉一口老血差点喷出来?或者你写的自动化测试脚本,好不容易跑起来了,结果因为太像机器人,导致测试结果不准确,甚至误判了重要的业务逻辑?

别慌!今天我就来教大家如何把你的 Headless Browser 打扮得像个真人一样,让那些反爬虫机制哭着喊着放你过去!

第一幕:了解你的敌人(反爬虫机制)

想要战胜敌人,首先要了解敌人。反爬虫机制五花八门,但万变不离其宗,它们的核心目标是区分真人机器。那么,它们通常会从哪些方面入手呢?

  • User-Agent: 这是最常见的反爬虫手段之一。网站会检查你的 User-Agent,如果发现是 Headless Browser 的默认 User-Agent (例如 "HeadlessChrome"),那肯定会被毫不留情地拒绝。

  • JavaScript 指纹: Headless Browser 的 JavaScript 环境与真实浏览器存在差异,这些差异可以被网站用来生成指纹,从而识别你的 Headless Browser。常见的指纹包括:

    • navigator 对象中的属性,例如 navigator.webdrivernavigator.pluginsnavigator.languages 等。
    • Canvas 指纹:通过绘制一些图形,然后获取 Canvas 元素的 toDataURL() 值,不同的浏览器渲染出来的结果会有细微的差异。
    • WebGL 指纹:类似 Canvas 指纹,利用 WebGL 渲染一些图形,然后获取指纹。
  • 行为模式: 机器的行为模式通常比较规律,例如快速访问大量页面、没有鼠标移动、没有键盘输入等。网站会分析你的行为模式,如果发现异常,就会认为你是机器人。

  • IP 地址: 如果你的 IP 地址被大量用于爬虫或者恶意攻击,那么很可能会被网站加入黑名单。

  • HTTP Headers: Headless Browser 默认发送的 HTTP Headers 可能会缺少一些真实浏览器应该有的 Header,或者 Header 的顺序不正确。

第二幕:乔装打扮(模拟真实用户环境)

了解了反爬虫机制,接下来就是乔装打扮,让你的 Headless Browser 看上去更像一个真人。

1. 修改 User-Agent

这是最基本的操作,你需要将 Headless Browser 的 User-Agent 修改成一个真实的 User-Agent。你可以从以下几个途径获取真实的 User-Agent:

  • 在线 User-Agent 列表: 网上有很多 User-Agent 列表,你可以随机选择一个。
  • 自己的浏览器: 打开你的浏览器,在控制台中输入 navigator.userAgent,就可以获取你的 User-Agent。
from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
driver = webdriver.Chrome(options=options)
driver.get("https://www.example.com")
print(driver.execute_script("return navigator.userAgent;")) #验证是否修改成功
driver.quit()

2. 隐藏 navigator.webdriver 属性

navigator.webdriver 属性是 Headless Browser 的一个明显的特征,我们需要将其隐藏。

from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
driver = webdriver.Chrome(options=options)

driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
  "source": """
    delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
    delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
    delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
"""
})
driver.get("https://www.example.com")
print(driver.execute_script("return navigator.webdriver;")) # 验证是否隐藏成功
driver.quit()

3. 修改其他 JavaScript 指纹

除了 navigator.webdriver 属性,还有很多其他的 JavaScript 指纹可以被用来识别 Headless Browser。我们需要尽可能地模拟真实浏览器的 JavaScript 环境。

  • navigator.pluginsnavigator.languages 这两个属性可以用来模拟浏览器安装的插件和支持的语言。
from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
driver = webdriver.Chrome(options=options)

driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
  "source": """
    Object.defineProperty(navigator, 'plugins', {
      get: () => [1, 2, 3, 4, 5],
    });
    Object.defineProperty(navigator, 'languages', {
      get: () => ['zh-CN', 'zh', 'en-US', 'en'],
    });
"""
})

driver.get("https://www.example.com")
print(driver.execute_script("return navigator.plugins;"))
print(driver.execute_script("return navigator.languages;"))
driver.quit()
  • Canvas 和 WebGL 指纹: 这两个指纹比较复杂,需要一些图像处理的知识。你可以随机生成一些图像,然后用 Canvas 或 WebGL 渲染出来,再获取指纹。或者使用一些现成的库来生成随机的指纹。

4. 模拟用户行为

仅仅修改 JavaScript 指纹还不够,我们需要模拟真实用户的行为,让网站觉得你不是一个机器人。

  • 随机延迟: 在访问页面、点击按钮、输入文本等操作之间加入随机延迟,模拟用户的思考时间。
import time
import random
from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
driver = webdriver.Chrome(options=options)

driver.get("https://www.example.com")
time.sleep(random.uniform(1, 3)) # 随机延迟 1-3 秒

# 点击按钮
button = driver.find_element("xpath", "//button[@id='myButton']")
button.click()
time.sleep(random.uniform(0.5, 1.5))

# 输入文本
input_field = driver.find_element("xpath", "//input[@id='myInput']")
input_field.send_keys("Hello, world!")
time.sleep(random.uniform(0.3, 0.8))

driver.quit()
  • 鼠标移动: 模拟鼠标移动的轨迹,可以使用一些库来实现,例如 pynput
from pynput.mouse import Controller
import time
import random

mouse = Controller()

def move_mouse(x, y):
  current_x, current_y = mouse.position
  steps = 10
  for i in range(steps):
    mouse.position = (current_x + (x - current_x) * (i + 1) / steps,
                      current_y + (y - current_y) * (i + 1) / steps)
    time.sleep(random.uniform(0.01, 0.05))

# 获取元素的位置
element = driver.find_element("xpath", "//button[@id='myButton']")
location = element.location
size = element.size
x = location['x'] + size['width'] / 2
y = location['y'] + size['height'] / 2

move_mouse(x, y) # 模拟鼠标移动到元素中心
  • 键盘输入: 模拟键盘输入的频率和速度,可以随机地插入一些错误,然后再删除。
import time
import random
from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
driver = webdriver.Chrome(options=options)

driver.get("https://www.example.com")
input_field = driver.find_element("xpath", "//input[@id='myInput']")

text = "Hello, world!"
for char in text:
  input_field.send_keys(char)
  time.sleep(random.uniform(0.05, 0.2))

  # 随机插入错误
  if random.random() < 0.1:
    input_field.send_keys(random.choice("abcdefghijklmnopqrstuvwxyz"))
    time.sleep(random.uniform(0.05, 0.1))
    input_field.send_keys(Keys.BACKSPACE) # 删除错误
    time.sleep(random.uniform(0.05, 0.1))

driver.quit()

5. 使用代理 IP

如果你的 IP 地址被网站加入了黑名单,那么你需要使用代理 IP 来隐藏你的真实 IP 地址。

  • 免费代理: 网上有很多免费的代理 IP,但是质量参差不齐,很多都不可用。
  • 付费代理: 付费代理 IP 的质量通常比较好,但是需要一定的成本。
from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
# 配置代理 IP
proxy = "your_proxy_ip:port"
options.add_argument('--proxy-server=%s' % proxy)

driver = webdriver.Chrome(options=options)

driver.get("https://www.example.com")
driver.quit()

6. 使用 Chrome 扩展

可以使用 Chrome 扩展来辅助反检测,例如:

  • Anti Detect: 可以自动修改 User-Agent、隐藏 navigator.webdriver 属性等。
  • Proxy SwitchyOmega: 可以方便地切换代理 IP。

第三幕:实战演练(代码示例)

下面是一个完整的代码示例,演示了如何使用 Selenium 和 ChromeOptions 来模拟真实用户环境:

import time
import random
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

def create_webdriver():
  options = webdriver.ChromeOptions()
  # 隐藏 webdriver 属性
  options.add_experimental_option("excludeSwitches", ["enable-automation"])
  options.add_experimental_option('useAutomationExtension', False)

  # 设置 user-agent
  user_agents = [
      "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
      "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0",
      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15"
  ]
  options.add_argument(f"user-agent={random.choice(user_agents)}")

  # 设置代理 (可选)
  # proxy = "your_proxy_ip:port"
  # options.add_argument('--proxy-server=%s' % proxy)

  driver = webdriver.Chrome(options=options)

  # 修改 navigator 对象
  driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
      "source": """
          delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
          delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
          delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
          Object.defineProperty(navigator, 'webdriver', {
              get: () => false
          });
          Object.defineProperty(navigator, 'plugins', {
              get: () => [1, 2, 3, 4, 5],
          });
          Object.defineProperty(navigator, 'languages', {
              get: () => ['zh-CN', 'zh', 'en-US', 'en'],
          });
      """
  })

  return driver

def simulate_human_behavior(driver):
  # 随机延迟
  time.sleep(random.uniform(1, 3))

  # 模拟鼠标移动 (简化的示例)
  # 获取元素的位置
  # element = driver.find_element("xpath", "//button[@id='myButton']")
  # location = element.location
  # size = element.size
  # x = location['x'] + size['width'] / 2
  # y = location['y'] + size['height'] / 2
  # print(f"Moving mouse to x:{x}, y:{y}") # For demonstration - REMOVE when running headlessly
  # # move_mouse(x, y) # 假设你已经定义了 move_mouse 函数

  # 模拟键盘输入 (简化的示例)
  input_field = None # 替换成你的输入框的 xpath
  if input_field:
    text = "Hello, world!"
    for char in text:
      input_field.send_keys(char)
      time.sleep(random.uniform(0.05, 0.2))
      # 随机插入错误
      if random.random() < 0.1:
        input_field.send_keys(random.choice("abcdefghijklmnopqrstuvwxyz"))
        time.sleep(random.uniform(0.05, 0.1))
        input_field.send_keys(Keys.BACKSPACE) # 删除错误
        time.sleep(random.uniform(0.05, 0.1))

if __name__ == '__main__':
  driver = create_webdriver()
  driver.get("https://www.httpbin.org/headers") #  方便测试网站,查看请求头
  simulate_human_behavior(driver) # 模拟用户行为
  time.sleep(5) # 保持浏览器打开一段时间方便观察
  driver.quit()

第四幕:进阶技巧(更高级的反检测手段)

以上只是一些基本的反检测手段,如果你的目标网站的反爬虫机制比较高级,那么你需要使用更高级的技巧。

  • 使用真实的浏览器: 与其费尽心思地模拟 Headless Browser,不如直接使用真实的浏览器。你可以使用 Selenium Grid 来管理多个真实的浏览器,然后让你的爬虫或者自动化测试脚本在这些浏览器上运行。
  • 验证码识别: 如果网站使用了验证码,那么你需要使用验证码识别技术来自动识别验证码。可以使用一些第三方的验证码识别服务,例如 2Captcha。
  • 机器学习: 可以使用机器学习算法来识别网站的反爬虫机制,然后根据不同的反爬虫机制采取不同的反检测策略。

第五幕:总结与建议

Headless Browser 的反检测是一个持续对抗的过程,网站的反爬虫机制会不断升级,你需要不断地学习新的反检测技巧。

  • 不要过度爬取: 如果你的爬虫访问网站的频率过高,那么很容易被网站识别为机器人。请合理控制爬取频率,遵守网站的 robots.txt 协议。
  • 尊重网站: 不要利用爬虫来获取网站的敏感信息,或者对网站进行恶意攻击。

一些建议:

  • 更新你的webdriver: 不断更新你的webdriver,以获得最新的补丁和功能,这有助于更好地模拟真实浏览器的行为。
  • 监控和适应: 持续监控你的爬虫或自动化测试脚本,并根据网站的反爬虫策略的变化进行调整。

最后,希望今天的讲座能够帮助大家更好地应对 Headless Browser 的反检测。记住,技术是把双刃剑,请合理使用,不要用于非法用途。

祝大家编码愉快,爬虫顺利!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注