基于MCP协议构建智能多模式网页抓取服务器,赋能AI助手生态
1. 项目概述与核心价值
最近在折腾AI助手生态时,我发现一个痛点:当我想让Claude、Cursor这类AI工具去帮我抓取和分析网页内容时,过程总是磕磕绊绊。要么是网站有反爬机制,普通的HTTP请求直接吃个403;要么是页面内容依赖JavaScript动态渲染,拿回来的HTML一片空白;再要么就是文章太长,超出了AI模型的上下文限制,导致信息不完整。为了解决这些问题,我花了不少时间研究,最终基于Model Context Protocol(MCP)规范,动手实现了一个功能强大的多模式抓取服务器——mult-fetch-mcp-server。
简单来说,这是一个桥接AI助手与外部网络世界的“智能爬虫引擎”。它不是一个独立的命令行工具,而是一个标准的MCP服务器,能够被Claude Desktop、Cursor等支持MCP协议的AI客户端直接调用。这意味着你可以在和AI对话时,直接让它“去网上查一下某个网页的内容”,而AI背后调用的就是这个服务器。它最核心的价值在于“智能”与“健壮”:它能根据目标网站的情况,自动在轻量级的Node.js原生请求和完整的浏览器自动化(Puppeteer)之间切换,确保尽可能高地获取到有效内容;同时,它内置了智能内容提取、大内容分块管理、代理支持等机制,让AI处理网络信息变得既强大又可靠。
2. 核心架构与设计思路拆解
2.1 为什么选择MCP协议?
在开始设计之前,我首先评估了各种让AI与外部工具交互的方案。最终选择基于Model Context Protocol(MCP)来构建,主要基于以下几点考量:
标准化与生态兼容性:MCP是由Anthropic主导的开放协议,旨在为AI模型提供一个标准化的方式来使用外部工具和资源。像Claude Desktop、Cursor这类主流AI应用都已原生支持MCP。这意味着我的服务器一旦开发完成,就能无缝集成到这些生态中,用户无需安装额外的插件或进行复杂的配置。
进程隔离与安全性:MCP采用客户端-服务器架构,通过Stdio(标准输入输出)或SSE进行通信。服务器作为一个独立的子进程运行,与AI客户端进程隔离。这种设计带来了两个好处:一是安全性,即使服务器崩溃也不会拖垮主AI应用;二是灵活性,服务器可以用任何语言编写(我选择了TypeScript),只要遵循协议即可。
协议驱动的清晰边界:MCP协议明确定义了Tools(工具)、Resources(资源)、Prompts(提示词模板)等核心概念。这为我的抓取服务器提供了清晰的功能边界。例如,每个抓取方法(fetch_html,fetch_json等)都可以定义为一个标准的Tool,AI客户端通过JSON-RPC over Stdio来调用它们,交互模式非常清晰。
2.2 多模式抓取引擎的设计
网页抓取的场景复杂多变,单一方法很难通吃。我的设计核心是一个具备“智能降级”能力的双引擎架构。
1. Node.js Fetch引擎(默认/优先)这是第一道防线,使用Node.js原生的http/https模块或类似node-fetch的库发起请求。它的优势是速度极快、资源消耗极低,适合绝大多数静态内容网站或API接口。我会为它配置完善的请求头(模拟真实浏览器)、超时控制、重定向跟随和代理支持。
2. 浏览器自动化引擎(Puppeteer)这是攻坚利器,用于对付那些“难啃的骨头”。当目标网站使用了客户端渲染(如React、Vue构建的单页应用)、设置了严格的反爬策略(如Cloudflare的5秒盾),或者内容需要通过交互(如点击“加载更多”)才能获取时,就需要启动一个真实的Chromium浏览器实例。通过Puppeteer,我们可以完整地加载页面、执行JavaScript、等待特定元素出现,甚至模拟滚动,从而拿到最终渲染后的HTML。
智能切换逻辑:服务器不是让用户手动选择模式,而是内置了自动判断逻辑。默认会先尝试Node.js Fetch。如果请求失败,特别是返回了403 Forbidden、429 Too Many Requests等典型反爬状态码,或者返回的HTML中缺少预期内容(可能是JS渲染),系统会自动切换到浏览器模式重试。用户也可以通过参数(useBrowser: true)强制指定模式。
2.3 内容处理流水线
拿到原始HTML或JSON数据只是第一步,如何将其处理成对AI友好的格式同样关键。我设计了一个模块化的内容处理流水线:
1. 智能内容提取(Readability)很多新闻、博客页面充斥着导航栏、侧边栏、广告、评论等“噪音”。直接把这些塞给AI,会浪费宝贵的上下文窗口,也干扰AI对核心内容的理解。我集成了Mozilla的Readability算法库,它能像浏览器的“阅读模式”一样,智能地识别并提取出文章的主体内容,过滤掉无关元素。
2. 格式转换根据AI的需求,提供多种输出格式:
html: 原始或经过处理的HTML。plaintext: 将HTML标签全部剥离,得到纯文本。markdown: 尝试将HTML转换为结构更清晰的Markdown格式,便于AI阅读和引用。json: 直接解析JSON数据。
3. 大内容分块管理这是解决AI上下文限制的核心。GPT-4、Claude等模型都有token上限。一个长篇文章可能轻松超过这个限制。我的服务器会在内容超过预设大小(如50KB)时,自动将其按字符或段落分割成多个连续的“块”(Chunk)。每个块都会携带元数据(如chunkId,totalSize,startCursor,endCursor),AI在收到第一块后,如果判断需要更多内容,可以根据这些元数据精准地请求后续的特定块,实现对大文档的“流式”读取。
2.4 健壮性增强设计
一个要7x24小时服务于AI的工具,稳定性至关重要。
全面的错误处理:对网络超时、DNS解析失败、证书错误、页面崩溃等各种异常情况都进行了捕获和分类。返回给AI的错误信息是结构化的,不仅包含错误原因,还会给出建议(例如“建议启用浏览器模式重试”)。
连接管理与资源回收:浏览器实例(Puppeteer)的创建和销毁成本很高。我设计了连接池和懒加载机制,避免频繁启停。同时,通过closeBrowser参数,允许AI在完成一系列相关任务后,主动通知服务器关闭浏览器以释放资源。
可观测性与调试:通过debug参数可以开启详细日志,所有日志都输出到标准错误流(stderr)并同时写入一个独立的日志文件,方便在出现问题时进行排查。日志模块化,前缀清晰(如[BROWSER-FETCH]),能快速定位问题环节。
3. 核心模块与实操要点解析
3.1 项目结构深度解读
一个清晰的项目结构是维护和扩展的基础。mult-fetch-mcp-server采用了典型的分层和模块化设计。
fetch-mcp/ ├── src/ │ ├── lib/ │ │ ├── fetchers/ # 抓取器实现层 │ │ │ ├── browser/ # 浏览器抓取引擎 │ │ │ │ ├── BrowserFetcher.ts # 核心抓取类,封装Puppeteer操作 │ │ │ │ ├── BrowserInstance.ts # 浏览器实例的生命周期管理(单例/池化) │ │ │ │ └── PageOperations.ts # 页面操作(跳转、等待、滚动、截图) │ │ │ ├── node/ # Node.js抓取引擎 │ │ │ └── common/ # 共享逻辑(请求头构造、代理配置、响应处理) │ │ ├── utils/ # 工具层 │ │ │ ├── ChunkManager.ts # 分块算法,决定如何切割内容 │ │ │ ├── ContentProcessor.ts # 格式转换(HTML->Text/Markdown) │ │ │ ├── ContentExtractor.ts # 集成Readability进行智能提取 │ │ │ ├── ContentSizeManager.ts # 内容大小计算与限制判断 │ │ │ └── ErrorHandler.ts # 统一错误分类与格式化 │ │ ├── server/ # MCP协议适配层 │ │ │ ├── index.ts # 服务器启动入口 │ │ │ ├── browser.ts # 浏览器相关MCP资源/工具注册 │ │ │ ├── fetcher.ts # 抓取工具的实现与暴露 │ │ │ ├── tools.ts # MCP Tools的注册与管理 │ │ │ ├── resources.ts # MCP Resources的定义(如访问日志文件) │ │ │ ├── prompts.ts # 预定义的提示词模板 │ │ │ └── types.ts # 服务器内部使用的类型定义 │ │ ├── i18n/ # 国际化(中英文文案) │ │ └── types.ts # 项目全局共享类型 │ ├── client.ts # 一个用于测试的独立MCP客户端 │ └── mcp-server.ts # MCP服务器主逻辑,组合各模块 ├── index.ts # 编译后的入口文件 └── tests/ # 测试套件,针对各模块和集成场景设计要点:
- 依赖倒置:
fetchers目录下的browser和node模块都实现同一个IFetcher接口。上层代码(如fetcher.ts)只依赖这个接口,从而轻松切换或扩展新的抓取方式。 - 关注点分离:抓取(
fetchers)、处理(utils)、协议(server)各司其职。utils里的模块都是纯函数,便于测试。 - 配置化:所有关键参数(超时、分块大小、代理等)都通过函数参数或环境变量注入,避免硬编码。
3.2 MCP服务器实现详解
MCP服务器的核心是实现协议规定的几个端点(initialize,tools/list,tools/call,resources/list,resources/read,prompts/list,prompts/get)。我使用官方的@modelcontextprotocol/sdk来简化这部分工作。
初始化(initialize):当AI客户端连接时,服务器会收到initialize请求,并回复自己的元数据(名称、版本、能力列表)。这里我声明服务器支持tools和prompts。
工具注册(tools/list & tools/call):这是核心。在tools.ts中,我定义了五个工具:
fetch_html: 输入URL,输出HTML字符串。fetch_json: 输入URL,输出解析后的JSON对象。fetch_txt: 输入文本文件的URL,输出内容。fetch_markdown: 输入URL,输出转换后的Markdown。fetch_plaintext: 输入URL,输出剥离标签后的纯文本。
每个工具的定义都包含严格的输入模式(JSON Schema),例如url字段必须是字符串格式的URI。当AI调用tools/call时,服务器会根据工具名路由到fetcher.ts中对应的处理函数。
提示词模板(prompts):这是一个提升用户体验的功能。我预定义了如fetch-website这样的提示词模板。当AI用户想抓取网站但不知如何组织指令时,可以直接获取这个模板,模板里已经写好了结构化的请求示例和参数说明,AI可以基于此生成更准确的调用。
资源(resources):当前版本主要用资源机制来提供调试日志的访问(如file:///logs/debug),未来可以扩展为提供服务器状态、缓存内容等。
通信流程:整个通信基于JSON-RPC 2.0 over Stdio。客户端(AI)启动服务器子进程,然后通过stdin发送JSON-RPC请求,服务器通过stdout返回响应。所有调试日志则定向到stderr,避免污染通信信道。
3.3 双模式抓取器实现细节
Node.js抓取器(node-fetcher): 它的实现相对直接,但细节决定成败。
- 请求头:我会设置一个看起来像主流浏览器的
User-Agent,并带上Accept、Accept-Language等头,减少被简单拦截的风险。 - 代理支持:优先读取请求参数中的
proxy,其次检查环境变量(HTTP_PROXY,HTTPS_PROXY),最后尝试读取系统代理设置。代理格式支持http://和socks://。 - 重试与超时:内置简单的重试逻辑(针对网络波动),并设置合理的连接超时和响应超时。
- 编码处理:自动根据HTTP头或HTML元标签检测页面编码(如
utf-8,gb2312),并正确解码,避免乱码。
浏览器抓取器(browser-fetcher): 这是复杂性最高的模块,关键在于平衡功能与资源消耗。
- 浏览器实例管理:我实现了一个
BrowserInstance类,采用懒加载的单例模式。只有在第一次需要浏览器模式时,才会启动Puppeteer。浏览器启动时,会传递代理配置(Puppeteer的--proxy-server参数)。 - 页面操作:
PageOperations类封装了常用操作序列:goto:导航到目标URL,并处理弹窗(如cookie同意框)。waitForSelector:等待关键元素(如文章主体article或#content)出现,确保内容已渲染。scrollToBottom:对于无限滚动的页面,模拟滚动到底部以触发加载。getContent:获取最终页面的document.documentElement.outerHTML。
- 性能优化:启动浏览器时,会传递
--no-sandbox、--disable-setuid-sandbox(仅在特定环境需要)、--disable-dev-shm-usage等参数来提升稳定性。还会禁用图片、字体等非必要资源的加载以加速。 - 错误恢复:如果页面崩溃或长时间无响应,会尝试刷新页面或重启浏览器实例。
模式切换决策点: 切换逻辑在顶层的fetcher.ts中。伪代码如下:
async function fetchWithStrategy(url, options) { let result; // 1. 如果用户强制指定了模式,则直接使用 if (options.useBrowser) return browserFetcher.fetch(url, options); if (options.useNodeFetch) return nodeFetcher.fetch(url, options); // 2. 默认先尝试Node模式 try { result = await nodeFetcher.fetch(url, options); // 检查结果是否“有效”:非错误状态码,且HTML包含一定量的文本内容 if (isValidContent(result)) { return result; } // 内容无效(可能是反爬返回的验证页面),触发降级 throw new ContentInvalidError(); } catch (error) { // 3. 如果是特定错误(403, 429, 内容无效)且允许自动检测,则降级 if (options.autoDetectMode && shouldFallbackToBrowser(error)) { console.warn(`[FETCHER] Falling back to browser mode due to: ${error.message}`); return browserFetcher.fetch(url, options); } // 否则,抛出原始错误 throw error; } }3.4 内容处理与分块算法
智能内容提取: 集成Readability库后,提取过程很简单:将HTML文档和URL传入,库会返回一个包含title,content,textContent,length,excerpt等属性的对象。关键在于“后处理”:
- 元数据保留:
includeMetadata参数决定是否将标题、作者等信息作为前言附加到最终内容中。 - 回退机制:
fallbackToOriginal参数为true时,如果Readability解析失败(返回null或内容过短),则自动退回使用原始HTML或经过简单清理的HTML,保证总有内容返回。
分块管理(ChunkManager): 这是应对大文本的核心。我的策略是按字符数分块,但尽量保证块在段落边界处切割,避免把一个句子或单词拆散。
- 计算与判断:
ContentSizeManager计算内容字符串的字节大小。如果超过contentSizeLimit(默认50KB),则触发分块。 - 生成块ID:为本次抓取会话生成一个唯一的
chunkId(例如基于URL和时间的哈希值)。 - 分块算法:将文本按字符遍历。预设一个“软”块大小(如
contentSizeLimit)。当累计字符数接近软限制时,向前寻找最近的段落分隔符(如\n\n,<p>结束标签)。如果找不到,再寻找句子分隔符(.,!,?后跟空格)。以此确定切割点。 - 构建响应:返回的不是完整内容,而是一个结构体,包含第一块文本和分块元数据。
{ "content": "[第一块文本,约50KB]", "metadata": { "chunked": true, "chunkId": "abc123", "totalSize": 215000, "currentChunk": 0, "totalChunks": 5, "startCursor": 0, "endCursor": 50000 } }- 后续请求:AI如果需要更多,可以在下次调用时带上
chunkId和startCursor: 50000参数。ChunkManager会根据chunkId找到对应的原始内容(实践中可能需要一个临时缓存或持久化存储),然后从startCursor位置开始,生成下一块。
注意:分块功能主要服务于AI客户端的流式读取。项目自带的
client.js演示工具中的--all-chunks参数,是为了演示而一次性获取所有块,在实际AI交互中,AI会根据自己的上下文窗口和需求,主动、迭代地请求后续块。
4. 实战:从安装配置到高级使用
4.1 环境准备与安装
首先,确保你的系统已安装Node.js(建议18.x或更高版本)和包管理器pnpm(也可以用npm或yarn,但项目脚本默认用pnpm)。
方法一:通过Smithery一键安装(推荐给Claude Desktop用户)这是最省心的方式,特别适合只想在Claude里用这个功能的用户。 Smithery 是一个MCP服务器注册中心。
npx -y @smithery/cli install @lmcc-dev/mult-fetch-mcp-server --client claude这条命令会自动下载服务器,并帮你配置好Claude Desktop的配置文件。完成后重启Claude即可。
方法二:本地克隆与开发适合开发者或想定制功能的用户。
git clone https://github.com/lmcc-dev/mult-fetch-mcp-server.git cd mult-fetch-mcp-server pnpm install # 安装依赖 pnpm run build # 编译TypeScript代码到dist目录方法三:全局安装如果你希望通过命令行直接测试服务器,可以全局安装。
pnpm add -g @lmcc-dev/mult-fetch-mcp-server # 之后可以直接运行 mult-fetch-mcp-server # 或用npx临时运行 npx @lmcc-dev/mult-fetch-mcp-server4.2 配置AI客户端(以Claude Desktop为例)
要让AI客户端知道这个服务器的存在,需要修改其MCP服务器配置文件。
配置文件位置:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
如果文件不存在,就创建一个。
配置内容(推荐使用npx方式): 这是最灵活的方式,无需关心服务器具体的安装路径。
{ "mcpServers": { "mult-fetch-mcp-server": { "command": "npx", "args": ["@lmcc-dev/mult-fetch-mcp-server"], "env": { "MCP_LANG": "zh" // 可选:设置服务器语言为中文,en为英文 } } } }command: 执行命令,这里用npx。args: 命令参数,npx会去自动查找并运行指定的npm包。env: 传递给服务器的环境变量。MCP_LANG控制服务器返回消息的语言。
配置内容(指定绝对路径方式): 如果你全局安装了,或者想指向一个特定的本地版本,可以这样配置。
{ "mcpServers": { "mult-fetch-mcp-server": { "command": "/usr/local/bin/node", // 你的Node.js路径 "args": ["/path/to/global/node_modules/@lmcc-dev/mult-fetch-mcp-server/dist/index.js"], "env": { "MCP_LANG": "en" } } } }保存配置文件后,必须完全重启Claude Desktop应用(不是关闭窗口,而是从任务栏或Dock退出再重新启动),配置才会生效。
4.3 基础使用:在AI对话中抓取网页
配置成功后,打开Claude,你就可以像使用内置功能一样让它抓取网页了。
直接指令: 你可以直接对Claude说:“请用fetch_html工具获取 https://example.com 的内容。” Claude会识别出可用的工具并调用它。
使用提示词模板(更推荐): 你可以说:“请使用fetch-website提示词模板,帮我获取这个URL的内容。” Claude会调用prompts/get获取预定义的模板,然后基于模板生成一个结构更完善的请求。这通常更可靠,因为模板里定义了必填参数和示例。
一个典型的交互过程:
- 你:“Claude,请帮我获取 https://news.example.com/article/123 这篇文章的主要内容,最好是纯文本格式。”
- Claude:(内部调用
fetch_plaintext工具,参数为{url: “https://news.example.com/article/123”, extractContent: true}) - mult-fetch-mcp-server:收到请求。先尝试Node.js模式抓取,发现网站有反爬,返回403。由于
autoDetectMode默认为true,自动切换至浏览器模式。启动Puppeteer,加载页面,等待文章主体渲染,然后提取主要内容,转换成纯文本。 - Claude:收到服务器返回的纯文本内容,然后结合你的问题进行分析和总结。
4.4 高级参数与场景实战
服务器工具支持丰富的参数,让你能精细控制抓取行为。
场景一:抓取需要登录的页面(配合浏览器Cookie)某些页面需要登录后才能查看。你可以先手动在浏览器中登录目标网站,然后使用浏览器模式的saveCookies功能。
// 第一次请求,使用浏览器模式并保存Cookies { "url": "https://example.com/dashboard", "useBrowser": true, "saveCookies": true } // 服务器会在后台保存该域名的Cookies。 // 后续请求同一域名的页面时,即使不指定`useBrowser`,如果降级到浏览器模式,也会自动携带这些Cookies。场景二:抓取JavaScript重度渲染的SPA应用对于Vue/React构建的单页应用,必须使用浏览器模式,并可能需要等待特定元素出现。
{ "url": "https://app.example.com/data-view", "useBrowser": true, "waitForSelector": ".data-table-loaded", // 等待数据表格加载完成的CSS选择器 "waitForTimeout": 10000 // 等待10秒 }场景三:处理超长文章或文档一篇很长的技术文档或报告,直接抓取可能会超出AI上下文。
{ "url": "https://example.com/very-long-report.pdf?view=html", "extractContent": true, "enableContentSplitting": true, "contentSizeLimit": 30000 // 将块大小设为30KB,更精细地控制 } // 服务器返回第一块内容和元数据。 // AI可以分析元数据,如果判断需要,再发起第二次请求: { "url": "https://example.com/very-long-report.pdf?view=html", "chunkId": "上一次返回的chunkId", "startCursor": 30000 // 请求第二块的起始位置 }场景四:通过代理访问特定网络环境下的资源
{ "url": "https://internal-wiki.company.com", "proxy": "http://proxy.company.com:8080", "useBrowser": true // 如果内部网站有复杂认证,可能需要浏览器模式 }或者通过环境变量设置全局代理:
# 启动Claude Desktop前,在终端中设置 export HTTPS_PROXY=http://proxy.company.com:8080 # 然后启动Claude Desktop,它继承的环境变量会被MCP服务器子进程读取。场景五:调试抓取失败的问题当抓取失败时,开启debug模式查看详细日志。
{ "url": "https://problematic-site.com", "debug": true }服务器会将详细的运行日志输出到stderr,Claude Desktop通常会在后台记录这些日志。你也可以通过读取资源的方式获取日志文件内容(如果服务器配置了该资源)。
4.5 使用自带的客户端进行测试
项目自带一个命令行客户端(client.js),非常适合在集成到AI客户端前进行功能测试和调试。
# 进入项目目录 cd mult-fetch-mcp-server # 测试抓取HTML pnpm run client fetch_html '{"url": "https://httpbin.org/html", "debug": true}' # 测试抓取JSON pnpm run client fetch_json '{"url": "https://api.github.com/users/github", "debug": true}' # 测试浏览器模式和内容提取 pnpm run client fetch_plaintext '{"url": "https://news.ycombinator.com", "extractContent": true, "useBrowser": true, "debug": true}' # 测试分块功能(演示一次性获取所有块) pnpm run client fetch_html '{"url": "https://example.com/long-article", "debug": true}' --all-chunks --max-chunks 5这个客户端会模拟AI客户端的行为,启动MCP服务器进程并调用指定工具,将结果打印到控制台。--debug参数会让你看到服务器端的所有日志,是排查问题的利器。
5. 常见问题、排查技巧与优化实践
5.1 安装与配置问题
Q1: 配置Claude Desktop后,重启没有看到新的工具?
- 检查配置文件路径和格式:确保JSON文件格式正确,没有尾随逗号。可以使用
jsonlint在线工具验证。 - 检查Claude Desktop版本:确保你的Claude Desktop版本支持MCP。较旧的版本可能不支持。
- 查看Claude日志:Claude Desktop通常会在其应用数据目录下生成日志文件。在macOS上,可以尝试查看
~/Library/Logs/Claude/下的文件,寻找MCP服务器加载相关的错误信息。 - 手动测试服务器:在终端运行
npx @lmcc-dev/mult-fetch-mcp-server,如果服务器能正常启动并等待输入(不立即退出),说明服务器本身没问题。如果报错,可能是Node.js版本或依赖问题。
Q2: 运行时报错“Cannot find module ...”
- 这通常是依赖没有安装好。在项目根目录重新运行
pnpm install。如果使用全局安装,确保全局node_modules的路径在系统的PATH环境变量中。
5.2 抓取失败问题排查
抓取失败是常态,需要系统性地排查。
第一步:开启Debug模式这是最重要的步骤。在请求参数中加入"debug": true。仔细阅读服务器返回的错误信息和stderr中的日志。日志前缀会告诉你问题发生在哪个环节([NODE-FETCH]、[BROWSER-FETCH]、[CONTENT-EXTRACTOR])。
第二步:分析错误类型
- 网络错误(ETIMEDOUT, ENOTFOUND):检查URL是否正确,网络是否通畅,代理配置是否有效。
- HTTP状态码错误(403, 429, 500):
403 Forbidden:几乎可以确定是触发了反爬。解决方案:尝试添加useBrowser: true参数。如果已在使用浏览器模式,尝试增加waitForTimeout,或检查是否需要处理cookie、验证码。429 Too Many Requests:请求过于频繁。解决方案:在参数中设置noDelay: false(默认),服务器会在请求间添加随机延迟。也可以考虑使用代理池轮换IP。5xx服务器错误:目标网站问题,稍后重试。
- 内容为空或不符合预期:
- 检查返回的HTML是否只是一个框架,主要内容由JS加载。解决方案:必须使用
useBrowser: true。 - 检查
extractContent是否误杀了主要内容。解决方案:尝试设置extractContent: false获取原始HTML对比,或设置fallbackToOriginal: true。
- 检查返回的HTML是否只是一个框架,主要内容由JS加载。解决方案:必须使用
第三步:使用浏览器开发者工具辅助对于复杂网站,手动用Chrome打开目标页面,打开开发者工具(F12):
- Network标签:查看页面加载了哪些资源,主文档的请求头是怎样的,有没有特殊的
Cookie或Authorization头?可以尝试在请求参数中的headers字段里模拟这些头。 - Console标签:是否有JS错误?页面是否依赖某些全局变量?
- 手动测试Readability:在Console中运行
document.documentElement.outerHTML复制HTML,然后写个小脚本用Readability库解析,看是否能正确提取内容。
5.3 性能与资源优化
问题:浏览器模式启动慢,内存占用高。
- 启用浏览器复用:确保不要每次请求都开关浏览器。服务器设计为单例复用。在一段对话中,首次使用浏览器模式会有启动开销,后续请求会快很多。对话结束后,AI客户端应发送一个
closeBrowser: true的请求来清理资源(虽然当前版本依赖超时自动关闭,但显式关闭更佳)。 - 优化浏览器启动参数:在
BrowserInstance.ts中,可以调整Puppeteer的launch参数。例如:const browser = await puppeteer.launch({ headless: 'new', // 使用新的Headless模式,性能更好 args: [ '--disable-gpu', '--disable-dev-shm-usage', // 解决某些Linux环境下的内存问题 '--no-sandbox', // 仅在容器或受信任环境使用 '--disable-setuid-sandbox', '--disable-features=site-per-process', // 可能提升稳定性 ], }); - 禁用非必要资源:在
PageOperations.ts的goto方法中,可以通过page.setRequestInterception(true)来拦截并阻止图片、样式表、字体等资源的加载,大幅提升页面加载速度,但可能影响页面布局。
问题:抓取大量页面时被屏蔽。
- 使用代理池:配置
proxy参数,并使用多个代理服务器轮换。你需要自己维护一个代理列表,并在每次请求时随机选择一个。这超出了当前服务器的范围,但你可以写一个外层包装器来实现。 - 降低请求频率:确保
noDelay: false(默认),服务器内置的延迟机制会有所帮助。对于大规模抓取,需要设计更复杂的速率限制策略。 - 模拟更真实的浏览器指纹:Puppeteer可以设置
userAgent、viewport、plugins等。更高级的库如puppeteer-extra配合stealth-plugin可以更好地隐藏自动化特征。
5.4 内容处理相关技巧
提高内容提取准确率:Readability库并非万能。对于结构特殊的网站,提取效果可能不好。
- 自定义选择器回退:可以扩展
ContentExtractor.ts,在Readability失败后,尝试用一些常见的文章内容选择器(如article,.post-content,#main)来手动提取。 - 提供提示:如果AI知道目标网站的结构,可以在请求时通过
headers传递一个自定义的X-Content-Selector头,告诉提取器使用特定的CSS选择器。
分块策略调优: 默认按50KB分块可能不适合所有场景。
- 对于代码仓库或JSON数据:按行分块可能比按字符分块更友好。可以考虑根据
Content-Type或文件扩展名选择不同的分块策略。 - 保留上下文:在分块时,可以在每个块的开头附加上一小段前文的摘要或关键句,帮助AI在读取后续块时保持连贯性。这需要更复杂的上下文管理逻辑。
5.5 安全与合规须知
重要提醒:此工具为技术演示与开发便利而创建,使用时必须遵守法律法规和目标网站的robots.txt协议。
- 尊重
robots.txt:在实现生产级爬虫时,应首先检查目标网站的robots.txt,并尊重Disallow规则。当前服务器未内置此功能,使用者应自行判断。 - 控制请求速率:避免对单一网站发起高频请求,以免对其服务器造成压力,导致IP被封锁。
- 数据使用:抓取到的内容应仅用于个人学习、分析或符合网站条款的用途。未经许可,不得用于商业用途或大规模复制传播。
- 隐私保护:不要抓取包含个人隐私信息的页面。
最后,这个项目的魅力在于它将复杂的网络抓取、内容处理和协议通信封装成了一个AI可以轻松调用的“技能”。在实际使用中,你会发现AI不仅能获取信息,还能结合上下文进行深度分析和创作,其能力边界被极大地扩展了。我自己在开发过程中也踩了不少坑,比如浏览器实例的内存泄漏、分块算法的边界条件处理、MCP协议版本的兼容性等。希望这篇详细的解读和指南,能帮助你更好地理解、使用甚至参与到这个项目的改进中来。如果在使用中遇到任何问题,或者有新的功能想法,非常欢迎在项目的GitHub仓库提交Issue或讨论。
