- 人工智能
- MCP 服务
【免费下载链接】web-search-mcp
A simple, locally hosted Web Search MCP server for use with Local LLMs
web-search-mcp是一个本地托管的 Web 搜索 MCP Server,通过 Playwright 管理多个无头浏览器实例来为 Local LLM 提供搜索与网页正文提取能力。它的核心组件BrowserPool(browser-pool.ts)用一个 Map 按浏览器类型缓存实例,并内置了类型轮换、健康检查、自动清理三大机制,让多实例长期运行不泄漏、不掉线。
BrowserPool 是什么?解决什么问题?
每次用 Playwright 都launch一个新浏览器,代价很高(冷启动慢、内存占用大);但一直复用同一个实例又可能遇到进程假死、会话断开等问题。BrowserPool的设计就是在这两者之间取平衡:
- 按类型缓存:内部用
Map<string, Browser>存放实例,每个浏览器类型(chromium / firefox / webkit)最多保留一个 - 按需复用:再次需要同类型浏览器时先体检,健康则直接复用
- 有损自愈:实例不健康就关闭并重新拉起,业务层无感知
项目中有两处独立的池实例在使用它:搜索引擎(search-engine.ts)和内容提取器(enhanced-content-extractor.ts),两者各自管理自己的浏览器生命周期。
一行配置:通过环境变量调节池子大小
BrowserPool构造函数完全由环境变量驱动(browser-pool.ts),在mcp.json的env中即可调整:
| 环境变量 | 默认值 | 作用 |
|---|---|---|
MAX_BROWSERS | 3 | 池内最多保留的浏览器实例数,超出后自动淘汰最旧实例 |
BROWSER_TYPES | chromium,firefox | 逗号分隔的轮换列表,可加webkit |
BROWSER_HEADLESS | true | 设为false才显示浏览器窗口 |
例如内存紧张时可设置MAX_BROWSERS=1;想只用 Chromium 则设BROWSER_TYPES=chromium。
机制一:浏览器类型轮换(Round-Robin)
每次调用getBrowser()时,池子会按顺序在BROWSER_TYPES列表中取下一个类型:
chromium → firefox → chromium → firefox → ...实现非常轻量:一个递增计数器对类型列表长度取模(browser-pool.ts),并通过getLastUsedBrowserType()告诉调用方本次实际拿到了哪种浏览器。
为什么要轮换?
- 规避针对性反爬:不同网站的 bot 检测策略不同,交替使用 Chromium 与 Firefox 能降低被单一指纹识别的概率
- 分散负载:避免所有自动化流量都带着同一浏览器内核特征
- 容错:某种类型在本机环境有兼容问题时(如某些服务器上 Firefox 的 isMobile 限制),轮换机制会自动绕过去
调用方拿到lastUsedBrowserType后还能做差异化处理——比如内容提取器会据此判断是否为 Firefox,跳过它不支持的isMobile上下文选项(enhanced-content-extractor.ts)。
机制二:健康检查,拒绝“僵尸浏览器”
复用前必须确认实例还活着。getBrowser()的体检分两步:
- 连接检查:
browser.isConnected()判断进程通道是否还在 - 响应性检查:尝试创建并立即关闭一个测试 context——这是真实操作,能发现"连接显示正常但实际无响应"的僵尸状态(browser-pool.ts)
体检失败时池子会:把该类型从 Map 中移除 → 尝试close()回收进程(关闭失败也只记日志、不阻塞)→ 走全新launch流程拉起替代实例。整个自愈过程对上层是透明的,业务代码永远只需要await pool.getBrowser()一行。
💡 类似的体检逻辑在搜索引擎侧也有独立实现
validateBrowserHealth(search-engine.ts),配合DEBUG_BROWSER_LIFECYCLE=true环境变量可输出详细的浏览器生命周期日志,便于排障。
机制三:自动清理,防止内存泄漏
浏览器实例是内存大户,BrowserPool在两个层面控制规模:
1. 容量淘汰(LRU 风格)
每次新实例入池后,如果browsers.size超过MAX_BROWSERS,就会取 Map 中最先进入的条目关闭并从池中删除(browser-pool.ts)。Map 的插入顺序天然充当了"最旧优先淘汰"的队列。
2. 会话级与进程级清理
- 搜索完成后,MCP Server 会调用
searchEngine.closeAll()清空池子,防止 EventEmitter 监听器随浏览器累积造成内存泄漏(index.ts) - 收到
SIGINT/SIGTERM信号时,优雅关闭流程会并发调用内容提取器与搜索引擎两个池的closeAll(),确保进程退出前所有浏览器都被回收(index.ts) - 检测到"浏览器已关闭"类错误(如
Browser has been closed)时,handleBrowserError会主动closeAll()刷新整个池,让下一次尝试拿到全新实例(search-engine.ts)
closeAll()本身也是容错的:对所有实例并发close(),单个失败不影响其他实例,最后清空 Map(browser-pool.ts)。
拉新实例时的"隐身"启动参数
池子新建浏览器时并非裸启动,而是一组反自动化检测参数(browser-pool.ts),常见的几个:
--disable-blink-features=AutomationControlled:关闭 Blink 自动化特征--no-sandbox、--disable-dev-shm-usage:适配容器/低共享内存环境--disable-gpu:无头服务器上禁用 GPU 渲染--disable-extensions:禁止扩展干扰
再加上调用方创建 context 时注入的随机 User-Agent、视口、时区以及移除navigator.webdriver的 stealth 脚本(enhanced-content-extractor.ts),整体大幅降低了被目标站点识别为自动化的概率。
常见问题排查速查
| 症状 | 建议操作 |
|---|---|
| 内存占用持续增长 | 调小MAX_BROWSERS;确认搜索工具执行完触发了closeAll |
| 浏览器启动失败 | 执行npx playwright install确认内核已安装;检查BROWSER_TYPES拼写 |
| 需要调试浏览器生命周期 | 设置DEBUG_BROWSER_LIFECYCLE=true,观察控制台日志 |
| 某些网站抓取被拦截 | 利用类型轮换交替内核;确认BROWSER_HEADLESS=true未被意外覆盖 |
相关文件一览
- 池核心实现:browser-pool.ts —— 轮换、体检、淘汰、
closeAll全在此文件 - 搜索侧消费者:search-engine.ts —— 浏览器错误处理与池刷新
- 提取侧消费者:enhanced-content-extractor.ts —— 浏览器兜底提取入口
- 服务装配与优雅退出:index.ts
- 配置说明与环境变量:README.md、docs/API.md
小结
BrowserPool用一个不到 130 行的类,把"多浏览器实例怎么管"这个问题拆解成了三个可独立理解的机制:按类型轮换分散风险、用真实操作做健康检查拒绝僵尸实例、按容量加生命周期钩子双保险自动清理。对新手而言,这套模式——缓存 + 体检 + 容量淘汰——也是管理任何昂贵共享资源(数据库连接、HTTP Agent、GPU 上下文)时的通用参考模板。
- 人工智能
- MCP 服务
【免费下载链接】web-search-mcp
A simple, locally hosted Web Search MCP server for use with Local LLMs
相关推荐
终极MCP服务器健康检查自动化:mcp-use监控集成完整指南
终极MCP服务器健康检查自动化:mcp use监控集成完整指南 MCP服务器健康检查是保障服务稳定运行的关键环节,而mcp use提供了一套完整的监控集成方案,
后端MCP 服务MCP ClientsAI Agent人工智能容器健康检查终极指南:如何实时监控与自动修复container30容器状态
容器健康检查终极指南:如何实时监控与自动修复container30容器状态 container30是一款专为Apple silicon优化的轻量级Linux容器
CLI虚拟化容器运行时云原生鸣潮 ok-ww 完整指南:后台自动战斗、刷声骸与一键日常
鸣潮 ok ww 完整指南:后台自动战斗、刷声骸与一键日常 ok ww 是一款面向《鸣潮》PC 端的免费开源自动化工具。它基于图像识别,通过模拟鼠标键盘操作完成
GUI 自动化计算机视觉RPA人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考