JS `Puppeteer`/`Playwright` 自动化框架的反检测技术

各位朋友,大家好!今天咱们来聊聊一个挺有意思的话题:JS自动化框架Puppeteer/Playwright的反检测技术。这俩框架,一个谷歌家的,一个微软家的,都是网页自动化测试的利器,但同时也成了爬虫工程师的“好伙伴”。服务器那边也不是吃素的,各种反爬机制层出不穷,咱们就来研究研究,怎么见招拆招,让咱们的自动化脚本更“隐蔽”。

第一章:摸清敌情,了解反爬手段

在咱们开始“反击”之前,得先了解对手都有哪些招数。服务器的反爬策略可谓五花八门,但常见的也就那么几种:

反爬手段 原理 应对策略
User-Agent检测 检查请求头中的User-Agent字段,判断是否为常见的爬虫User-Agent。 自定义User-Agent,模拟真实浏览器。
IP封锁 识别并封锁来自同一IP地址的频繁请求。 使用代理IP。
Cookie验证 设置Cookie,验证客户端是否支持Cookie,或者Cookie是否正确。 正确处理Cookie,包括设置、传递和更新。
JavaScript检测 通过JavaScript代码检测浏览器环境,例如是否存在headless特征,是否安装了特定的插件等。 隐藏headless特征,模拟真实浏览器环境。
验证码 在关键操作前要求用户输入验证码,防止机器人操作。 图像识别,或使用打码平台。
行为模式分析 分析用户的行为模式,例如访问速度、访问频率、页面跳转等,判断是否为机器人。 控制访问速度,模拟真实用户的行为模式。
蜜罐陷阱 在页面中隐藏一些链接或元素,正常用户无法访问,但爬虫可能会访问,从而被识别。 避免访问隐藏的链接或元素。
字体反爬 使用自定义字体,将网页中的数字或文字替换成特殊字符,防止爬虫直接抓取。 分析字体文件,将特殊字符还原成原始字符。
TLS指纹检测 检测客户端TLS指纹,与已知爬虫客户端的TLS指纹进行比对。 伪造TLS指纹,使其与真实浏览器一致。
Canvas指纹检测 通过Canvas绘制图像,提取图像的哈希值作为指纹,用于识别用户。 模拟Canvas指纹,使其与真实浏览器一致。

第二章:化腐朽为神奇,反检测实战演练

了解了敌情,咱们就可以开始制定反击策略了。下面咱们就来逐个击破这些反爬手段,让咱们的Puppeteer/Playwright脚本顺利“过关”。

2.1 User-Agent伪装术

这是最基本,也是最有效的反爬手段之一。服务器会检查请求头中的User-Agent字段,判断是否为常见的爬虫User-Agent。咱们要做的就是伪装成一个真实的浏览器User-Agent。

  • Puppeteer:
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // 随机选择一个User-Agent
  const userAgents = [
    '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 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0',
  ];
  const randomUserAgent = userAgents[Math.floor(Math.random() * userAgents.length)];

  await page.setUserAgent(randomUserAgent);

  await page.goto('https://www.example.com');

  await browser.close();
})();
  • Playwright:
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage({
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
  });

  await page.goto('https://www.example.com');

  await browser.close();
})();

小技巧: 可以维护一个User-Agent池,定期更新,这样更不容易被识别。还可以根据目标网站的统计信息,选择最常用的User-Agent。

2.2 IP代理,瞒天过海

如果服务器检测到来自同一IP地址的频繁请求,就会封锁该IP。这时候,咱们就需要使用IP代理了。

  • Puppeteer:
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    args: ['--proxy-server=http://your_proxy_ip:your_proxy_port'], // 替换成你的代理IP和端口
  });
  const page = await browser.newPage();

  await page.goto('https://www.example.com');

  await browser.close();
})();
  • Playwright:
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({
    proxy: {
      server: 'http://your_proxy_ip:your_proxy_port', // 替换成你的代理IP和端口
    },
  });
  const page = await browser.newPage();

  await page.goto('https://www.example.com');

  await browser.close();
})();

注意事项:

  • 选择信誉良好的代理IP提供商,避免使用免费代理,因为免费代理通常速度慢、不稳定,而且容易被封锁。
  • 定期更换代理IP,避免长时间使用同一个IP。
  • 可以使用代理池,随机选择代理IP。
  • 有些网站可能需要验证代理IP,确保代理IP可用。

2.3 Cookie处理,有礼有节

