伪指纹浏览器开发的那些事
什么是伪指纹浏览器开发
就是通过开源的chromium浏览器进行二次简单的封装不涉及到重新编译chromium,配合puppeteer进行轻微的指纹修改开发
一、如何操作
本次操作客户端以前端擅长的electron来举例子,至于electron是什么,打开文心一言看看...
第一步下载chromium到本地客户端
登录官网,看到如下界面
可以发现箭头处指定是浏览器对应的版本buildId和系统,这里可以直接手动点击下载到本地,也可以通过@puppeteer/browsers
这个库使用js代码去下载。这里说说如何使用它下载
const { app } = require('electron')
const browserApi = require('@puppeteer/browsers')
const axios = require('axios')
// browser缓存路径,避免和electron一起打包占用安装包体积和打包时间
const cacheDir = `${app.getPath('cache')}/myBrowser`
browserApi.install({
cacheDir, // 自己想要下载的路径,用来给puppeteer去调用
browser: browserApi.Browser.CHROMIUM,
// buildId: '1247373',
// baseUrl: 'https://commondatastorage.googleapis.com/chromium-browser-snapshots'
})
耐心的小伙伴肯定发现了这里buildId版本号和baseUrl下载url我打了注释,是因为@puppeteer/browsers
默认下载的chromium版本比较旧,那么我们怎么获取这个最新版本buildId和baseUrl呢,还是官网那个界面打开控制台,可以看到如下请求链接
然后看到请求结果
这就是最新的buildId了,然后封装成函数调用
// 获取最新的chromium构建ID
function getLastBuildId(platform) {
return axios
.get(
`https://download-chromium.appspot.com/rev/${browserApi.BrowserPlatform.MAC}?type=snapshots`
)
.then((res) => res.data.content)
}
baseUrl可以在界面点击下载时候,看到控制台有一个请求,那就是baseUrl了
下载好后,可以去我们定义的下载保存地址,通过终端去打开就可以看到了
二、第二步启动chromium
使用puppeteer-core
这个库,启动我们下好的chromium
const puppeteer = require('puppeteer-core')
const browserApi = require('@puppeteer/browsers')
// browser缓存路径
const cacheDir = `${app.getPath('cache')}/myBrowser`
// 获取安装的浏览器路径
function getBrowserPath() {
return browserApi
.getInstalledBrowsers({ cacheDir })
.then((list) => list[0]?.executablePath)
}
// 浏览器生成
const createBrowser = async (proxyServer, userAgent) => {
const browser = await puppeteer.launch({
args: [
`--proxy-server=${proxyServer}`,
`--user-agent="${userAgent}"`,
'--no-first-run',
'--no-zygote',
'--disable-extensions',
'--disable-infobars',
'--disable-automation',
'--no-default-browser-check',
'--disable-device-orientation',
'--disable-metrics-reporting',
'--disable-logging'
],
headless: false,
defaultViewport: null,
ignoreHTTPSErrors: true,
ignoreDefaultArgs: [
'--enable-infobars',
'--enable-features=NetworkService,NetworkServiceInProcess',
'--enable-automation',
'about:blank'
],
executablePath: await getBrowserPath()
})
return browser
}
通过puppeteer.launch
启动一个浏览器,至于启动参数这里我只说指纹相关的两个参数--proxy-server
和--user-agent
,其他AI一下。
--proxy-server
代理服务,浏览器访问的出口IP,即你用自己启动的浏览器访问google时候,那边服务端获取的ip就是你的代理ip,测试时候可以自己在另外一台机器上装个Squid
测试。--user-agent
即浏览器的window.navigator.userAgent,简单指纹一般都是依赖于它生成
三、开发过程中用到的功能点
看完puppeteer官网,我们知道操作chromium依赖于一套协议chromedevtools.github.io/devtools-pr…
3.1 更换dock图标
比如多开浏览器,我如何更换chromium的桌面dock图标,去标识这是我启动的第几个浏览器。我们可以使用Browser.setDockTile
去操作浏览器更换dock图标
const pages = await browser.pages()
const page = pages[0]
const session = await pages[0].target().createCDPSession()
await session.send('Browser.setDockTile', {
image: new Buffer.from(fs.readFileSync(file)).toString('base64')
})
效果如下:
更多的协议操作需要自己摸索了,提示下,AI搜索chrome cdp协议
3.2 增加默认书签
这里我没找到协议,直接通过类似爬虫的方式,先进入标签管理页面,直接操作js新增,也算是一个技巧性的骚操作
await page.goto('chrome://bookmarks/') // 进入标签管理页面
await page.evaluate(async () => {
// 类似在控制台直接操作一样,下面的代码控制台一样可以达到效果
const defaultBookmarks = [
{
title: "文心一言",
url: "https://yiyan.baidu.com/",
},
{
title: "掘金",
url: "https://juejin.cn/",
},
];
defaultBookmarks.forEach((item) => {
chrome.bookmarks.create({
parentId: "1",
...item,
});
});
});
await page.goto('自己的本来要跳的首页')
3.3 如何使用已经打开的浏览器
const browserWSEndpoint = browser.wsEndpoint() // 获取本次打开的浏览器链接,留作下一次使用
// 保存下来, 比如直接存在一个变量map中,给它定义一个唯一的browserId,下一次好直接获取
browserMap.set(browserId, browserWSEndpoint)
...
// 再次打开新页面,要用到上一次打开的浏览器
const browser = puppeteer.connect({
...launchOptions, // 和自己首次打开浏览器的配置一样
browserWSEndpoint: browserMap.set(browserId)
})
这样就可以使用之前打开的浏览器打开网页了
3.4 如何把浏览器的信息显示在网页上
比如代理、userAgent、地区、浏览器名称等信息,先写个页面,然后轮询从localStorage直到获取信息为止。
// 浏览器代理信息页
await page.goto('浏览器信息页')
// 设置localStorage
await page.evaluate(
(values) => {
window.localStorage.setItem('browserInfo', values)
},
JSON.stringify(browserData)
)
page在打开页面后,并不会在页面中马上能获取到这里注入的browserInfo
,可以通过轮询方式去扫描localStorage中是否存在我们注入的变量,这里举个react中的例子,在页面ready后去轮询处理
useEffect(() => {
let loopId = null
const clearLoop = () => {
loopId && clearTimeout(loopId)
}
// 轮询直到获取browserInfo
const loop = () => {
loopId = setTimeout(() => {
const localData = window.localStorage.getItem('browserInfo')
if (localData) {
Promise.resolve()
.then(() => {
setInfo(JSON.parse(localData))
})
.catch(() => {
message.error('获取浏览器信息失败')
})
} else {
loop()
}
}, 1500)
}
loop()
return () => {
clearLoop()
}
})
3.5 校验代理
一般的代理服务为了不让别人也能用都会加上账密校验,所以我们还需要在启动后,调用方法去校验
// 校验proxy
if (proxyData.proxyServer) {
await page.authenticate({
username: proxyData.proxyUser,
password: proxyData.proxyPwd
})
}
四、遇到了哪一些问题
4.1 mac下关闭浏览器关不掉
当我们点击左上角关闭浏览器按钮或者是关闭所有页面时候,底部的dock中依旧存在着,我们不希望像mac其他软件一样保留在dock中,不然下一次打开浏览器时候,会出现相同标识的浏览器,可以这么解决
// 每次页面关闭时候,查看浏览器是不是还有页面了,没有就关闭
browser.on('targetdestroyed', async () => {
const pages = await browser.pages()
if (!pages.length) {
await browser.close()
}
})
4.2 当我们之间关闭电脑屏幕时候,比如盖上电脑,再次打开时候,关闭不了浏览器
打上log,可以发现熄屏时候,会触发puppeteer定义的browser的disconnected事件,但是再次打开电脑时候浏览器是可以正常使用的,也就是说,puppeteer和我们打开的chromium断连了,所以我们需要在disconnected事件里再此尝试链接下chromium,如果不行才认为是浏览器被关闭了
browser.on('disconnected', () => {
const cacheData = browserMap.get(browserId)
puppeteer
.connect({
...launchOptions,
browserWSEndpoint: cacheData.browserWSEndpoint
})
.then((newBrowser) => {
browser = newBrowser
log.info(
'browser disconnected but browser is exist',
)
initEvent()
})
.catch((err) => {
log.info(
'browser disconnected success',
)
})
})
结语
puppeteer很强大,chromium也强大,就是那个官网文档啊,写的真是让人...,所以多问问AI吧
来源:juejin.cn/post/7327642905245433891