尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AI辅助开发多开浏览器:从技术选型到指纹隔离实战

AI辅助开发多开浏览器:从技术选型到指纹隔离实战 做技术这几年我越来越相信一件事与其追着各种 AI 新闻看热闹不如让 AI 老老实实帮你把一个真实项目落地。最近我给自己定了个小目标——用 AI 辅助开发一个属于自己的多开浏览器名字就叫“奇异博士浏览器”听起来中二但做起来是真有意思。所谓多开浏览器本质上就是能在同一台电脑上同时运行多个彼此隔离的浏览器实例每个实例有独立的用户数据、独立的指纹特征互不干扰。它最常见的用途是开发测试、隐私隔离、多账号管理你要是做爬虫采集或者广告投放数据分析这东西基本是刚需。更难得的是这次开发过程我几乎全程让 AI 打主力从技术选型到代码生成再到报错排查全部有 AI 参与。这套“AI 辅助开发完整项目”的流程我觉得很值得拿出来聊聊适合想入门 AI 编程、又不想停留在“问一句答一句”阶段的朋友参考。选择“奇异博士”这个名字没有太玄学的含义单纯是因为这个项目能同时变出多个“平行的浏览器分身”和奇异博士的能力有点神似。项目目标非常明确不依赖任何商业指纹浏览器用一套开源技术栈自己做出来一个能创建、管理、启动多个隔离浏览器实例的工具。整个过程里AI 负责输出代码和排查思路我负责设计架构和验证结果。今天这篇就把完整实战过程、核心代码、踩过的坑都写出来希望给你一个可以直接参考复现的样板。1. 项目整体设计与技术选型拆解1.1 多开浏览器的本质不是开几个窗口那么简单很多人以为多开浏览器就是多打开几个窗口其实完全不是一回事。普通浏览器开多个窗口共享同一个用户数据目录、同一个进程池Cookie、LocalStorage、指纹特征全部一致。网站如果想识别你轻而易举就能判断“这几个窗口来自同一台设备的同一个浏览器”。真正意义上的多开浏览器核心是做到“实例隔离”。每个浏览器实例必须有独立的用户数据目录user-data-dir、独立的渲染进程、独立网络堆栈最好还能自定义 User-Agent、Canvas 指纹、WebRTC 等硬件特征这样不同实例之间才不会留下关联性。这里面最关键的技术点是 Chromium 的--user-data-dir参数。只要给浏览器进程指定一个全新的、空的数据目录它就会认为自己是第一次运行所有 Cookie、缓存、扩展程序全部重来。这也是几乎所有多开浏览器、指纹浏览器的基础原理。理解了这个你就明白为什么商业产品动辄收费很高因为后面那些指纹篡改和自动化控制代码才是真正的技术壁垒。1.2 为什么选择 Electron Playwright AI 这套组合在做技术选型的时候我把几个方案摆在桌面上逐一对比过直接用表格列出来更清楚。方案优点缺点适用场景Chrome 启动参数 批处理最简单零开发成本无法精细化控制指纹不能自动化操作临时多开场景Node.js Puppeteer生态成熟文档多上手快新版对浏览器版本绑定较紧API 偶尔变动自动化脚本、爬虫Node.js Playwright支持 Chromium / Firefox / WebKitAPI 更现代参数相对复杂跨浏览器测试、多实例管理Electron Playwright能做出带界面的桌面应用控制能力最强开发量最大需要前端基础想做成产品级工具我最后选了 Electron Playwright 的组合。Electron 负责做桌面壳给用户提供一个管理面板用来创建实例、查看运行状态、一键启动。Playwright 负责真正去拉起 Chromium 实例、注入脚本、控制页面行为。AI 在这个过程中承担了三件事第一件事是选型咨询。我把自己的想法和约束条件抛给大模型让它帮忙列举不同方案的优劣势避免了我自己去翻大量文档。第二件事是代码实现。所有核心模块比如多实例管理器、指纹注入脚本、实例健康检查都是通过 AI 对话生成的。第三件事是排错。项目运行中遇到各种奇奇怪怪的错误我直接把报错日志贴给 AI让它给出排查方向和修复代码。这套流程走下来我的体感是AI 不是替代你思考而是把“从零写代码”变成了“审代码、改代码、验证代码”。看起来差别不大但开发效率至少翻了一倍。1.3 AI 不是万能但在这些环节确实高效我不太喜欢吹“AI 自动生成整个应用”那种说法至少现阶段不现实。实际用下来觉得 AI 在几个特定环节特别能打。第一个是样板代码生成速度极快。比如“用 Playwright 启动三个独立浏览器实例并保存用户目录”这种需求正常人写大概要查半天文档AI 十秒钟就能给出可用代码而且参数基本正确。第二个是对报错信息的理解能力远超搜索引擎。以前遇到报错你要复制错误信息去 Google翻几篇博客才能定位问题。现在直接把完整的堆栈贴给 AI它通常能一次给出准确的原因和修复方案。第三个是对需求的理解能力前提是你的 Prompt 足够清晰。AI 能把你模糊的“我想要一个类似 XX 的东西”翻译成具体的技术架构。但 AI 也有明显短板。当项目变大、涉及多个文件协同、状态管理复杂的时候AI 生成的代码就容易出现“单点正确、整体混乱”的情况。所以你需要自己把控架构不能让 AI 放飞自我。这也是我为什么坚持自己设计整体方案只把具体实现细节交给 AI。2. 核心功能实现与关键技术细节2.1 多实例启动的核心user-data-dir 的玩法多开浏览器的地基就是给每个实例分配独立的用户数据目录。Chromium 系的浏览器包括 Chrome、Edge还有 Playwright 拉起的 Chromium都认这个参数。我写了一个简单的批处理脚本适合不想装任何依赖、快速体验多开效果的朋友。新建一个multi-launch.bat内容如下。echo off setlocal enabledelayedexpansion set BASE_DIR%LOCALAPPDATA%\BrowserProfiles set COUNT3 for /L %%i in (1,1,%COUNT%) do ( set PROFILE_DIR!BASE_DIR!\Profile_00%%i if not exist !PROFILE_DIR! mkdir !PROFILE_DIR! start C:\Program Files\Google\Chrome\Application\chrome.exe ^ --user-data-dir!PROFILE_DIR! ^ --no-first-run ^ --no-default-browser-check ^ --disable-blink-featuresAutomationControlled ) echo 已启动 %COUNT% 个独立浏览器实例 pause这里有三个参数值得展开说说。--user-data-dir指定用户数据目录这是隔离的关键。如果这个目录不存在浏览器会自动创建。--no-first-run是跳过首次运行引导页否则每次新建目录都会弹欢迎界面很烦人。--disable-blink-featuresAutomationControlled则是隐藏自动化控制标记这个参数在后面的反检测环节很重要。如果你用 Playwright则不是传参而是通过launchPersistentContext方法它会自动帮我们管理用户数据目录。看下面这段 Node.js 代码。const { chromium } require(playwright); async function createBrowserInstance(profileId) { const userDataDir ./profiles/profile_${profileId}; const context await chromium.launchPersistentContext(userDataDir, { headless: false, viewport: { width: 1280, height: 800 }, args: [ --disable-blink-featuresAutomationControlled, --no-first-run, --disable-infobars, ], }); const page context.pages()[0] || await context.newPage(); return { context, page, userDataDir }; } // 启动3个隔离实例 (async () { const instances []; for (let i 1; i 3; i) { const instance await createBrowserInstance(i); instances.push(instance); console.log(实例 ${i} 已启动用户目录: ${instance.userDataDir}); } })();这里有个细节很容易踩坑launchPersistentContext必须在创建实例时就指定用户数据目录之后每次调用都用同一个目录才能保持登录态。如果你在多个地方分别launch不传目录或者传了不同目录那就相当于每次都是新浏览器。多开管理器的本质就是管理这些目录的创建、增删、切换。2.2 指纹隔离不只是改个 User-Agent有了独立的用户数据目录只能保证数据层面隔离。但网站识别一台设备数据只是其中一环更重要的是指纹。浏览器指纹由很多信息组合而成包括 User-Agent、Canvas、WebGL、WebRTC、时区、语言、屏幕分辨率、字体列表等等。如果这些信息完全一致网站很容易判断“这些实例背后其实是同一个人”。所以多开浏览器的进阶功能就是指纹伪装。这里我实现了三个最核心的点也是 AI 代码贡献最大的部分。第一个是 User-Agent 随机化。这个最简单在 Playwright 中直接配置即可。const userAgents [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, ]; function getRandomUserAgent() { return userAgents[Math.floor(Math.random() * userAgents.length)]; }第二个是注入脚本覆盖navigator.webdriver。默认情况下自动化浏览器会暴露navigator.webdriver true网站一查就知道你是机器人。可以通过addInitScript在页面加载前把属性覆盖掉。await context.addInitScript(() { Object.defineProperty(navigator, webdriver, { get: () undefined, }); window.chrome { runtime: {}, }; });第三个就是 Canvas 指纹随机化。Canvas 指纹的原理是让页面绘制一段文字或图形然后用toDataURL读取像素数据不同设备绘制的结果略有差异形成唯一标识。想伪造就必须在toDataURL和getImageData方法里做手脚给它返回一个被轻微扰动的结果。await context.addInitScript(() { const originalToDataURL HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL function(...args) { const imageData originalToDataURL.apply(this, args); // 这里对图片数据做轻微扰动让每次生成的结果独一无二 return imageData.replace(/data:image\/png;base64,/, data:image/png;base64,); }; });上面这段是对 AI 生成代码的简化处理实际上做 Canvas 指纹扰动比这复杂通常要通过getImageData修改像素值再重新绘制。但思路是一致的让每次生成的指纹数据不同且与真实设备不一致。只要这三个点做到位基础的多开反检测就算完成了。2.3 用 AI 生成核心代码Prompt 才是关键这个项目里我给 AI 的最高频指令就两类写功能和修 Bug。写功能时我发现 Prompt 的质量直接决定代码质量。举个例子我想实现“实例健康检查”一开始我的提问是“写一个检查浏览器是否还活着的代码”AI 给出的方案很笼统只是简单地try catch调一个页面。后来我把 Prompt 改成了带上下文和约束的描述我正在开发一个基于 Playwright 的多开浏览器管理器。请帮我实现一个函数输入是一个BrowserContext对象输出是布尔值表示该浏览器实例是否健康可用。健康标准包括底层浏览器进程未退出、任意一个页面能正常执行document.title并返回非空结果。请用 Node.js 实现并处理可能出现的超时异常。改完之后AI 生成的代码直接可用还主动加了Promise.race做超时控制。这个经验很重要AI 不是读心术你把边界条件、输入输出约束、异常处理方式描述得越清晰它返回的代码质量就越高。还有一个好用的小技巧让 AI 给代码加注释。不是简单的中文注释而是让它标注“这段代码为什么这样写”这样你审查代码的时候能快速理解设计意图避免把隐患代码合并进去。尤其是涉及浏览器指纹这种偏底层的逻辑AI 往往能给出超出你预期的细节解释。3. 从零开始完整实操流程实录3.1 环境准备与项目骨架搭建工欲善其事必先利其器。先把开发环境准备好。我的推荐配置是Node.js 18 以上版本LTS 即可代码编辑器VS Code 或者其他你熟悉的都行Playwright作为核心自动化库Electron作为桌面壳如果你只想要命令行版本可以先跳过初始化项目非常简单就几步命令。mkdir strange-doctor-browser cd strange-doctor-browser npm init -y npm install playwright electron npx playwright install chromium这里有个小坑必须提醒npx playwright install chromium会下载一个独立的 Chromium 内核大概一百多 MB网络不好的时候容易超时。如果下载失败可以设置镜像源或者直接使用系统安装的 Chrome通过executablePath指定路径。项目结构我建议这样分模块别把所有代码堆在一个文件里。strange-doctor-browser/ ├── main.js # Electron 主进程入口 ├── manager/ │ └── instance-manager.js # 多开实例管理器 ├── fingerprint/ │ └── fingerprint.js # 指纹注入脚本 ├── ui/ │ ├── index.html # 管理界面 │ └── renderer.js # 渲染进程逻辑 └── profiles/ # 浏览器用户数据目录自动生成先搭好目录结构再让 AI 填充内容。从工程化角度来说这个阶段克服的是“烂开始”的心态你不需要一次写完所有代码但一定要把边界划清楚。3.2 让 AI 写第一个“多开管理器”项目骨架有了接下来就是核心模块。我让 AI 帮我写instance-manager.js这是个类负责创建新实例列出所有实例状态关闭指定实例清理用户数据目录AI 第一次给出的代码长这样的简化版。const { chromium } require(playwright); const fs require(fs); const path require(path); class InstanceManager { constructor(profileRoot ./profiles) { this.profileRoot profileRoot; this.instances new Map(); if (!fs.existsSync(profileRoot)) { fs.mkdirSync(profileRoot, { recursive: true }); } } async createInstance(id) { const userDataDir path.join(this.profileRoot, profile_${id}); const context await chromium.launchPersistentContext(userDataDir, { headless: false, args: [--disable-blink-featuresAutomationControlled], }); const page context.pages()[0] || await context.newPage(); this.instances.set(id, { context, page, userDataDir }); console.log(创建实例 ${id} 成功); return { id, ...this.instances.get(id) }; } async closeInstance(id) { const instance this.instances.get(id); if (instance) { await instance.context.close(); this.instances.delete(id); console.log(关闭实例 ${id} 成功); } } listInstances() { return Array.from(this.instances.entries()).map(([id, info]) ({ id, userDataDir: info.userDataDir, pageUrl: info.page.url(), })); } } module.exports InstanceManager;这个代码胜在直接能用逻辑干净。但它也暴露了 AI 代码的两个通病一是没有做启动失败的重试机制二是没有考虑多个实例同时启动时端口或资源竞争问题。所以我后面自己加了一段包装在createInstance外层套了个retry函数失败自动隔 500 毫秒重试一次。这个改进指南后面再说。3.3 联调与自测本地跑通三个隔离实例代码写完就要验证。我的做法是先跑一个命令行 demo不做 UI只是持续开着三个实例手动确认它们互相隔离。我写了一个测试脚本test.js。const InstanceManager require(./manager/instance-manager); (async () { const manager new InstanceManager(); await manager.createInstance(id1); await manager.createInstance(id2); await manager.createInstance(id3); // 分别在三个标签页里访问不同网站 const pages [https://example.com, https://httpbin.org/headers, https://www.baidu.com]; let idx 0; for (const [id, info] of manager.instances) { await info.page.goto(pages[idx], { waitUntil: domcontentloaded }); const title await info.page.title(); console.log(实例 ${id} 页面标题: ${title}); } // 等待片刻观察稳定状态 await new Promise(resolve setTimeout(resolve, 5000)); console.log(当前活跃实例:); console.log(manager.listInstances()); for (const id of [id1, id2, id3]) { await manager.closeInstance(id); } process.exit(0); })();跑完发现一个非常典型的问题三个实例同时启动时Playwright 默认会启动三个独立的浏览器进程这没问题。但如果网速慢goto到不同网站时某些页面会长时间 pending导致domcontentloaded事件迟迟不触发。解决方案是给每个goto加上timeout: 30000同时把超时错误单独捕获避免一个页面卡死拖垮整个实例。本地跑通之后接下来就可以做界面了。Electron 主进程拉起一个窗口渲染进程展示实例列表通过 IPC 和主进程通信调InstanceManager的方法。这个部分 AI 也很擅长它可以直接生成完整的main.js和index.html你只要把数据流捋清楚。我这里贴一下主进程的关键代码。const { app, BrowserWindow, ipcMain } require(electron); const path require(path); const InstanceManager require(./manager/instance-manager); const manager new InstanceManager(); function createWindow() { const win new BrowserWindow({ width: 1000, height: 700, webPreferences: { preload: path.join(__dirname, preload.js), contextIsolation: true, }, }); win.loadFile(ui/index.html); } app.whenReady().then(createWindow); ipcMain.handle(create-instance, async (event, id) { return await manager.createInstance(id); }); ipcMain.handle(close-instance, async (event, id) { await manager.closeInstance(id); return { success: true }; }); ipcMain.handle(list-instances, () { return manager.listInstances(); }); app.on(window-all-closed, () { if (process.platform ! darwin) app.quit(); });到这一步一个带界面、能创建实例、能关闭实例的“奇异博士浏览器”就已经基本成型了。剩下的优化工作主要是视觉细节和错误提示。4. 常见问题与排查技巧实录4.1 实例为什么总是崩溃或卡死我实际操作中遇到最多的问题就是实例无响应尤其是同时启动三个以上实例时。排查下来主要原因有三个。一是用户数据目录被占用。同一个用户数据目录不能同时被两个 Chromium 进程启动否则第二个进程会报错退出。解决方法是每次启动前检查目录锁文件是否存在如果存在先清理或提示用户确认没有残留进程。二是系统资源不足。每个 Chromium 实例大概会占用 200-300MB 内存同时开五六个实例内存轻松上 1.5GB。如果电脑本身只有 8GB 内存卡顿甚至崩溃很正常。解决方法是控制并发数或者在代码里增加总实例数上限比如最多 5 个。三是和安全软件的冲突。某些杀毒软件会拦截浏览器进程的某些操作导致实例崩溃。这个属于环境问题只能建议开发测试时关闭实时保护。我把这几个典型原因整理成了表格。故障现象常见原因解决办法实例启动后立刻退出用户数据目录被占用清理残留进程更换目录打开页面时卡死网络慢导致goto超时设置timeout参数捕获超时错误多个实例同时开启后系统卡顿内存不足限制最大实例数降低并发页面提示“请开启 JavaScript”注入脚本导致 JS 异常检查addInitScript的代码是否报错4.2 网站还是识别出我是同一台电脑这是多开浏览器最核心也最头疼的问题。如果你只是做了 user-data-dir 隔离而没做指纹伪装网站很容易发现异常。识别逻辑通常是这样的两个浏览器实例的 User-Agent 一样、Canvas 指纹一样、WebRTC 暴露的内网 IP 一样网站通过某一种信息就能将它们关联。我的经验是分三层排查。第一层看基础属性包括 User-Agent、平台、语言、时区。如果这些不一致说明配置还没生效。第二层看自动化标记navigator.webdriver是不是true这是最容易被忽略的。第三层看高级指纹包括 Canvas、WebGL、WebRTC、字体列表。这一层最难伪装也最容易被用于高级风控。坦白说要做到完美指纹伪装复杂度远超一篇文章能覆盖的范围。但如果你的使用场景是普通测试和隐私隔离做好前三层已经能满足绝大多数需求。我的建议是优先把基础属性做对再逐步叠加高级指纹伪装。4.3 AI 生成的代码报错怎么办和 AI 协作开发遇到最多的场景就是它生成的代码在本机跑不过。我原来也踩过坑后来总结了一套高效的反馈流程。第一步是让 AI 学会“看日志”。把完整的错误堆栈直接贴给它不要自己加工转述。AI 对英文错误信息的理解能力很强你转述反而会丢失信息。第二步是追问“为什么会这样”光让它改代码还不够要让它解释错误原因这样你才能判断它的修复方向是否正确。第三步是让它给多个方案特别是当涉及“改一行代码”和“重构整个函数”两种选择时让 AI 对比利弊你来拍板。举个例子我遇到过一个很诡异的问题Playwright 启动的浏览器实例page.evaluate执行多次后返回结果变成undefined。贴上错误日志后AI 判断是变量作用域问题还主动提醒检查是不是在回调里使用了未声明的变量。结果一查果然是我在setInterval里引用了一个已经销毁的page对象。这种问题如果没有 AI 帮忙定位光靠人肉排查至少得花半小时。5. 一些值得收藏的实操心得到这里“奇异博士浏览器”的核心功能已经全部讲完了。但我还是想把一些零散但实用的心得写出来这些都是文档里很难找到的东西。第一做多开浏览器这类工具时一定要把用户数据目录的命名和管理规范化。我的做法是profile_时间戳_随机数而不是简单的递增数字因为递增数字容易猜而且重启后容易混淆。同时还要定期清理不再使用的目录否则硬盘会被不知不觉塞满。第二Electron 开发和调试时建议把headless: false改成有头模式这样能看到浏览器实际运行效果。等逻辑稳定后再考虑无头模式方便后面做自动化部署。第三启动多个实例的时候给浏览器窗口加上--window-position参数让每个窗口出现在屏幕不同位置看起来更清晰也方便识别。const args [ --disable-blink-featuresAutomationControlled, --window-position${xOffset},${yOffset}, ];最后我想分享一个对整个项目最有帮助的小技巧写一个README.md把你和 AI 的对话精华整理进去。特别是那些“为什么这样设计”的答案写下来能极大提升你的项目可维护性。我这次做奇异博士浏览器边做边记录最后 README 比代码还长但每次回看都觉得值得。这个项目目前还在持续迭代中后续我想加入的功能包括更精细的指纹配置面板、代理配置管理、批量创建实例的模板系统甚至让 AI 直接生成指定网站的自动化操作流程。多开浏览器这个方向技术深挖下去很有意思而且 AI 的加入让开发门槛低了很多强烈推荐你也上手试试。
返回列表