npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@iss-ai/browser-pool

v0.0.6

Published

`BrowserPool` 是一个用于管理和调度无头浏览器(Headless Browser)资源的高效任务池,基于 [Playwright](https://playwright.dev/) 实现。它专门为高并发爬虫、自动化测试、AI 会话调度等场景设计,支持多工作节点(Worker)复用、超时控制、状态隔离、持久化用户状态以及动态适配不同的浏览器内核(如 Chromium, Camoufox 等)。

Readme

BrowserPool 浏览器任务池

BrowserPool 是一个用于管理和调度无头浏览器(Headless Browser)资源的高效任务池,基于 Playwright 实现。它专门为高并发爬虫、自动化测试、AI 会话调度等场景设计,支持多工作节点(Worker)复用、超时控制、状态隔离、持久化用户状态以及动态适配不同的浏览器内核(如 Chromium, Camoufox 等)。


主要特性

  1. 高并发与复用:预启动和复用多个浏览器 Worker,避免频繁开启和关闭浏览器进程带来的资源消耗。
  2. 多内核支持:原生支持 Playwright 提供的 Chromium、Firefox、Webkit,并支持通过配置动态加载专门为反爬优化的 camoufox 引擎。
  3. 状态隔离与持久化
    • 极致隔离(默认):每个任务执行前会自动重置隐身上下文,确保任务之间数据(Cookies/LocalStorage)互不污染。
    • 状态注入:支持通过 storageState 为所有隔离的任务注入相同的账号 Cookies。
    • 持久化模式:支持通过 userDataDir 绑定本地文件夹,自动保存和维持扫码登录等持久化状态。
  4. 异常容灾:自动监测 Worker 内页面的崩溃或超时。当发生异常时,会自动销毁该节点并在后台无缝替换为健康的新 Worker。

接口说明 (API Reference)

1. BrowserPoolOptions 配置接口

在创建 BrowserPool 时传入的配置参数:

export interface BrowserPoolOptions {
  /** 最大并发工作节点 (页面) 数量,默认为 2 */
  maxWorkers?: number;
  /** 最小保持待命的闲置节点数量,默认为 0 */
  minWorkers?: number;
  /** 浏览器引擎类型: 'chromium', 'firefox', 'webkit' 或 'camoufox',默认为 'chromium' */
  browserType?: 'chromium' | 'firefox' | 'webkit' | 'camoufox';
  /**
   * 传递给底层引擎的启动参数 (等价于 Playwright 的 LaunchOptions)。常用参数包括:
   * - `headless`: boolean (是否为无头模式,默认 true)
   * - `args`: string[] (传递给浏览器进程的附加命令行参数,例如扩展插件配置)
   * - `proxy`: { server, username, password } (全局代理配置)
   * - `executablePath`: string (自定义浏览器可执行文件的绝对路径)
   * - `timeout`: number (等待浏览器启动的最大超时时间)
   */
  launchOptions?: LaunchOptions;
  /** 单个任务执行的最大超时时间(毫秒),超时将强制中断任务并重建 Worker,默认 30000ms */
  taskTimeoutMs?: number;
  /** 等待获取空闲 Worker 的最大排队超时时间(毫秒),默认 60000ms */
  acquireTimeoutMs?: number;
  /** Worker 空闲超过此时间(毫秒)且数量大于 minWorkers 时将被自动销毁,默认 60000ms */
  idleTimeoutMs?: number;
  /** 可选:持久化用户数据目录。开启后浏览器将记住所有的 Cookies/缓存,多 Worker 之间将共享状态,不再保持强隔离。 */
  userDataDir?: string;
  /** 可选:状态注入文件路径或 JSON 对象。在无痕隔离模式下,用于注入预先保存的登录态 Cookie。 */
  storageState?: string | { cookies: any[]; origins: any[] };
  /** 动态上下文配置解析器。用于在创建每个 Worker 时,动态提供代理、User-Agent、分辨率等,实现指纹隔离。 */
  getContextOptions?: () => BrowserContextOptions | Promise<BrowserContextOptions>;
  /** 自动拦截并丢弃指定的资源类型,例如 ['image', 'media'] 以提升加载速度并节省带宽。 */
  blockAssets?: ('image' | 'stylesheet' | 'media' | 'font' | 'script' | 'document')[];
  /** 是否为 Chromium 引擎启用 Stealth 反反爬插件伪装(默认: false)。 */
  stealth?: boolean;
  /** 是否开启静态资源(JS/CSS/Image)的内存级 LRU 缓存加速(默认: false)。 */
  cacheAssets?: boolean;
  /** 全局任务执行间隔(毫秒),用于严格限流,防止瞬时高并发被封 IP(默认: 0)。 */
  delayBetweenTasksMs?: number;
  /** 对空闲 Worker 进行僵尸进程心跳检测的时间间隔(毫秒),默认 30000ms。 */
  healthCheckIntervalMs?: number;
  /** 自定义任务队列提供者,默认使用内部单机 `MemoryQueueProvider`。 */
  queueProvider?: IQueueProvider<any>;
  /** 智能预热缓冲池大小。当空闲 Worker 低于该值时会自动在后台预启动浏览器,实现 0ms 等待(默认: 0)。 */
  preWarmBuffer?: number;
  /** 分布式队列提供者(如 Redis)。开启后系统将转入 Pub/Sub 分布式模式,接受跨机器的任务调度。 */
  distributedQueueProvider?: IDistributedQueueProvider;
  /**
   * 内置 Redis 队列提供者的连接 URL。配置后自动开启分布式 MQ 模式。
   * 支持带有密码的 URL,例如:'redis://:[email protected]:6379'
   */
  redisUrl?: string;
  /** 内置 Redis 队列提供者的额外配置项(对应 ioredis 的 RedisOptions)。 */
  redisOptions?: RedisOptions;
  /**
   * 是否开启 Redis 队列的可靠模式 (At-Least-Once Delivery)。
   * 如果设为 true,引擎将使用双队列和 ACK 机制确保哪怕断电宕机也不丢任务。
   */
  redisReliableMode?: boolean;
  /**
   * 是否在每次任务前彻底销毁并重建独立的 BrowserContext (默认: true)。
   * 如果设为 false,则进入极速软隔离模式:复用上下文,仅清理 Cookies 和新建标签页。
   */
  strictIsolation?: boolean;
  /**
   * 自动注册进程退出信号 (SIGINT/SIGTERM) 并执行优雅停机。
   * 可以传入 true (默认等待 10000ms),也可以传入具体的超时毫秒数。
   */
  autoGracefulShutdown?: boolean | number;
}

2. BrowserPool

constructor(options?: BrowserPoolOptions)

构造函数,实例化浏览器任务池。此时不会立即启动浏览器进程。

async init(): Promise<void>

初始化连接池。拉起浏览器引擎,并创建指定数量(maxWorkers)的空闲 Worker。一般无需手动调用,在第一次执行 execute 时会自动触发。

async execute<T>(task: (worker: Worker) => Promise<T>, options?: ExecuteOptions): Promise<T>

将任务派发给任务池,排队等待空闲的 Worker 并执行。

  • 参数:
    • task 为回调函数,接收一个当前分配给该任务的健康 Worker 对象。
    • options (可选) 为重试和优先级配置,例如 { retries: 3, retryDelayMs: 2000, priority: 10 }
  • 返回值: 返回该任务回调中最终执行完成并返回的结果。
  • 超时机制: 受 acquireTimeoutMs (排队超时) 和 taskTimeoutMs (执行超时) 双重控制。

registerAction<P, R>(action: string, handler: ActionHandler<P, R>)

预先注册一个命名动作(Action)的爬取逻辑。专为微服务或分布式架构设计。

  • 参数:
    • action: 任务名称(字符串)。
    • handler: 具体的爬虫处理回调 (worker, payload) => Promise<R>

async dispatch<P, R>(action: string, payload: P, options?: ExecuteOptions): Promise<R | string>

派发一个已被 registerAction 注册的任务。

  • 如果未配置 distributedQueueProvider:该任务将直接放入本地内存队列,其行为与 execute 完全一致,并最终返回 Promise 的执行结果。
  • 如果配置了 distributedQueueProvider:任务参数将被序列化并压入外部 MQ。该调用将立即返回一个由于推送而产生的标志字符(如 Job ID),具体的执行由后台的所有跨机消费者节点接管。

async destroy(options?: { gracefulTimeoutMs?: number }): Promise<void>

销毁并关闭连接池,彻底终止底层浏览器进程,清空排队队列。

  • 参数: options.gracefulTimeoutMs (可选) 如果提供该参数,框架将拒绝新任务,并等待最多指定时间让正在运行的 Worker 结束它们的当前任务,实现优雅停机。

async stats(): Promise<{ totalWorkers: number; idleWorkers: number; busyWorkers: number; localQueueLength: number; }>

返回当前浏览器池的实时运行监控数据,非常适合用于对接 Grafana 或 Prometheus 等监控大盘。


使用示例

示例 1: 基础无痕并发(强隔离,不带 Cookie)

适用于需要完全干净的运行环境、无需账号登录的场景。

import { BrowserPool } from './src/pool/BrowserPool';

async function main() {
  const pool = new BrowserPool({
    maxWorkers: 3,
    browserType: 'chromium', // 使用默认内核
    launchOptions: { headless: true },
  });

  // 并发派发 3 个独立任务
  const results = await Promise.all([
    pool.execute(async worker => {
      await worker.page!.goto('https://example.com/1');
      return worker.page!.title();
    }),
    pool.execute(async worker => {
      await worker.page!.goto('https://example.com/2');
      return worker.page!.title();
    }),
    pool.execute(async worker => {
      await worker.page!.goto('https://example.com/3');
      return worker.page!.title();
    }),
  ]);

  console.log('Results:', results);
  await pool.destroy();
}

示例 2: 使用 Camoufox 与状态注入(带 Cookie 但互相隔离)

适用于需要使用高级反爬引擎,并且多个并发任务都需要携带预先登录好的 Cookie。

import { BrowserPool } from './src/pool/BrowserPool';

async function main() {
  const pool = new BrowserPool({
    maxWorkers: 2,
    browserType: 'camoufox', // 动态加载 camoufox-js,强化反反爬能力
    storageState: './cookies.json', // 注入提前保存的登录状态文件
    launchOptions: { headless: false },
  });

  await pool.execute(async worker => {
    // 此时的 worker.page 已经携带了 cookies.json 中的登录态,但不会污染其他并发任务
    await worker.page!.goto('https://google.com');
    // 执行操作...
  });

  await pool.destroy();
}

示例 3: 持久化目录模式(共享状态,自动记录登录)

最简单的人工辅助模式:像日常浏览器一样,第一次打开时可人工干预扫码登录,后续所有的重启和执行都会持久化保留这个登录状态。所有任务共享相同的登录账号。

import { BrowserPool } from './src/pool/BrowserPool';

async function main() {
  const pool = new BrowserPool({
    maxWorkers: 1, // 这种模式下通常配置为 1 做队列处理即可
    userDataDir: './user_data_profile', // 指定一个本地文件夹存放持久化数据
    launchOptions: { headless: false }, // 必须显示浏览器,以便人类进行扫码/点击确认
  });

  await pool.execute(async worker => {
    await worker.page!.goto('https://google.com');
    // 如果没有登录,你可以在这里通过 await worker.page.waitForTimeout(60000) 给自己扫码时间
    // 一旦扫码成功,所有的缓存、Session 都会永久保留在 './user_data_profile' 中
  });
}

示例 4: 配置代理 (Proxy)

无论是 chromium, firefox 还是 camoufox 引擎,代理的配置方式都是一致的,直接在 launchOptions 中传入 proxy 即可。

const pool = new BrowserPool({
  maxWorkers: 1,
  browserType: 'chromium',
  launchOptions: {
    proxy: {
      server: 'http://myproxy.com:3128',
      username: 'my_username',
      password: 'my_password',
    },
  },
});

示例 5: 加载扩展插件 (Extensions)

Chromium 引擎 (推荐)

支持通过 args 参数直接加载本地未打包的扩展。注意:必须配置持久化目录(userDataDir) 并且通常需要关闭无头模式。

import path from 'path';

const pathToExtension = path.join(__dirname, 'my-chrome-extension');

const pool = new BrowserPool({
  maxWorkers: 1,
  userDataDir: './user_data_profile',
  launchOptions: {
    headless: false,
    args: [`--disable-extensions-except=${pathToExtension}`, `--load-extension=${pathToExtension}`],
  },
});

Firefox / Camoufox 引擎

由于 Playwright 对 Firefox 扩展原生支持有限,不支持动态命令行加载。如果必须在 Firefox 引擎中使用插件,推荐使用 预配置 Profile (用户目录) 方案

  1. 手动打开正常的 Firefox/Camoufox。
  2. 建立一个固定的 Profile,并在其中手动安装好需要的 .xpi 插件。
  3. 在代码的 userDataDir 中指定这个已经安装好插件的 Profile 目录。
const pool = new BrowserPool({
  browserType: 'camoufox',
  userDataDir: './my_preconfigured_firefox_profile', // 指向你预先配置好的目录
  launchOptions: {
    headless: false,
  },
});

示例 6: 深层特征与 IP 指纹隔离 (Context Isolation)

如果需要防止 IP 和设备特征被封禁,你可以提供 getContextOptions 方法。非持久化模式下,每次创建新的并发 Worker 时,都会调用此方法动态注入一套全新的环境指纹(包括代理 IP、User-Agent、分辨率、语言等)。

const pool = new BrowserPool({
  maxWorkers: 5,
  // 动态返回一套全新的浏览器指纹,让并发的每个 Worker 都是完全独立的真实设备
  getContextOptions: async () => {
    return {
      proxy: { server: await fetchMyProxyIpFromProvider() },
      userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...', // 随机 UA
      viewport: { width: 1920, height: 1080 }, // 随机分辨率
      locale: 'zh-CN',
      timezoneId: 'Asia/Shanghai',
    };
  },
});

注意:该功能在配置了 userDataDir(持久化模式)时由于强制环境共享,将不起作用。

示例 7: 任务自动重试与优先级队列 (Task Retry & Priority)

在执行不稳定网络环境的爬取任务时,可以使用重试和优先级控制。

// 提交一个高优先级、最多重试3次的任务
pool
  .execute(
    async worker => {
      await worker.page!.goto('https://example.com/unstable-page');
      // ... 其他操作
    },
    {
      retries: 3, // 失败最多自动重试3次
      retryDelayMs: 2000, // 每次重试前间隔2秒
      priority: 10, // 优先级(数字越大越优先出队执行)
    }
  )
  .then(res => {
    console.log('执行成功', res);
  })
  .catch(err => {
    console.error('重试3次依然失败', err);
  });

示例 8: 按需拉起与弹性缩容 (Lazy Init & Auto Scaling)

通过配置弹性参数,可以避免启动时占用大量空闲内存。

const pool = new BrowserPool({
  minWorkers: 1, // 初始化时只拉起 1 个 Worker 待命
  maxWorkers: 10, // 任务高峰期,最多自动扩容到 10 个并发 Worker
  idleTimeoutMs: 60000, // 当 Worker 闲置超过 60 秒且当前 Worker 数大于 minWorkers 时,自动销毁释放资源
});

示例 9: 自动资源拦截加速 (Asset Blocking)

如果你只关心页面数据而不需要渲染图片或样式,可以通过内置的 blockAssets 极大提升页面加载速度和节省带宽。

const pool = new BrowserPool({
  maxWorkers: 3,
  // 框架层自动注入 page.route,拦截并中止这几类请求
  blockAssets: ['image', 'stylesheet', 'media', 'font'],
});

示例 10: 智能预热池 (Warm-up Buffer)

如果你的业务对请求响应时间要求极高,开启预热池可以提前在后台启动空闲浏览器页签。当新任务到达时可以达到 0ms 启动延时。

const pool = new BrowserPool({
  maxWorkers: 10,
  preWarmBuffer: 2, // 只要系统内的空闲 Worker 数量少于 2,且未达到 maxWorkers 限制,就会在后台自动创建新的 Worker 待命
});

示例 11: 分布式架构 Action (Pub/Sub Pattern)

BrowserPool 支持注册跨机器的分布式任务。你可以通过向 registerAction 注册处理器,并使用 dispatch 发送 JSON 格式的任务载荷,这完美兼容外挂式的 Redis 或 MQ。如果不外挂任何 MQ,默认会在单机内存中运行,与 execute 的行为一致。

// 1. 预先注册 Action 处理器
pool.registerAction('scrape_page', async (worker, payload: { url: string }) => {
  await worker.page!.goto(payload.url);
  return await worker.page!.title();
});

// 2. 派发任务 (单机环境下会返回执行结果,分布式环境下会立即压入 MQ)
const result = await pool.dispatch(
  'scrape_page',
  { url: 'https://example.com' },
  {
    priority: 10, // 同样支持优先级等选项
  }
);
console.log(result);

如何外挂真实的 Redis/MQ 队列:

由于我们内置了强大的 RedisQueueProvider,你完全不需要自己写任何接口代码!只需要在初始化时传入 redisUrl,底层就会自动实例化一套基于 ioredis 的双工长轮询机制,接管整个集群的任务分发与消费。

const pool = new BrowserPool({
  maxWorkers: 5,
  /**
   * 内置 Redis 队列提供者的连接 URL。配置后自动开启分布式 MQ 模式。
   * 支持带有密码的 URL,例如:'redis://:[email protected]:6379'
   */
  redisUrl: 'redis://127.0.0.1:6379',
  /** 内置 Redis 队列提供者的额外配置项(对应 ioredis 的 RedisOptions)。 */
  redisOptions: {
    // ...
  },
});

如果你使用的是除 Redis 外的其他非标 MQ (如 RabbitMQ, Kafka),依然可以通过传入实现了 IDistributedQueueProviderdistributedQueueProvider 参数进行自定义外挂。

⚠️ 特别说明:任务路由机制

当你配置了 Redis 参数开启分布式模式后,任务的流向取决于你调用的 API:

  • 走 Redis 全局分发 (dispatch):只要你调用了 pool.dispatch(action, payload),任务就会自动序列化并推入 Redis,由整个集群池共同竞争消费。
  • 只走本地执行 (execute):如果你调用了 pool.execute(async worker => { ... }),因为传递的是原生 JS 函数(无法跨机器序列化),这些任务绝对不会进入 Redis,而是像以前一样只在你当前的 Node.js 进程本地内存中排队执行。

这种设计让你可以混用分布式架构与本地快捷脚本,两者互不干扰!

示例 12: 无头环境终极隐身伪装 (Stealth Plugin)

原生的 Chromium 容易被 Cloudflare 等盾牌识别。如果开启 stealth,框架会在底层自动集成 playwright-extrapuppeteer-extra-plugin-stealth 进行指纹擦除(需确保项目安装了这两个依赖)。

const pool = new BrowserPool({
  browserType: 'chromium',
  stealth: true, // 启动 Stealth 反检测机制
});

示例 13: 全局生命周期与监控事件流 (EventEmitter)

BrowserPool 继承了原生的 EventEmitter,你可以轻松外挂各类监控探针或日志采集系统。

pool.on('worker:created', worker => console.log('新 Worker 上线'));
pool.on('worker:zombie', worker => console.warn('检测到无响应的僵尸进程!'));
pool.on('task:success', (task, result) => console.log('任务爬取成功', result));
pool.on('task:fail', (task, error) => console.error('任务彻底失败', error));
pool.on('pool:exhausted', () => console.log('队列所有任务已清空'));

示例 14: 静态请求拦截与内存级加速 (Asset In-Memory Cache)

爬取不同页面时常常需要加载同样的公共库(如 jQuery / Vue 等)。配合资源拦截功能,你可以开启 cacheAssets

const pool = new BrowserPool({
  // 首个页面下载静态文件后,会存入 LRU 内存缓存。后续其他并发请求直接 0ms 拦截返回,不走网络!
  cacheAssets: true,
  blockAssets: ['image', 'media'], // 依然阻断图片和视频
});

示例 15: 全局防封并发限流锁 (Global Rate Limiting)

为了避免池子因为空闲 Worker 太多而造成瞬时高并发被目标网站封 IP,可以设置严格的请求间隔。

const pool = new BrowserPool({
  maxWorkers: 20,
  // 无论有多少个 Worker 闲置待命,向网站派发任务的时间间隔至少为 2000 毫秒
  delayBetweenTasksMs: 2000,
});

示例 16: 僵尸进程健康度探针 (Zombie Health Check)

针对爬虫代码因未知的异步错误卡死、内存泄露或无限阻塞等“假死”情况,框架提供自带的心跳扫描。

const pool = new BrowserPool({
  // 每 30 秒巡查一次,向空闲 Worker 发送 evaluate('1')。
  // 若 3 秒未响应,主动判定为僵尸,将其猎杀并重建全新的 Worker 替补!
  healthCheckIntervalMs: 30000,
});

示例 17: 极速复用软隔离 (Fast Isolation)

当你的爬虫任务非常简单且并发量极大(例如:爬取纯公开数据、不需要登录、不需要频繁换 IP 和 LocalStorage),你可以关闭严格隔离。 关闭后,引擎不会频繁销毁并重建 BrowserContext,而是直接复用上下文,仅清空 Cookies 并新建标签页,能将任务切换的引擎损耗从 ~15ms 压缩到 ~1ms

const pool = new BrowserPool({
  maxWorkers: 10,
  strictIsolation: false, // 开启极速软隔离模式
});

示例 18: 优雅停机 (Graceful Shutdown)

在微服务或云原生环境下,当进程收到 SIGTERM 退出信号时,为了防止正在执行爬取任务的 Worker 被暴力斩断导致数据丢失,你可以开启自动优雅停机机制。

const pool = new BrowserPool({
  maxWorkers: 5,
  // 仅需一行代码开启。当按下 Ctrl+C 或收到 k8s SIGTERM 信号时,
  // 引擎会自动拦截并给予运行中的爬虫最多 10000ms 的收尾时间。
  autoGracefulShutdown: true,
  // 或者传入具体的毫秒数: autoGracefulShutdown: 15000
});

注:开启该选项后,引擎会在底层自动替你接管 process.on('SIGTERM')SIGINT 事件,超时或完成后自动安全退出。

示例 19: 分布式可靠队列 (At-Least-Once Delivery)

在默认情况下,Redis 队列为了追求极致的高并发吞吐,采用的是 BRPOP (弹出即删除) 的火炮发射模式。如果遇到机器突然断电宕机,正在处理的任务可能会永久丢失。 如果你的业务是对数据一致性要求极高的核心数据爬取,可以开启可靠模式。开启后,底层将自动启用 BRPOPLPUSH 机制,将弹出的任务安全地备份在 processing 队列中,只有当代码完美执行结束,框架才会发送 ACK 信号予以彻底销毁。

const pool = new BrowserPool({
  redisUrl: 'redis://127.0.0.1:6379',
  redisReliableMode: true, // 开启双队列绝对可靠投递
});