各位朋友,大家好!今天咱们来聊聊一个挺有意思的话题: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握手过程。
思路:
- 使用支持自定义TLS指纹的代理服务器,例如某些付费代理服务。
- 在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 字体反爬,解密字符
有些网站会使用自定义字体,将网页中的数字或文字替换成特殊字符,防止爬虫直接抓取。
- 应对策略: 分析字体文件,将特殊字符还原成原始字符。
步骤:
- 找到自定义字体文件的URL。
- 下载字体文件。
- 使用字体解析库,例如
fontkit
,解析字体文件。 - 找到字符编码与实际字符的映射关系。
- 将网页中的特殊字符替换成原始字符。
第三章:总结与展望
咱们今天聊了很多关于Puppeteer/Playwright反检测的技术,从User-Agent伪装到Canvas指纹模拟,可谓是“十八般武艺”都用上了。但是,反爬虫技术也在不断发展,我们需要不断学习和探索新的反检测手段。
一些建议:
- 持续学习: 关注最新的反爬虫技术,及时更新反检测策略。
- 模块化: 将反检测代码模块化,方便复用和维护。
- 自动化: 将反检测流程自动化,提高效率。
- 合作: 与其他爬虫工程师交流经验,共同进步。
希望今天的分享对大家有所帮助! 祝大家“爬虫之路”一帆风顺,越爬越开心! 下次再见!