[Langchain网页抓取与天气查询实战]MCP篇
·
MCP (Model Context Protocol) 网页抓取与天气查询实战
📁 项目概述
| 项目 | 内容 |
|---|---|
| 功能 | MCP 服务端提供工具,客户端由 LLM 自动判断调用哪个工具 |
| 工具 | fetch_webpage (网页抓取)、get_weather (天气查询) |
| 通信协议 | SSE (Server-Sent Events) |
🏗️ 架构设计
┌─────────────────────────────────────────────────────────────┐ │ 用户输入 │ │ "上海天气怎么样?" │ └─────────────────────────┬───────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ MCP Web 客户端 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ LLM (qwen-plus) │ │ │ │ tool_choice="auto" │ │ │ │ 自动判断是否需要调用工具 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────┴───────────┐ │ │ ▼ ▼ │ │ [无需工具,直接回答] [需要工具,调用 MCP] │ └─────────────────────────┬───────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ MCP Web 服务端 │ │ ┌──────────────┐ ┌──────────────────┐ │ │ │ fetch_webpage│ │ get_weather │ │ │ │ 网页抓取工具 │ │ 天气查询工具 │ │ │ └──────────────┘ └──────────────────┘ │ │ │ │ │ │ ▼ ▼ │ │ httpx.get() OpenWeather API │ │ 任意网页 2d49a9ae... │ └─────────────────────────────────────────────────────────────┘
📂 文件清单
| 文件 | 说明 |
|---|---|
McpServer.py |
MCP 服务端,提供两个工具 |
McpClient.py |
MCP 客户端,LLM 自动路由 |
🔧 核心实现
1. MCP 服务端 (McpServer.py)
1.1 极简 MCP 服务类
class MCPWebServer:
"""极简版 MCP 服务类,支持 SSE 传输协议"""
def __init__(self, name: str, host: str, port: int):
self.name = name
self.host = host
self.port = port
self._tools = {} # 存储注册的工具函数
def tool(self):
"""实现 @mcp.tool() 装饰器"""
def decorator(func):
self._tools[func.__name__] = func
return func
return decorator
def run(self, transport: str):
"""启动 MCP 服务"""
logger.info(f"启动 MCP Web 服务器,监听 http://{self.host}:{self.port}/sse")
self._keep_alive()
设计要点:
-
无第三方依赖,纯原生实现
-
适配 Python 3.13+
-
@mcp.tool()装饰器注册工具函数 -
_tools字典存储所有已注册的工具
1.2 工具1: 网页抓取 (fetch_webpage)
@mcp.tool()
def fetch_webpage(url: str, max_length: int = 5000) -> str:
"""
抓取指定网页的文本内容。
参数:
url: 网页的完整 URL 地址
max_length: 最大抓取字符数,默认 5000
返回: 网页的文本内容(已去除 HTML 标签)
"""
headers = {"User-Agent": "Mozilla/5.0..."}
resp = httpx.get(url, headers=headers, timeout=15)
# HTML 标签去除
html = re.sub(r'<script[^>]*>.*?</script>', '', html, flags=re.DOTALL)
html = re.sub(r'<style[^>]*>.*?</style>', '', html, flags=re.DOTALL)
text = re.sub(r'<[^>]+>', '', html)
# 截断超长内容
if len(text) > max_length:
text = text[:max_length] + "...[内容已截断]"
return json.dumps({"url": url, "content": text, "status": "success"})
功能特点:
-
✅ 自动去除
<script>和<style>标签 -
✅ 移除所有 HTML 标签
-
✅ 清理多余空白字符
-
✅ 自动截断超长内容
-
❌ 无法处理 JavaScript 动态渲染的页面
1.3 工具2: 天气查询 (get_weather)
@mcp.tool()
def get_weather(city: str) -> str:
"""
查询指定城市的即时天气信息。
参数 city: 城市英文名,如 Beijing
返回: OpenWeather API 的 JSON 字符串
"""
api_key = os.getenv("OPENWEATHER_API_KEY")
if not api_key:
api_key = "####################" # 备用默认值
params = {
"q": city,
"appid": api_key,
"units": "metric",
"lang": "zh_cn"
}
resp = httpx.get("https://api.openweathermap.org/data/2.5/weather", params=params)
return json.dumps(resp.json())
API Key 优先级:
环境变量 OPENWEATHER_API_KEY > 内置默认值
2. MCP 客户端 (McpClient_WebFetch.py)
2.1 MCP 客户端类
# ---------------------- LLM 配置 ----------------------
# 使用阿里云 DashScope API
client = OpenAI(
api_key=os.getenv("QWEN_API_KEY", os.getenv("aliQwen-api", "")),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
MODEL_NAME = "qwen-plus"
class MCPWebClient:
"""MCP Web 服务客户端,由 LLM 自动判断调用哪个工具"""
def __init__(self, mcp_instance):
self.mcp_instance = mcp_instance
self.available_tools = mcp_instance._tools
logger.info(f"已连接 MCP 服务,可用工具: {list(self.available_tools.keys())}")
def call_tool(self, tool_name: str, **kwargs):
"""调用指定工具"""
if tool_name not in self.available_tools:
logger.error(f"工具 '{tool_name}' 不存在")
return None
return self.available_tools[tool_name](**kwargs)
2.2 动态构建 Tools Schema
def build_tools_schema():
"""根据 MCP 注册的工具,自动生成 OpenAI function calling 的 tools 参数"""
tools = []
# 工具1: fetch_webpage
tools.append({
"type": "function",
"function": {
"name": "fetch_webpage",
"description": "抓取指定网页的文本内容...",
"parameters": {
"type": "object",
"properties": {
"url": {"type": "string", "description": "网页 URL"},
"max_length": {"type": "integer", "default": 5000}
},
"required": ["url"]
}
}
})
# 工具2: get_weather
tools.append({
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的即时天气信息...",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市英文名"}
},
"required": ["city"]
}
}
})
return tools
2.3 核心对话逻辑 (三阶段)
def chat(user_input: str, mcp_client: MCPWebClient):
"""LLM 自动判断是否调用工具,并返回最终回复"""
# ========== 阶段1: LLM 判断 ==========
response = client.chat.completions.create(
model="qwen-plus",
messages=[{"role": "user", "content": user_input}],
tools=build_tools_schema(),
tool_choice="auto" # LLM 自动判断是否调用工具
)
# ========== 阶段2: 执行工具 ==========
if message.tool_calls:
for tool_call in message.tool_calls:
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
# 执行 MCP 工具
tool_result = mcp_client.call_tool(tool_name, **tool_args)
# 把工具结果返回给 LLM
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": tool_result
})
# ========== 阶段3: 生成最终回复 ==========
final_response = client.chat.completions.create(
model="qwen-plus",
messages=messages
)
return final_response.choices[0].message.content
三阶段流程图:
用户输入 │ ▼ ┌───────────────────────────────────────┐ │ 阶段1: LLM 判断 │ │ tool_choice="auto" │ │ 判断是否需要调用工具? │ └───────────────┬───────────────────────┘ │ ┌───────┴───────┐ ▼ ▼ 是,需要工具 否,不需要工具 │ │ ▼ │ ┌───────────────────┐ │ │ 阶段2: 执行工具 │ │ │ mcp_client │ │ │ .call_tool() │ │ └─────────┬─────────┘ │ │ │ ▼ │ ┌───────────────────┐ │ │ 阶段3: 生成回复 │ │ │ LLM 根据工具结果 │ │ │ 生成最终回复 │ │ └─────────┬─────────┘ │ │ │ └──────┬──────┘ ▼ 最终回复
🚀 使用方法
环境准备
# 安装依赖 pip install loguru httpx openai python-dotenv # 或使用 uv uv pip install loguru httpx openai python-dotenv
预期输出:
2026-05-05 09:00:00 | INFO | MCP Web 服务器 (提供网页抓取 + 天气查询) 2026-05-05 09:00:00 | INFO | 监听地址: http://127.0.0.1:8000/sse 2026-05-05 09:00:00 | INFO | 已注册工具: ['fetch_webpage', 'get_weather']
启动客户端
# 终端2 python McpClient_WebFetch.py
对话示例
==================================================
MCP 智能客户端 (输入问题,LLM 自动选择工具)
可用工具: fetch_webpage / get_weather
输入 'quit' 退出
==================================================
你: 上海天气怎么样
[LLM 决定调用工具] get_weather({"city": "Shanghai"})
[工具返回结果] {"coord": {"lon": 121.45, "lat": 31.22}, "weather": [{"description": "多云"}], "main": {"temp": 24.55...}}
助手: 上海目前天气为**多云**,气温约 **24.6°C**,体感温度约 **24.0°C**;
- 湿度:37%
- 气压:1017 hPa
- 风速:3.94 m/s
你: 帮我抓取百度首页
[LLM 决定调用工具] fetch_webpage({"url": "https://www.baidu.com"})
[工具返回结果] {"url": "https://www.baidu.com", "content": "", "status": "success"}
助手: 已成功访问百度首页,但网页内容未返回(可能因反爬机制)。
你: 你好
助手: 你好!有什么我可以帮你的吗?
你: quit
再见!
📊 测试结果
| 测试场景 | 输入 | LLM 判断 | 工具调用 | 结果 |
|---|---|---|---|---|
| 天气查询 | "北京今天天气怎么样" | ✅ 调用工具 | get_weather(city="Beijing") |
✅ 成功,20.1°C 阴 |
| 天气查询 | "上海天气怎么样" | ✅ 调用工具 | get_weather(city="Shanghai") |
✅ 成功,24.6°C 多云 |
| 网页抓取 | "帮我抓取百度首页" | ✅ 调用工具 | fetch_webpage(url="...") |
⚠️ 成功但内容为空 |
| 普通对话 | "你好" | ❌ 不调用 | 无 | ✅ 直接回答 |
⚠️ 已知问题
1. 网页抓取返回空内容
现象: 抓取百度等大型网站时,返回 content: ""
原因:
-
目标网站有反爬机制
-
网页内容由 JavaScript 动态渲染
-
简单的 HTML 解析无法获取动态内容
解决方案:
-
对于动态网页,考虑使用 Selenium/Playwright
-
对于反爬网站,考虑添加更多请求头或使用代理
2. Python 3.13 兼容性问题
现象: McpServerByFastMCP.py 使用 FastMCP 时报错
ModuleNotFoundError: No module named 'pywintypes'
原因: pywin32 尚未适配 Python 3.13
解决方案: 使用本项目的极简版 MCP 服务类,不依赖 FastMCP
📚 扩展方向
1. 增加更多工具
@mcp.tool() def search_news(keyword: str) -> str: """搜索新闻""" # 实现搜索逻辑 pass @mcp.tool() def translate(text: str, target_lang: str) -> str: """翻译文本""" # 实现翻译逻辑 pass
2. 支持 STDIO 传输协议
# 当前: SSE mcp.run(transport="sse") # 扩展: STDIO mcp.run(transport="stdio")
3. 集成 LangChain
from langchain.agents import initialize_agent, Tool from langchain.llms import OpenAI # 将 MCP 工具转换为 LangChain Tools tools = [ Tool( name="fetch_webpage", func=fetch_webpage, description="抓取网页内容" ), Tool( name="get_weather", func=get_weather, description="查询天气" ) ] # 使用 LangChain Agent agent = initialize_agent(tools, llm, agent="zero-shot-react-description")
📝 总结
本项目实现了一个完整的 MCP (Model Context Protocol) 示例:
| 组件 | 实现 |
|---|---|
| 服务端 | 极简版 MCP 服务类,无第三方依赖 |
| 工具注册 | @mcp.tool() 装饰器模式 |
| 客户端 | LLM 自动判断工具调用 |
| 工具描述 | 动态生成 OpenAI function calling schema |
| 通信协议 | SSE (Server-Sent Events) |
核心价值:
-
理解了 MCP 协议的基本工作原理
-
掌握了 LLM Function Calling 的使用
-
实现了 Tool 动态注册和调用机制
-
为 LangChain Agent 开发打下基础
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)