有些网站会设置Cookie,验证客户端是否支持Cookie,或者Cookie是否正确。咱们需要正确处理Cookie,包括设置、传递和更新。

  • Puppeteer:
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // 设置Cookie
  await page.setCookie({
    name: 'your_cookie_name',
    value: 'your_cookie_value',
    domain: 'www.example.com',
  });

  await page.goto('https://www.example.com');

  // 获取Cookie
  const cookies = await page.cookies();
  console.log(cookies);

  await browser.close();
})();
  • Playwright:
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  // 设置Cookie
  await page.context().addCookies([{
    name: 'your_cookie_name',
    value: 'your_cookie_value',
    domain: 'www.example.com',
    url: 'https://www.example.com'
  }]);

  await page.goto('https://www.example.com');

  // 获取Cookie
  const cookies = await page.context().cookies();
  console.log(cookies);

  await browser.close();
})();

小技巧:

  • 在访问网站之前,先访问一个Cookie设置页面,获取必要的Cookie。
  • 定期更新Cookie,避免Cookie过期。
  • 有些网站会使用JavaScript设置Cookie,需要等待JavaScript执行完毕才能获取到Cookie。

2.4 Headless模式,隐身大法

Puppeteer和Playwright都支持Headless模式,也就是无头浏览器模式。这种模式下,浏览器不会显示界面,运行速度更快。但是,Headless模式也更容易被服务器检测到。我们需要隐藏Headless特征,模拟真实浏览器环境。

  • Puppeteer:
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    headless: false, // 关闭Headless模式
    args: [
      '--disable-gpu',
      '--disable-dev-shm-usage',
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--window-size=1920,1080', // 设置窗口大小
    ],
  });
  const page = await browser.newPage();

  // 模拟浏览器环境
  await page.evaluateOnNewDocument(() => {
    // 移除navigator.webdriver属性
    Object.defineProperty(navigator, 'webdriver', {
      get: () => false,
    });

    // 修改userAgent,防止被检测为headless
    Object.defineProperty(navigator, 'userAgent', {
      get: () => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
    });

    // 修改语言
    Object.defineProperty(navigator, 'language', {
      get: () => 'zh-CN',
    });

    Object.defineProperty(navigator, 'languages', {
      get: () => ['zh-CN', 'zh'],
    });
  });

  await page.goto('https://www.example.com');

  await browser.close();
})();
  • Playwright:
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({
    headless: false, // 关闭Headless模式
  });
  const context = await browser.newContext({
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
    locale: 'zh-CN',
    viewport: { width: 1920, height: 1080 }, // 设置视口大小
  });
  const page = await context.newPage();

  await page.evaluateOnNewDocument(() => {
    // 移除navigator.webdriver属性
    Object.defineProperty(navigator, 'webdriver', {
      get: () => false,
    });
  });

  await page.goto('https://www.example.com');

  await browser.close();
})();

重点:

  • navigator.webdriver属性: 这是Headless浏览器最明显的特征。咱们需要移除或修改这个属性。
  • userAgent: 使用真实的浏览器User-Agent。
  • language: 设置浏览器语言。
  • viewport: 设置视口大小,模拟真实浏览器窗口大小。
  • 插件: 安装一些常用的浏览器插件,例如AdBlock、uBlock Origin等。
  • WebGL: 启用WebGL,模拟真实浏览器环境。

2.5 验证码识别,火眼金睛

遇到验证码,是爬虫工程师最头疼的事情之一。验证码的种类繁多,识别难度也各不相同。

  • 简单的图像验证码: 可以使用OCR技术进行识别,例如Tesseract.js。
  • 复杂的图像验证码: 可以使用深度学习模型进行识别,例如CNN。
  • 滑动验证码: 可以使用模拟鼠标滑动的方式进行破解。
  • 点选验证码: 可以使用图像识别技术,识别出需要点选的物体。
  • 打码平台: 如果自己无法破解验证码,可以使用打码平台,将验证码发送给人工进行识别。

代码示例 (使用2captcha打码平台):

const puppeteer = require('puppeteer');
const Captcha = require('2captcha');

const solver = new Captcha.Solver('YOUR_2CAPTCHA_API_KEY'); // 替换成你的2captcha API Key

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.goto('https://www.example.com/captcha'); // 替换成包含验证码的页面

  // 获取验证码图片
  const captchaImage = await page.$('#captcha-image'); // 替换成验证码图片的selector
  const base64 = await captchaImage.screenshot({ encoding: 'base64' });

  // 使用2captcha识别验证码
  solver.imageCaptcha({
    base64: base64
  }).then(async (response) => {
    console.log('Captcha solved:', response.data);

    // 填写验证码
    await page.type('#captcha-input', response.data); // 替换成验证码输入框的selector

    // 提交表单
    await page.click('#submit-button'); // 替换成提交按钮的selector

    await browser.close();
  }).catch((error) => {
    console.log('Captcha solving error:', error);
    await browser.close();
  });
})();

2.6 行为模拟,以假乱真

