使用AI开发属于自己的多开浏览器——奇异博士浏览器实战
本文介绍如何开发一款轻量级多开浏览器"奇异博士浏览器",解决商业指纹浏览器价格高、功能臃肿的问题。文章分析了市面主流浏览器的痛点,提出只需实现WebRTC、时区、地理位置和语言等核心功能即可满足需求。技术方案采用Electron+Node.js架构,详细展示了浏览器实例管理器的核心代码实现,包括创建隔离的浏览器实例、数据持久化、代理设置等功能。通过自定义配置文件和session
·
使用AI开发属于自己的多开浏览器——奇异博士浏览器实战
指纹浏览器管理多开AI开发指南-奇异博士
前言
市面上的指纹浏览器(多开浏览器)价格昂贵,功能臃肿。以某主流浏览器为例:
- 启动速度慢,体验笨拙
- 免费版仅支持5个浏览器实例
- 配置项繁杂,99%都是多余的
- 企业版高达617美刀/月(约4000+人民币)
- 专业版也要9美刀/月,且数量有限
实际上,对于大多数多账号管理场景,真正需要的核心配置只有:
- WebRTC
- 时区
- 地理位置
- 语言
其他如噪音、厂商、CPU等配置基本用不上。
既然是AI时代,为什么不自己动手打造一个轻量、快速、免费的多开浏览器呢?
技术选型与架构
遇到的技术难题
- 浏览器指纹隔离:每个浏览器实例需要独立的指纹环境
- 数据持久化:关闭后再次打开需要保留登录状态和历史记录
- 多实例管理:支持创建无限个浏览器实例并分组管理
- 跨平台打包:需要打包成桌面应用
技术方案
基于 Electron + Node.js 架构:
奇异博士浏览器
├── main/ # 主进程
│ ├── index.js # 入口文件
│ ├── browser-manager.js # 浏览器实例管理
│ └── profile-manager.js # 配置文件管理
├── renderer/ # 渲染进程(UI界面)
│ ├── index.html
│ ├── styles.css
│ └── app.js
├── profiles/ # 浏览器配置存储目录
└── package.json
核心实现代码
1. 浏览器实例管理器
// browser-manager.js
const { BrowserWindow, session } = require('electron');
const path = require('path');
const fs = require('fs');
class BrowserManager {
constructor() {
this.browsers = new Map();
this.profilesDir = path.join(__dirname, '../profiles');
this.ensureProfilesDir();
}
ensureProfilesDir() {
if (!fs.existsSync(this.profilesDir)) {
fs.mkdirSync(this.profilesDir, { recursive: true });
}
}
// 创建新的浏览器实例
async createBrowser(config) {
const { id, name, country, proxy, group } = config;
const profilePath = path.join(this.profilesDir, id);
// 创建独立的session分区,实现数据隔离
const partition = `persist:browser_${id}`;
const ses = session.fromPartition(partition);
// 设置代理
if (proxy) {
await ses.setProxy({ proxyRules: proxy });
}
// 根据国家设置语言
const language = this.getLanguageByCountry(country);
const win = new BrowserWindow({
width: 1280,
height: 800,
title: name,
webPreferences: {
partition: partition,
nodeIntegration: false,
contextIsolation: true,
// 注入指纹配置
preload: path.join(__dirname, 'preload.js')
}
});
// 设置语言
win.webContents.session.setSpellCheckerLanguages([language]);
// 保存配置
this.saveProfile(id, { name, country, proxy, group, language });
this.browsers.set(id, win);
win.on('closed', () => {
this.browsers.delete(id);
});
return win;
}
// 启动已有浏览器
async launchBrowser(id) {
const profile = this.loadProfile(id);
if (!profile) {
throw new Error('Profile not found');
}
const partition = `persist:browser_${id}`;
const ses = session.fromPartition(partition);
if (profile.proxy) {
await ses.setProxy({ proxyRules: profile.proxy });
}
const win = new BrowserWindow({
width: 1280,
height: 800,
title: profile.name,
webPreferences: {
partition: partition,
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
this.browsers.set(id, win);
win.on('closed', () => {
this.browsers.delete(id);
});
// 加载上次的页面或默认页面
win.loadURL('https://www.google.com');
return win;
}
getLanguageByCountry(country) {
const languageMap = {
'CN': 'zh-CN',
'US': 'en-US',
'ES': 'es-ES',
'JP': 'ja-JP',
'KR': 'ko-KR',
'FR': 'fr-FR',
'DE': 'de-DE',
'RU': 'ru-RU'
};
return languageMap[country] || 'en-US';
}
saveProfile(id, profile) {
const profilePath = path.join(this.profilesDir, `${id}.json`);
fs.writeFileSync(profilePath, JSON.stringify(profile, null, 2));
}
loadProfile(id) {
const profilePath = path.join(this.profilesDir, `${id}.json`);
if (fs.existsSync(profilePath)) {
return JSON.parse(fs.readFileSync(profilePath, 'utf-8'));
}
return null;
}
getAllProfiles() {
const profiles = [];
const files = fs.readdirSync(this.profilesDir);
for (const file of files) {
if (file.endsWith('.json')) {
const id = file.replace('.json', '');
const profile = this.loadProfile(id);
if (profile) {
profiles.push({ id, ...profile });
}
}
}
return profiles;
}
deleteProfile(id) {
const profilePath = path.join(this.profilesDir, `${id}.json`);
if (fs.existsSync(profilePath)) {
fs.unlinkSync(profilePath);
}
// 删除session数据
const partition = `persist:browser_${id}`;
session.fromPartition(partition).clearStorageData();
}
}
module.exports = new BrowserManager();
2. 指纹注入脚本
// preload.js
const { contextBridge } = require('electron');
// 注入时区和地理位置
contextBridge.exposeInMainWorld('fingerprint', {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
language: navigator.language
});
// 覆盖WebRTC,防止真实IP泄露
const originalRTCPeerConnection = window.RTCPeerConnection;
window.RTCPeerConnection = function(...args) {
const pc = new originalRTCPeerConnection(...args);
// 可以在这里添加更多的WebRTC控制逻辑
return pc;
};
3. 主进程入口
// main/index.js
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const browserManager = require('./browser-manager');
const { v4: uuidv4 } = require('uuid');
let mainWindow;
function createMainWindow() {
mainWindow = new BrowserWindow({
width: 1000,
height: 700,
title: 'Dr.Strange Browser',
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
}
app.whenReady().then(createMainWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// IPC通信:获取所有浏览器配置
ipcMain.handle('get-profiles', () => {
return browserManager.getAllProfiles();
});
// IPC通信:创建新浏览器
ipcMain.handle('create-browser', async (event, config) => {
const id = uuidv4();
await browserManager.createBrowser({ id, ...config });
return { id, ...config };
});
// IPC通信:启动浏览器
ipcMain.handle('launch-browser', async (event, id) => {
await browserManager.launchBrowser(id);
return true;
});
// IPC通信:删除浏览器
ipcMain.handle('delete-browser', (event, id) => {
browserManager.deleteProfile(id);
return true;
});
4. 前端界面
<!-- renderer/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Dr.Strange Browser</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<header>
<h1>Dr.Strange Browser</h1>
<button id="btn-new" class="btn-primary">+ New Environment</button>
</header>
<div class="filter-bar">
<select id="group-filter">
<option value="">All Groups</option>
</select>
<input type="text" id="search" placeholder="Search...">
</div>
<div id="browser-list" class="browser-list"></div>
</div>
<!-- 新建弹窗 -->
<div id="modal" class="modal hidden">
<div class="modal-content">
<h2>Create New Environment</h2>
<form id="create-form">
<div class="form-group">
<label>Name</label>
<input type="text" id="input-name" required>
</div>
<div class="form-group">
<label>Country</label>
<select id="input-country">
<option value="US">United States</option>
<option value="CN">China</option>
<option value="ES">Spain</option>
<option value="JP">Japan</option>
<option value="KR">Korea</option>
<option value="FR">France</option>
<option value="DE">Germany</option>
</select>
</div>
<div class="form-group">
<label>Proxy (optional)</label>
<input type="text" id="input-proxy" placeholder="http://ip:port">
</div>
<div class="form-group">
<label>Group</label>
<input type="text" id="input-group" placeholder="e.g. Work, Personal">
</div>
<div class="form-actions">
<button type="button" id="btn-cancel">Cancel</button>
<button type="submit" class="btn-primary">Create</button>
</div>
</form>
</div>
</div>
<script src="app.js"></script>
</body>
</html>
/* renderer/styles.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
min-height: 100vh;
color: #fff;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
}
header h1 {
font-size: 28px;
background: linear-gradient(90deg, #00d9ff, #00ff88);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.btn-primary {
background: linear-gradient(90deg, #00d9ff, #00ff88);
border: none;
padding: 12px 24px;
border-radius: 8px;
color: #1a1a2e;
font-weight: bold;
cursor: pointer;
transition: transform 0.2s;
}
.btn-primary:hover {
transform: scale(1.05);
}
.filter-bar {
display: flex;
gap: 15px;
margin-bottom: 20px;
}
.filter-bar select,
.filter-bar input {
padding: 10px 15px;
border-radius: 8px;
border: 1px solid #333;
background: #0f0f23;
color: #fff;
}
.browser-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}
.browser-card {
background: rgba(255, 255, 255, 0.05);
border-radius: 12px;
padding: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.browser-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0, 217, 255, 0.2);
}
.browser-card h3 {
margin-bottom: 10px;
}
.browser-card .meta {
color: #888;
font-size: 14px;
margin-bottom: 15px;
}
.browser-card .actions {
display: flex;
gap: 10px;
}
.browser-card .btn-launch {
flex: 1;
padding: 10px;
border: none;
border-radius: 6px;
background: #00d9ff;
color: #1a1a2e;
font-weight: bold;
cursor: pointer;
}
.browser-card .btn-delete {
padding: 10px 15px;
border: 1px solid #ff4757;
border-radius: 6px;
background: transparent;
color: #ff4757;
cursor: pointer;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
}
.modal.hidden {
display: none;
}
.modal-content {
background: #1a1a2e;
padding: 30px;
border-radius: 16px;
width: 400px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #888;
}
.form-group input,
.form-group select {
width: 100%;
padding: 12px;
border-radius: 8px;
border: 1px solid #333;
background: #0f0f23;
color: #fff;
}
.form-actions {
display: flex;
gap: 15px;
justify-content: flex-end;
}
.form-actions button {
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
}
#btn-cancel {
background: transparent;
border: 1px solid #666;
color: #fff;
}
// renderer/app.js
const { ipcRenderer } = require('electron');
const browserList = document.getElementById('browser-list');
const modal = document.getElementById('modal');
const btnNew = document.getElementById('btn-new');
const btnCancel = document.getElementById('btn-cancel');
const createForm = document.getElementById('create-form');
const groupFilter = document.getElementById('group-filter');
let profiles = [];
async function loadProfiles() {
profiles = await ipcRenderer.invoke('get-profiles');
renderProfiles();
updateGroupFilter();
}
function renderProfiles(filter = '') {
const filtered = profiles.filter(p => {
if (filter && p.group !== filter) return false;
return true;
});
browserList.innerHTML = filtered.map(profile => `
<div class="browser-card" data-id="${profile.id}">
<h3>${profile.name}</h3>
<div class="meta">
<div>Country: ${profile.country}</div>
<div>Group: ${profile.group || 'None'}</div>
${profile.proxy ? `<div>Proxy: ${profile.proxy}</div>` : ''}
</div>
<div class="actions">
<button class="btn-launch" onclick="launchBrowser('${profile.id}')">Launch</button>
<button class="btn-delete" onclick="deleteBrowser('${profile.id}')">Delete</button>
</div>
</div>
`).join('');
}
function updateGroupFilter() {
const groups = [...new Set(profiles.map(p => p.group).filter(Boolean))];
groupFilter.innerHTML = `
<option value="">All Groups</option>
${groups.map(g => `<option value="${g}">${g}</option>`).join('')}
`;
}
async function launchBrowser(id) {
await ipcRenderer.invoke('launch-browser', id);
}
async function deleteBrowser(id) {
if (confirm('Are you sure you want to delete this browser?')) {
await ipcRenderer.invoke('delete-browser', id);
loadProfiles();
}
}
btnNew.addEventListener('click', () => {
modal.classList.remove('hidden');
});
btnCancel.addEventListener('click', () => {
modal.classList.add('hidden');
});
createForm.addEventListener('submit', async (e) => {
e.preventDefault();
const config = {
name: document.getElementById('input-name').value,
country: document.getElementById('input-country').value,
proxy: document.getElementById('input-proxy').value || null,
group: document.getElementById('input-group').value || null
};
await ipcRenderer.invoke('create-browser', config);
modal.classList.add('hidden');
createForm.reset();
loadProfiles();
});
groupFilter.addEventListener('change', (e) => {
renderProfiles(e.target.value);
});
// 初始化
loadProfiles();
5. package.json 配置
{
"name": "dr-strange-browser",
"version": "1.0.0",
"description": "A lightweight multi-browser manager",
"main": "main/index.js",
"scripts": {
"start": "electron .",
"build": "electron-builder"
},
"dependencies": {
"uuid": "^9.0.0"
},
"devDependencies": {
"electron": "^28.0.0",
"electron-builder": "^24.0.0"
},
"build": {
"appId": "com.drstrange.browser",
"productName": "Dr.Strange Browser",
"directories": {
"output": "dist"
},
"win": {
"target": "nsis",
"icon": "assets/icon.ico"
},
"mac": {
"target": "dmg",
"icon": "assets/icon.icns"
}
}
}
运行与打包
开发运行
# 安装依赖
npm install
# 启动开发模式
npm start
打包发布
# 打包为安装程序
npm run build
打包后:
- 源代码约 900MB
- 安装包约 200MB
实现效果
- 启动速度:不到1秒即可启动浏览器实例
- 无限实例:可创建无限个浏览器环境
- 数据隔离:每个实例独立的Cookie、缓存、历史记录
- 状态保持:关闭后再次打开,登录状态和历史记录完整保留
- 分组管理:支持按分组快速筛选
- 语言自动配置:选择国家后自动设置对应语言
避坑指南
- Electron版本选择:建议使用LTS版本,避免兼容性问题
- Session分区:使用
persist:前缀确保数据持久化 - WebRTC泄露:需要在preload脚本中处理,否则会暴露真实IP
- 打包体积:可以使用
electron-builder的asar压缩减小体积 - 跨平台兼容:路径处理使用
path.join(),避免硬编码分隔符
总结和下载
在AI时代,只要有想法,就能把它变成现实。与其每月花费数百甚至数千元使用商业指纹浏览器,不如花几天时间用AI辅助开发一个属于自己的多开浏览器。
核心功能其实并不复杂:
- Electron提供浏览器内核
- Session分区实现数据隔离
- 简单的配置管理实现多实例
这个项目的完整代码已开源,可以访问博客 xoxome.online 在免费原创工具箱中找到"奇异博士"进行下载使用。
免费下载使用
本文基于实际开发经验整理,如有问题欢迎评论区交流。
更多推荐


所有评论(0)