1. 从“我写爬虫喂 AI”到“AI 自己爬自己存”的转变
先说清楚这篇要解决什么问题:在 Java + Spring Boot 环境里,用 MCP(Model Context Protocol)把“抓网页”和“写数据库”包装成 AI 能直接调用的工具,让模型自己决定什么时候爬、爬哪个页面、把哪些字段落库。适合谁?适合已经有一个 Spring Boot 后端、手头有 MyBatis-Plus 或 JPA、想让 AI 接管一部分数据采集流程的 Java 开发者。核心检索词就是 Java MCP 爬虫落库,读完你能拿到一套可复制的配置和一次完整的验证动作。
我之前的做法很笨:写个定时任务,Jsoup 抓页面,正则清洗,拼成一段 Prompt 丢给大模型做分析。整个链路里 AI 只负责最后“动嘴”,前面 80% 的脏活全是我在干。问题不在于爬虫难写,而在于每次换一个数据源,我都要改代码、加解析规则、重新部署。AI 明明有能力判断“这个页面里哪个是价格、哪个是标题”,却因为拿不到工具而只能干看着。
MCP 改变的就是这一层。它定义了一套 Client 和 Server 之间的通信标准,把“工具”抽象成带参数 Schema 的可调用单元。你的 Java 应用作为 MCP Client,把工具列表发给大模型;模型根据用户指令返回“我要调 web_fetch,参数是 url=xxx”;Client 执行完把结果回传,模型再决定下一步是继续爬还是调 save_product 落库。整个过程模型是决策者,你的 Java 代码只是执行者。
这里有个容易被忽略的点:多工具共用一套 Key 的配置管理。当你同时接 MCP Server、Cline、Codex 这类工具时,如果每个都单独配一套 Base URL 和 API Key,改一次密钥要改五个地方,迟早出错。所以这篇会把 TaoToken 统一 Key 通道的配置方式一起给出来,让 MCP 服务、编码工具、对话工具共用同一个入口,接入成本压到最低。
下面按“环境准备 → 写工具 → 注册 MCP Server → 配置统一 Key → 验证抓取落库 → 排错”的顺序走,代码都能直接抄。
2. TaoToken 统一 Key 与 MCP 接入前置准备
在写业务代码之前,先把“模型从哪来”这件事定下来。MCP 本身只负责工具调用协议,真正做决策的大模型需要一个兼容 OpenAI 或 Anthropic 接口的通道。TaoToken 在这里扮演的角色就是统一入口:一个 Base URL、一个 API Key,同时给 MCP Client、Cline、Codex、Claude Code 这些工具用。
官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 通道地址是 https://taotoken.net/api ,注意 API 地址后面不加任何查询参数。你需要在控制台里创建一个 Key,控制台入口在 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,Key 列表页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。创建完先复制出来,后面配置里要用。
模型 ID 这块,做 MCP 工具决策建议用带 Function Calling 能力的模型,比如 claude-sonnet 系列或 gpt-4o 系列。具体可用列表以控制台为准,不要凭记忆写。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,遇到参数不确定就去翻。
Java 侧依赖先补齐。MCP Server 用官方 SDK,爬虫用 Jsoup,数据库用 MyBatis-Plus,再加一个 HTTP 客户端用于调模型接口:
<dependency> <groupId>io.modelcontextprotocol</groupId> <artifactId>mcp-server</artifactId> <version>0.4.0</version> </dependency> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.18.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency>数据库表结构很简单,一张 product_data 表,字段包括 id、product_name、price、source_url、create_time。建表语句:
CREATE TABLE product_data ( id BIGINT AUTO_INCREMENT PRIMARY KEY, product_name VARCHAR(255) NOT NULL, price VARCHAR(64), source_url VARCHAR(512), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );对应的实体类和 Mapper 用 MyBatis-Plus 生成即可,这里不展开。重点在于:工具方法里注入 Mapper,MCP Server 启动时把这些方法注册成工具。前置准备做到这一步就够了,接下来进入可复制配置环节。
3. 可复制配置:MCP Server 注册工具与统一 Key 写入
这一节是全文最核心的部分,所有配置片段都能直接复制。先写工具类,把爬取、落库、查询三个能力封装好:
import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.springframework.stereotype.Component; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @Component public class WebTools { @Resource private ProductDataMapper productDataMapper; public String fetchWebPage(String url) { try { Document doc = Jsoup.connect(url) .userAgent("Mozilla/5.0 (compatible; DataCollector/1.0)") .timeout(10000) .get(); return doc.body().text(); } catch (Exception e) { return "爬取失败: " + e.getMessage(); } } public String saveProductData(String productName, String price, String sourceUrl) { ProductData data = new ProductData(); data.setProductName(productName); data.setPrice(price); data.setSourceUrl(sourceUrl); data.setCreateTime(LocalDateTime.now()); productDataMapper.insert(data); return "已保存:" + productName + " 价格:" + price; } public String queryProducts(String keyword) { List<ProductData> list = productDataMapper.selectList( new QueryWrapper<ProductData>().like("product_name", keyword)); if (list.isEmpty()) return "没找到相关产品"; return list.stream() .map(p -> p.getProductName() + " : " + p.getPrice()) .collect(Collectors.joining("\n")); } }然后是 MCP Server 启动类,把三个方法注册成工具,每个工具都要定义 inputSchema,模型才知道参数怎么填:
import io.modelcontextprotocol.server.*; import io.modelcontextprotocol.server.transport.StdioServerTransport; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @Component public class McpServerStarter { @Autowired private WebTools webTools; @PostConstruct public void start() { McpServer server = McpServer.create(); server.addTool(McpTool.newBuilder() .name("web_fetch") .description("爬取指定URL的网页文本内容,返回纯文本") .inputSchema(JsonSchema.builder() .addProperty("url", JsonType.STRING, "要爬取的网页地址") .required("url").build()) .handler(params -> { String url = params.get("url").getAsString(); return ToolCallResult.success(new TextContent(webTools.fetchWebPage(url))); }).build()); server.addTool(McpTool.newBuilder() .name("save_product") .description("将产品信息保存到数据库") .inputSchema(JsonSchema.builder() .addProperty("productName", JsonType.STRING, "产品名称") .addProperty("price", JsonType.STRING, "价格") .addProperty("sourceUrl", JsonType.STRING, "来源URL") .required("productName", "price", "sourceUrl").build()) .handler(params -> { String r = webTools.saveProductData( params.get("productName").getAsString(), params.get("price").getAsString(), params.get("sourceUrl").getAsString()); return ToolCallResult.success(new TextContent(r)); }).build()); server.addTool(McpTool.newBuilder() .name("query_products") .description("按关键字查询已保存的产品数据") .inputSchema(JsonSchema.builder() .addProperty("keyword", JsonType.STRING, "产品名称关键字") .required("keyword").build()) .handler(params -> { String r = webTools.queryProducts(params.get("keyword").getAsString()); return ToolCallResult.success(new TextContent(r)); }).build()); server.start(new StdioServerTransport()); } }接下来是统一 Key 的配置。MCP Client 调模型时需要一个 OpenAI 兼容的配置,写成 JSON 放在项目 resources 目录下,命名 model-config.json:
{ "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的TaoToken密钥", "model": "claude-sonnet-4-20250514", "timeout": 60000 }如果你同时用 Cline 或 Codex,它们的配置也指向同一个 Base URL 和 Key。Codex 的 auth.json 放在 ~/.codex/auth.json:
{ "OPENAI_API_KEY": "sk-你的TaoToken密钥", "OPENAI_BASE_URL": "https://taotoken.net/api" }Cline 的 MCP 配置在 settings.json 里,关键是三件套齐全:Base URL、Key、Model ID。缺任何一个都会在调用时报 401 或 model not found。Claude Code 的接入同理,Base URL 填 https://taotoken.net/api ,Key 用同一个,模型 ID 按控制台列表填。这样 MCP Server、编码工具、对话工具共用一套凭证,改 Key 只改一处。
4. 验证请求:一次抓取任务从触发到入库
配置写完,跑一次完整链路验证。启动 Spring Boot 应用,MCP Server 会通过 STDIO 挂起等待 Client 连接。Java 侧写一个简单的 Client 测试类:
import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.transport.StdioClientTransport; import java.util.Map; public class AiClient { public static void main(String[] args) { McpClient client = McpClient.withTransport( new StdioClientTransport("java -jar your-mcp-server.jar") ).build(); client.listTools().forEach(t -> System.out.println(t.getName() + ": " + t.getDescription())); String pageContent = client.callTool("web_fetch", Map.of("url", "https://example.com/product/123")); System.out.println("抓取结果前200字: " + pageContent.substring(0, 200)); String saveResult = client.callTool("save_product", Map.of( "productName", "示例产品A", "price", "199.00", "sourceUrl", "https://example.com/product/123" )); System.out.println(saveResult); String queryResult = client.callTool("query_products", Map.of("keyword", "示例产品")); System.out.println("查询结果: " + queryResult); } }运行后你应该看到三段输出:工具列表包含 web_fetch、save_product、query_products;抓取结果返回页面正文文本;保存返回“已保存:示例产品A 价格:199.00”;查询返回“示例产品A : 199.00”。去数据库 select 一下,create_time 字段有值,说明落库成功。
真正让 AI 自主决策的版本,是把工具列表转成 Function Calling 定义发给模型。用户说“去 example.com/product/123 把价格爬下来存库”,模型返回 tool_calls 数组,里面是 web_fetch 和 save_product 的调用参数。你的 Client 依次执行,把结果回传,模型最后回复“已完成,价格 199.00 已入库”。这一步的 HTTP 请求用 OkHttp 发到 https://taotoken.net/api/v1/chat/completions ,Header 里带 Authorization: Bearer sk-xxx,body 里 tools 字段填 MCP 工具转出来的 JSON Schema。
验证成功的标志有三个:数据库多了一行记录;日志里能看到模型返回的 tool_calls;再次 query_products 能查到刚存的数据。如果只做到前两步但数据库没数据,大概率是 save_product 的 handler 里事务没提交,或者 Mapper 注入失败。
5. 本篇常见错误排查:401、local proxy failed 与 reading choices
排错这块我踩过的坑比较集中,按报错信息对照着查最快。
401 Unauthorized:九成是 Key 没配对。检查 model-config.json 里的 apiKey 是否以 sk- 开头,Base URL 是否是 https://taotoken.net/api 且没有多余斜杠。如果你在 Codex 的 auth.json 里写了 OPENAI_BASE_URL 但 Key 还是旧的,也会 401。统一 Key 的意义就在这里:改一处,所有工具生效。另外注意 Key 有没有过期或被删除,去控制台确认。
local proxy failed / connection refused:这个报错通常出现在 MCP Client 用 STDIO 启动 Server 时。原因是启动命令路径不对,或者 jar 包没打全。检查 StdioClientTransport 里的命令是不是java -jar加绝对路径。如果 Server 启动时抛了异常直接退出,Client 就会报连接失败。先把 Server 单独跑一遍,确认能正常挂起再让 Client 连。
reading choices 报错 / choices 字段为空:这是模型返回体解析失败。常见原因是模型 ID 写错了,或者请求体里 tools 字段格式不符合 OpenAI 规范。检查 model 字段是否和控制台列表一致,tools 里每个 function 的 parameters 必须是合法的 JSON Schema。如果返回体里没有 choices,先打印原始响应看看是不是返回了 error 对象。
OAuth 相关报错:如果你用的是 Claude Code 或某些需要 OAuth 的工具,报 OAuth token invalid 时,说明它没走 API Key 而是走了 OAuth 流程。这时候要在工具配置里显式指定用 API Key 模式,Base URL 填 https://taotoken.net/api ,不要让它去读本地的 OAuth 缓存。
工具被调用但参数为空:模型没按 Schema 填参数。在 Prompt 里加一句“调用工具时必须填写所有 required 字段”,或者在工具 description 里把参数说明写得更明确。实测下来,description 写得越具体,模型填参准确率越高。
数据库写入成功但查询不到:检查 MyBatis-Plus 的驼峰映射是否开启,product_name 字段和实体类属性名是否对应。如果用了 @TableField 注解但值写错,插入会静默失败。
6. 语义一致 CTA:把统一 Key 用在你的 MCP 工作流里
走到这里,你已经有一套能跑的 Java MCP 爬虫落库链路了。接下来最省事的做法是把统一 Key 铺到所有工具上,避免每个工具单独配一套凭证。需要创建或管理 Key 就去 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,接入参数不确定就翻 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。
如果你主要用 MCP 做工具调用和模型对话验证,模型对话入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite ,可以先把工具 Schema 贴进去试一轮,确认模型能正确选工具再写进 Java 代码。如果你打算长期跑编码 Agent 或让 MCP 接管更多自动化任务,Coding Plan 在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,按用量规划比临时充更稳。
Claude Code 接入的完整配置在 https://taotoken.net/claude-code?utm_source=taotoken_aicg_blog_end&utm_content=claude-code&utm_campaign=rewrite ,Anthropic 兼容通道说明在 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude-code-anthropic&utm_campaign=rewrite 。控制台总入口 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 可以看用量和余额。
最后给一个实用技巧:把 MCP Server 的启动命令写成一个 shell 脚本,里面 export 好 TAOTOKEN_API_KEY 环境变量,Java 代码里用 System.getenv 读取,这样 Key 不会硬编码进 jar 包。脚本内容大概是这样:
#!/bin/bash export TAOTOKEN_API_KEY="sk-你的密钥" export TAOTOKEN_BASE_URL="https://taotoken.net/api" java -jar your-mcp-server.jarClient 的 StdioClientTransport 里改成调用这个脚本,Key 就统一从环境变量走。换 Key 只改脚本一处,MCP Server、Cline、Codex 全部生效。这套跑通之后,你只需要对 AI 说“去把某页面价格爬下来存库”,剩下的它自己完成。