前端爬虫无间道-puppeteer检测与绕过

8,027 阅读3分钟

puppeteer?

api

使用符合webdriver标准的方法控制Chromium,同样的还有selenium

这些控制浏览器行为的协议本身是为了自动化测试,但既然自动化我们就不单可以用来测试了。

这篇文章不针对任何特殊的行为,只是介绍puppeteer的一种一般用法,做个启发

检测方法

puppeteer访问自家网站是自动化工具,访问别人家的网站就是恶意的模拟器

有js中检测它的方法,这位老哥研究很多,他应该是主攻设备指纹和模拟器识别:

antoinevastel

Detecting Chrome headless

可通用操作的一般方法

// 打开浏览器
let browser = await puppeteer.launch({
    headless: false, // 有界面模式
    devtools: true, // 自动打开devtool
    defaultViewport: {
        width: 1500,
        height: 800
    }
})
// 打开新页面
const page = await browser.newPage();
// 模拟一下ua,不然默认会带Chrome Headless
page.setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36")

很多网站可能会根据navigator.webdriver直接判断出不出验证码等策略,所以很多情况绕过这个属性检测后面就很简单。

// 打开拦截请求
await page.setRequestInterception(true);
// 请求拦截器
// 这里的作用是在所有js执行前都插入我们的js代码抹掉puppeteer的特征
page.on("request", async (req, res2) => {
    // 非js脚本返回
    // 如果html中有inline的script检测html中也要改,一般没有
    if (req.resourceType() !== "script") {
        req.continue()
        return
    }
    // 获取url
    const url = req.url()
    await new Promise((resolve, reject) => {
        // 使用request/axios等请求库获取js文件
        request.get(url, (err, _res) => {
           // 删掉navigator.webdriver
           // 这里不排除有其它特征检测,每个网站需要定制化修改
            let newRes = "navigator.webdriver && delete Navigator.prototype.webdriver;" + _res.body
            // 返回删掉了webdriver的js
            req.respond({
                body: newRes
            })
            resolve()
        })
    })

})
// 进入页面url
await page.goto(startPage);

如果遇到需要加cookie的情况,可以使用 await page.setCookie({name, value})

进入完页面最好等个几秒,等页面资源加载

使用await page.waitFor(5000);

如遇到了登陆,或者需要点击输入等操作的以下代码可以参考

// 等待某个元素出现
const element = await page.waitForSelector('.selector', {
    visible: true,
    timeout: 5000
});
// 确认会出现,直接选择
const box = await page.$("#selector .selector")
// 点击元素
await box.click()
// 选取一堆元素,返回数组
const inputs = await page.?("#selector")
// 聚焦到第一个input
await inputs[0].focus()
// 键盘输入
await page.keyboard.type("account/password", {
    delay: 200
});
// 同样的,这些代码之间最好有个停顿

这里有很好用的一个方法,有时候我们不想研究太多代码,可以尝试在当前网页环境中发送请求,这样referrer, cookie都不用设置。如果还需要页面的什么值,说不定直接window[key]也能获取到

从node的执行环境获取当前浏览器页面的环境:

const executionContext = await page.mainFrame().executionContext();
// 请求的方法可能会调用多次,写成函数
async function fetchDataInBrowser(executionContext, query) {
    // evaluate相当于在context里面执行,如果需要传参数放到query那里
    const result = await executionContext.evaluate((query) => {
        // 这里已经在浏览器环境了,可以获取window上的一些值
        let sampleId = window.sampleId
        return new Promise((resolve, reject) => {
            // 浏览器直接使用fetch,credentials会带着cookie
            fetch(query, {
                credentials: 'include'
            })
            .then(function (response) {
                return response.json();
            })
            .catch(error => reject(error))
            .then(function (myJson) {
                resolve(myJson)
            })
        })
    }, query);
    return result
}

const result = await fetchDataInBrowser(executionContext, pageUrl)

完成就可以关闭它了

await browser.close()