服务器会分析用户的行为模式,例如访问速度、访问频率、页面跳转等,判断是否为机器人。我们需要控制访问速度,模拟真实用户的行为模式。

  • 控制访问速度: 在请求之间添加随机延迟。
  • 模拟鼠标移动: 模拟鼠标在页面上的移动轨迹。
  • 模拟键盘输入: 模拟用户在键盘上的输入。
  • 模拟页面跳转: 模拟用户在页面之间的跳转。
  • 随机访问页面: 随机访问一些无关紧要的页面,增加迷惑性。

代码示例 (模拟鼠标移动):

const puppeteer = require('puppeteer');

async function simulateMouseMove(page, element) {
  const boundingBox = await element.boundingBox();
  const x = boundingBox.x + boundingBox.width / 2;
  const y = boundingBox.y + boundingBox.height / 2;

  await page.mouse.move(x, y, { steps: Math.floor(Math.random() * 20) + 5 }); // 随机步数
}

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.goto('https://www.example.com');

  const element = await page.$('#target-element'); // 替换成目标元素的selector

  await simulateMouseMove(page, element);

  await element.click();

  await browser.close();
})();

2.7 TLS指纹,瞒天过海升级版

TLS指纹是客户端在TLS握手过程中发送的一系列参数的哈希值,可以用于识别客户端。不同的浏览器和操作系统,其TLS指纹也不同。

  • ja3: 一种常用的TLS指纹算法。

可以使用第三方库,例如node-ja3来伪造TLS指纹。

代码示例 (伪造TLS指纹):

由于涉及到比较底层的网络操作,直接在Puppeteer/Playwright中模拟TLS指纹比较困难。通常需要使用一些更底层的库,或者使用一些代理服务,这些代理服务可以修改TLS握手过程。

思路:

  1. 使用支持自定义TLS指纹的代理服务器,例如某些付费代理服务。
  2. 在Node.js中使用https模块,并配置ALPNProtocols等参数,模拟浏览器的TLS指纹。然后,将Puppeteer/Playwright的请求转发到这个代理服务器。

2.8 Canvas指纹,像素级的较量

Canvas指纹是通过Canvas绘制图像,提取图像的哈希值作为指纹,用于识别用户。不同的浏览器和操作系统,其Canvas指纹也略有不同。

  • 应对策略: 模拟Canvas指纹,使其与真实浏览器一致。

代码示例 (模拟Canvas指纹):

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.evaluateOnNewDocument(() => {
    // 覆写Canvas API
    const originalGetContext = HTMLCanvasElement.prototype.getContext;
    HTMLCanvasElement.prototype.getContext = function(contextType, ...args) {
      const context = originalGetContext.apply(this, [contextType, ...args]);

      if (contextType === '2d') {
        // 覆写fillText方法,添加随机噪声
        const originalFillText = context.fillText;
        context.fillText = function(text, x, y) {
          originalFillText.apply(this, [text, x + Math.random() * 0.1, y + Math.random() * 0.1]); // 添加微小的随机偏移
        };

        // 覆写strokeRect方法,添加随机噪声
        const originalStrokeRect = context.strokeRect;
        context.strokeRect = function(x, y, width, height) {
          originalStrokeRect.apply(this, [x + Math.random() * 0.1, y + Math.random() * 0.1, width + Math.random() * 0.1, height + Math.random() * 0.1]); // 添加微小的随机偏移
        };
      }

      return context;
    };
  });

  await page.goto('https://www.example.com/canvas-test'); // 替换成包含Canvas测试的页面

  await browser.close();
})();

原理: 通过覆写Canvas API,在绘制图像时添加一些随机噪声,使得每次生成的Canvas指纹都略有不同,从而避免被识别。

2.9 字体反爬,解密字符

有些网站会使用自定义字体,将网页中的数字或文字替换成特殊字符,防止爬虫直接抓取。

  • 应对策略: 分析字体文件,将特殊字符还原成原始字符。

步骤:

  1. 找到自定义字体文件的URL。
  2. 下载字体文件。
  3. 使用字体解析库,例如fontkit,解析字体文件。
  4. 找到字符编码与实际字符的映射关系。
  5. 将网页中的特殊字符替换成原始字符。

第三章:总结与展望

咱们今天聊了很多关于Puppeteer/Playwright反检测的技术,从User-Agent伪装到Canvas指纹模拟,可谓是“十八般武艺”都用上了。但是,反爬虫技术也在不断发展,我们需要不断学习和探索新的反检测手段。

一些建议:

  • 持续学习: 关注最新的反爬虫技术,及时更新反检测策略。
  • 模块化: 将反检测代码模块化,方便复用和维护。
  • 自动化: 将反检测流程自动化,提高效率。
  • 合作: 与其他爬虫工程师交流经验,共同进步。

希望今天的分享对大家有所帮助! 祝大家“爬虫之路”一帆风顺,越爬越开心! 下次再见!

发表回复

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