puppeteer?
使用符合webdriver标准的方法控制Chromium,同样的还有selenium
这些控制浏览器行为的协议本身是为了自动化测试,但既然自动化我们就不单可以用来测试了。
这篇文章不针对任何特殊的行为,只是介绍puppeteer的一种一般用法,做个启发
检测方法
puppeteer访问自家网站是自动化工具,访问别人家的网站就是恶意的模拟器
有js中检测它的方法,这位老哥研究很多,他应该是主攻设备指纹和模拟器识别:
可通用操作的一般方法
// 打开浏览器
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()